← back to Model Arena
auto-save: 2026-07-23T06:49:07 (7 files) — data/challenges.json data/costlog.jsonl public/index.html server.js data/artifacts/9de63223a377/
2897af6627b3356335758c6ee4fedb569566b027 · 2026-07-23 06:49:15 -0700 · Steve Abrams
Files touched
A data/artifacts/9de63223a377/gemma3-12b.htmlA data/artifacts/9de63223a377/gemma3-12b.pngA data/artifacts/9de63223a377/gpt.htmlA data/artifacts/9de63223a377/gpt.pngA data/artifacts/9de63223a377/grok.htmlA data/artifacts/9de63223a377/grok.pngA data/artifacts/9de63223a377/hermes3-8b.htmlA data/artifacts/9de63223a377/hermes3-8b.pngA data/artifacts/9de63223a377/kimi.htmlA data/artifacts/9de63223a377/kimi.pngA data/artifacts/9de63223a377/qwen25-7b.htmlA data/artifacts/9de63223a377/qwen25-7b.pngA data/artifacts/9de63223a377/qwen3-14b.htmlA data/artifacts/9de63223a377/qwen3-14b.pngA data/artifacts/ca355985ca95/gpt.htmlA data/artifacts/ca355985ca95/grok.htmlA data/artifacts/ca355985ca95/grok.pngA data/artifacts/ca355985ca95/qwen25-7b.htmlA data/artifacts/ca355985ca95/qwen25-7b.pngM data/challenges.jsonM data/costlog.jsonlA idea-run/challenges.txtA idea-run/create-cycle.shA idea-run/harvest.mdM public/index.htmlM server.js
Diff
commit 2897af6627b3356335758c6ee4fedb569566b027
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 23 06:49:15 2026 -0700
auto-save: 2026-07-23T06:49:07 (7 files) — data/challenges.json data/costlog.jsonl public/index.html server.js data/artifacts/9de63223a377/
---
data/artifacts/9de63223a377/gemma3-12b.html | 80 ++
data/artifacts/9de63223a377/gemma3-12b.png | Bin 0 -> 2729 bytes
data/artifacts/9de63223a377/gpt.html | 1175 +++++++++++++++++++++++++++
data/artifacts/9de63223a377/gpt.png | Bin 0 -> 295053 bytes
data/artifacts/9de63223a377/grok.html | 558 +++++++++++++
data/artifacts/9de63223a377/grok.png | Bin 0 -> 48690 bytes
data/artifacts/9de63223a377/hermes3-8b.html | 55 ++
data/artifacts/9de63223a377/hermes3-8b.png | Bin 0 -> 20947 bytes
data/artifacts/9de63223a377/kimi.html | 446 ++++++++++
data/artifacts/9de63223a377/kimi.png | Bin 0 -> 158616 bytes
data/artifacts/9de63223a377/qwen25-7b.html | 26 +
data/artifacts/9de63223a377/qwen25-7b.png | Bin 0 -> 2728 bytes
data/artifacts/9de63223a377/qwen3-14b.html | 103 +++
data/artifacts/9de63223a377/qwen3-14b.png | Bin 0 -> 19697 bytes
data/artifacts/ca355985ca95/gpt.html | 272 +++++++
data/artifacts/ca355985ca95/grok.html | 206 +++++
data/artifacts/ca355985ca95/grok.png | Bin 0 -> 632051 bytes
data/artifacts/ca355985ca95/qwen25-7b.html | 114 +++
data/artifacts/ca355985ca95/qwen25-7b.png | Bin 0 -> 188873 bytes
data/challenges.json | 166 ++++
data/costlog.jsonl | 5 +
idea-run/challenges.txt | 7 +
idea-run/create-cycle.sh | 15 +
idea-run/harvest.md | 5 +
public/index.html | 2 +-
server.js | 25 +-
26 files changed, 3253 insertions(+), 7 deletions(-)
diff --git a/data/artifacts/9de63223a377/gemma3-12b.html b/data/artifacts/9de63223a377/gemma3-12b.html
new file mode 100644
index 0000000..1332d59
--- /dev/null
+++ b/data/artifacts/9de63223a377/gemma3-12b.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Lion vs Tiger - Jungle Showdown</title>
+<style>
+body {
+ margin: 0;
+ overflow: hidden;
+ background-color: #228B22; /* Forest Green */
+}
+
+#container {
+ position: relative;
+ width: 100vw;
+ height: 100vh;
+}
+
+.animal {
+ position: absolute;
+ width: 150px;
+ height: 150px;
+ animation-duration: 2s;
+ animation-iteration-count: infinite;
+ transform-origin: center;
+}
+
+#lion {
+ top: 50%;
+ left: 10%;
+ transform: translate(-50%, -50%);
+ background-image: url("https://huggingface.co/datasets/stabilityai/stable-diffusion-2-base/raw/main/lion_example.png"); /* Replace with a suitable lion image from Hugging Face or elsewhere */
+ background-size: cover;
+}
+
+#tiger {
+ top: 50%;
+ right: 10%;
+ transform: translate(-50%, -50%);
+ background-image: url("https://huggingface.co/datasets/stabilityai/stable-diffusion-2-base/raw/main/tiger_example.png"); /* Replace with a suitable tiger image from Hugging Face or elsewhere */
+ background-size: cover;
+}
+
+@keyframes lionRoar {
+ 0% { transform: translate(-50%, -50%) rotate(0deg); }
+ 50% { transform: translate(-50%, -50%) rotate(360deg); }
+ 100% { transform: translate(-50%, -50%) rotate(0deg); }
+}
+
+@keyframes tigerPounce {
+ 0% { transform: translate(-50%, -50%) scale(1); }
+ 50% { transform: translate(-50%, -50%) scale(1.2) rotate(90deg); }
+ 100% { transform: translate(-50%, -50%) scale(1); }
+}
+
+</style>
+</head>
+<body>
+
+<div id="container">
+ <div class="animal" id="lion"></div>
+ <div class="animal" id="tiger"></div>
+</div>
+
+<script>
+const lion = document.getElementById('lion');
+const tiger = document.getElementById('tiger');
+
+lion.addEventListener('animationiteration', () => {
+ lion.style.animationName = 'lionRoar';
+});
+
+tiger.addEventListener('animationiteration', () => {
+ tiger.style.animationName = 'tigerPounce';
+});
+
+
+</script>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9de63223a377/gemma3-12b.png b/data/artifacts/9de63223a377/gemma3-12b.png
new file mode 100644
index 0000000..5d5e824
Binary files /dev/null and b/data/artifacts/9de63223a377/gemma3-12b.png differ
diff --git a/data/artifacts/9de63223a377/gpt.html b/data/artifacts/9de63223a377/gpt.html
new file mode 100644
index 0000000..b4035b6
--- /dev/null
+++ b/data/artifacts/9de63223a377/gpt.html
@@ -0,0 +1,1175 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<title>Lion vs Tiger – Jungle Arena (Model-Inspired Visualizer)</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ background: #02060c;
+ color: #f6f6f6;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ height: 100%;
+ overflow: hidden;
+ }
+ body {
+ display: flex;
+ flex-direction: column;
+ }
+ #app {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ }
+
+ /* Layout */
+ header {
+ padding: 10px 16px 6px;
+ background: radial-gradient(circle at 0 0, #1b3b51 0, #040812 55%, #000 100%);
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ }
+ header .title {
+ font-weight: 600;
+ font-size: 18px;
+ letter-spacing: .03em;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+ header .subtitle {
+ font-size: 11px;
+ opacity: .7;
+ }
+ header .badge {
+ border-radius: 999px;
+ border: 1px solid rgba(255, 255, 255, 0.16);
+ font-size: 10px;
+ padding: 2px 8px;
+ text-transform: uppercase;
+ letter-spacing: .16em;
+ background: linear-gradient(90deg, rgba(180,134,255,.22), rgba(41,196,255,.16));
+ }
+
+ main {
+ display: grid;
+ grid-template-columns: minmax(0, 3fr) minmax(280px, 2.2fr);
+ gap: 10px;
+ padding: 6px 10px 10px;
+ height: calc(100% - 52px);
+ box-sizing: border-box;
+ }
+ @media (max-width: 840px) {
+ main {
+ grid-template-columns: 1fr;
+ grid-template-rows: minmax(240px, 1.2fr) minmax(200px, 1fr);
+ }
+ }
+
+ /* Left: Canvas / Scene */
+ #scene-panel {
+ position: relative;
+ background: radial-gradient(circle at 50% -40%, #30556a 0, #03040a 55%, #000 100%);
+ border-radius: 10px;
+ border: 1px solid rgba(255,255,255,0.06);
+ overflow: hidden;
+ box-shadow:
+ 0 0 0 1px rgba(255,255,255,0.02),
+ 0 20px 40px rgba(0,0,0,0.8);
+ }
+ #viewport {
+ width: 100%;
+ height: 100%;
+ display: block;
+ background: transparent;
+ }
+ #scene-overlay {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ }
+ .corner-label {
+ position: absolute;
+ font-size: 10px;
+ letter-spacing: .18em;
+ text-transform: uppercase;
+ opacity: .6;
+ padding: 6px 9px;
+ border-radius: 0 0 8px 0;
+ background: linear-gradient(90deg, rgba(0,0,0,0.7), rgba(0,0,0,0));
+ backdrop-filter: blur(10px);
+ }
+ .corner-label.right {
+ right: 0;
+ left: auto;
+ text-align: right;
+ border-radius: 0 0 0 8px;
+ background: linear-gradient(270deg, rgba(0,0,0,0.7), rgba(0,0,0,0));
+ }
+
+ .hud {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ padding: 8px 10px;
+ box-sizing: border-box;
+ }
+ .hud-card {
+ background: radial-gradient(circle at 0 0, rgba(175,255,192,0.11), rgba(10,22,11,0.96));
+ border-radius: 9px;
+ padding: 6px 8px;
+ border: 1px solid rgba(166,255,179,0.25);
+ width: 176px;
+ pointer-events: auto;
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.6);
+ }
+ .hud-card.right {
+ background: radial-gradient(circle at 100% 0, rgba(255,208,158,0.16), rgba(25,17,4,0.96));
+ border-color: rgba(255,203,141,0.3);
+ }
+ .hud-title {
+ font-size: 11px;
+ letter-spacing: .2em;
+ text-transform: uppercase;
+ opacity: .8;
+ margin-bottom: 2px;
+ }
+ .hud-name {
+ font-size: 16px;
+ font-weight: 600;
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ }
+ .hud-badge {
+ font-size: 9px;
+ padding: 1px 6px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.25);
+ text-transform: uppercase;
+ letter-spacing: .14em;
+ opacity: .9;
+ }
+ .stat-row {
+ margin-top: 4px;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ font-size: 10px;
+ }
+ .stat-label {
+ opacity: .7;
+ }
+ .stat-bar {
+ position: relative;
+ height: 5px;
+ border-radius: 999px;
+ background: rgba(255,255,255,0.08);
+ overflow: hidden;
+ }
+ .stat-fill {
+ position: absolute;
+ inset: 0;
+ width: 0%;
+ border-radius: inherit;
+ background: linear-gradient(90deg, #9bffae, #58ffbf);
+ transition: width .6s ease-out;
+ }
+ .right .stat-fill {
+ background: linear-gradient(90deg, #ffc36f, #ff8b46);
+ }
+ .hud-meta {
+ margin-top: 4px;
+ font-size: 9px;
+ display: flex;
+ justify-content: space-between;
+ opacity: .75;
+ }
+
+ /* Right: Controls & Logs */
+ #side-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ }
+ .card {
+ background: radial-gradient(circle at top, #151b2b 0, #050711 55%, #000 100%);
+ border-radius: 10px;
+ border: 1px solid rgba(255,255,255,0.08);
+ padding: 8px 9px;
+ box-shadow:
+ 0 0 0 1px rgba(0,0,0,0.6),
+ 0 18px 40px rgba(0,0,0,0.7);
+ }
+ .card-header {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ margin-bottom: 3px;
+ }
+ .card-title {
+ font-size: 12px;
+ letter-spacing: .18em;
+ text-transform: uppercase;
+ opacity: .85;
+ }
+ .card-sub {
+ font-size: 10px;
+ opacity: .6;
+ }
+
+ /* Controls */
+ .control-row {
+ display: flex;
+ gap: 6px;
+ margin-top: 4px;
+ flex-wrap: wrap;
+ }
+ .control-group {
+ flex: 1;
+ min-width: 120px;
+ }
+ .control-label {
+ font-size: 10px;
+ opacity: .8;
+ margin-bottom: 2px;
+ display: flex;
+ justify-content: space-between;
+ }
+ .slider {
+ width: 100%;
+ }
+ input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 100%;
+ height: 4px;
+ border-radius: 999px;
+ background: rgba(255,255,255,0.1);
+ outline: none;
+ }
+ input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 13px;
+ height: 13px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 30% 30%, #ffffff, #8df0ff);
+ border: 1px solid rgba(0,0,0,0.5);
+ box-shadow: 0 0 0 3px rgba(141,240,255,0.2);
+ cursor: pointer;
+ margin-top: -4.5px;
+ }
+ input[type="range"]::-moz-range-thumb {
+ width: 13px;
+ height: 13px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 30% 30%, #ffffff, #8df0ff);
+ border: 1px solid rgba(0,0,0,0.5);
+ box-shadow: 0 0 0 3px rgba(141,240,255,0.2);
+ cursor: pointer;
+ }
+ input[type="range"]::-moz-range-track {
+ height: 4px;
+ border-radius: 999px;
+ background: rgba(255,255,255,0.1);
+ }
+
+ select {
+ background: rgba(9,13,24,0.96);
+ color: #f2f2f2;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.18);
+ padding: 2px 7px;
+ font-size: 10px;
+ outline: none;
+ }
+
+ .button-row {
+ display: flex;
+ gap: 6px;
+ margin-top: 7px;
+ flex-wrap: wrap;
+ }
+ button {
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.2);
+ padding: 5px 9px;
+ font-size: 11px;
+ background: radial-gradient(circle at 0 0, #2dcbff 0, #083651 55%, #040913 100%);
+ color: #f7f7f7;
+ cursor: pointer;
+ letter-spacing: .14em;
+ text-transform: uppercase;
+ transition: transform .06s ease-out, box-shadow .1s ease-out, background .15s;
+ flex: 1;
+ min-width: 90px;
+ }
+ button.secondary {
+ background: radial-gradient(circle at 0 0, #afb7d0 0, #202636 55%, #02050d 100%);
+ }
+ button:active {
+ transform: translateY(1px) scale(.99);
+ box-shadow: 0 0 0 rgba(0,0,0,0);
+ }
+ button:disabled {
+ opacity: .4;
+ cursor: default;
+ box-shadow: none;
+ background: radial-gradient(circle at 0 0, #555 0, #222 55%, #000 100%);
+ }
+
+ /* Toggles */
+ .toggle-row {
+ display: flex;
+ gap: 6px;
+ margin-top: 4px;
+ flex-wrap: wrap;
+ font-size: 10px;
+ }
+ .toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 2px 7px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.14);
+ background: rgba(0,0,0,0.4);
+ cursor: pointer;
+ }
+ .toggle input {
+ display: none;
+ }
+ .toggle-pill {
+ width: 20px;
+ height: 10px;
+ border-radius: 999px;
+ background: rgba(255,255,255,0.15);
+ position: relative;
+ }
+ .toggle-pill::after {
+ content: "";
+ position: absolute;
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #fff;
+ top: 1px;
+ left: 1px;
+ transition: transform .15s ease-out, background .15s;
+ }
+ .toggle input:checked + .toggle-pill {
+ background: linear-gradient(90deg, #6aff8c, #4dffc4);
+ }
+ .toggle input:checked + .toggle-pill::after {
+ transform: translateX(10px);
+ background: #021006;
+ }
+
+ /* Log */
+ #log {
+ height: 150px;
+ overflow-y: auto;
+ font-size: 11px;
+ line-height: 1.35;
+ padding-right: 2px;
+ }
+ #log::-webkit-scrollbar {
+ width: 6px;
+ }
+ #log::-webkit-scrollbar-track {
+ background: rgba(255,255,255,0.02);
+ }
+ #log::-webkit-scrollbar-thumb {
+ background: rgba(255,255,255,0.2);
+ border-radius: 999px;
+ }
+ .log-entry {
+ margin-bottom: 4px;
+ }
+ .log-time {
+ opacity: .5;
+ font-size: 9px;
+ margin-right: 4px;
+ }
+ .log-tag {
+ font-size: 9px;
+ padding: 0 5px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.26);
+ margin-right: 4px;
+ text-transform: uppercase;
+ letter-spacing: .16em;
+ opacity: .85;
+ }
+ .tag-lion {
+ border-color: rgba(139,255,187,0.7);
+ color: #9bffc1;
+ }
+ .tag-tiger {
+ border-color: rgba(255,199,136,0.7);
+ color: #ffce92;
+ }
+ .tag-engine {
+ border-color: rgba(153,210,255,0.7);
+ color: #a8d9ff;
+ }
+
+ .log-highlight {
+ color: #ffe29b;
+ }
+
+ /* Footer meta */
+ .footer-meta {
+ margin-top: 4px;
+ font-size: 9px;
+ opacity: .6;
+ display: flex;
+ justify-content: space-between;
+ gap: 8px;
+ flex-wrap: wrap;
+ }
+ .pill {
+ padding: 2px 7px;
+ border-radius: 999px;
+ border: 1px solid rgba(255,255,255,0.18);
+ }
+
+ /* Floating center label */
+ #center-label {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ text-align: center;
+ pointer-events: none;
+ font-size: 11px;
+ letter-spacing: .25em;
+ text-transform: uppercase;
+ opacity: .2;
+ }
+
+ /* Tooltip helper text */
+ .tiny {
+ font-size: 9px;
+ opacity: .65;
+ }
+</style>
+</head>
+<body>
+<div id="app">
+ <header>
+ <div>
+ <div class="title">
+ LION vs TIGER
+ <span class="badge">Jungle Arena · Model-Inspired</span>
+ </div>
+ <div class="subtitle">Procedural duel visualizer using physics-inspired animation; styles echo modern diffusion/3D model UIs.</div>
+ </div>
+ <div class="subtitle">
+ Engine: <strong>Client-side JS</strong> · Graphics: <strong>Canvas 2D</strong> · Models referenced: <strong>Stable Diffusion, ControlNet, DreamShaper, Llama-3</strong>
+ </div>
+ </header>
+ <main>
+ <section id="scene-panel">
+ <canvas id="viewport"></canvas>
+ <div id="scene-overlay">
+ <div class="corner-label">MODEL-INSPIRED JUNGLE RENDER</div>
+ <div class="corner-label right">LION · TIGER · PHYSICS LOOP</div>
+
+ <div class="hud">
+ <div class="hud-card" id="hud-lion">
+ <div class="hud-title">Left Agent · Feline-01</div>
+ <div class="hud-name">
+ <span>Panthera leo</span>
+ <span class="hud-badge">Lion</span>
+ </div>
+ <div class="stat-row">
+ <div class="stat-label">Stamina</div>
+ <div class="stat-bar"><div class="stat-fill" id="lion-stamina"></div></div>
+ </div>
+ <div class="stat-row">
+ <div class="stat-label">Momentum</div>
+ <div class="stat-bar"><div class="stat-fill" id="lion-momentum"></div></div>
+ </div>
+ <div class="hud-meta">
+ <span>Style: Bold frontal</span>
+ <span>Bias: Arena control</span>
+ </div>
+ </div>
+
+ <div class="hud-card right" id="hud-tiger">
+ <div class="hud-title">Right Agent · Feline-02</div>
+ <div class="hud-name">
+ <span>Panthera tigris</span>
+ <span class="hud-badge">Tiger</span>
+ </div>
+ <div class="stat-row">
+ <div class="stat-label">Stamina</div>
+ <div class="stat-bar"><div class="stat-fill" id="tiger-stamina"></div></div>
+ </div>
+ <div class="stat-row">
+ <div class="stat-label">Momentum</div>
+ <div class="stat-bar"><div class="stat-fill" id="tiger-momentum"></div></div>
+ </div>
+ <div class="hud-meta">
+ <span>Style: Ambush arcs</span>
+ <span>Bias: Flank + feints</span>
+ </div>
+ </div>
+ </div>
+
+ <div id="center-label">SIMULATED DUEL · NO REAL ANIMALS</div>
+ </div>
+ </section>
+
+ <section id="side-panel">
+ <div class="card">
+ <div class="card-header">
+ <div>
+ <div class="card-title">Engine Controls</div>
+ <div class="card-sub">Jungle context uses model-style prompts; combat is physics-inspired, not literal biology.</div>
+ </div>
+ </div>
+
+ <div class="control-row">
+ <div class="control-group">
+ <div class="control-label">
+ <span>Prompt Preset</span>
+ <span id="preset-label" style="opacity:.7;">Cinematic Jungle</span>
+ </div>
+ <select id="preset">
+ <option value="cinematic">Cinematic Jungle (SDXL aesthetic)</option>
+ <option value="lowlight">Low-Light Infrared (ControlNet edges)</option>
+ <option value="topdown">Top-Down Tactical (Segmentation map)</option>
+ <option value="stylized">Stylized Neon (DreamShaper)</option>
+ </select>
+ </div>
+ <div class="control-group">
+ <div class="control-label">
+ <span>Simulation Tempo</span>
+ <span id="tempo-label" style="opacity:.7;">1.0x</span>
+ </div>
+ <input id="tempo" class="slider" type="range" min="0.5" max="1.5" step="0.05" value="1">
+ </div>
+ </div>
+
+ <div class="control-row">
+ <div class="control-group">
+ <div class="control-label">
+ <span>Lion Aggression</span>
+ <span id="lion-aggr-label" style="opacity:.7;">0.60</span>
+ </div>
+ <input id="lion-aggr" class="slider" type="range" min="0.1" max="1.0" step="0.05" value="0.6">
+ </div>
+ <div class="control-group">
+ <div class="control-label">
+ <span>Tiger Aggression</span>
+ <span id="tiger-aggr-label" style="opacity:.7;">0.55</span>
+ </div>
+ <input id="tiger-aggr" class="slider" type="range" min="0.1" max="1.0" step="0.05" value="0.55">
+ </div>
+ </div>
+
+ <div class="toggle-row">
+ <label class="toggle">
+ <input type="checkbox" id="toggle-orbits" checked>
+ <span class="toggle-pill"></span>
+ <span>Show positional paths</span>
+ </label>
+ <label class="toggle">
+ <input type="checkbox" id="toggle-pulses" checked>
+ <span class="toggle-pill"></span>
+ <span>Contact pulses</span>
+ </label>
+ <label class="toggle">
+ <input type="checkbox" id="toggle-auto" checked>
+ <span class="toggle-pill"></span>
+ <span>Loop encounters</span>
+ </label>
+ </div>
+
+ <div class="button-row">
+ <button id="btn-simulate">Run encounter</button>
+ <button id="btn-reset" class="secondary">Reset arena</button>
+ </div>
+
+ <div class="footer-meta">
+ <div class="pill">
+ Model notes: imagine SDXL / Stable Diffusion controlling jungle lighting and motion blur; ControlNet guiding trajectories; Llama-style reasoning scoring tactics.
+ </div>
+ </div>
+ </div>
+
+ <div class="card" style="flex:1;min-height:120px;">
+ <div class="card-header">
+ <div>
+ <div class="card-title">Arena Log</div>
+ <div class="card-sub">Procedural narration mixing physics states with AI-style commentary.</div>
+ </div>
+ </div>
+ <div id="log"></div>
+ <div class="tiny" style="margin-top:4px;">
+ This is a stylized, algorithmic duel visualizer. It does not represent real animal fights and should not be used to justify harm to animals in any way.
+ </div>
+ </div>
+ </section>
+ </main>
+</div>
+
+<script>
+(function(){
+ const canvas = document.getElementById('viewport');
+ const ctx = canvas.getContext('2d');
+
+ function resize() {
+ const rect = canvas.parentElement.getBoundingClientRect();
+ const dpr = window.devicePixelRatio || 1;
+ canvas.width = rect.width * dpr;
+ canvas.height = rect.height * dpr;
+ canvas.style.width = rect.width + 'px';
+ canvas.style.height = rect.height + 'px';
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ }
+ resize();
+ window.addEventListener('resize', resize);
+
+ const UI = {
+ lionStamina: document.getElementById('lion-stamina'),
+ lionMomentum: document.getElementById('lion-momentum'),
+ tigerStamina: document.getElementById('tiger-stamina'),
+ tigerMomentum: document.getElementById('tiger-momentum'),
+ log: document.getElementById('log'),
+ tempo: document.getElementById('tempo'),
+ tempoLabel: document.getElementById('tempo-label'),
+ lionAggr: document.getElementById('lion-aggr'),
+ lionAggrLabel: document.getElementById('lion-aggr-label'),
+ tigerAggr: document.getElementById('tiger-aggr'),
+ tigerAggrLabel: document.getElementById('tiger-aggr-label'),
+ preset: document.getElementById('preset'),
+ presetLabel: document.getElementById('preset-label'),
+ btnSimulate: document.getElementById('btn-simulate'),
+ btnReset: document.getElementById('btn-reset'),
+ toggleOrbits: document.getElementById('toggle-orbits'),
+ togglePulses: document.getElementById('toggle-pulses'),
+ toggleAuto: document.getElementById('toggle-auto')
+ };
+
+ function addLog(tag, text, type) {
+ const entry = document.createElement('div');
+ entry.className = 'log-entry';
+ const time = document.createElement('span');
+ time.className = 'log-time';
+ time.textContent = '[' + new Date().toLocaleTimeString('en-US', {hour12:false, hour:'2-digit', minute:'2-digit', second:'2-digit'}) + ']';
+ const label = document.createElement('span');
+ label.className = 'log-tag ' + (type === 'lion' ? 'tag-lion' : type === 'tiger' ? 'tag-tiger' : 'tag-engine');
+ label.textContent = tag;
+ const msg = document.createElement('span');
+ msg.innerHTML = text;
+ entry.appendChild(time);
+ entry.appendChild(label);
+ entry.appendChild(msg);
+ UI.log.appendChild(entry);
+ UI.log.scrollTop = UI.log.scrollHeight;
+ }
+
+ UI.tempo.addEventListener('input', () => {
+ UI.tempoLabel.textContent = UI.tempo.value + 'x';
+ });
+ UI.lionAggr.addEventListener('input', () => {
+ UI.lionAggrLabel.textContent = parseFloat(UI.lionAggr.value).toFixed(2);
+ });
+ UI.tigerAggr.addEventListener('input', () => {
+ UI.tigerAggrLabel.textContent = parseFloat(UI.tigerAggr.value).toFixed(2);
+ });
+ UI.preset.addEventListener('change', () => {
+ const map = {
+ cinematic: 'Cinematic Jungle',
+ lowlight: 'Low-Light Infrared',
+ topdown: 'Top-Down Tactical',
+ stylized: 'Stylized Neon'
+ };
+ UI.presetLabel.textContent = map[UI.preset.value] || 'Custom';
+ });
+
+ const state = {
+ t: 0,
+ lion: { stamina: 1, momentum: 0.5 },
+ tiger: { stamina: 1, momentum: 0.5 },
+ pulses: [],
+ trailsLion: [],
+ trailsTiger: [],
+ running: true,
+ encounterPhase: 0,
+ encounterTimer: 0,
+ lastEncounterTime: 0
+ };
+
+ function resetArena(logIt=true) {
+ state.lion.stamina = 1;
+ state.tiger.stamina = 1;
+ state.lion.momentum = 0.5;
+ state.tiger.momentum = 0.5;
+ state.pulses = [];
+ state.trailsLion = [];
+ state.trailsTiger = [];
+ state.encounterPhase = 0;
+ state.encounterTimer = 0;
+ state.lastEncounterTime = performance.now();
+ updateHUD();
+ if (logIt) {
+ addLog('ENGINE', 'Arena reset. Jungle cover re-synthesized; both agents at full stamina.', 'engine');
+ }
+ }
+
+ UI.btnReset.addEventListener('click', () => {
+ resetArena(true);
+ });
+
+ function updateHUD() {
+ UI.lionStamina.style.width = (state.lion.stamina * 100).toFixed(1) + '%';
+ UI.tigerStamina.style.width = (state.tiger.stamina * 100).toFixed(1) + '%';
+ UI.lionMomentum.style.width = (state.lion.momentum * 100).toFixed(1) + '%';
+ UI.tigerMomentum.style.width = (state.tiger.momentum * 100).toFixed(1) + '%';
+ }
+
+ function spawnEncounter() {
+ state.encounterPhase = 1;
+ state.encounterTimer = 0;
+ state.lastEncounterTime = performance.now();
+ const preset = UI.preset.value;
+ const sceneDesc = {
+ cinematic: 'Overhead canopy splits moonlight into sharp slashes across the ground.',
+ lowlight: 'Infrared silhouettes cut through the dark, edges glowing like ControlNet outlines.',
+ topdown: 'From a tactical top-down view, paths and heat signatures bloom like segmentation masks.',
+ stylized: 'Neon gradients pulse along vines; the jungle reads like a DreamShaper storyboard.'
+ }[preset] || 'Jungle density updated for the new loop.';
+ addLog('ENGINE', 'New encounter loop queued. ' + sceneDesc, 'engine');
+ }
+
+ UI.btnSimulate.addEventListener('click', () => {
+ spawnEncounter();
+ addLog('ENGINE', 'Manual encounter trigger: agents will converge once orbits align.', 'engine');
+ });
+
+ function pushPulse(x, y, color) {
+ state.pulses.push({x,y,r:4,alpha:0.8,color});
+ }
+
+ function decideExchange() {
+ const lionA = parseFloat(UI.lionAggr.value);
+ const tigerA = parseFloat(UI.tigerAggr.value);
+ const sumA = lionA + tigerA;
+ const lionBias = sumA > 0 ? lionA / sumA : 0.5;
+ const randomFactor = Math.random() * 0.4 + 0.8;
+ const lionScore = lionBias * randomFactor * (0.7 + 0.6*Math.random());
+ const tigerScore = (1 - lionBias) * (2 - randomFactor) * (0.7 + 0.6*Math.random());
+
+ const diff = lionScore - tigerScore;
+ let winner, loser, impact;
+ if (Math.abs(diff) < 0.12) {
+ winner = 'clash';
+ impact = 0.08 + Math.random()*0.05;
+ } else if (diff > 0) {
+ winner = 'lion';
+ loser = 'tiger';
+ impact = 0.09 + Math.random()*0.09;
+ } else {
+ winner = 'tiger';
+ loser = 'lion';
+ impact = 0.09 + Math.random()*0.09;
+ }
+
+ return { winner, loser, impact };
+ }
+
+ function narrateExchange(res) {
+ const preset = UI.preset.value;
+ const stylePhrase = {
+ cinematic: 'Camera tracks through leaves like a stabilized cinematic rig.',
+ lowlight: 'Infrared grain and edge-enhanced silhouettes recall ControlNet-guided shots.',
+ topdown: 'Paths arc like colored masks on a segmentation map.',
+ stylized: 'Colors overcrank into neon gradients, a DreamShaper storyboard in motion.'
+ }[preset];
+
+ if (res.winner === 'clash') {
+ addLog('ENGINE',
+ 'Both agents commit at once—heavy contact, but no decisive control shift. ' +
+ '<span class="log-highlight">' + stylePhrase + '</span>',
+ 'engine');
+ return;
+ }
+ if (res.winner === 'lion') {
+ addLog('LION',
+ 'Lion uses frontal momentum to collapse distance, driving the tiger back a half-step. ' +
+ 'Claws stay low, prioritizing balance over overextension. ' +
+ '<span class="log-highlight">' + stylePhrase + '</span>',
+ 'lion');
+ } else {
+ addLog('TIGER',
+ 'Tiger slips off-line with a lateral feint, turning the lion\'s charge into a glancing contact. ' +
+ 'Counterweight in the hindquarters launches a sharp return. ' +
+ '<span class="log-highlight">' + stylePhrase + '</span>',
+ 'tiger');
+ }
+ }
+
+ function autoScheduleEncounters(dtMs) {
+ if (!UI.toggleAuto.checked) return;
+ const now = performance.now();
+ if (now - state.lastEncounterTime > 5500 + Math.random()*3000) {
+ spawnEncounter();
+ }
+ }
+
+ function update(dt) {
+ const tempo = parseFloat(UI.tempo.value);
+ const scaled = dt * tempo;
+ state.t += scaled;
+ autoScheduleEncounters(dt*1000);
+
+ const centerX = canvas.clientWidth / 2;
+ const centerY = canvas.clientHeight / 2 + 40;
+ const baseRadius = Math.min(canvas.clientWidth, canvas.clientHeight) * 0.27;
+ const lionAngle = state.t * 0.7;
+ const tigerAngle = Math.PI + state.t * 0.72;
+ const phaseBlend = 0.4 + 0.6 * Math.sin(state.t*0.4) * 0.5;
+
+ const lionR = baseRadius * (0.78 + 0.12 * Math.sin(state.t*0.9));
+ const tigerR = baseRadius * (0.78 + 0.12 * Math.cos(state.t*0.92 + 1.2));
+
+ const lionX = centerX + Math.cos(lionAngle) * lionR * (1 - 0.12*phaseBlend);
+ const lionY = centerY + Math.sin(lionAngle) * lionR * (0.6 + 0.1*Math.sin(state.t*0.5));
+ const tigerX = centerX + Math.cos(tigerAngle) * tigerR * (1 - 0.12*(1-phaseBlend));
+ const tigerY = centerY + Math.sin(tigerAngle) * tigerR * (0.6 + 0.1*Math.cos(state.t*0.5+1.2));
+
+ if (UI.toggleOrbits.checked) {
+ state.trailsLion.push({x:lionX, y:lionY, alpha:0.9});
+ state.trailsTiger.push({x:tigerX, y:tigerY, alpha:0.9});
+ if (state.trailsLion.length > 220) state.trailsLion.shift();
+ if (state.trailsTiger.length > 220) state.trailsTiger.shift();
+ } else {
+ state.trailsLion.length = 0;
+ state.trailsTiger.length = 0;
+ }
+
+ const dx = tigerX - lionX;
+ const dy = tigerY - lionY;
+ const dist = Math.sqrt(dx*dx + dy*dy);
+ const closeThreshold = baseRadius * 0.38;
+ const normalized = Math.max(0, Math.min(1, (closeThreshold - dist) / closeThreshold));
+
+ state.lion.momentum = 0.35 + 0.65 * normalized;
+ state.tiger.momentum = 0.35 + 0.65 * normalized;
+
+ if (state.encounterPhase === 1 && normalized > 0.75) {
+ state.encounterPhase = 2;
+ const res = decideExchange();
+ const drain = res.impact * (0.5 + 0.6*normalized);
+
+ if (res.winner === 'lion') {
+ state.tiger.stamina = Math.max(0, state.tiger.stamina - drain);
+ } else if (res.winner === 'tiger') {
+ state.lion.stamina = Math.max(0, state.lion.stamina - drain);
+ } else {
+ state.lion.stamina = Math.max(0, state.lion.stamina - drain*0.5);
+ state.tiger.stamina = Math.max(0, state.tiger.stamina - drain*0.5);
+ }
+
+ narrateExchange(res);
+
+ if (UI.togglePulses.checked) {
+ pushPulse((lionX+tigerX)/2, (lionY+tigerY)/2, res.winner==='tiger' ? '#ffb46a' : res.winner==='lion' ? '#9dffb3' : '#c7f1ff');
+ }
+
+ const sumStamina = state.lion.stamina + state.tiger.stamina;
+ if (sumStamina < 0.6) {
+ addLog('ENGINE', 'Both agents hit deep fatigue thresholds; sequence trends toward disengagement rather than escalation.', 'engine');
+ } else {
+ if (state.lion.stamina > state.tiger.stamina + 0.18) {
+ addLog('LION', 'Lion maintains slightly higher reserves, favoring stable positional dominance over risky commits.', 'lion');
+ } else if (state.tiger.stamina > state.lion.stamina + 0.18) {
+ addLog('TIGER', 'Tiger\'s explosiveness remains higher; short bursts keep initiative unpredictable.', 'tiger');
+ }
+ }
+ }
+
+ if (state.encounterPhase === 2) {
+ state.encounterTimer += scaled;
+ if (state.encounterTimer > 3.3) {
+ state.encounterPhase = 0;
+ state.encounterTimer = 0;
+ addLog('ENGINE', 'Momentum dissipates; both agents reset distance, using foliage as soft cover.', 'engine');
+ }
+ }
+
+ UI.lionStamina.style.width = (state.lion.stamina * 100).toFixed(1) + '%';
+ UI.tigerStamina.style.width = (state.tiger.stamina * 100).toFixed(1) + '%';
+ UI.lionMomentum.style.width = (state.lion.momentum * 100).toFixed(1) + '%';
+ UI.tigerMomentum.style.width = (state.tiger.momentum * 100).toFixed(1) + '%';
+
+ for (let i=state.pulses.length-1;i>=0;i--) {
+ const p = state.pulses[i];
+ p.r += 60*dt;
+ p.alpha -= 1.2*dt;
+ if (p.alpha <= 0) state.pulses.splice(i,1);
+ }
+
+ state._posLion = {x:lionX,y:lionY};
+ state._posTiger = {x:tigerX,y:tigerY};
+ state._center = {x:centerX,y:centerY};
+ state._closeNorm = normalized;
+ }
+
+ function drawBackground() {
+ const w = canvas.clientWidth;
+ const h = canvas.clientHeight;
+ const grad = ctx.createRadialGradient(
+ w*0.5, h*0.1, 10,
+ w*0.5, h*0.9, Math.max(w,h)
+ );
+ grad.addColorStop(0, '#1c3045');
+ grad.addColorStop(0.32, '#071119');
+ grad.addColorStop(0.75, '#02040a');
+ grad.addColorStop(1, '#000000');
+ ctx.fillStyle = grad;
+ ctx.fillRect(0,0,w,h);
+
+ const bands = 14;
+ for (let i=0;i<bands;i++) {
+ const y = h*0.3 + (i/bands)*h*0.55;
+ ctx.beginPath();
+ ctx.moveTo(-20,y);
+ const amp = 10 + i*i*0.4;
+ const freq = 0.005 + i*0.001;
+ for (let x=-20;x<=w+40;x+=18) {
+ const offset = Math.sin((x*freq)+state.t*0.4 + i)*amp;
+ ctx.lineTo(x, y + offset);
+ }
+ ctx.strokeStyle = `rgba(${10+i*8}, ${40+i*6}, ${20+i*4}, ${0.06 + i*0.01})`;
+ ctx.lineWidth = 24 - i*1.2;
+ ctx.stroke();
+ }
+
+ for (let i=0;i<80;i++) {
+ const x = (i*53 + 17) % w;
+ const y = h*0.4 + ((i*97)%Math.floor(h*0.5));
+ const noise = Math.sin(i*12.3 + state.t*0.6);
+ const alpha = 0.04 + 0.04 * noise*noise;
+ ctx.fillStyle = `rgba(13,105,60,${alpha})`;
+ const wLeaf = 20 + (i%7)*6;
+ const hLeaf = 10 + (i%5)*4;
+ ctx.save();
+ ctx.translate(x,y);
+ ctx.rotate(((i*17.3)%360) * Math.PI/180);
+ ctx.beginPath();
+ ctx.ellipse(0,0,wLeaf,hLeaf,0,0,Math.PI*2);
+ ctx.fill();
+ ctx.restore();
+ }
+ }
+
+ function drawTrails() {
+ const fadeTrail = (trail, colorStart, colorEnd) => {
+ for (let i=0;i<trail.length;i++) {
+ const p = trail[i];
+ const t = i / trail.length;
+ const a = p.alpha * (0.08 + 0.65 * t*t);
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, 6 + 10*t, 0, Math.PI*2);
+ const r = colorStart[0] + (colorEnd[0]-colorStart[0])*t;
+ const g = colorStart[1] + (colorEnd[1]-colorStart[1])*t;
+ const b = colorStart[2] + (colorEnd[2]-colorStart[2])*t;
+ ctx.strokeStyle = `rgba(${r|0},${g|0},${b|0},${a.toFixed(3)})`;
+ ctx.lineWidth = 1.5 + 2.5*t;
+ ctx.stroke();
+ p.alpha *= 0.97;
+ }
+ };
+ fadeTrail(state.trailsLion, [132,255,194], [49,210,143]);
+ fadeTrail(state.trailsTiger, [255,201,142], [255,144,90]);
+ }
+
+ function drawPulses() {
+ for (const p of state.pulses) {
+ const g = ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,p.r);
+ g.addColorStop(0, p.color.replace(')',','+p.alpha+')').replace('rgb','rgba'));
+ }
+ // Manual pulses drawing (no gradient colorStop bug):
+ for (const p of state.pulses) {
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, p.r, 0, Math.PI*2);
+ ctx.strokeStyle = p.color.replace(')', ','+p.alpha+')').replace('rgb','rgba');
+ ctx.lineWidth = 2;
+ ctx.stroke();
+ }
+ }
+
+ function drawAgents() {
+ const lion = state._posLion;
+ const tiger = state._posTiger;
+ const center = state._center;
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.arc(center.x, center.y, Math.min(canvas.clientWidth, canvas.clientHeight)*0.31, 0, Math.PI*2);
+ ctx.strokeStyle = 'rgba(180,240,255,0.15)';
+ ctx.lineWidth = 1;
+ ctx.setLineDash([4,6]);
+ ctx.stroke();
+ ctx.restore();
+ ctx.setLineDash([]);
+
+ if (lion && tiger) {
+ const midX = (lion.x + tiger.x)/2;
+ const midY = (lion.y + tiger.y)/2;
+ const n = state._closeNorm || 0;
+ if (n > 0.15) {
+ ctx.beginPath();
+ ctx.moveTo(lion.x, lion.y);
+ ctx.lineTo(tiger.x, tiger.y);
+ const alpha = 0.1 + 0.4*n;
+ ctx.strokeStyle = `rgba(200,240,255,${alpha.toFixed(3)})`;
+ ctx.lineWidth = 1 + 3*n;
+ ctx.stroke();
+
+ const arcR = 18 + 22*n;
+ ctx.beginPath();
+ ctx.arc(midX, midY, arcR, 0, Math.PI*2);
+ ctx.strokeStyle = `rgba(148,227,255,${(0.18+0.32*n).toFixed(3)})`;
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+ }
+
+ function drawCat(pos, primary, accent, facingLeft) {
+ if (!pos) return;
+ ctx.save();
+ ctx.translate(pos.x, pos.y);
+ ctx.scale(facingLeft ? -1 : 1, 1);
+
+ const pulse = 1 + 0.02*Math.sin(state.t*4 + (facingLeft?0:1.4));
+ const bodyLen = 50 * pulse;
+ const bodyHeight = 22 * (1 + 0.05*Math.cos(state.t*3));
+
+ ctx.beginPath();
+ ctx.ellipse(0,0,bodyLen,bodyHeight,0,0,Math.PI*2);
+ const grad = ctx.createLinearGradient(-bodyLen,0,bodyLen,0);
+ grad.addColorStop(0, primary);
+ grad.addColorStop(0.4, 'rgba(0,0,0,0.7)');
+ grad.addColorStop(1, primary);
+ ctx.fillStyle = grad;
+ ctx.globalAlpha = 0.9;
+ ctx.fill();
+
+ ctx.globalAlpha = 1;
+ ctx.beginPath();
+ ctx.ellipse(bodyLen*0.45, -bodyHeight*0.1, 18, 15, 0, 0, Math.PI*2);
+ ctx.fillStyle = accent;
+ ctx.fill();
+
+ ctx.fillStyle = '#050608';
+ ctx.beginPath();
+ ctx.ellipse(bodyLen*0.52, -bodyHeight*0.12, 7, 6, 0, 0, Math.PI*2);
+ ctx.fill();
+
+ const eyeOffsetY = -bodyHeight*0.14;
+ const eyeOffsetX = bodyLen*0.56;
+ ctx.beginPath();
+ ctx.ellipse(eyeOffsetX, eyeOffsetY, 2.2, 3.4, 0, 0, Math.PI*2);
+ ctx.fillStyle = '#eafffb';
+ ctx.fill();
+ ctx.beginPath();
+ ctx.ellipse(eyeOffsetX+0.2, eyeOffsetY-0.2, 0.9, 1.3, 0, 0, Math.PI*2);
+ ctx.fillStyle = '#1a3a48';
+ ctx.fill();
+
+ ctx.strokeStyle = accent;
+ ctx.lineWidth = 1.1;
+ ctx.beginPath();
+ ctx.moveTo(bodyLen*0.52, eyeOffsetY+4);
+ ctx.lineTo(bodyLen*0.62, eyeOffsetY+6);
+ ctx.stroke();
+
+ ctx.strokeStyle = 'rgba(0,0,0,0.5)';
+ ctx.lineWidth = 1.3;
+ const legPhase = Math.sin(state.t*3);
+ for (let i=0;i<3;i++) {
+ const lx = -bodyLen*0.25 + i*bodyLen*0.25;
+ const ly = bodyHeight*0.6;
+ ctx.beginPath();
+ ctx.moveTo(lx, ly);
+ ctx.quadraticCurveTo(lx+4*legPhase, ly+10, lx, ly+22);
+ ctx.stroke();
+ }
+
+ ctx.beginPath();
+ const tailBaseX = -bodyLen*0.52;
+ const tailBaseY = -bodyHeight*0.1;
+ const tailWave = Math.sin(state.t*2.5 + (facingLeft?0:Math.PI));
+ ctx.moveTo(tailBaseX, tailBaseY);
+ const ctrl1X = tailBaseX - 18;
+ const ctrl1Y = tailBaseY - 18*tailWave;
+ const ctrl2X = tailBaseX - 32;
+ const ctrl2Y = tailBaseY + 12*tailWave;
+ const tipX = tailBaseX - 40;
+ const tipY = tailBaseY + 8;
+ ctx.bezierCurveTo(ctrl1X,ctrl1Y,ctrl2X,ctrl2Y,tipX,tipY);
+ ctx.strokeStyle = accent;
+ ctx.lineWidth = 3;
+ ctx.stroke();
+
+ ctx.restore();
+ }
+
+ drawCat(lion, '#2d7a4e', '#9dffb3', false);
+ drawCat(tiger, '#7a422d', '#ffb46a', true);
+
+ if (lion) {
+ const r = 9 + 10*state.lion.momentum;
+ ctx.beginPath();
+ ctx.arc(lion.x, lion.y, r, 0, Math.PI*2);
+ ctx.strokeStyle = 'rgba(132,255,194,0.4)';
+ ctx.lineWidth = 1.2;
+ ctx.stroke();
+ }
+ if (tiger) {
+ const r = 9 + 10*state.tiger.momentum;
+ ctx.beginPath();
+ ctx.arc(tiger.x, tiger.y, r, 0, Math.PI*2);
+ ctx.strokeStyle = 'rgba(255,201,142,0.4)';
+ ctx.lineWidth = 1.2;
+ ctx.stroke();
+ }
+ }
+
+ function render() {
+ drawBackground();
+ drawTrails();
+ drawAgents();
+ drawPulses();
+ }
+
+ let last = performance.now();
+ function loop(now) {
+ const dt = Math.min(0.05, (now-last)/1000);
+ last = now;
+ update(dt);
+ render();
+ requestAnimationFrame(loop);
+ }
+
+ resetArena(false);
+ addLog('ENGINE', 'Visualizer ready. Choose a preset, tweak aggressions, then run an encounter. All motion is simulated, not taken from real fights.', 'engine');
+ requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9de63223a377/gpt.png b/data/artifacts/9de63223a377/gpt.png
new file mode 100644
index 0000000..d268730
Binary files /dev/null and b/data/artifacts/9de63223a377/gpt.png differ
diff --git a/data/artifacts/9de63223a377/grok.html b/data/artifacts/9de63223a377/grok.html
new file mode 100644
index 0000000..ad4cd57
--- /dev/null
+++ b/data/artifacts/9de63223a377/grok.html
@@ -0,0 +1,558 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Lion vs Tiger — Jungle Clash</title>
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+html,body{width:100%;height:100%;overflow:hidden;background:#0a1a0a;font-family:system-ui,sans-serif}
+canvas{display:block;width:100%;height:100%}
+#hud{position:absolute;top:0;left:0;right:0;padding:12px 18px;display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none;z-index:10}
+.badge{background:rgba(0,0,0,.55);backdrop-filter:blur(6px);border:1px solid rgba(255,200,80,.35);border-radius:10px;padding:8px 14px;color:#f5e6c8;font-size:13px;letter-spacing:.03em}
+.badge b{color:#ffd76a}
+.title{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none;z-index:5;opacity:1;transition:opacity 1.2s}
+.title h1{font-size:clamp(28px,6vw,56px);color:#ffe08a;text-shadow:0 0 20px #c90,0 4px 0 #3a2000;letter-spacing:.08em;font-weight:800}
+.title p{color:#c8e6a0;margin-top:8px;font-size:14px;opacity:.85}
+.title.fade{opacity:0}
+#bar{position:absolute;bottom:18px;left:50%;transform:translateX(-50%);width:min(420px,86vw);display:flex;gap:10px;align-items:center;z-index:10}
+.hp{flex:1;height:14px;background:#1a1008;border-radius:8px;border:1px solid rgba(255,220,120,.3);overflow:hidden;position:relative}
+.hp>i{display:block;height:100%;width:100%;transform-origin:left;transition:transform .25s}
+.hp.lion>i{background:linear-gradient(90deg,#e8a020,#ffd060)}
+.hp.tiger>i{background:linear-gradient(90deg,#e05010,#ff9040);transform-origin:right}
+.vs{color:#ffe9a0;font-weight:800;font-size:14px;text-shadow:0 0 8px #a60}
+#tip{position:absolute;bottom:48px;left:50%;transform:translateX(-50%);color:rgba(220,255,180,.7);font-size:12px;z-index:10;white-space:nowrap}
+</style>
+</head>
+<body>
+<div id="hud">
+ <div class="badge">Model: <b>Canvas-Sim · HF-Style Diffusion Sketch</b></div>
+ <div class="badge">Scene: <b>Jungle Apex Clash</b></div>
+</div>
+<div class="title" id="title">
+ <h1>LION VS TIGER</h1>
+ <p>procedural graphic sim · no external weights</p>
+</div>
+<div id="tip">click / tap to roar & clash</div>
+<div id="bar">
+ <div class="hp lion" title="Lion"><i id="lionHp"></i></div>
+ <div class="vs">VS</div>
+ <div class="hp tiger" title="Tiger"><i id="tigerHp"></i></div>
+</div>
+<canvas id="c"></canvas>
+<script>
+const canvas=document.getElementById('c');
+const ctx=canvas.getContext('2d');
+let W,H,dpr;
+function resize(){
+ dpr=Math.min(devicePixelRatio||1,2);
+ W=innerWidth;H=innerHeight;
+ canvas.width=W*dpr;canvas.height=H*dpr;
+ ctx.setTransform(dpr,0,0,dpr,0,0);
+}
+resize();
+addEventListener('resize',resize);
+
+// seeded rng for reproducible "model" look
+function mulberry32(a){return function(){let t=a+=0x6D2B79F5;t=Math.imul(t^t>>>15,t|1);t^=t+Math.imul(t^t>>>7,t|61);return((t^t>>>14)>>>0)/4294967296}}
+const rnd=mulberry32(0xL10N^0xT1GR);
+
+// jungle palette
+const COL={
+ canopy:['#0d2a12','#143818','#1a4a20','#0f3015'],
+ trunk:['#3a2818','#2a1c10','#4a3220'],
+ leaf:['#1e6b2e','#2a8a3a','#165022','#3aaa48','#0e3a18'],
+ ground:['#2a1e10','#1a140c','#3a2818'],
+ vine:'#1a5a28',
+ mist:'rgba(40,80,50,.08)'
+};
+
+// particles
+const dust=[],roars=[],sparks=[];
+function burst(x,y,n,hue){
+ for(let i=0;i<n;i++){
+ const a=rnd()*Math.PI*2,sp=2+rnd()*6;
+ sparks.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-2,life:1,hue});
+ }
+}
+
+// flora
+const trees=[],bushes=[],vines=[],fireflies=[];
+function buildWorld(){
+ trees.length=0;bushes.length=0;vines.length=0;fireflies.length=0;
+ for(let i=0;i<14;i++){
+ trees.push({
+ x:rnd()*W,y:H*0.55+rnd()*H*0.25,
+ h:120+rnd()*220,w:18+rnd()*28,
+ lean:(rnd()-.5)*.15,shade:rnd()
+ });
+ }
+ trees.sort((a,b)=>a.y-b.y);
+ for(let i=0;i<40;i++){
+ bushes.push({x:rnd()*W,y:H*0.62+rnd()*H*0.35,r:20+rnd()*50,c:COL.leaf[i%COL.leaf.length]});
+ }
+ for(let i=0;i<18;i++){
+ vines.push({x:rnd()*W,y:H*0.05+rnd()*H*0.2,len:80+rnd()*180,sw:rnd()*Math.PI*2});
+ }
+ for(let i=0;i<35;i++){
+ fireflies.push({x:rnd()*W,y:H*0.3+rnd()*H*0.5,p:rnd()*Math.PI*2,s:.5+rnd()});
+ }
+}
+buildWorld();
+addEventListener('resize',()=>{buildWorld();placeFighters()});
+
+// fighters
+const lion={x:0,y:0,face:1,hp:100,shake:0,frame:0,state:'idle',timer:0,name:'Lion'};
+const tiger={x:0,y:0,face:-1,hp:100,shake:0,frame:0,state:'idle',timer:0,name:'Tiger'};
+function placeFighters(){
+ const mid=H*0.72;
+ lion.x=W*0.32;lion.y=mid;
+ tiger.x=W*0.68;tiger.y=mid;
+}
+placeFighters();
+
+function drawMane(ctx,x,y,s,t){
+ ctx.save();
+ for(let i=0;i<16;i++){
+ const a=-Math.PI*.15+i/15*Math.PI*1.3+(Math.sin(t*.05+i)*.08);
+ const r=s*(1.1+Math.sin(t*.08+i*1.3)*.08);
+ ctx.strokeStyle=`hsl(${32+i*2},75%,${35+i%4*5}%)`;
+ ctx.lineWidth=s*0.18;
+ ctx.lineCap='round';
+ ctx.beginPath();
+ ctx.moveTo(x,y);
+ ctx.quadraticCurveTo(x+Math.cos(a)*r*.6,y+Math.sin(a)*r*.55-s*.1,x+Math.cos(a)*r,y+Math.sin(a)*r*.9);
+ ctx.stroke();
+ }
+ ctx.restore();
+}
+
+function drawLion(f,t){
+ const {x,y}=f;
+ const bob=Math.sin(t*.06)*3;
+ const sx=f.face;
+ ctx.save();
+ ctx.translate(x+(f.shake?(rnd()-.5)*f.shake:0),y+bob+(f.shake?(rnd()-.5)*f.shake:0));
+ ctx.scale(sx,1);
+ // shadow
+ ctx.fillStyle='rgba(0,0,0,.35)';
+ ctx.beginPath();ctx.ellipse(0,12,48,10,0,0,Math.PI*2);ctx.fill();
+ // hind leg
+ ctx.fillStyle='#c4892a';
+ ctx.beginPath();ctx.moveTo(-20,0);ctx.quadraticCurveTo(-38,20,-30,38);ctx.lineTo(-18,38);ctx.quadraticCurveTo(-22,18,-8,5);ctx.fill();
+ // body
+ ctx.fillStyle='#d4a03a';
+ ctx.beginPath();ctx.ellipse(0,-8,40,22,0,0,Math.PI*2);ctx.fill();
+ // belly
+ ctx.fillStyle='#e8c878';
+ ctx.beginPath();ctx.ellipse(4,-2,28,14,0,0,Math.PI*2);ctx.fill();
+ // front leg
+ ctx.fillStyle='#c4892a';
+ ctx.beginPath();ctx.moveTo(18,0);ctx.quadraticCurveTo(28,22,22,40);ctx.lineTo(34,40);ctx.quadraticCurveTo(40,18,30,-5);ctx.fill();
+ // back leg far
+ ctx.fillStyle='#b87a22';
+ ctx.beginPath();ctx.moveTo(-10,5);ctx.lineTo(-5,40);ctx.lineTo(6,40);ctx.lineTo(8,5);ctx.fill();
+ // mane
+ drawMane(ctx,28,-28,34,t);
+ // head
+ ctx.fillStyle='#e0b040';
+ ctx.beginPath();ctx.ellipse(30,-30,22,20,0,0,Math.PI*2);ctx.fill();
+ // ear
+ ctx.fillStyle='#d49830';
+ ctx.beginPath();ctx.ellipse(18,-48,7,9, -.3,0,Math.PI*2);ctx.fill();
+ ctx.beginPath();ctx.ellipse(40,-47,7,9,.3,0,Math.PI*2);ctx.fill();
+ // snout
+ ctx.fillStyle='#f0d080';
+ ctx.beginPath();ctx.ellipse(42,-26,12,10,0,0,Math.PI*2);ctx.fill();
+ // nose
+ ctx.fillStyle='#3a2010';
+ ctx.beginPath();ctx.ellipse(50,-28,5,4,0,0,Math.PI*2);ctx.fill();
+ // eyes
+ ctx.fillStyle='#1a1008';
+ ctx.beginPath();ctx.ellipse(34,-36,3.2,3.5,0,0,Math.PI*2);ctx.fill();
+ ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(35,-37,1.1,0,Math.PI*2);ctx.fill();
+ // mouth roar
+ if(f.state==='attack'||f.state==='roar'){
+ ctx.strokeStyle='#2a1008';ctx.lineWidth=2;
+ ctx.beginPath();ctx.arc(46,-22,6,0.2,Math.PI-0.2);ctx.stroke();
+ ctx.fillStyle='#fff';
+ ctx.fillRect(42,-22,3,5);ctx.fillRect(48,-22,3,5);
+ }else{
+ ctx.strokeStyle='#2a1008';ctx.lineWidth=1.5;
+ ctx.beginPath();ctx.moveTo(44,-20);ctx.quadraticCurveTo(48,-17,52,-21);ctx.stroke();
+ }
+ // tail
+ ctx.strokeStyle='#d4a03a';ctx.lineWidth=6;ctx.lineCap='round';
+ ctx.beginPath();ctx.moveTo(-38,-10);
+ const tw=Math.sin(t*.1)*12;
+ ctx.bezierCurveTo(-60,-30+tw,-70,10+tw,-55,28);
+ ctx.stroke();
+ ctx.fillStyle='#c4892a';
+ ctx.beginPath();ctx.arc(-55,28,7,0,Math.PI*2);ctx.fill();
+ // claws flash
+ if(f.state==='attack'){
+ ctx.strokeStyle='rgba(255,230,150,.85)';ctx.lineWidth=2;
+ for(let i=0;i<3;i++){
+ ctx.beginPath();ctx.moveTo(38,20+i*6);ctx.lineTo(58+i*4,10+i*8);ctx.stroke();
+ }
+ }
+ ctx.restore();
+}
+
+function drawTiger(f,t){
+ const {x,y}=f;
+ const bob=Math.sin(t*.06+1)*.3*3;
+ const sx=f.face;
+ ctx.save();
+ ctx.translate(x+(f.shake?(rnd()-.5)*f.shake:0),y+bob+(f.shake?(rnd()-.5)*f.shake:0));
+ ctx.scale(sx,1);
+ ctx.fillStyle='rgba(0,0,0,.35)';
+ ctx.beginPath();ctx.ellipse(0,12,50,10,0,0,Math.PI*2);ctx.fill();
+ // legs
+ ctx.fillStyle='#e07020';
+ ctx.beginPath();ctx.moveTo(-22,0);ctx.quadraticCurveTo(-40,22,-32,40);ctx.lineTo(-20,40);ctx.quadraticCurveTo(-24,18,-10,5);ctx.fill();
+ ctx.beginPath();ctx.moveTo(16,2);ctx.quadraticCurveTo(26,24,20,42);ctx.lineTo(32,42);ctx.quadraticCurveTo(38,20,28,-4);ctx.fill();
+ // body
+ ctx.fillStyle='#f07828';
+ ctx.beginPath();ctx.ellipse(0,-8,44,23,0,0,Math.PI*2);ctx.fill();
+ // stripes body
+ ctx.strokeStyle='#1a0c08';ctx.lineWidth=3.5;ctx.lineCap='round';
+ for(let i=0;i<6;i++){
+ const bx=-28+i*11;
+ ctx.beginPath();ctx.moveTo(bx,-22);ctx.quadraticCurveTo(bx+4,-8,bx,-+2);ctx.stroke();
+ }
+ // white belly
+ ctx.fillStyle='#f5e0c0';
+ ctx.beginPath();ctx.ellipse(2,0,26,12,0,0,Math.PI*2);ctx.fill();
+ // head
+ ctx.fillStyle='#f08030';
+ ctx.beginPath();ctx.ellipse(32,-32,23,21,0,0,Math.PI*2);ctx.fill();
+ // head stripes
+ ctx.strokeStyle='#1a0c08';ctx.lineWidth=3;
+ ctx.beginPath();ctx.moveTo(20,-48);ctx.lineTo(26,-34);ctx.stroke();
+ ctx.beginPath();ctx.moveTo(32,-50);ctx.lineTo(34,-36);ctx.stroke();
+ ctx.beginPath();ctx.moveTo(44,-48);ctx.lineTo(40,-34);ctx.stroke();
+ // ears
+ ctx.fillStyle='#e06820';
+ ctx.beginPath();ctx.moveTo(16,-48);ctx.lineTo(12,-62);ctx.lineTo(26,-52);ctx.fill();
+ ctx.beginPath();ctx.moveTo(42,-50);ctx.lineTo(48,-64);ctx.lineTo(52,-48);ctx.fill();
+ ctx.fillStyle='#f5b0a0';
+ ctx.beginPath();ctx.moveTo(17,-50);ctx.lineTo(15,-58);ctx.lineTo(23,-52);ctx.fill();
+ // muzzle
+ ctx.fillStyle='#f5e0c0';
+ ctx.beginPath();ctx.ellipse(44,-28,13,11,0,0,Math.PI*2);ctx.fill();
+ // nose
+ ctx.fillStyle='#2a1010';
+ ctx.beginPath();ctx.ellipse(54,-30,5,4,0,0,Math.PI*2);ctx.fill();
+ // eyes
+ ctx.fillStyle='#1a1008';
+ ctx.beginPath();ctx.ellipse(36,-38,3.5,3.8,0,0,Math.PI*2);ctx.fill();
+ ctx.fillStyle='#c0ff40';ctx.beginPath();ctx.arc(36.5,-38,1.6,0,Math.PI*2);ctx.fill();
+ ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(37.2,-39,0.8,0,Math.PI*2);ctx.fill();
+ // whiskers
+ ctx.strokeStyle='rgba(40,20,10,.5)';ctx.lineWidth=1;
+ for(let i=0;i<3;i++){
+ ctx.beginPath();ctx.moveTo(48,-26+i*4);ctx.lineTo(68,-24+i*5);ctx.stroke();
+ }
+ // mouth
+ if(f.state==='attack'||f.state==='roar'){
+ ctx.fillStyle='#2a0808';
+ ctx.beginPath();ctx.ellipse(50,-20,8,6,0,0,Math.PI);ctx.fill();
+ ctx.fillStyle='#fff';
+ ctx.fillRect(45,-22,3,6);ctx.fillRect(52,-22,3,6);
+ }else{
+ ctx.strokeStyle='#2a1008';ctx.lineWidth=1.5;
+ ctx.beginPath();ctx.moveTo(46,-20);ctx.quadraticCurveTo(52,-16,56,-21);ctx.stroke();
+ }
+ // tail
+ ctx.strokeStyle='#f07828';ctx.lineWidth=7;ctx.lineCap='round';
+ ctx.beginPath();ctx.moveTo(-40,-8);
+ const tw=Math.sin(t*.12+2)*14;
+ ctx.bezierCurveTo(-65,-25+tw,-80,5+tw,-62,30);
+ ctx.stroke();
+ // tail stripes + tip
+ ctx.strokeStyle='#1a0c08';ctx.lineWidth=3;
+ ctx.beginPath();ctx.moveTo(-55,-12);ctx.lineTo(-58,0);ctx.stroke();
+ ctx.fillStyle='#1a0c08';
+ ctx.beginPath();ctx.arc(-62,30,6,0,Math.PI*2);ctx.fill();
+ if(f.state==='attack'){
+ ctx.strokeStyle='rgba(255,120,40,.9)';ctx.lineWidth=2.5;
+ for(let i=0;i<3;i++){
+ ctx.beginPath();ctx.moveTo(36,18+i*7);ctx.lineTo(60+i*3,6+i*9);ctx.stroke();
+ }
+ }
+ ctx.restore();
+}
+
+function drawTree(tr,t){
+ const {x,y,h,w,lean,shade}=tr;
+ ctx.save();
+ ctx.translate(x,y);
+ ctx.transform(1,0,lean,1,0,0);
+ // trunk
+ const g=ctx.createLinearGradient(-w/2,0,w/2,0);
+ g.addColorStop(0,'#2a1c10');g.addColorStop(.5,'#4a3222');g.addColorStop(1,'#2a1c10');
+ ctx.fillStyle=g;
+ ctx.beginPath();
+ ctx.moveTo(-w*.45,0);ctx.lineTo(-w*.25,-h);ctx.lineTo(w*.25,-h);ctx.lineTo(w*.45,0);
+ ctx.closePath();ctx.fill();
+ // canopy layers
+ const layers=4+((shade*3)|0);
+ for(let i=0;i<layers;i++){
+ const cy=-h+20-i*28;
+ const cr=50+i*18+shade*20;
+ ctx.fillStyle=COL.leaf[(i+((shade*10)|0))%COL.leaf.length];
+ ctx.beginPath();
+ ctx.ellipse(Math.sin(t*.02+i+x)*.5*8,cy,cr*(.9+shade*.2),cr*.62,0,0,Math.PI*2);
+ ctx.fill();
+ }
+ ctx.restore();
+}
+
+function drawBush(b){
+ ctx.fillStyle=b.c;
+ ctx.beginPath();
+ ctx.ellipse(b.x,b.y,b.r,b.r*.55,0,0,Math.PI*2);
+ ctx.fill();
+ ctx.fillStyle='rgba(0,0,0,.12)';
+ ctx.beginPath();
+ ctx.ellipse(b.x,b.y+b.r*.1,b.r*.7,b.r*.3,0,0,Math.PI*2);
+ ctx.fill();
+}
+
+function drawVine(v,t){
+ ctx.strokeStyle=COL.vine;ctx.lineWidth=3;ctx.lineCap='round';
+ ctx.beginPath();
+ ctx.moveTo(v.x,v.y);
+ const sway=Math.sin(t*.03+v.sw)*18;
+ ctx.bezierCurveTo(v.x+sway,v.y+v.len*.3,v.x-sway*.5,v.y+v.len*.6,v.x+sway*.3,v.y+v.len);
+ ctx.stroke();
+ // leaves on vine
+ ctx.fillStyle='#2a8a38';
+ for(let i=1;i<=3;i++){
+ const py=v.y+v.len*(i/4);
+ const px=v.x+Math.sin(t*.03+v.sw+i)*.5*sway;
+ ctx.beginPath();ctx.ellipse(px+8,py,10,5,.5,0,Math.PI*2);ctx.fill();
+ }
+}
+
+function drawGround(){
+ const g=ctx.createLinearGradient(0,H*.55,0,H);
+ g.addColorStop(0,'#1a2e14');
+ g.addColorStop(.3,'#243818');
+ g.addColorStop(.6,'#2a2410');
+ g.addColorStop(1,'#1a140c');
+ ctx.fillStyle=g;
+ ctx.fillRect(0,H*.55,W,H*.45);
+ // grass tufts
+ ctx.strokeStyle='#2a6a28';ctx.lineWidth=2;
+ for(let i=0;i<60;i++){
+ const gx=(i*97.3+50)%W, gy=H*.6+((i*53)%(H*.38));
+ ctx.beginPath();ctx.moveTo(gx,gy);ctx.quadraticCurveTo(gx-4,gy-12,gx+2,gy-20);ctx.stroke();
+ ctx.beginPath();ctx.moveTo(gx,gy);ctx.quadraticCurveTo(gx+5,gy-10,gx-1,gy-18);ctx.stroke();
+ }
+}
+
+function drawCanopyBg(t){
+ // deep jungle bg
+ const bg=ctx.createLinearGradient(0,0,0,H*.6);
+ bg.addColorStop(0,'#061208');
+ bg.addColorStop(.4,'#0c2210');
+ bg.addColorStop(1,'#1a3a18');
+ ctx.fillStyle=bg;
+ ctx.fillRect(0,0,W,H);
+ // distant silhouettes
+ ctx.fillStyle='rgba(10,30,15,.7)';
+ for(let i=0;i<8;i++){
+ const x=(i/8)*W+Math.sin(t*.01+i)*10;
+ ctx.beginPath();
+ ctx.moveTo(x-80,H*.58);
+ ctx.quadraticCurveTo(x,H*.2+i*8,x+80,H*.58);
+ ctx.fill();
+ }
+ // god rays
+ ctx.save();
+ ctx.globalCompositeOperation='lighter';
+ for(let i=0;i<5;i++){
+ const rx=W*.15+i*W*.18+Math.sin(t*.008+i)*30;
+ const grd=ctx.createLinearGradient(rx,0,rx-40,H*.7);
+ grd.addColorStop(0,'rgba(180,220,100,.04)');
+ grd.addColorStop(1,'rgba(180,220,100,0)');
+ ctx.fillStyle=grd;
+ ctx.beginPath();ctx.moveTo(rx-30,0);ctx.lineTo(rx+30,0);ctx.lineTo(rx-60,H*.7);ctx.lineTo(rx-120,H*.7);ctx.fill();
+ }
+ ctx.restore();
+}
+
+function drawFireflies(t){
+ for(const f of fireflies){
+ const a=.4+.6*Math.sin(t*.05*f.s+f.p);
+ const fx=f.x+Math.sin(t*.02+f.p)*20;
+ const fy=f.y+Math.cos(t*.025+f.p)*12;
+ ctx.fillStyle=`rgba(180,255,100,${a})`;
+ ctx.beginPath();ctx.arc(fx,fy,2.2,0,Math.PI*2);ctx.fill();
+ ctx.fillStyle=`rgba(200,255,140,${a*.35})`;
+ ctx.beginPath();ctx.arc(fx,fy,6,0,Math.PI*2);ctx.fill();
+ }
+}
+
+// simple "neural" noise overlay to nod at generative models
+function drawModelGrain(t){
+ ctx.save();
+ ctx.globalAlpha=.03;
+ for(let i=0;i<80;i++){
+ const x=(rnd()*W + t*.2*i)%W;
+ const y=(rnd()*H)%H;
+ ctx.fillStyle=rnd()>.5?'#9f6':'#fc6';
+ ctx.fillRect(x,y,1.5,1.5);
+ }
+ ctx.restore();
+}
+
+let time=0, clashCD=0;
+function update(dt){
+ time++;
+ // AI-ish idle behavior
+ [lion,tiger].forEach((f,idx)=>{
+ f.timer--;
+ f.shake*=.85;
+ if(f.timer<=0 && f.state!=='dead'){
+ const r=rnd();
+ if(r<.15){f.state='roar';f.timer=40;roars.push({x:f.x,y:f.y-60,life:1,side:idx});}
+ else if(r<.35 && clashCD<=0){f.state='attack';f.timer=28;}
+ else {f.state='idle';f.timer=30+rnd()*60;}
+ }
+ if(f.state==='attack' && f.timer===14){
+ // hit check
+ const other=f===lion?tiger:lion;
+ const dx=Math.abs(f.x-other.x);
+ if(dx<160){
+ other.hp=Math.max(0,other.hp-8-rnd()*10);
+ other.shake=14;
+ other.state='hit';other.timer=18;
+ burst((f.x+other.x)/2,(f.y+other.y)/2-20,18,f===lion?40:20);
+ // knockback
+ other.x+=f.face*18;
+ clashCD=25;
+ }
+ }
+ // keep on screen
+ f.x=Math.max(60,Math.min(W-60,f.x));
+ // approach slowly
+ if(f.state==='idle'){
+ const other=f===lion?tiger:lion;
+ const dx=other.x-f.x;
+ if(Math.abs(dx)>140) f.x+=Math.sign(dx)*.35;
+ f.face=dx>=0?1:-1;
+ }
+ });
+ if(clashCD>0)clashCD--;
+ // sparks
+ for(let i=sparks.length-1;i>=0;i--){
+ const s=sparks[i];
+ s.x+=s.vx;s.y+=s.vy;s.vy+=.2;s.life-=.03;
+ if(s.life<=0)sparks.splice(i,1);
+ }
+ for(let i=roars.length-1;i>=0;i--){
+ roars[i].life-=.025;
+ if(roars[i].life<=0)roars.splice(i,1);
+ }
+ // hp bars
+ document.getElementById('lionHp').style.transform=`scaleX(${lion.hp/100})`;
+ document.getElementById('tigerHp').style.transform=`scaleX(${tiger.hp/100})`;
+ if(lion.hp<=0||tiger.hp<=0){
+ const winner=lion.hp<=0?tiger:lion;
+ winner.state='roar';
+ }
+}
+
+function drawRoars(){
+ for(const r of roars){
+ ctx.save();
+ ctx.translate(r.x,r.y);
+ ctx.globalAlpha=r.life*.7;
+ ctx.strokeStyle=r.side===0?'#ffd070':'#ff9040';
+ ctx.lineWidth=3;
+ for(let i=1;i<=3;i++){
+ const rad=(1-r.life)*30+i*14;
+ ctx.beginPath();ctx.arc(0,0,rad, -1.2+r.side, -.2+r.side);ctx.stroke();
+ }
+ ctx.fillStyle=r.side===0?'#ffe0a0':'#ffb080';
+ ctx.font=`bold ${16+(1-r.life)*10}px system-ui`;
+ ctx.textAlign='center';
+ ctx.fillText(r.side===0?'ROAR':'RAWR',0,-10);
+ ctx.restore();
+ }
+}
+
+function drawSparks(){
+ for(const s of sparks){
+ ctx.globalAlpha=s.life;
+ ctx.fillStyle=`hsl(${s.hue},90%,60%)`;
+ ctx.beginPath();ctx.arc(s.x,s.y,3*s.life,0,Math.PI*2);ctx.fill();
+ }
+ ctx.globalAlpha=1;
+}
+
+function frame(){
+ update(1);
+ drawCanopyBg(time);
+ // far trees
+ for(const tr of trees){
+ if(tr.y<H*.7) drawTree(tr,time);
+ }
+ drawGround();
+ for(const v of vines) drawVine(v,time);
+ for(const b of bushes) drawBush(b);
+ // fighters (depth sort)
+ if(lion.y<=tiger.y){drawLion(lion,time);drawTiger(tiger,time);}
+ else{drawTiger(tiger,time);drawLion(lion,time);}
+ // near trees
+ for(const tr of trees){
+ if(tr.y>=H*.7) drawTree(tr,time);
+ }
+ drawFireflies(time);
+ drawSparks();
+ drawRoars();
+ drawModelGrain(time);
+ // vignette
+ const vig=ctx.createRadialGradient(W/2,H/2,H*.2,W/2,H/2,H*.75);
+ vig.addColorStop(0,'rgba(0,0,0,0)');
+ vig.addColorStop(1,'rgba(0,0,0,.45)');
+ ctx.fillStyle=vig;ctx.fillRect(0,0,W,H);
+
+ // watermark nod to HF-style pipeline
+ ctx.save();
+ ctx.globalAlpha=.35;
+ ctx.fillStyle='#cfe8a8';
+ ctx.font='11px system-ui';
+ ctx.fillText('pipeline: procedural-canvas ≈ SD-sketch · depth-jungle · animal-lora sim', 16, H-12);
+ ctx.restore();
+
+ requestAnimationFrame(frame);
+}
+
+// title fade
+setTimeout(()=>document.getElementById('title').classList.add('fade'),2800);
+
+// interaction
+function poke(ev){
+ const rect=canvas.getBoundingClientRect();
+ const px=(ev.clientX??ev.touches?.[0]?.clientX??W/2)-rect.left;
+ const target=px<W/2?lion:tiger;
+ target.state='roar';target.timer=35;
+ roars.push({x:target.x,y:target.y-60,life:1,side:target===lion?0:1});
+ // both leap toward center
+ lion.state='attack';lion.timer=30;lion.face=1;
+ tiger.state='attack';tiger.timer=30;tiger.face=-1;
+ lion.x+=10;tiger.x-=10;
+ burst(W/2,H*.65,24,30);
+ clashCD=10;
+}
+canvas.addEventListener('click',poke);
+canvas.addEventListener('touchstart',e=>{e.preventDefault();poke(e);},{passive:false});
+
+frame();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9de63223a377/grok.png b/data/artifacts/9de63223a377/grok.png
new file mode 100644
index 0000000..2be9d85
Binary files /dev/null and b/data/artifacts/9de63223a377/grok.png differ
diff --git a/data/artifacts/9de63223a377/hermes3-8b.html b/data/artifacts/9de63223a377/hermes3-8b.html
new file mode 100644
index 0000000..6d5122e
--- /dev/null
+++ b/data/artifacts/9de63223a377/hermes3-8b.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Lion vs Tiger in the Jungle</title>
+ <style>
+ body {
+ background-color: #f0e68c;
+ font-family: Arial, sans-serif;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ margin: 0;
+ }
+
+ .jungle {
+ background-image: url(https://dummyimage.com/600x400/2c3e50/white.png&text=Jungle);
+ background-size: cover;
+ width: 95vw;
+ height: 85vh;
+ border-radius: 10px;
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+ display: flex;
+ justify-content: space-evenly;
+ align-items: center;
+ }
+
+ .animal {
+ background-size: cover;
+ width: 150px;
+ height: 100px;
+ }
+
+ #lion {
+ background-image: url(https://dummyimage.com/600x400/crimson/white.png&text=Lion);
+ }
+
+ #tiger {
+ background-image: url(https://dummyimage.com/600x400/tomato/white.png&text=Tiger);
+ }
+ </style>
+</head>
+<body>
+ <div class="jungle">
+ <div id="lion" class="animal"></div>
+ <div id="tiger" class="animal"></div>
+ </div>
+
+ <script>
+
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9de63223a377/hermes3-8b.png b/data/artifacts/9de63223a377/hermes3-8b.png
new file mode 100644
index 0000000..7d0aefc
Binary files /dev/null and b/data/artifacts/9de63223a377/hermes3-8b.png differ
diff --git a/data/artifacts/9de63223a377/kimi.html b/data/artifacts/9de63223a377/kimi.html
new file mode 100644
index 0000000..0b30891
--- /dev/null
+++ b/data/artifacts/9de63223a377/kimi.html
@@ -0,0 +1,446 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Lion vs Tiger - Jungle Arena</title>
+<style>
+* { margin: 0; padding: 0; box-sizing: border-box; }
+body { overflow: hidden; background: #0a1f0a; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
+canvas { display: block; width: 100vw; height: 100vh; }
+#ui { position: absolute; top: 0; left: 0; width: 100%; pointer-events: none; }
+.health-bar { position: absolute; top: 20px; width: 300px; height: 30px; background: rgba(0,0,0,0.6); border: 2px solid #fff; border-radius: 15px; overflow: hidden; }
+#lion-hp { left: 20px; }
+#tiger-hp { right: 20px; }
+.hp-fill { height: 100%; transition: width 0.3s; }
+.lion-fill { background: linear-gradient(90deg, #FFD700, #FFA500); width: 100%; }
+.tiger-fill { background: linear-gradient(90deg, #FF6347, #FF4500); width: 100%; }
+.label { position: absolute; top: 55px; font-size: 24px; font-weight: bold; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.8); }
+#lion-label { left: 20px; }
+#tiger-label { right: 20px; text-align: right; width: 300px; }
+#vs { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); font-size: 48px; font-weight: 900; color: #ff0000; text-shadow: 3px 3px 0 #000, -1px -1px 0 #fff; letter-spacing: 4px; }
+</style>
+</head>
+<body>
+<canvas id="arena"></canvas>
+<div id="ui">
+ <div id="vs">VS</div>
+ <div class="health-bar" id="lion-hp"><div class="hp-fill lion-fill" id="lion-fill"></div></div>
+ <div class="health-bar" id="tiger-hp"><div class="hp-fill tiger-fill" id="tiger-fill"></div></div>
+ <div class="label" id="lion-label">🦁 LION</div>
+ <div class="label" id="tiger-label">🐅 TIGER</div>
+</div>
+
+<script>
+const canvas = document.getElementById('arena');
+const ctx = canvas.getContext('2d');
+let width, height;
+
+function resize() {
+ width = canvas.width = window.innerWidth;
+ height = canvas.height = window.innerHeight;
+}
+window.addEventListener('resize', resize);
+resize();
+
+// Utility
+const rand = (min, max) => Math.random() * (max - min) + min;
+const lerp = (a, b, t) => a + (b - a) * t;
+
+// Particle System
+class Particle {
+ constructor(x, y, type) {
+ this.x = x;
+ this.y = y;
+ this.type = type; // 'blood', 'dust', 'leaf'
+ this.vx = rand(-3, 3);
+ this.vy = rand(-3, 3);
+ this.life = 1;
+ this.decay = rand(0.02, 0.05);
+
+ if (type === 'blood') {
+ this.color = `hsl(${rand(0, 20)}, 100%, ${rand(40, 60)}%)`;
+ this.size = rand(2, 5);
+ } else if (type === 'dust') {
+ this.color = `rgba(139, 69, 19, ${this.life})`;
+ this.size = rand(3, 8);
+ this.vy = rand(-1, -3);
+ } else {
+ this.color = `hsl(${rand(100, 140)}, 80%, 40%)`;
+ this.size = rand(4, 7);
+ this.vy = rand(2, 5);
+ }
+ }
+
+ update() {
+ this.x += this.vx;
+ this.y += this.vy;
+ this.life -= this.decay;
+ if (this.type === 'dust') this.vy *= 0.95;
+ }
+
+ draw(ctx) {
+ ctx.globalAlpha = Math.max(0, this.life);
+ ctx.fillStyle = this.color;
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.globalAlpha = 1;
+ }
+}
+
+let particles = [];
+
+function createParticles(x, y, type, count = 5) {
+ for (let i = 0; i < count; i++) particles.push(new Particle(x, y, type));
+}
+
+// Environment
+const trees = [];
+for (let i = 0; i < 20; i++) {
+ trees.push({
+ x: rand(0, window.innerWidth),
+ y: rand(0, window.innerHeight),
+ size: rand(40, 80),
+ color: `hsl(${rand(100, 140)}, 60%, ${rand(20, 35)}%)`
+ });
+}
+
+// Animal Class
+class Animal {
+ constructor(x, y, type) {
+ this.x = x;
+ this.y = y;
+ this.type = type;
+ this.hp = 100;
+ this.maxHp = 100;
+ this.angle = 0;
+ this.state = 'idle';
+ this.stateTimer = 0;
+ this.target = null;
+ this.speed = type === 'lion' ? 2.2 : 2.5;
+ this.attackRange = 60;
+ this.attackCooldown = 0;
+ this.hitFlash = 0;
+ this.animFrame = 0;
+ this.scale = 1;
+ }
+
+ takeDamage(amount) {
+ this.hp = Math.max(0, this.hp - amount);
+ this.hitFlash = 10;
+ createParticles(this.x, this.y, 'blood', 8);
+ this.state = 'hit';
+ this.stateTimer = 20;
+
+ // Update UI
+ const fillId = this.type === 'lion' ? 'lion-fill' : 'tiger-fill';
+ document.getElementById(fillId).style.width = (this.hp / this.maxHp * 100) + '%';
+ }
+
+ update() {
+ this.animFrame++;
+ if (this.hitFlash > 0) this.hitFlash--;
+ if (this.attackCooldown > 0) this.attackCooldown--;
+
+ if (this.state === 'hit') {
+ this.stateTimer--;
+ if (this.stateTimer <= 0) this.state = 'chase';
+ return;
+ }
+
+ if (this.target && this.target.hp > 0) {
+ const dx = this.target.x - this.x;
+ const dy = this.target.y - this.y;
+ const dist = Math.sqrt(dx * dx + dy * dy);
+ this.angle = Math.atan2(dy, dx);
+
+ if (dist > this.attackRange) {
+ this.x += Math.cos(this.angle) * this.speed;
+ this.y += Math.sin(this.angle) * this.speed;
+ this.state = 'chase';
+ // Dust trail
+ if (this.animFrame % 10 === 0) createParticles(this.x, this.y, 'dust', 1);
+ } else {
+ this.state = 'attack';
+ if (this.attackCooldown <= 0) {
+ this.target.takeDamage(rand(8, 15));
+ this.attackCooldown = 40;
+ // Visual swipe effect
+ createParticles(this.x + Math.cos(this.angle)*40, this.y + Math.sin(this.angle)*40, 'dust', 3);
+ }
+ }
+ }
+
+ // Keep in bounds
+ this.x = Math.max(50, Math.min(width - 50, this.x));
+ this.y = Math.max(50, Math.min(height - 50, this.y));
+ }
+
+ draw(ctx) {
+ ctx.save();
+ ctx.translate(this.x, this.y);
+ ctx.rotate(this.angle);
+
+ // Hit flash
+ if (this.hitFlash > 0) {
+ ctx.globalCompositeOperation = 'source-atop';
+ ctx.fillStyle = 'white';
+ ctx.fillRect(-50, -50, 100, 100);
+ ctx.globalCompositeOperation = 'source-over';
+ }
+
+ // Shadow
+ ctx.fillStyle = 'rgba(0,0,0,0.4)';
+ ctx.beginPath();
+ ctx.ellipse(0, 25, 35, 12, 0, 0, Math.PI * 2);
+ ctx.fill();
+
+ const walkOffset = this.state === 'chase' ? Math.sin(this.animFrame * 0.2) * 5 : 0;
+
+ // Body
+ const bodyGradient = ctx.createRadialGradient(0, 0, 5, 0, 0, 35);
+ if (this.type === 'lion') {
+ bodyGradient.addColorStop(0, '#FFD700');
+ bodyGradient.addColorStop(1, '#B8860B');
+ } else {
+ bodyGradient.addColorStop(0, '#FF8C00');
+ bodyGradient.addColorStop(1, '#CC5500');
+ }
+ ctx.fillStyle = bodyGradient;
+
+ // Main body
+ ctx.beginPath();
+ ctx.ellipse(0, walkOffset, 40, 22, 0, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Tiger stripes
+ if (this.type === 'tiger') {
+ ctx.fillStyle = '#1a1a1a';
+ ctx.save();
+ ctx.clip();
+ for (let i = -30; i < 30; i += 12) {
+ ctx.beginPath();
+ ctx.moveTo(i, -20);
+ ctx.lineTo(i + 5, 0);
+ ctx.lineTo(i, 20);
+ ctx.lineTo(i - 3, 0);
+ ctx.fill();
+ }
+ ctx.restore();
+ }
+
+ // Legs (animated)
+ ctx.fillStyle = this.type === 'lion' ? '#B8860B' : '#CC5500';
+ const legPositions = [[-20, -15], [20, -15], [-20, 15], [20, 15]];
+ legPositions.forEach((pos, i) => {
+ const legMove = this.state === 'chase' ? Math.sin(this.animFrame * 0.3 + i * Math.PI/2) * 8 : 0;
+ ctx.beginPath();
+ ctx.ellipse(pos[0], pos[1] + legMove + walkOffset*0.5, 8, 12, 0, 0, Math.PI * 2);
+ ctx.fill();
+ });
+
+ // Head
+ ctx.fillStyle = this.type === 'lion' ? '#FFD700' : '#FF8C00';
+ ctx.beginPath();
+ ctx.arc(30, walkOffset * 0.5, 18, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Mane for Lion
+ if (this.type === 'lion') {
+ ctx.fillStyle = '#8B4513';
+ for (let i = 0; i < 12; i++) {
+ const angle = (i / 12) * Math.PI * 2;
+ const mx = 30 + Math.cos(angle) * 22;
+ const my = Math.sin(angle) * 22 + walkOffset * 0.5;
+ ctx.beginPath();
+ ctx.arc(mx, my, 9, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ // Inner mane
+ ctx.fillStyle = '#A0522D';
+ for (let i = 0; i < 8; i++) {
+ const angle = (i / 8) * Math.PI * 2 + 0.4;
+ const mx = 30 + Math.cos(angle) * 18;
+ const my = Math.sin(angle) * 18 + walkOffset * 0.5;
+ ctx.beginPath();
+ ctx.arc(mx, my, 6, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ }
+
+ // Face details
+ ctx.fillStyle = '#000';
+ // Eyes
+ ctx.beginPath();
+ ctx.arc(35, -6 + walkOffset * 0.5, 3, 0, Math.PI * 2);
+ ctx.arc(35, 6 + walkOffset * 0.5, 3, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Nose
+ ctx.fillStyle = '#333';
+ ctx.beginPath();
+ ctx.arc(42, walkOffset * 0.5, 4, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Ears
+ ctx.fillStyle = this.type === 'lion' ? '#8B4513' : '#FF8C00';
+ [-1, 1].forEach(side => {
+ ctx.beginPath();
+ ctx.arc(25, side * 16 + walkOffset * 0.5, 6, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.fillStyle = '#333';
+ ctx.beginPath();
+ ctx.arc(25, side * 16 + walkOffset * 0.5, 3, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.fillStyle = this.type === 'lion' ? '#8B4513' : '#FF8C00';
+ });
+
+ // Tail
+ ctx.strokeStyle = this.type === 'lion' ? '#8B4513' : '#FF8C00';
+ ctx.lineWidth = 6;
+ ctx.lineCap = 'round';
+ ctx.beginPath();
+ ctx.moveTo(-35, walkOffset);
+ const tailWag = Math.sin(this.animFrame * 0.1) * 10;
+ ctx.quadraticCurveTo(-55, tailWag, -65, tailWag * 1.5);
+ ctx.stroke();
+
+ // Attack swipe effect
+ if (this.state === 'attack' && this.attackCooldown > 30) {
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)';
+ ctx.lineWidth = 3;
+ ctx.beginPath();
+ const swipeProgress = (40 - this.attackCooldown) / 10;
+ ctx.arc(45, 0, 20 + swipeProgress * 20, -0.5, 0.5);
+ ctx.stroke();
+ }
+
+ ctx.restore();
+ }
+}
+
+// Initialize combatants
+const lion = new Animal(width * 0.3, height * 0.5, 'lion');
+const tiger = new Animal(width * 0.7, height * 0.5, 'tiger');
+lion.target = tiger;
+tiger.target = lion;
+
+// Camera shake
+let shake = 0;
+
+function drawEnvironment() {
+ // Ground gradient
+ const grad = ctx.createRadialGradient(width/2, height/2, 0, width/2, height/2, width);
+ grad.addColorStop(0, '#1a4d1a');
+ grad.addColorStop(1, '#0a1f0a');
+ ctx.fillStyle = grad;
+ ctx.fillRect(0, 0, width, height);
+
+ // Grid pattern for depth
+ ctx.strokeStyle = 'rgba(0, 50, 0, 0.3)';
+ ctx.lineWidth = 1;
+ for (let i = 0; i < width; i += 100) {
+ ctx.beginPath();
+ ctx.moveTo(i, 0);
+ ctx.lineTo(i, height);
+ ctx.stroke();
+ }
+ for (let i = 0; i < height; i += 100) {
+ ctx.beginPath();
+ ctx.moveTo(0, i);
+ ctx.lineTo(width, i);
+ ctx.stroke();
+ }
+
+ // Trees (back)
+ trees.sort((a, b) => a.y - b.y);
+ trees.forEach(tree => {
+ // Only draw if behind fighters
+ if (tree.y < Math.min(lion.y, tiger.y) - 50) {
+ drawTree(tree);
+ }
+ });
+}
+
+function drawTree(tree) {
+ ctx.fillStyle = '#3d2817';
+ ctx.fillRect(tree.x - tree.size/4, tree.y - tree.size/2, tree.size/2, tree.size);
+
+ ctx.fillStyle = tree.color;
+ ctx.beginPath();
+ ctx.arc(tree.x, tree.y - tree.size/2, tree.size/1.5, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.fillStyle = 'rgba(0,0,0,0.2)';
+ ctx.beginPath();
+ ctx.arc(tree.x, tree.y - tree.size/2, tree.size/1.5, 0, Math.PI * 2);
+ ctx.fill();
+}
+
+function drawForeground() {
+ trees.forEach(tree => {
+ if (tree.y >= Math.min(lion.y, tiger.y) - 50) {
+ drawTree(tree);
+ }
+ });
+}
+
+// Loop
+function loop() {
+ // Update
+ if (shake > 0) shake *= 0.9;
+ if (shake < 0.5) shake = 0;
+
+ lion.update();
+ tiger.update();
+
+ particles = particles.filter(p => {
+ p.update();
+ return p.life > 0;
+ });
+
+ // Check winner
+ if (lion.hp <= 0 || tiger.hp <= 0) {
+ const winner = lion.hp > 0 ? lion : tiger;
+ winner.scale = lerp(winner.scale, 1.2, 0.05);
+ }
+
+ // Draw
+ ctx.save();
+ if (shake > 0) {
+ ctx.translate(rand(-shake, shake), rand(-shake, shake));
+ }
+
+ drawEnvironment();
+
+ // Sort by Y for depth
+ const entities = [lion, tiger].sort((a, b) => a.y - b.y);
+ entities.forEach(e => {
+ ctx.save();
+ ctx.translate(e.x, e.y);
+ ctx.scale(e.scale, e.scale);
+ ctx.translate(-e.x, -e.y);
+ e.draw(ctx);
+ ctx.restore();
+ });
+
+ particles.forEach(p => p.draw(ctx));
+ drawForeground();
+
+ ctx.restore();
+
+ requestAnimationFrame(loop);
+}
+
+// Start
+loop();
+
+// Add some initial drama
+setTimeout(() => {
+ createParticles(width/2, height/2, 'dust', 20);
+ shake = 10;
+}, 500);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9de63223a377/kimi.png b/data/artifacts/9de63223a377/kimi.png
new file mode 100644
index 0000000..7af6d3f
Binary files /dev/null and b/data/artifacts/9de63223a377/kimi.png differ
diff --git a/data/artifacts/9de63223a377/qwen25-7b.html b/data/artifacts/9de63223a377/qwen25-7b.html
new file mode 100644
index 0000000..9680206
--- /dev/null
+++ b/data/artifacts/9de63223a377/qwen25-7b.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>AI Model Arena</title>
+ <style>
+ body {
+ font-family: Arial, sans-serif;
+ text-align: center;
+ background-color: #f0f0f0;
+ }
+ .container {
+ margin-top: 50px;
+ }
+ .animal {
+ width: 200px;
+ height: 150px;
+ display: inline-block;
+ margin: 20px;
+ background-size: cover;
+ background-position: center;
+ border-radius: 10px;
+ }
+ #lion {
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAA1BMVEX///8zq6ujMwMCQjIyMSwwAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3JkLzEwMDA2MC8xOTAwAAAADnRJTUUH3gUxMTYiMTEyNTIyQlEQSkpKb2tpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpaenpa
\ No newline at end of file
diff --git a/data/artifacts/9de63223a377/qwen25-7b.png b/data/artifacts/9de63223a377/qwen25-7b.png
new file mode 100644
index 0000000..0558580
Binary files /dev/null and b/data/artifacts/9de63223a377/qwen25-7b.png differ
diff --git a/data/artifacts/9de63223a377/qwen3-14b.html b/data/artifacts/9de63223a377/qwen3-14b.html
new file mode 100644
index 0000000..d02cca3
--- /dev/null
+++ b/data/artifacts/9de63223a377/qwen3-14b.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Lion vs Tiger</title>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ background: #228B22;
+ overflow: hidden;
+ font-family: Arial, sans-serif;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: white;
+ }
+ .scene {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ }
+ .jungle {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"><rect width="100%" height="100%" fill="#228B22"/><circle cx="100" cy="150" r="50" fill="#006400"/><circle cx="300" cy="120" r="40" fill="#006400"/><circle cx="500" cy="180" r="60" fill="#006400"/><polygon points="100,200 150,250 50,250" fill="#006400"/><polygon points="300,220 350,270 250,270" fill="#006400"/><polygon points="500,240 550,290 450,290" fill="#006400"/></svg>') no-repeat center center;
+ background-size: cover;
+ }
+ .animal {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 200px;
+ }
+ .lion {
+ left: 20%;
+ }
+ .tiger {
+ right: 20%;
+ }
+ .lion svg, .tiger svg {
+ width: 100%;
+ height: auto;
+ }
+ .text {
+ position: absolute;
+ bottom: 20px;
+ text-align: center;
+ width: 100%;
+ }
+ @keyframes grow {
+ 0% { transform: translateY(-50%) scale(1); }
+ 50% { transform: translateY(-50%) scale(1.1); }
+ 100% { transform: translateY(-50%) scale(1); }
+ }
+ .lion svg {
+ animation: grow 2s infinite;
+ }
+ </style>
+</head>
+<body>
+ <div class="scene">
+ <div class="jungle"></div>
+ <div class="animal lion">
+ <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <circle cx="100" cy="70" r="50" fill="#FFD700"/>
+ <path d="M50,80 Q70,60 100,80 T150,80 Q170,60 150,80 T100,80" fill="#FFA500"/>
+ <circle cx="70" cy="70" r="10" fill="black"/>
+ <circle cx="130" cy="70" r="10" fill="black"/>
+ <circle cx="100" cy="100" r="5" fill="black"/>
+ <path d="M50,120 Q70,100 100,120 T150,120 Q170,100 150,120 T100,120" fill="#FFD700"/>
+ </svg>
+ </div>
+ <div class="animal tiger">
+ <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
+ <path d="M50,80 Q70,60 100,80 T150,80 Q170,60 150,80 T100,80" fill="#DEB887"/>
+ <path d="M50,120 Q70,100 100,120 T150,120 Q170,100 150,120 T100,120" fill="#DEB887"/>
+ <path d="M60,100 Q70,90 80,100 T90,110 Q100,100 90,110 T80,100" fill="white"/>
+ <path d="M110,100 Q120,90 130,100 T140,110 Q150,100 140,110 T130,100" fill="white"/>
+ <circle cx="70" cy="70" r="10" fill="black"/>
+ <circle cx="130" cy="70" r="10" fill="black"/>
+ <circle cx="100" cy="100" r="5" fill="black"/>
+ </svg>
+ </div>
+ <div class="text">
+ <h1>Lion vs Tiger</h1>
+ <p>In the heart of the jungle, a fierce showdown between nature's kings!</p>
+ </div>
+ </div>
+ <script>
+ // Simple interaction: click to reset animation
+ document.querySelector('.lion svg').addEventListener('click', () => {
+ document.querySelector('.lion svg').style.animation = 'grow 2s infinite';
+ });
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9de63223a377/qwen3-14b.png b/data/artifacts/9de63223a377/qwen3-14b.png
new file mode 100644
index 0000000..19dd7fc
Binary files /dev/null and b/data/artifacts/9de63223a377/qwen3-14b.png differ
diff --git a/data/artifacts/ca355985ca95/gpt.html b/data/artifacts/ca355985ca95/gpt.html
new file mode 100644
index 0000000..a912f7e
--- /dev/null
+++ b/data/artifacts/ca355985ca95/gpt.html
@@ -0,0 +1,272 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>ARENA Particles</title>
+<style>
+html, body {
+ margin: 0;
+ padding: 0;
+ background: radial-gradient(circle at center, #050510 0%, #020209 45%, #000000 100%);
+ overflow: hidden;
+ height: 100%;
+ width: 100%;
+ font-family: system-ui, sans-serif;
+ color: #eee;
+}
+canvas {
+ display: block;
+ position: absolute;
+ inset: 0;
+}
+#info {
+ position: fixed;
+ left: 50%;
+ bottom: 18px;
+ transform: translateX(-50%);
+ font-size: 12px;
+ color: #aaa;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ user-select: none;
+ pointer-events: none;
+ text-shadow: 0 0 8px rgba(0,255,255,0.35);
+}
+</style>
+</head>
+<body>
+<canvas id="c"></canvas>
+<div id="info">click to explode / reform</div>
+<script>
+(() => {
+ const canvas = document.getElementById('c');
+ const ctx = canvas.getContext('2d');
+
+ let W = innerWidth;
+ let H = innerHeight;
+ canvas.width = W;
+ canvas.height = H;
+
+ const DPR = Math.min(2, window.devicePixelRatio || 1);
+ canvas.width = W * DPR;
+ canvas.height = H * DPR;
+ ctx.scale(DPR, DPR);
+
+ const PARTICLE_COUNT = 2000;
+ const particles = [];
+ let targets = [];
+ let state = 'assemble';
+ let lastTime = performance.now();
+ let mouse = { x: W / 2, y: H / 2, down: false };
+
+ const params = {
+ baseSpeed: 0.04,
+ explodeForce: 5.0,
+ friction: 0.94,
+ attraction: 0.08
+ };
+
+ function resize() {
+ W = innerWidth;
+ H = innerHeight;
+ canvas.style.width = W + 'px';
+ canvas.style.height = H + 'px';
+ canvas.width = W * DPR;
+ canvas.height = H * DPR;
+ ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
+ buildWordTargets();
+ // Re-map each particle to nearest new target index
+ particles.forEach((p, i) => {
+ const t = targets[i % targets.length];
+ p.tx = t.x;
+ p.ty = t.y;
+ });
+ }
+ addEventListener('resize', resize);
+
+ function buildWordTargets() {
+ const off = document.createElement('canvas');
+ const octx = off.getContext('2d');
+ const maxWidth = Math.min(W * 0.9, 800);
+ const fontSize = Math.min(260, Math.max(120, H * 0.3));
+ off.width = maxWidth;
+ off.height = fontSize * 1.6;
+ octx.clearRect(0, 0, off.width, off.height);
+ octx.fillStyle = '#ffffff';
+ octx.textAlign = 'center';
+ octx.textBaseline = 'middle';
+ octx.font = `bold ${fontSize}px system-ui, sans-serif`;
+ octx.fillText('ARENA', off.width / 2, off.height / 2);
+
+ const img = octx.getImageData(0, 0, off.width, off.height).data;
+ const gap = 3;
+ const pts = [];
+ for (let y = 0; y < off.height; y += gap) {
+ for (let x = 0; x < off.width; x += gap) {
+ const idx = (y * off.width + x) * 4 + 3;
+ if (img[idx] > 10) {
+ const gx = (W - off.width) / 2 + x;
+ const gy = (H - off.height) / 2 + y;
+ pts.push({ x: gx, y: gy });
+ }
+ }
+ }
+ // If too many points, sample down to ~PARTICLE_COUNT
+ if (pts.length > PARTICLE_COUNT) {
+ const step = pts.length / PARTICLE_COUNT;
+ const sampled = [];
+ for (let i = 0; i < PARTICLE_COUNT; i++) {
+ sampled.push(pts[Math.floor(i * step)]);
+ }
+ targets = sampled;
+ } else {
+ targets = pts;
+ }
+ }
+
+ function randRange(a, b) {
+ return a + Math.random() * (b - a);
+ }
+
+ function initParticles() {
+ buildWordTargets();
+ const centerX = W / 2;
+ const centerY = H / 2;
+ for (let i = 0; i < PARTICLE_COUNT; i++) {
+ const angle = Math.random() * Math.PI * 2;
+ const radius = randRange(0, Math.min(W, H) * 0.5);
+ const startX = centerX + Math.cos(angle) * radius;
+ const startY = centerY + Math.sin(angle) * radius;
+ const t = targets[i % targets.length];
+ particles.push({
+ x: startX,
+ y: startY,
+ vx: randRange(-1, 1),
+ vy: randRange(-1, 1),
+ tx: t.x,
+ ty: t.y,
+ size: randRange(1.2, 2.2),
+ timeOffset: Math.random() * 1000,
+ colorPhase: Math.random() * Math.PI * 2
+ });
+ }
+ }
+
+ function explode() {
+ state = 'explode';
+ const cx = W / 2;
+ const cy = H / 2;
+ particles.forEach(p => {
+ const dx = p.x - cx + randRange(-50, 50);
+ const dy = p.y - cy + randRange(-50, 50);
+ const dist = Math.max(10, Math.hypot(dx, dy));
+ const force = params.explodeForce * (0.5 + Math.random());
+ p.vx += (dx / dist) * force;
+ p.vy += (dy / dist) * force;
+ });
+ // auto assemble after short delay
+ setTimeout(() => { state = 'assemble'; }, 900);
+ }
+
+ canvas.addEventListener('click', () => {
+ explode();
+ });
+
+ canvas.addEventListener('mousemove', e => {
+ const rect = canvas.getBoundingClientRect();
+ mouse.x = (e.clientX - rect.left);
+ mouse.y = (e.clientY - rect.top);
+ });
+
+ function update(dt) {
+ const t = performance.now() * 0.001;
+ const attractStrength = params.attraction * (state === 'assemble' ? 1 : 0.15);
+ const friction = params.friction;
+
+ for (let i = 0; i < particles.length; i++) {
+ const p = particles[i];
+
+ const tt = targets[i % targets.length];
+ if (tt) {
+ p.tx = tt.x;
+ p.ty = tt.y;
+ }
+
+ // spring toward target
+ const dx = p.tx - p.x;
+ const dy = p.ty - p.y;
+ p.vx += dx * attractStrength * dt;
+ p.vy += dy * attractStrength * dt;
+
+ // subtle mouse repulsion
+ const mdx = p.x - mouse.x;
+ const mdy = p.y - mouse.y;
+ const md2 = mdx * mdx + mdy * mdy;
+ if (md2 < 80 * 80) {
+ const mDist = Math.sqrt(md2) || 1;
+ const mF = (1 - mDist / 80) * 120 * dt;
+ p.vx += (mdx / mDist) * mF;
+ p.vy += (mdy / mDist) * mF;
+ }
+
+ // integrate
+ p.vx *= friction;
+ p.vy *= friction;
+ p.x += p.vx * params.baseSpeed * dt * 60;
+ p.y += p.vy * params.baseSpeed * dt * 60;
+
+ // slight float
+ const wobble = Math.sin(t * 2 + p.timeOffset) * 0.15;
+ p.y += wobble;
+ }
+ }
+
+ function draw() {
+ ctx.clearRect(0, 0, W, H);
+ ctx.save();
+ const gradient = ctx.createRadialGradient(
+ W / 2, H / 2, 0,
+ W / 2, H / 2, Math.max(W, H) * 0.7
+ );
+ gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
+ gradient.addColorStop(1, 'rgba(0, 0, 0, 0.9)');
+ ctx.fillStyle = gradient;
+ ctx.fillRect(0, 0, W, H);
+
+ ctx.globalCompositeOperation = 'lighter';
+ ctx.lineWidth = 0;
+ ctx.shadowBlur = 12;
+
+ for (let i = 0; i < particles.length; i++) {
+ const p = particles[i];
+ const life = (Math.sin(performance.now() * 0.001 + p.colorPhase) + 1) / 2;
+ const hue = 190 + life * 100; // teal -> magenta spectrum
+ const alpha = 0.5 + life * 0.5;
+
+ ctx.fillStyle = `hsla(${hue}, 95%, 60%, ${alpha})`;
+ ctx.shadowColor = `hsla(${hue}, 95%, 65%, 0.9)`;
+
+ const r = p.size + life * 1.2;
+
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ ctx.restore();
+ }
+
+ function loop(now) {
+ const dt = Math.min(50, now - lastTime);
+ lastTime = now;
+ update(dt);
+ draw();
+ requestAnimationFrame(loop);
+ }
+
+ initParticles();
+ requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ca355985ca95/grok.html b/data/artifacts/ca355985ca95/grok.html
new file mode 100644
index 0000000..5a5c8a8
--- /dev/null
+++ b/data/artifacts/ca355985ca95/grok.html
@@ -0,0 +1,206 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>ARENA Particles</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%;cursor:pointer}
+</style>
+</head>
+<body>
+<canvas id="c"></canvas>
+<script>
+const canvas=document.getElementById('c');
+const ctx=canvas.getContext('2d');
+let W,H,CX,CY;
+const PARTICLE_COUNT=2000;
+const particles=[];
+let targets=[];
+let state='assemble';
+let holdTimer=0;
+const HOLD_DURATION=90;
+let explodeFrame=0;
+
+function resize(){
+ W=canvas.width=window.innerWidth;
+ H=canvas.height=window.innerHeight;
+ CX=W/2;CY=H/2;
+ buildTargets();
+ if(particles.length)assignTargets();
+}
+window.addEventListener('resize',resize);
+
+function buildTargets(){
+ const off=document.createElement('canvas');
+ const oc=off.getContext('2d');
+ const fontSize=Math.min(W*0.22,H*0.35,180);
+ off.width=W;off.height=H;
+ oc.fillStyle='#fff';
+ oc.font=`bold ${fontSize}px Arial,Helvetica,sans-serif`;
+ oc.textAlign='center';
+ oc.textBaseline='middle';
+ oc.fillText('ARENA',CX,CY);
+ const img=oc.getImageData(0,0,W,H);
+ const pts=[];
+ const gap=3;
+ for(let y=0;y<H;y+=gap){
+ for(let x=0;x<W;x+=gap){
+ const i=(y*W+x)*4;
+ if(img.data[i+3]>128)pts.push({x,y});
+ }
+ }
+ targets=pts;
+}
+
+const NEON=[
+ [0,255,255],[255,0,255],[0,200,255],[180,0,255],
+ [0,255,180],[255,50,200],[100,150,255]
+];
+
+function createParticle(){
+ const c=NEON[Math.floor(Math.random()*NEON.length)];
+ return{
+ x:Math.random()*W,
+ y:Math.random()*H,
+ vx:(Math.random()-0.5)*8,
+ vy:(Math.random()-0.5)*8,
+ tx:CX,ty:CY,
+ ox:0,oy:0,
+ r:1.2+Math.random()*1.8,
+ color:c,
+ glow:0.6+Math.random()*0.4,
+ delay:Math.random()*40,
+ friction:0.88+Math.random()*0.06,
+ spring:0.04+Math.random()*0.04
+ };
+}
+
+function assignTargets(){
+ const n=particles.length;
+ const t=targets;
+ if(!t.length)return;
+ for(let i=0;i<n;i++){
+ const p=particles[i];
+ const tgt=t[i%t.length];
+ p.tx=tgt.x+(Math.random()-0.5)*2;
+ p.ty=tgt.y+(Math.random()-0.5)*2;
+ p.ox=p.tx;p.oy=p.ty;
+ }
+ for(let i=n-1;i>0;i--){
+ const j=Math.floor(Math.random()*(i+1));
+ const a=particles[i],b=particles[j];
+ const tx=a.tx,ty=a.ty;
+ a.tx=b.tx;a.ty=b.ty;a.ox=a.tx;a.oy=a.ty;
+ b.tx=tx;b.ty=ty;b.ox=b.tx;b.oy=b.ty;
+ }
+}
+
+function init(){
+ resize();
+ particles.length=0;
+ for(let i=0;i<PARTICLE_COUNT;i++)particles.push(createParticle());
+ assignTargets();
+ state='assemble';
+ holdTimer=0;
+}
+
+function explode(){
+ state='explode';
+ explodeFrame=0;
+ for(const p of particles){
+ const angle=Math.random()*Math.PI*2;
+ const speed=6+Math.random()*18;
+ p.vx=Math.cos(angle)*speed;
+ p.vy=Math.sin(angle)*speed;
+ p.delay=Math.random()*30;
+ }
+}
+
+function reform(){
+ buildTargets();
+ assignTargets();
+ state='assemble';
+ holdTimer=0;
+}
+
+canvas.addEventListener('click',()=>{
+ if(state==='hold'||state==='assemble')explode();
+ else if(state==='explode')reform();
+});
+
+function update(){
+ if(state==='assemble'){
+ let allClose=true;
+ for(const p of particles){
+ if(p.delay>0){p.delay--;allClose=false;continue;}
+ const dx=p.tx-p.x,dy=p.ty-p.y;
+ p.vx+=(dx)*p.spring;
+ p.vy+=(dy)*p.spring;
+ p.vx*=p.friction;
+ p.vy*=p.friction;
+ p.x+=p.vx;
+ p.y+=p.vy;
+ if(Math.abs(dx)>2||Math.abs(dy)>2||Math.abs(p.vx)>0.3||Math.abs(p.vy)>0.3)allClose=false;
+ }
+ if(allClose){state='hold';holdTimer=0;}
+ }else if(state==='hold'){
+ holdTimer++;
+ for(const p of particles){
+ const dx=p.tx-p.x,dy=p.ty-p.y;
+ p.vx+=dx*0.08+ (Math.random()-0.5)*0.15;
+ p.vy+=dy*0.08+ (Math.random()-0.5)*0.15;
+ p.vx*=0.85;p.vy*=0.85;
+ p.x+=p.vx;p.y+=p.vy;
+ }
+ }else if(state==='explode'){
+ explodeFrame++;
+ for(const p of particles){
+ p.vx*=0.99;p.vy*=0.99;
+ p.vy+=0.02;
+ p.x+=p.vx;p.y+=p.vy;
+ if(p.x<0||p.x>W)p.vx*=-0.8;
+ if(p.y<0||p.y>H)p.vy*=-0.8;
+ p.x=Math.max(0,Math.min(W,p.x));
+ p.y=Math.max(0,Math.min(H,p.y));
+ }
+ if(explodeFrame>100)reform();
+ }
+}
+
+function draw(){
+ ctx.fillStyle='rgba(0,0,0,0.25)';
+ ctx.fillRect(0,0,W,H);
+ ctx.globalCompositeOperation='lighter';
+ for(const p of particles){
+ const [r,g,b]=p.color;
+ const radius=p.r;
+ const grad=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,radius*4);
+ grad.addColorStop(0,`rgba(${r},${g},${b},${p.glow})`);
+ grad.addColorStop(0.4,`rgba(${r},${g},${b},${p.glow*0.35})`);
+ grad.addColorStop(1,`rgba(${r},${g},${b},0)`);
+ ctx.beginPath();
+ ctx.fillStyle=grad;
+ ctx.arc(p.x,p.y,radius*4,0,Math.PI*2);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.fillStyle=`rgba(${r},${g},${b},1)`;
+ ctx.arc(p.x,p.y,radius*0.6,0,Math.PI*2);
+ ctx.fill();
+ }
+ ctx.globalCompositeOperation='source-over';
+}
+
+function loop(){
+ update();
+ draw();
+ requestAnimationFrame(loop);
+}
+
+init();
+loop();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ca355985ca95/grok.png b/data/artifacts/ca355985ca95/grok.png
new file mode 100644
index 0000000..83adcb8
Binary files /dev/null and b/data/artifacts/ca355985ca95/grok.png differ
diff --git a/data/artifacts/ca355985ca95/qwen25-7b.html b/data/artifacts/ca355985ca95/qwen25-7b.html
new file mode 100644
index 0000000..ce4016c
--- /dev/null
+++ b/data/artifacts/ca355985ca95/qwen25-7b.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Arena Particle System</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ background-color: black;
+ }
+ canvas {
+ display: block;
+ width: 100%;
+ height: 100vh;
+ }
+ </style>
+</head>
+<body>
+<canvas id="particles"></canvas>
+<script>
+const canvas = document.getElementById('particles');
+const ctx = canvas.getContext('2d');
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+let particles = [];
+let arenaLetters = "ARENA";
+const particleCount = 2000;
+const maxSpeed = 5;
+const gravity = 0.1;
+const colors = ['#FFD700', '#32CD32', '#F0E68C', '#9ACD32', '#ADFF2F'];
+
+function createParticles(count) {
+ for (let i = 0; i < count; i++) {
+ particles.push({
+ x: Math.random() * canvas.width,
+ y: Math.random() * canvas.height,
+ vx: (Math.random() - 0.5) * maxSpeed,
+ vy: (Math.random() - 0.5) * maxSpeed,
+ color: colors[Math.floor(Math.random() * colors.length)]
+ });
+ }
+}
+
+function updateParticles() {
+ particles.forEach((particle, index) => {
+ particle.vy += gravity;
+ particle.x += particle.vx;
+ particle.y += particle.vy;
+
+ if (particle.x < 0 || particle.x > canvas.width) {
+ particle.vx = -particle.vx;
+ }
+
+ if (particle.y < 0 || particle.y > canvas.height) {
+ particle.vy = -particle.vy;
+ }
+ });
+}
+
+function drawParticles() {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ particles.forEach(particle => {
+ ctx.beginPath();
+ ctx.arc(particle.x, particle.y, 2, 0, Math.PI * 2);
+ ctx.fillStyle = particle.color;
+ ctx.fill();
+ });
+}
+
+function drawArena() {
+ const fontSize = 150;
+ ctx.font = `${fontSize}px Arial`;
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+
+ const letters = arenaLetters.split('');
+ const letterSpacing = canvas.width / (letters.length + 1);
+ let x = letterSpacing;
+
+ letters.forEach(letter => {
+ ctx.fillText(letter, x, canvas.height / 2 + fontSize / 2);
+ x += letterSpacing;
+ });
+}
+
+function explodeParticles() {
+ particles.forEach(particle => {
+ particle.vx = (Math.random() - 0.5) * maxSpeed * 10;
+ particle.vy = (Math.random() - 0.5) * maxSpeed * 10;
+ });
+}
+
+createParticles(particleCount);
+
+function animate() {
+ updateParticles();
+ drawParticles();
+ requestAnimationFrame(animate);
+}
+
+document.addEventListener('click', () => {
+ particles.forEach(particle => { particle.vx = Math.random() * (Math.random() < 0.5 ? -maxSpeed : maxSpeed); });
+ explodeParticles();
+ setTimeout(() => animate(), 1000);
+});
+
+drawArena();
+animate();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ca355985ca95/qwen25-7b.png b/data/artifacts/ca355985ca95/qwen25-7b.png
new file mode 100644
index 0000000..9155053
Binary files /dev/null and b/data/artifacts/ca355985ca95/qwen25-7b.png differ
diff --git a/data/challenges.json b/data/challenges.json
index 58463b2..b2734a4 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -1474,5 +1474,171 @@
"judging": false,
"aiPick": "claude-code",
"judged_at": "2026-07-23T13:41:50.912Z"
+ },
+ {
+ "id": "9de63223a377",
+ "title": "LIon V Tiger",
+ "prompt": "LIon V Tiger in the jungle using known graphic models and huggiunf face suggested models",
+ "category": "Custom",
+ "created_at": "2026-07-23T13:46:16.613Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 60,
+ "cost": 0,
+ "started_at": "2026-07-23T13:46:16.625Z",
+ "finished_at": "2026-07-23T13:47:16.284Z",
+ "bytes": 3943,
+ "thumb": true
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "done",
+ "error": null,
+ "seconds": 21,
+ "cost": 0,
+ "started_at": "2026-07-23T13:47:16.285Z",
+ "finished_at": "2026-07-23T13:47:37.780Z",
+ "bytes": 1878,
+ "thumb": true
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "done",
+ "error": null,
+ "seconds": 7,
+ "cost": 0,
+ "started_at": "2026-07-23T13:47:37.781Z",
+ "finished_at": "2026-07-23T13:47:44.576Z",
+ "bytes": 1243,
+ "thumb": true
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 153,
+ "cost": 0,
+ "started_at": "2026-07-23T13:46:16.627Z",
+ "finished_at": "2026-07-23T13:48:49.966Z",
+ "bytes": 12587,
+ "thumb": true
+ },
+ {
+ "model": "kimi",
+ "status": "done",
+ "error": null,
+ "seconds": 126,
+ "cost": 0.0155,
+ "started_at": "2026-07-23T13:46:16.628Z",
+ "finished_at": "2026-07-23T13:48:22.278Z",
+ "bytes": 12554,
+ "thumb": true
+ },
+ {
+ "model": "gpt",
+ "status": "done",
+ "error": null,
+ "seconds": 108,
+ "cost": 0.1542,
+ "started_at": "2026-07-23T13:46:16.647Z",
+ "finished_at": "2026-07-23T13:48:04.281Z",
+ "bytes": 41442,
+ "thumb": true
+ },
+ {
+ "model": "grok",
+ "status": "done",
+ "error": null,
+ "seconds": 108,
+ "cost": 0.1214,
+ "started_at": "2026-07-23T13:46:16.649Z",
+ "finished_at": "2026-07-23T13:48:04.775Z",
+ "bytes": 19252,
+ "thumb": true
+ }
+ ],
+ "judging": true
+ },
+ {
+ "id": "ca355985ca95",
+ "title": "Particle Text Reactor",
+ "prompt": "Build a single self-contained HTML file: a canvas particle system where ~2000 particles assemble into the word \"ARENA\", hold, then explode outward and reform on click. Smooth 60fps, glowing neon particles, physics-based motion. Output ONLY the HTML.",
+ "category": "Games",
+ "created_at": "2026-07-23T13:46:18.891Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "running",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": "2026-07-23T13:47:44.578Z",
+ "finished_at": null
+ },
+ {
+ "model": "gemma3-12b",
+ "status": "queued",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ {
+ "model": "hermes3-8b",
+ "status": "queued",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 19,
+ "cost": 0,
+ "started_at": "2026-07-23T13:48:49.967Z",
+ "finished_at": "2026-07-23T13:49:08.530Z",
+ "bytes": 2933,
+ "thumb": true
+ },
+ {
+ "model": "kimi",
+ "status": "running",
+ "error": null,
+ "seconds": null,
+ "cost": null,
+ "started_at": "2026-07-23T13:48:22.281Z",
+ "finished_at": null
+ },
+ {
+ "model": "gpt",
+ "status": "done",
+ "error": null,
+ "seconds": 24,
+ "cost": 0.0318,
+ "started_at": "2026-07-23T13:48:04.283Z",
+ "finished_at": "2026-07-23T13:48:28.246Z",
+ "bytes": 8096
+ },
+ {
+ "model": "grok",
+ "status": "done",
+ "error": null,
+ "seconds": 26,
+ "cost": 0.0309,
+ "started_at": "2026-07-23T13:48:04.785Z",
+ "finished_at": "2026-07-23T13:48:30.707Z",
+ "bytes": 5051,
+ "thumb": true
+ }
+ ]
}
]
\ No newline at end of file
diff --git a/data/costlog.jsonl b/data/costlog.jsonl
index f4b7d12..433b04d 100644
--- a/data/costlog.jsonl
+++ b/data/costlog.jsonl
@@ -22,3 +22,8 @@
{"ts":"2026-07-23T13:35:27.034Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":91,"output_tokens":12426,"cost_usd":0.174123}
{"ts":"2026-07-23T13:35:48.910Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":92,"output_tokens":7702,"cost_usd":0.01931}
{"ts":"2026-07-23T13:35:51.283Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":295,"output_tokens":11002,"cost_usd":0.165915}
+{"ts":"2026-07-23T13:48:04.275Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":101,"output_tokens":11000,"cost_usd":0.154177}
+{"ts":"2026-07-23T13:48:04.744Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":306,"output_tokens":8034,"cost_usd":0.121428}
+{"ts":"2026-07-23T13:48:22.273Z","provider":"moonshot","model":"kimi-k2.5","task":"model-arena","input_tokens":103,"output_tokens":6177,"cost_usd":0.015504}
+{"ts":"2026-07-23T13:48:28.243Z","provider":"openai","model":"gpt-5.1","task":"model-arena","input_tokens":134,"output_tokens":2253,"cost_usd":0.031776}
+{"ts":"2026-07-23T13:48:30.706Z","provider":"xai","model":"grok-4.5","task":"model-arena","input_tokens":340,"output_tokens":1992,"cost_usd":0.0309}
diff --git a/idea-run/challenges.txt b/idea-run/challenges.txt
new file mode 100644
index 0000000..abef292
--- /dev/null
+++ b/idea-run/challenges.txt
@@ -0,0 +1,7 @@
+Particle Text Reactor|Build a single self-contained HTML file: a canvas particle system where ~2000 particles assemble into the word "ARENA", hold, then explode outward and reform on click. Smooth 60fps, glowing neon particles, physics-based motion. Output ONLY the HTML.
+Gravity Slingshot Playground|Build a single self-contained HTML file: an interactive gravity playground. Drag to slingshot balls; they collide elastically, leave fading trails, and are pulled by a central mass. Add a few obstacles. Pure canvas + JS, 60fps. Output ONLY the HTML.
+Sorting Race Visualizer|Build a single self-contained HTML file: three sorting algorithms (bubble, quicksort, merge) racing side by side as animated bar charts, each labeled, with a Start/Shuffle button. Color-code comparisons vs swaps. Pure canvas or divs. Output ONLY the HTML.
+Warp Starfield Tunnel|Build a single self-contained HTML file: a procedurally generated warp-speed starfield tunnel. Mouse position steers the camera; scroll changes speed; stars streak with motion blur. 60fps canvas. Output ONLY the HTML.
+Living Fractal Tree|Build a single self-contained HTML file: a recursive fractal tree that grows on load and sways in simulated wind, with seasonal leaf particles falling. A slider controls branch depth. Pure canvas + JS. Output ONLY the HTML.
+Playable Asteroids|Build a single self-contained HTML file: a fully playable Asteroids clone. Arrow keys rotate/thrust, space shoots, asteroids split when hit, score + lives shown, wraparound edges. Vector graphics on canvas, 60fps. Output ONLY the HTML.
+Generative Mondrian Machine|Build a single self-contained HTML file: a generative art machine that produces a new animated Piet-Mondrian-style composition every few seconds, rectangles growing and recoloring with easing. A Regenerate button. Pure DOM or canvas. Output ONLY the HTML.
diff --git a/idea-run/create-cycle.sh b/idea-run/create-cycle.sh
new file mode 100755
index 0000000..2ef3db5
--- /dev/null
+++ b/idea-run/create-cycle.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+# create-cycle.sh <1-based-index> → creates + runs challenge N, prints its id.
+set -euo pipefail
+IDX="$1"
+LINE=$(sed -n "${IDX}p" "$HOME/Projects/model-arena/idea-run/challenges.txt")
+TITLE="${LINE%%|*}"; PROMPT="${LINE#*|}"
+python3 - "$TITLE" "$PROMPT" > /tmp/cyc-payload.json <<'PY'
+import json,sys
+json.dump({"title":sys.argv[1],"prompt":sys.argv[2],
+ "models":["qwen3-14b","gemma3-12b","hermes3-8b","qwen25-7b","kimi","gpt","grok"]},
+ sys.stdout)
+PY
+curl -s -m 20 -u admin:DW2024! -X POST http://127.0.0.1:9758/api/challenges \
+ -H 'Content-Type: application/json' -d @/tmp/cyc-payload.json \
+ | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('id') or d.get('error'))"
diff --git a/idea-run/harvest.md b/idea-run/harvest.md
new file mode 100644
index 0000000..5f5b36d
--- /dev/null
+++ b/idea-run/harvest.md
@@ -0,0 +1,5 @@
+# Model Arena — 2-Hour Idea Run · harvest
+Working models: qwen3-14b, gemma3-12b, hermes3-8b, qwen25-7b (local $0) · kimi ($0.03) · gpt ($0.10) · grok ($0.10)
+Claude EXCLUDED — Anthropic key out of credit (billing, Steve to fund).
+keep-alive now excludes model-arena so batches don't get killed mid-run.
+
diff --git a/public/index.html b/public/index.html
index 30d7a17..193dfae 100644
--- a/public/index.html
+++ b/public/index.html
@@ -282,7 +282,7 @@ async function loadList(){
const div = document.createElement('div'); div.className='card';
const chips = c.runs.map(r=>{
const win = c.winner===r.model ? ' winner' : '';
- return `<span class="chip ${r.status}${win}">${mLabel(r.model)}${c.winner===r.model?' 👑':''}${r.status==='error'?' ✗':''}</span>`;
+ return `<span class="chip ${r.status}${win}" data-build-model="${r.model}" title="Build ${mLabel(r.model)}'s version in a real Claude/iTerm session">${mLabel(r.model)}${c.winner===r.model?' 👑':''}${r.status==='error'?' ✗':''} <b class="bx">⚒</b></span>`;
}).join('');
const shots = c.runs.filter(r=>r.status==='done'&&r.thumb).slice(0,4);
const shotsHtml = shots.length
diff --git a/server.js b/server.js
index 3ee7baa..dd7ba44 100644
--- a/server.js
+++ b/server.js
@@ -507,15 +507,28 @@ const server = http.createServer(async (req, res) => {
return send(res, 201, nc);
}
- if ((m = p.match(/^\/api\/challenges\/([a-f0-9]+)\/build$/)) && req.method === 'POST') {
+ if ((m = p.match(/^\/api\/challenges\/([a-f0-9]+)\/build(?:\/([\w-]+))?$/)) && req.method === 'POST') {
const c = challenges.find(x => x.id === m[1]);
if (!c) return send(res, 404, { error: 'not found' });
if (process.platform !== 'darwin') return send(res, 400, { error: 'iTerm launch is macOS-only' });
- // write the brief to a temp file so any quotes/newlines in the prompt can't break escaping
- const tmp = '/tmp/arena-build-' + c.id + '.md';
- const brief = `Build this Model Arena challenge as a real, working project.\n\nTITLE: ${c.title}\nCATEGORY: ${c.category || 'Custom'}\n\nCHALLENGE:\n${c.prompt}\n\nDeliver a complete, working result (a single-file HTML unless it clearly needs more). When done, open it in a browser to verify it works.`;
+ const modelId = m[2] || null;
+ // seed the brief with the CHOSEN model's actual artifact so Claude builds THAT attempt forward
+ let brief, tmp, safeTitle = c.title.replace(/[^\w \-]/g, '').slice(0, 34);
+ if (modelId) {
+ const run = c.runs.find(r => r.model === modelId);
+ let art = ''; try { art = fs.readFileSync(path.join(ART, c.id, modelId + '.html'), 'utf8'); } catch {}
+ const ml = mLabelServer(modelId);
+ brief = `Take ${ml}'s attempt at this Model Arena challenge and build it into a real, working project — keep what works, fix its defects, and extend it.\n\nTITLE: ${c.title}\nCATEGORY: ${c.category || 'Custom'}\n\nCHALLENGE:\n${c.prompt}\n\n`
+ + (art
+ ? `=== ${ml}'s ARTIFACT (your starting point — improve & complete this) ===\n${art}\n\nWhen done, open the result in a browser to verify it works.`
+ : `(${ml} produced no usable artifact for this challenge — build it from scratch to fulfil the challenge above.)`);
+ tmp = '/tmp/arena-build-' + c.id + '-' + modelId + '.md';
+ safeTitle = (mLabelServer(modelId) + ' / ' + safeTitle).replace(/[^\w \-\/]/g, '').slice(0, 40);
+ } else {
+ brief = `Build this Model Arena challenge as a real, working project.\n\nTITLE: ${c.title}\nCATEGORY: ${c.category || 'Custom'}\n\nCHALLENGE:\n${c.prompt}\n\nDeliver a complete, working result. When done, open it in a browser to verify.`;
+ tmp = '/tmp/arena-build-' + c.id + '.md';
+ }
try { fs.writeFileSync(tmp, brief); } catch (e) { return send(res, 500, { error: 'temp write failed' }); }
- const safeTitle = c.title.replace(/[^\w \-]/g, '').slice(0, 40);
// iTerm runs: cd ~/Projects, print a banner, launch interactive claude seeded with the brief
const payload = `cd "${os.homedir()}/Projects" && clear && printf '\\n==== ARENA BUILD: ${safeTitle} ====\\n\\n' && claude "$(cat ${tmp})"`;
const osaEsc = s => s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
@@ -528,7 +541,7 @@ const server = http.createServer(async (req, res) => {
end tell
end tell`;
execFile('osascript', ['-e', script], { timeout: 15000 }, (err) => { if (err) console.error('[build] osascript:', err.message); });
- return send(res, 200, { ok: true, launched: c.id });
+ return send(res, 200, { ok: true, launched: c.id, model: modelId });
}
if ((m = p.match(/^\/api\/challenges\/([a-f0-9]+)\/vote$/)) && req.method === 'POST') {
← 584400e arena: Build-in-iTerm button per challenge (+ detail view) —
·
back to Model Arena
·
arena: move Build-in-iTerm from whole-challenge to per-MODEL 1e4746a →