← back to Model Arena
complete five animated Grateful Dead challenges
bab5adf2c8b65dafd0fe4f4ca8541661c3b4fd7f · 2026-09-01 14:41:44 -0700 · Steve Abrams
Files touched
A data/artifacts/1c80df76af60/claude-code.htmlA data/artifacts/1c80df76af60/qwen25-7b.htmlA data/artifacts/1c80df76af60/qwen3-14b.htmlA data/artifacts/246049758201/claude-code.htmlA data/artifacts/246049758201/qwen25-7b.htmlA data/artifacts/246049758201/qwen3-14b.htmlA data/artifacts/9682a8139b09/claude-code.htmlA data/artifacts/9682a8139b09/claude-code.pngA data/artifacts/9682a8139b09/qwen25-7b.htmlM data/artifacts/9682a8139b09/qwen25-7b.pngA data/artifacts/9682a8139b09/qwen3-14b.htmlM data/artifacts/9682a8139b09/qwen3-14b.pngA data/artifacts/c5c20cf0ae13/claude-code.htmlA data/artifacts/c5c20cf0ae13/qwen25-7b.htmlM data/artifacts/c5c20cf0ae13/qwen25-7b.pngA data/artifacts/c5c20cf0ae13/qwen3-14b.htmlA data/artifacts/ee75e7535787/claude-code.htmlA data/artifacts/ee75e7535787/qwen25-7b.htmlA data/artifacts/ee75e7535787/qwen3-14b.htmlM data/challenges.jsonA verification/grateful-dead-dance-five-live.pngA verification/grateful-dead-dance-five-proof.jsonA verification/grateful-dead-dance-five.e2e.js
Diff
commit bab5adf2c8b65dafd0fe4f4ca8541661c3b4fd7f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 1 14:41:44 2026 -0700
complete five animated Grateful Dead challenges
---
data/artifacts/1c80df76af60/claude-code.html | 830 +++++++++++++++++++++
data/artifacts/1c80df76af60/qwen25-7b.html | 98 +++
data/artifacts/1c80df76af60/qwen3-14b.html | 448 ++++++++++++
data/artifacts/246049758201/claude-code.html | 773 +++++++++++++++++++
data/artifacts/246049758201/qwen25-7b.html | 113 +++
data/artifacts/246049758201/qwen3-14b.html | 488 ++++++++++++
data/artifacts/9682a8139b09/claude-code.html | 685 +++++++++++++++++
data/artifacts/9682a8139b09/claude-code.png | Bin 0 -> 351737 bytes
data/artifacts/9682a8139b09/qwen25-7b.html | 78 ++
data/artifacts/9682a8139b09/qwen25-7b.png | Bin 2730 -> 22038 bytes
data/artifacts/9682a8139b09/qwen3-14b.html | 438 +++++++++++
data/artifacts/9682a8139b09/qwen3-14b.png | Bin 31191 -> 69766 bytes
data/artifacts/c5c20cf0ae13/claude-code.html | 896 +++++++++++++++++++++++
data/artifacts/c5c20cf0ae13/qwen25-7b.html | 125 ++++
data/artifacts/c5c20cf0ae13/qwen25-7b.png | Bin 133700 -> 2731 bytes
data/artifacts/c5c20cf0ae13/qwen3-14b.html | 324 ++++++++
data/artifacts/ee75e7535787/claude-code.html | 813 ++++++++++++++++++++
data/artifacts/ee75e7535787/qwen25-7b.html | 211 ++++++
data/artifacts/ee75e7535787/qwen3-14b.html | 386 ++++++++++
data/challenges.json | 754 ++++++++++++++++++-
verification/grateful-dead-dance-five-live.png | Bin 0 -> 90055 bytes
verification/grateful-dead-dance-five-proof.json | 44 ++
verification/grateful-dead-dance-five.e2e.js | 90 +++
23 files changed, 7568 insertions(+), 26 deletions(-)
diff --git a/data/artifacts/1c80df76af60/claude-code.html b/data/artifacts/1c80df76af60/claude-code.html
new file mode 100644
index 0000000..d14dcd3
--- /dev/null
+++ b/data/artifacts/1c80df76af60/claude-code.html
@@ -0,0 +1,830 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Midnight Woodland Jam — An Artistic Rhythm Simulation</title>
+<style>
+ :root{
+ --bg:#080a17;
+ --surface:rgba(14,17,34,.72);
+ --surface-solid:#0e1122;
+ --text:#f3eee4;
+ --muted:#9aa3c4;
+ --line:rgba(180,196,255,.16);
+ --accent:#8fb8ff;
+ --accent-warm:#ffb27a;
+ --focus:#ffe9a8;
+ --radius:14px;
+ --shadow:0 8px 24px rgba(0,0,0,.45);
+ --hover:0 16px 48px rgba(0,0,0,.55);
+ --glow:0 0 24px rgba(143,184,255,.35);
+ --serif:'Didot','Bodoni MT','Playfair Display',Georgia,serif;
+ --sans:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
+ }
+ *{box-sizing:border-box}
+ html,body{height:100%}
+ body{
+ margin:0;background:var(--bg);color:var(--text);font-family:var(--sans);
+ overflow:hidden;-webkit-font-smoothing:antialiased;
+ }
+ #stage{position:fixed;inset:0;display:block}
+
+ /* ---- Header ---- */
+ .masthead{
+ position:fixed;top:0;left:0;right:0;z-index:5;
+ padding:24px 32px;pointer-events:none;
+ display:flex;justify-content:space-between;align-items:flex-start;gap:24px;
+ animation:fadeUp .9s cubic-bezier(.22,1,.36,1) both;
+ }
+ .masthead h1{
+ font-family:var(--serif);font-weight:500;letter-spacing:.01em;
+ font-size:clamp(28px,4.4vw,50px);line-height:1;margin:0;
+ text-shadow:0 2px 30px rgba(0,0,0,.7);
+ }
+ .masthead .kicker{
+ font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--muted);
+ margin:0 0 10px;
+ }
+ .live-tag{
+ pointer-events:none;font-size:12px;letter-spacing:.15em;text-transform:uppercase;
+ color:var(--muted);text-align:right;line-height:1.7;
+ text-shadow:0 1px 12px rgba(0,0,0,.8);
+ }
+ .live-tag strong{color:var(--text);font-weight:600}
+
+ /* ---- Control dock ---- */
+ .dock{
+ position:fixed;left:0;right:0;bottom:0;z-index:6;
+ padding:16px 20px calc(16px + env(safe-area-inset-bottom));
+ display:flex;flex-wrap:wrap;gap:16px;align-items:stretch;justify-content:center;
+ background:linear-gradient(to top,rgba(6,8,20,.92),rgba(6,8,20,.4) 70%,transparent);
+ animation:fadeUp 1s .15s cubic-bezier(.22,1,.36,1) both;
+ }
+ fieldset{
+ border:1px solid var(--line);border-radius:var(--radius);
+ background:var(--surface);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
+ margin:0;padding:12px 14px 14px;box-shadow:var(--shadow);
+ display:flex;flex-direction:column;gap:10px;min-width:0;
+ }
+ legend{
+ font-size:12px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);
+ padding:0 6px;
+ }
+ .row{display:flex;gap:8px;flex-wrap:wrap}
+
+ button.ctl,.seg button{
+ font-family:var(--sans);font-size:13px;color:var(--text);
+ background:rgba(255,255,255,.04);border:1px solid var(--line);
+ border-radius:10px;padding:9px 13px;cursor:pointer;
+ transition:background .18s ease,border-color .18s ease,transform .12s ease,box-shadow .18s ease;
+ letter-spacing:.02em;white-space:nowrap;
+ }
+ button.ctl:hover,.seg button:hover{background:rgba(255,255,255,.09);transform:translateY(-1px)}
+ .seg button[aria-pressed="true"],button.ctl[aria-pressed="true"]{
+ background:linear-gradient(180deg,rgba(143,184,255,.28),rgba(143,184,255,.12));
+ border-color:rgba(143,184,255,.55);box-shadow:var(--glow);color:#fff;
+ }
+ .seg{display:flex;gap:6px;flex-wrap:wrap}
+ .movement-seg button{min-width:112px;text-align:center;flex-direction:column;display:flex;gap:2px}
+ .movement-seg button small{display:block;font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}
+ .movement-seg button[aria-pressed="true"] small{color:rgba(255,255,255,.75)}
+
+ label.slider{display:flex;flex-direction:column;gap:7px;font-size:12px;color:var(--muted);letter-spacing:.06em}
+ label.slider .val{color:var(--text);font-variant-numeric:tabular-nums}
+ input[type="range"]{
+ -webkit-appearance:none;appearance:none;width:170px;height:4px;border-radius:4px;
+ background:rgba(255,255,255,.18);outline:none;cursor:pointer;margin:4px 0;
+ }
+ input[type="range"]::-webkit-slider-thumb{
+ -webkit-appearance:none;width:18px;height:18px;border-radius:50%;
+ background:var(--accent);border:2px solid #fff;box-shadow:var(--glow);
+ }
+ input[type="range"]::-moz-range-thumb{
+ width:16px;height:16px;border-radius:50%;background:var(--accent);border:2px solid #fff;
+ }
+
+ /* Focus visibility — strong states */
+ :focus-visible{outline:3px solid var(--focus);outline-offset:2px;border-radius:6px}
+ button:focus-visible,input:focus-visible,summary:focus-visible{
+ outline:3px solid var(--focus);outline-offset:2px;box-shadow:0 0 0 5px rgba(255,233,168,.18);
+ }
+
+ /* ---- Curator note ---- */
+ .note{
+ position:fixed;left:20px;bottom:calc(100% );z-index:6;
+ }
+ details.note-panel{
+ position:fixed;right:20px;top:88px;z-index:6;width:min(340px,calc(100vw - 40px));
+ background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);
+ backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);box-shadow:var(--shadow);
+ animation:fadeUp 1.1s .3s cubic-bezier(.22,1,.36,1) both;
+ }
+ details.note-panel summary{
+ list-style:none;cursor:pointer;padding:12px 16px;
+ font-size:12px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);
+ display:flex;justify-content:space-between;align-items:center;
+ }
+ details.note-panel summary::-webkit-details-marker{display:none}
+ details.note-panel summary .chev{transition:transform .25s ease}
+ details.note-panel[open] summary .chev{transform:rotate(180deg)}
+ .note-body{padding:0 16px 16px;font-size:14px;line-height:1.6;color:var(--text)}
+ .note-body p{margin:0 0 12px}
+ .note-body .honest{
+ font-size:12.5px;color:var(--muted);border-top:1px solid var(--line);padding-top:12px;line-height:1.55;
+ }
+ .note-body .honest strong{color:var(--accent)}
+
+ @keyframes fadeUp{from{opacity:0;transform:translateY(28px)}to{opacity:1;transform:none}}
+
+ .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}
+
+ @media (max-width:720px){
+ .masthead{padding:16px 18px}
+ details.note-panel{top:auto;bottom:200px;right:12px;left:12px;width:auto}
+ .dock{gap:12px;padding:12px 12px calc(12px + env(safe-area-inset-bottom))}
+ input[type="range"]{width:130px}
+ }
+ @media (prefers-reduced-motion:reduce){
+ .masthead,.dock,details.note-panel{animation-duration:.01ms}
+ }
+</style>
+</head>
+<body>
+ <canvas id="stage" role="img"
+ aria-label="An animated moonlit forest clearing where stylized human silhouettes, deer, bears, wolves, frogs and fireflies move to a simulated instrumental pulse around an abstract cluster of speaker-like geometric forms."></canvas>
+
+ <header class="masthead">
+ <div>
+ <p class="kicker">A living tableau</p>
+ <h1>Midnight Woodland Jam</h1>
+ </div>
+ <p class="live-tag" aria-hidden="true">
+ Movement · <strong id="tagMovement">Gathering</strong><br>
+ Pulse · <strong id="tagState">Playing</strong>
+ </p>
+ </header>
+
+ <details class="note-panel">
+ <summary>Curator's Note <span class="chev">▾</span></summary>
+ <div class="note-body">
+ <p>Under a low moon, a clearing keeps a rhythm no one can hear. Figures spin and bend; deer turn their heads; bears sway; wolves loop the tree line; frogs answer the mushroom-light; fireflies braid themselves into brief spirals. Three movements — <em>Gathering</em>, <em>Deep Jam</em>, <em>Dawn Return</em> — shift the palette, the crowd, the camera, and the swarm.</p>
+ <p class="honest"><strong>Honest note:</strong> These visuals are an <strong>artistic rhythm simulation</strong>. The "pulse" is a beat generated entirely in code to choreograph motion. <strong>No audio is played, recorded, streamed, or analyzed</strong>, and the central shapes are abstract — not a real stage, brand, or logo.</p>
+ </div>
+ </details>
+
+ <div class="dock" role="region" aria-label="Tableau controls">
+ <fieldset>
+ <legend>Movement</legend>
+ <div class="seg movement-seg" role="group" aria-label="Choose a visual movement">
+ <button class="ctl" data-move="0" aria-pressed="true">Gathering<small>dusk · sparse</small></button>
+ <button class="ctl" data-move="1" aria-pressed="false">Deep Jam<small>peak · dense</small></button>
+ <button class="ctl" data-move="2" aria-pressed="false">Dawn Return<small>pale · rising</small></button>
+ </div>
+ </fieldset>
+
+ <fieldset>
+ <legend>Choreography</legend>
+ <div class="seg" role="group" aria-label="Isolate choreography">
+ <button class="ctl" id="isoBoth" aria-pressed="true">Everyone</button>
+ <button class="ctl" id="isoHumans" aria-pressed="false">Humans only</button>
+ <button class="ctl" id="isoAnimals" aria-pressed="false">Animals only</button>
+ </div>
+ </fieldset>
+
+ <fieldset>
+ <legend>Playback</legend>
+ <div class="row">
+ <button class="ctl" id="pauseBtn" aria-pressed="false">Pause everything</button>
+ <button class="ctl" id="reduceBtn" aria-pressed="false">Reduced motion</button>
+ </div>
+ </fieldset>
+
+ <fieldset>
+ <legend>Visual tempo</legend>
+ <label class="slider" for="tempo">
+ <span>Simulated pulse speed · <span class="val" id="tempoVal">1.0×</span></span>
+ <input type="range" id="tempo" min="0.3" max="2" step="0.05" value="1" aria-describedby="tempoHelp">
+ <span id="tempoHelp" class="sr-only">Adjusts how fast the imagined instrumental pulse drives all movement.</span>
+ </label>
+ </fieldset>
+ </div>
+
+ <div class="sr-only" aria-live="polite" id="announce"></div>
+
+<script>
+(function(){
+ "use strict";
+ const canvas = document.getElementById('stage');
+ const ctx = canvas.getContext('2d');
+ let W=0,H=0,dpr=1;
+
+ /* ---------- utilities ---------- */
+ const clamp=(v,a,b)=>v<a?a:v>b?b:v;
+ const lerp=(a,b,t)=>a+(b-a)*t;
+ function mulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296;}}
+ function hash(i){let x=Math.sin(i*12.9898)*43758.5453;return x-Math.floor(x);}
+ function noise(x){const i=Math.floor(x),f=x-i;const u=f*f*(3-2*f);return lerp(hash(i),hash(i+1),u);}
+ function hex(h){h=h.replace('#','');return [parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16)];}
+ const rgba=(c,a)=>`rgba(${c[0]|0},${c[1]|0},${c[2]|0},${a})`;
+ const lerpC=(a,b,t)=>[lerp(a[0],b[0],t),lerp(a[1],b[1],t),lerp(a[2],b[2],t)];
+
+ /* ---------- movement themes ---------- */
+ const THEMES=[
+ { // Gathering — cool indigo dusk
+ skyTop:hex('#070a1a'), skyBot:hex('#182247'), moon:hex('#d3e3ff'), glow:hex('#5f78c8'),
+ accentA:hex('#7fa8e6'), accentB:hex('#a8e0d6'), sil:hex('#0a0d1e'), tree:hex('#0b1024'),
+ ground:hex('#141d3f'), fire:hex('#bcd2ff'),
+ zoom:0.9, horizon:0.60, density:0.55, spiral:0.0, mush:0.9
+ },
+ { // Deep Jam — warm magenta/amber peak
+ skyTop:hex('#140a24'), skyBot:hex('#3a1544'), moon:hex('#ffd9c0'), glow:hex('#c85aa0'),
+ accentA:hex('#ff6f9d'), accentB:hex('#ffb257'), sil:hex('#120817'), tree:hex('#180a26'),
+ ground:hex('#3a1a44'), fire:hex('#ffd18a'),
+ zoom:1.16, horizon:0.55, density:1.0, spiral:1.0, mush:1.0
+ },
+ { // Dawn Return — pale rose gold rising
+ skyTop:hex('#2a2440'), skyBot:hex('#d8a79c'), moon:hex('#fff0d2'), glow:hex('#f0b79f'),
+ accentA:hex('#f2b6a6'), accentB:hex('#ffe6b0'), sil:hex('#241a2e'), tree:hex('#3a2c45'),
+ ground:hex('#c79a94'), fire:hex('#fff0cf'),
+ zoom:1.0, horizon:0.5, density:0.7, spiral:0.35, mush:0.6
+ }
+ ];
+ // live (interpolated) theme
+ let theme=JSON.parse(JSON.stringify(THEMES[0]));
+ let target=THEMES[0];
+ let curMove=0;
+
+ function setMovement(i){
+ curMove=i; target=THEMES[i];
+ document.querySelectorAll('.movement-seg button').forEach(b=>b.setAttribute('aria-pressed', b.dataset.move==i?'true':'false'));
+ const names=['Gathering','Deep Jam','Dawn Return'];
+ document.getElementById('tagMovement').textContent=names[i];
+ announce('Movement changed to '+names[i]);
+ }
+ function tickTheme(dt){
+ const s=clamp(dt*1.4,0,1);
+ for(const k of ['skyTop','skyBot','moon','glow','accentA','accentB','sil','tree','ground','fire']){
+ theme[k]=lerpC(theme[k],target[k],s);
+ }
+ for(const k of ['zoom','horizon','density','spiral','mush']){
+ theme[k]=lerp(theme[k],target[k],s);
+ }
+ }
+
+ /* ---------- creatures ---------- */
+ const rnd=mulberry32(1337);
+ const creatures=[];
+ const HUMAN_BEHAV=['spin','bend','clap','drift'];
+
+ // cluster anchors around the speaker (fractional, within clearing ellipse)
+ const groups=[
+ {x:0.30,y:0.74},{x:0.68,y:0.75},{x:0.42,y:0.83},{x:0.60,y:0.68}
+ ];
+
+ function inClearing(fx,fy){
+ const dx=(fx-0.5)/0.30, dy=(fy-0.75)/0.16;
+ return dx*dx+dy*dy<=1;
+ }
+ function scatter(cx,cy,r){
+ let fx,fy,g=0;
+ do{ const a=rnd()*Math.PI*2, rr=Math.sqrt(rnd())*r;
+ fx=cx+Math.cos(a)*rr*0.9; fy=cy+Math.sin(a)*rr*0.34;
+ }while(!inClearing(fx,fy) && g++<12);
+ return {fx,fy};
+ }
+
+ // Humans (kind:'human')
+ for(let i=0;i<15;i++){
+ const gi=i%groups.length;
+ const p=scatter(groups[gi].x,groups[gi].y,0.16);
+ creatures.push({
+ kind:'human', side:'human',
+ fx:p.fx, fy:p.fy, home:{...p},
+ behav:HUMAN_BEHAV[i%4],
+ off:rnd()*10, seed:rnd()*100,
+ scale:lerp(0.85,1.15,rnd()),
+ driftG:gi, driftTarget:(gi+1)%groups.length
+ });
+ }
+ // Deer
+ for(let i=0;i<5;i++){const p=scatter(lerp(0.2,0.8,rnd()),lerp(0.66,0.84,rnd()),0.2);
+ creatures.push({kind:'deer',side:'animal',fx:p.fx,fy:p.fy,off:rnd()*10,seed:rnd()*100,scale:lerp(.9,1.1,rnd()),face:rnd()<.5?1:-1});}
+ // Bears
+ for(let i=0;i<3;i++){const p=scatter(lerp(0.15,0.85,rnd()),lerp(0.7,0.86,rnd()),0.25);
+ creatures.push({kind:'bear',side:'animal',fx:p.fx,fy:p.fy,off:rnd()*10,seed:rnd()*100,scale:lerp(1,1.25,rnd()),face:rnd()<.5?1:-1});}
+ // Wolves (loop runs) — anchor + loop radius
+ for(let i=0;i<4;i++){const cx=lerp(0.18,0.82,rnd()),cy=lerp(0.72,0.85,rnd());
+ creatures.push({kind:'wolf',side:'animal',fx:cx,fy:cy,cx,cy,off:rnd()*10,seed:rnd()*100,scale:lerp(.85,1.05,rnd()),loopR:lerp(0.05,0.09,rnd()),dir:rnd()<.5?1:-1});}
+ // Mushrooms + frogs beside them
+ const mushrooms=[];
+ for(let i=0;i<7;i++){const p=scatter(lerp(0.16,0.84,rnd()),lerp(0.72,0.9,rnd()),0.3);
+ mushrooms.push({fx:p.fx,fy:p.fy,scale:lerp(.7,1.2,rnd()),seed:rnd()*100});
+ if(i<6){creatures.push({kind:'frog',side:'animal',fx:p.fx+0.02,fy:p.fy+0.005,off:rnd()*10,seed:rnd()*100,scale:lerp(.8,1.1,rnd()),face:rnd()<.5?1:-1});}
+ }
+
+ // Fireflies
+ const fireflies=[];
+ for(let i=0;i<95;i++){
+ fireflies.push({fx:rnd(),fy:lerp(0.25,0.92,rnd()),seed:rnd()*1000,
+ spGroup:i%5, phase:rnd()*Math.PI*2, sr:lerp(0.04,0.13,rnd()), vx:0,vy:0,
+ x:0,y:0,init:false});
+ }
+ const spiralCenters=[{fx:0.5,fy:0.5},{fx:0.32,fy:0.44},{fx:0.68,fy:0.46},{fx:0.42,fy:0.58},{fx:0.6,fy:0.6}];
+
+ // stars
+ const stars=[];
+ for(let i=0;i<130;i++){stars.push({fx:rnd(),fy:rnd()*0.55,r:rnd()*1.3+0.2,tw:rnd()*6});}
+
+ // trees (silhouette line)
+ const treesFar=[],treesMid=[];
+ for(let i=0;i<26;i++){treesFar.push({fx:i/25+ (rnd()-.5)*0.02, h:lerp(0.1,0.22,rnd()), w:lerp(0.02,0.05,rnd()),seed:rnd()*10});}
+ for(let i=0;i<18;i++){treesMid.push({fx:i/17+(rnd()-.5)*0.03, h:lerp(0.18,0.34,rnd()), w:lerp(0.03,0.07,rnd()),seed:rnd()*10});}
+
+ /* ---------- state ---------- */
+ const state={ paused:false, reduce:false, tempo:1, show:{human:true,animal:true}, t:0, last:0, energy:0 };
+ // pulse: returns per-offset envelope 0..1
+ function beatEnv(phase,off){
+ const p=(phase+off);
+ let b=p-Math.floor(p);
+ // two-hit shuffle for a more musical, less metronomic feel
+ const e1=Math.pow(1-b,2.2);
+ const e2=b>0.5?Math.pow(1-(b-0.5)*2,2.8)*0.55:0;
+ return clamp(e1+e2,0,1);
+ }
+
+ /* ---------- resize ---------- */
+ function resize(){
+ dpr=Math.min(window.devicePixelRatio||1,2);
+ W=window.innerWidth;H=window.innerHeight;
+ canvas.width=W*dpr;canvas.height=H*dpr;canvas.style.width=W+'px';canvas.style.height=H+'px';
+ ctx.setTransform(dpr,0,0,dpr,0,0);
+ }
+ window.addEventListener('resize',resize);resize();
+
+ /* ---------- drawing helpers ---------- */
+ function drawSky(){
+ const hz=theme.horizon;
+ const g=ctx.createLinearGradient(0,0,0,H);
+ g.addColorStop(0,rgba(theme.skyTop,1));
+ g.addColorStop(clamp(hz,0.05,0.95),rgba(theme.skyBot,1));
+ g.addColorStop(1,rgba(theme.ground,1));
+ ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
+ }
+ function drawStars(t){
+ for(const s of stars){
+ const tw=0.4+0.6*Math.abs(Math.sin(t*0.7+s.tw));
+ const a=tw*(0.6- theme.horizon*0.3)* (curMove===2?0.4:1);
+ if(a<=0.02)continue;
+ ctx.fillStyle=rgba(theme.moon,a*0.8);
+ ctx.beginPath();ctx.arc(s.fx*W,s.fy*H,s.r,0,7);ctx.fill();
+ }
+ }
+ function drawMoon(t){
+ const mx=W*0.78,my=H*0.2;
+ const pulse=0.9+state.energy*0.25;
+ const R=Math.min(W,H)*0.07;
+ // glow
+ const gg=ctx.createRadialGradient(mx,my,R*0.3,mx,my,R*6*pulse);
+ gg.addColorStop(0,rgba(theme.glow,0.5));
+ gg.addColorStop(0.4,rgba(theme.glow,0.16));
+ gg.addColorStop(1,rgba(theme.glow,0));
+ ctx.fillStyle=gg;ctx.fillRect(0,0,W,H);
+ // disc
+ const dg=ctx.createRadialGradient(mx-R*0.3,my-R*0.3,R*0.2,mx,my,R);
+ dg.addColorStop(0,rgba(theme.moon,1));
+ dg.addColorStop(1,rgba(theme.moon,0.82));
+ ctx.fillStyle=dg;ctx.beginPath();ctx.arc(mx,my,R,0,7);ctx.fill();
+ }
+ function drawTrees(t){
+ const hz=theme.horizon*H;
+ const zf=1+(theme.zoom-1)*0.25;
+ // far
+ ctx.fillStyle=rgba(theme.tree,0.65);
+ for(const tr of treesFar){drawTree(tr.fx*W,hz+6,tr.h*H*zf,tr.w*W*zf,theme.tree,0.6);}
+ ctx.fillStyle=rgba(theme.tree,0.9);
+ for(const tr of treesMid){drawTree(tr.fx*W,hz+18,tr.h*H*zf*1.1,tr.w*W*zf*1.15,theme.sil,0.92);}
+ }
+ function drawTree(x,base,h,w,col,a){
+ ctx.fillStyle=rgba(col,a);
+ // trunk
+ ctx.fillRect(x-w*0.12,base-h*0.5,w*0.24,h*0.5);
+ // conical canopy
+ ctx.beginPath();
+ ctx.moveTo(x,base-h);
+ ctx.lineTo(x+w*0.6,base-h*0.35);
+ ctx.lineTo(x+w*0.3,base-h*0.4);
+ ctx.lineTo(x+w*0.75,base-h*0.05);
+ ctx.lineTo(x-w*0.75,base-h*0.05);
+ ctx.lineTo(x-w*0.3,base-h*0.4);
+ ctx.lineTo(x-w*0.6,base-h*0.35);
+ ctx.closePath();ctx.fill();
+ }
+ function drawGround(){
+ const cy=0.75*H;
+ const g=ctx.createRadialGradient(0.5*W,cy,10,0.5*W,cy,0.5*W);
+ g.addColorStop(0,rgba(theme.glow,0.22+state.energy*0.12));
+ g.addColorStop(0.5,rgba(theme.ground,0.5));
+ g.addColorStop(1,rgba(theme.ground,0));
+ ctx.save();
+ ctx.translate(0.5*W,cy);ctx.scale(1,0.34);
+ ctx.fillStyle=g;ctx.beginPath();ctx.arc(0,0,0.5*W,0,7);ctx.fill();
+ ctx.restore();
+ }
+
+ function stageTransform(){
+ const cx=0.5*W, cy=0.68*H, z=theme.zoom;
+ ctx.translate(cx,cy);ctx.scale(z,z);ctx.translate(-cx,-cy);
+ }
+
+ // Abstract speaker cluster + hand-painted light (NOT a real stage/logo)
+ function drawSpeakers(t){
+ const cx=0.5*W, cy=0.63*H;
+ const s=Math.min(W,H)*0.09;
+ const e=state.energy;
+ ctx.save();ctx.translate(cx,cy);
+ // painted light aura
+ const ag=ctx.createRadialGradient(0,0,s*0.3,0,0,s*4);
+ ag.addColorStop(0,rgba(theme.accentA,0.28+e*0.25));
+ ag.addColorStop(0.5,rgba(theme.accentB,0.10+e*0.1));
+ ag.addColorStop(1,rgba(theme.accentA,0));
+ ctx.fillStyle=ag;ctx.fillRect(-s*4,-s*4,s*8,s*8);
+
+ // hand-painted light rays (brush strokes)
+ const rays=7;
+ for(let i=0;i<rays;i++){
+ const a=(i/rays)*Math.PI*2 + t*0.15 + noise(i*3+t*0.2)*0.6;
+ const len=s*(2.2+ e*2.2 + noise(i*7+t)*0.8);
+ ctx.save();ctx.rotate(a);
+ const lg=ctx.createLinearGradient(0,0,0,-len);
+ lg.addColorStop(0,rgba(i%2?theme.accentB:theme.accentA,0.35+e*0.3));
+ lg.addColorStop(1,rgba(theme.accentA,0));
+ ctx.fillStyle=lg;
+ ctx.beginPath();ctx.moveTo(-s*0.16,0);
+ ctx.quadraticCurveTo(-s*0.05,-len*0.6,0,-len);
+ ctx.quadraticCurveTo(s*0.05,-len*0.6,s*0.16,0);
+ ctx.closePath();ctx.fill();
+ ctx.restore();
+ }
+
+ // geometric speaker forms — stacked prisms/cones (abstract)
+ const bob=e*s*0.12;
+ const sil=rgba(theme.sil,0.95);
+ // left & right monoliths
+ for(const dxs of [-1,1]){
+ ctx.save();ctx.translate(dxs*s*1.5,bob*(dxs>0?1:0.6));
+ ctx.fillStyle=sil;
+ ctx.strokeStyle=rgba(theme.accentA,0.5);ctx.lineWidth=1.4;
+ roundRect(-s*0.5,-s*1.6,s,s*2.2,6);ctx.fill();ctx.stroke();
+ // cone driver circles pulsing
+ for(let k=0;k<3;k++){
+ const yy=-s*1.15+k*s*0.75;
+ ctx.beginPath();ctx.arc(0,yy,s*0.30*(1+ (k===1?e*0.15:0)),0,7);
+ ctx.fillStyle=rgba(theme.accentB,0.18+ (k===1?e*0.35:0.1));ctx.fill();
+ ctx.strokeStyle=rgba(theme.accentA,0.6);ctx.lineWidth=1.2;ctx.stroke();
+ }
+ ctx.restore();
+ }
+ // center pillar — triangular prism
+ ctx.save();ctx.translate(0,-bob);
+ ctx.fillStyle=sil;ctx.strokeStyle=rgba(theme.accentB,0.55);ctx.lineWidth=1.6;
+ ctx.beginPath();
+ ctx.moveTo(0,-s*2.0);ctx.lineTo(s*0.85,-s*0.2);ctx.lineTo(-s*0.85,-s*0.2);ctx.closePath();
+ ctx.fill();ctx.stroke();
+ // pulsing core
+ ctx.beginPath();ctx.arc(0,-s*0.9,s*0.34*(0.8+e*0.5),0,7);
+ ctx.fillStyle=rgba(theme.accentA,0.3+e*0.5);ctx.fill();
+ ctx.restore();
+
+ ctx.restore();
+ }
+ function roundRect(x,y,w,h,r){ctx.beginPath();ctx.moveTo(x+r,y);ctx.arcTo(x+w,y,x+w,y+h,r);ctx.arcTo(x+w,y+h,x,y+h,r);ctx.arcTo(x,y+h,x,y,r);ctx.arcTo(x,y,x+w,y,r);ctx.closePath();}
+
+ function drawMushrooms(t){
+ for(const m of mushrooms){
+ const x=m.fx*W,y=m.fy*H,s=m.scale*Math.min(W,H)*0.012;
+ const glow=(0.5+0.5*Math.sin(t*1.4+m.seed))*theme.mush;
+ // glow
+ const gg=ctx.createRadialGradient(x,y-s,0,x,y-s,s*5);
+ gg.addColorStop(0,rgba(theme.accentB,0.4*glow*theme.mush));
+ gg.addColorStop(1,rgba(theme.accentB,0));
+ ctx.fillStyle=gg;ctx.fillRect(x-s*5,y-s*6,s*10,s*10);
+ // stem
+ ctx.fillStyle=rgba(theme.sil,0.9);ctx.fillRect(x-s*0.28,y-s*1.1,s*0.56,s*1.2);
+ // cap
+ ctx.fillStyle=rgba(theme.accentB,0.55+0.4*glow);
+ ctx.beginPath();ctx.ellipse(x,y-s*1.1,s*1.05,s*0.7,0,Math.PI,0);ctx.fill();
+ }
+ }
+
+ /* ---- creature drawing ---- */
+ function drawHuman(a,t,phase){
+ const x=a.fx*W,y=a.fy*H;
+ const s=a.scale*Math.min(W,H)*0.028*theme.density*0.9+a.scale*Math.min(W,H)*0.02;
+ const e=beatEnv(phase,a.off)*(state.reduce?0.12:1);
+ const sway=noise(a.seed+t*0.4)-0.5;
+ ctx.save();ctx.translate(x,y);
+ let rot=0, sqz=1, armUp=0, hip=0, lift=0;
+ if(a.behav==='spin'){ rot=(t*0.9+a.off)*1.1*(state.reduce?0.1:1)+ sway*0.4; armUp=0.3+e*0.3;}
+ else if(a.behav==='bend'){ sqz=1-0.22*e; lift=e*s*0.05; armUp=0.15+e*0.2;}
+ else if(a.behav==='clap'){ armUp=0.55+e*0.55; lift=-e*s*0.14;}
+ else { rot=sway*0.25; armUp=0.2+ (0.5+0.5*Math.sin(t+a.off))*0.2; }
+ ctx.rotate(rot*0.12 + sway*0.05);
+ ctx.translate(0,lift);
+ ctx.strokeStyle=rgba(theme.sil,0.95);
+ ctx.fillStyle=rgba(theme.sil,0.95);
+ ctx.lineWidth=s*0.26;ctx.lineCap='round';ctx.lineJoin='round';
+ const hipY=-s*sqz*1.0, shY=-s*sqz*1.9, headY=shY-s*0.5;
+ // legs
+ const kneeSpread=s*0.28*(1+ (a.behav==='bend'?e*0.6:0));
+ ctx.beginPath();
+ ctx.moveTo(0,hipY);ctx.lineTo(-kneeSpread,-s*0.1);ctx.moveTo(0,hipY);ctx.lineTo(kneeSpread,-s*0.1);
+ ctx.stroke();
+ // torso
+ ctx.beginPath();ctx.moveTo(0,hipY);ctx.lineTo(0,shY);ctx.stroke();
+ // arms
+ const ang=lerp(2.5,1.25,clamp(armUp,0,1)); // from down to overhead
+ const aL=Math.PI+ang, aR=-ang;
+ const armLen=s*0.9;
+ ctx.beginPath();
+ ctx.moveTo(0,shY);ctx.lineTo(Math.cos(aL)*armLen*0+ -Math.sin(ang)*armLen, shY-Math.cos(ang)*armLen*(armUp>0.5?1:0.4)*0 - armLen*Math.sin(clamp(armUp,0,1)*1.2)*0);
+ ctx.stroke();
+ // simpler explicit arms:
+ ctx.beginPath();
+ const handY=shY - Math.sin(clamp(armUp,0,1)*1.4)*armLen;
+ const handX=Math.cos(clamp(armUp,0,1)*1.4)*armLen*0.7;
+ ctx.moveTo(0,shY);ctx.lineTo(-handX,handY);
+ ctx.moveTo(0,shY);ctx.lineTo(handX,handY);
+ ctx.stroke();
+ if(a.behav==='clap'&&armUp>0.9){ // clap flash overhead
+ ctx.fillStyle=rgba(theme.accentB,0.4*e);
+ ctx.beginPath();ctx.arc(0,handY,s*0.4,0,7);ctx.fill();
+ }
+ // head
+ ctx.fillStyle=rgba(theme.sil,0.97);
+ ctx.beginPath();ctx.arc(0,headY,s*0.32,0,7);ctx.fill();
+ ctx.restore();
+ }
+
+ function drawDeer(a,t,phase){
+ const x=a.fx*W,y=a.fy*H,s=a.scale*Math.min(W,H)*0.03;
+ const e=beatEnv(phase,a.off)*(state.reduce?0.15:1);
+ const step=(0.5+0.5*Math.sin(t*2+a.off))*e;
+ const headTurn=Math.sin(t*0.7+a.seed)*0.5;
+ ctx.save();ctx.translate(x,y-step*s*0.15);ctx.scale(a.face,1);
+ ctx.fillStyle=rgba(theme.sil,0.95);ctx.strokeStyle=rgba(theme.sil,0.95);
+ ctx.lineWidth=s*0.14;ctx.lineCap='round';
+ // legs
+ ctx.beginPath();
+ ctx.moveTo(-s*0.5,-s*0.5);ctx.lineTo(-s*0.55+step*s*0.2,0);
+ ctx.moveTo(-s*0.25,-s*0.5);ctx.lineTo(-s*0.2,0);
+ ctx.moveTo(s*0.3,-s*0.5);ctx.lineTo(s*0.3-step*s*0.2,0);
+ ctx.moveTo(s*0.5,-s*0.5);ctx.lineTo(s*0.55,0);
+ ctx.stroke();
+ // body
+ ctx.beginPath();ctx.ellipse(0,-s*0.7,s*0.62,s*0.34,0,0,7);ctx.fill();
+ // neck+head
+ ctx.save();ctx.translate(s*0.55,-s*0.95);ctx.rotate(-0.5+headTurn*0.4);
+ ctx.fillRect(-s*0.08,-s*0.6,s*0.18,s*0.7);
+ ctx.beginPath();ctx.ellipse(0,-s*0.6,s*0.22,s*0.14,0,0,7);ctx.fill();
+ // antlers
+ ctx.lineWidth=s*0.07;
+ ctx.beginPath();
+ ctx.moveTo(-s*0.05,-s*0.7);ctx.lineTo(-s*0.2,-s*1.05);ctx.moveTo(-s*0.14,-s*0.9);ctx.lineTo(-s*0.28,-s*0.95);
+ ctx.moveTo(s*0.05,-s*0.7);ctx.lineTo(s*0.18,-s*1.05);ctx.moveTo(s*0.12,-s*0.9);ctx.lineTo(s*0.26,-s*0.95);
+ ctx.stroke();
+ ctx.restore();
+ ctx.restore();
+ }
+
+ function drawBear(a,t,phase){
+ const x=a.fx*W,y=a.fy*H,s=a.scale*Math.min(W,H)*0.036;
+ const e=beatEnv(phase,a.off)*(state.reduce?0.12:1);
+ const rock=Math.sin(t*1.6+a.off)*(0.2+e*0.35);
+ ctx.save();ctx.translate(x,y);ctx.rotate(rock*0.14);ctx.scale(a.face,1);
+ ctx.fillStyle=rgba(theme.sil,0.96);
+ // legs
+ ctx.beginPath();ctx.ellipse(-s*0.3,-s*0.15,s*0.22,s*0.3,0,0,7);ctx.fill();
+ ctx.beginPath();ctx.ellipse(s*0.3,-s*0.15,s*0.22,s*0.3,0,0,7);ctx.fill();
+ // body
+ ctx.beginPath();ctx.ellipse(0,-s*0.75,s*0.62,s*0.7,0,0,7);ctx.fill();
+ // head
+ ctx.beginPath();ctx.arc(0,-s*1.5,s*0.42,0,7);ctx.fill();
+ // ears
+ ctx.beginPath();ctx.arc(-s*0.3,-s*1.85,s*0.16,0,7);ctx.arc(s*0.3,-s*1.85,s*0.16,0,7);ctx.fill();
+ ctx.restore();
+ }
+
+ function drawWolf(a,t,phase){
+ // gentle looping run
+ const ang=(t*0.5*a.dir + a.off)*(state.reduce?0.15:1);
+ const lx=(a.cx+Math.cos(ang)*a.loopR)*W;
+ const ly=(a.cy+Math.sin(ang)*a.loopR*0.4)*H;
+ a.fx=lx/W; a.fy=ly/H;
+ const s=a.scale*Math.min(W,H)*0.024;
+ const dir=Math.cos(ang)*a.dir<0?-1:1;
+ const bob=Math.sin(t*6+a.off)*s*0.08*(state.reduce?0.2:1);
+ ctx.save();ctx.translate(lx,ly+bob);ctx.scale(dir,1);
+ ctx.fillStyle=rgba(theme.sil,0.95);ctx.strokeStyle=rgba(theme.sil,0.95);
+ ctx.lineWidth=s*0.13;ctx.lineCap='round';
+ const gait=Math.sin(t*8+a.off);
+ ctx.beginPath();
+ ctx.moveTo(-s*0.4,-s*0.4);ctx.lineTo(-s*0.4+gait*s*0.3,0);
+ ctx.moveTo(s*0.35,-s*0.4);ctx.lineTo(s*0.35-gait*s*0.3,0);
+ ctx.stroke();
+ // body
+ ctx.beginPath();ctx.ellipse(0,-s*0.6,s*0.6,s*0.26,0,0,7);ctx.fill();
+ // tail
+ ctx.beginPath();ctx.moveTo(-s*0.55,-s*0.6);ctx.quadraticCurveTo(-s*0.95,-s*0.5,-s*0.85,-s*0.9);ctx.lineWidth=s*0.2;ctx.stroke();
+ // head
+ ctx.save();ctx.translate(s*0.6,-s*0.7);
+ ctx.beginPath();ctx.moveTo(0,0);ctx.lineTo(s*0.4,-s*0.05);ctx.lineTo(s*0.15,s*0.2);ctx.closePath();ctx.fill();
+ // ears
+ ctx.beginPath();ctx.moveTo(-s*0.02,-s*0.05);ctx.lineTo(-s*0.05,-s*0.3);ctx.lineTo(s*0.12,-s*0.1);ctx.closePath();ctx.fill();
+ ctx.restore();
+ ctx.restore();
+ }
+
+ function drawFrog(a,t,phase){
+ const x=a.fx*W,y=a.fy*H,s=a.scale*Math.min(W,H)*0.013;
+ const e=beatEnv(phase,a.off)*(state.reduce?0.1:1);
+ const jump=Math.max(0,Math.sin(t*3+a.off))*e;
+ const hop=jump*s*2.2;
+ const sq=1+jump*0.3, st=1-jump*0.2;
+ ctx.save();ctx.translate(x,y-hop);ctx.scale(a.face,1);
+ ctx.fillStyle=rgba(theme.sil,0.95);
+ // legs
+ ctx.beginPath();ctx.ellipse(-s*0.7,s*0.1,s*0.3*sq,s*0.16,0.6,0,7);ctx.fill();
+ ctx.beginPath();ctx.ellipse(s*0.7,s*0.1,s*0.3*sq,s*0.16,-0.6,0,7);ctx.fill();
+ // body
+ ctx.beginPath();ctx.ellipse(0,0,s*0.7*sq,s*0.55*st,0,0,7);ctx.fill();
+ // eyes
+ ctx.beginPath();ctx.arc(-s*0.28,-s*0.5,s*0.2,0,7);ctx.arc(s*0.28,-s*0.5,s*0.2,0,7);ctx.fill();
+ ctx.fillStyle=rgba(theme.accentB,0.8);
+ ctx.beginPath();ctx.arc(-s*0.28,-s*0.5,s*0.08,0,7);ctx.arc(s*0.28,-s*0.5,s*0.08,0,7);ctx.fill();
+ ctx.restore();
+ }
+
+ /* ---- fireflies ---- */
+ function updateFireflies(t,dt){
+ const spiral=theme.spiral;
+ for(const f of fireflies){
+ if(!f.init){f.x=f.fx*W;f.y=f.fy*H;f.init=true;}
+ let tx,ty;
+ if(spiral>0.05){
+ const c=spiralCenters[f.spGroup];
+ const rot=t*0.6*(1+f.spGroup*0.1)+f.phase;
+ const rad=f.sr*Math.min(W,H)* (0.4+ (f.phase%1));
+ tx=c.fx*W + Math.cos(rot)*rad;
+ ty=c.fy*H + Math.sin(rot)*rad*0.7;
+ }else{ tx=null; }
+ // wander target
+ const wx=(0.5+0.45*(noise(f.seed*0.13+t*0.15)-0.5)*2)*W;
+ const wy=(0.55+0.4*(noise(f.seed*0.21+t*0.12+50)-0.5)*2)*H;
+ let gx = spiral>0.05 ? lerp(wx,tx,spiral) : wx;
+ let gy = spiral>0.05 ? lerp(wy,ty,spiral) : wy;
+ const sp=state.reduce?0.4:2.2;
+ f.vx += (gx-f.x)*0.0009*sp; f.vy += (gy-f.y)*0.0009*sp;
+ f.vx*=0.94; f.vy*=0.94;
+ f.x+=f.vx*dt*60; f.y+=f.vy*dt*60;
+ }
+ }
+ function drawFireflies(t){
+ ctx.globalCompositeOperation='lighter';
+ for(const f of fireflies){
+ const blink=0.4+0.6*Math.abs(Math.sin(t*(1.5+ (f.seed%1)) +f.phase));
+ const r=Math.min(W,H)*0.004*(0.6+blink*0.8);
+ const g=ctx.createRadialGradient(f.x,f.y,0,f.x,f.y,r*4);
+ g.addColorStop(0,rgba(theme.fire,0.9*blink));
+ g.addColorStop(0.4,rgba(theme.accentB,0.4*blink));
+ g.addColorStop(1,rgba(theme.fire,0));
+ ctx.fillStyle=g;ctx.beginPath();ctx.arc(f.x,f.y,r*4,0,7);ctx.fill();
+ }
+ ctx.globalCompositeOperation='source-over';
+ }
+
+ function drawVignette(){
+ const g=ctx.createRadialGradient(0.5*W,0.5*H,Math.min(W,H)*0.3,0.5*W,0.5*H,Math.max(W,H)*0.75);
+ g.addColorStop(0,'rgba(0,0,0,0)');g.addColorStop(1,'rgba(0,0,0,0.55)');
+ ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
+ }
+
+ /* ---------- main loop ---------- */
+ function render(t,dt){
+ tickTheme(dt);
+ // global pulse energy (throb) for lighting even in reduced motion (slow)
+ const phase = t*state.tempo;
+ state.energy = state.reduce
+ ? 0.35+0.25*Math.sin(t*0.6)
+ : lerp(state.energy, 0.3+0.7*beatEnv(phase,0), 0.25);
+
+ drawSky();
+ drawStars(t);
+ drawMoon(t);
+ drawTrees(t);
+ drawGround();
+
+ ctx.save();
+ stageTransform();
+ drawSpeakers(t);
+ drawMushrooms(t);
+
+ // creatures sorted by y (depth)
+ const vis=creatures.filter(c=> (c.side==='human'&&state.show.human)||(c.side==='animal'&&state.show.animal));
+ // handle human drift between groups
+ if(!state.reduce){
+ for(const c of creatures){
+ if(c.kind==='human'&&c.behav==='drift'){
+ const g=groups[c.driftTarget];
+ const spd=0.06*dt;
+ c.fx=lerp(c.fx,g.x,spd); c.fy=lerp(c.fy,g.y,spd);
+ if(Math.abs(c.fx-g.x)<0.01){ c.driftG=c.driftTarget; c.driftTarget=(c.driftTarget+ (Math.random()<0.5?1:2))%groups.length; }
+ }
+ }
+ }
+ vis.sort((a,b)=>a.fy-b.fy);
+ for(const c of vis){
+ if(c.kind==='human')drawHuman(c,t,phase);
+ else if(c.kind==='deer')drawDeer(c,t,phase);
+ else if(c.kind==='bear')drawBear(c,t,phase);
+ else if(c.kind==='wolf')drawWolf(c,t,phase);
+ else if(c.kind==='frog')drawFrog(c,t,phase);
+ }
+ ctx.restore();
+
+ updateFireflies(t,dt);
+ drawFireflies(t);
+ drawVignette();
+ }
+
+ function frame(now){
+ if(!state.last)state.last=now;
+ let dt=(now-state.last)/1000; state.last=now;
+ dt=clamp(dt,0,0.05);
+ if(!state.paused){ state.t += dt; }
+ render(state.t,state.paused?0.0001:dt);
+ requestAnimationFrame(frame);
+ }
+ requestAnimationFrame(frame);
+
+ /* ---------- controls ---------- */
+ function announce(msg){document.getElementById('announce').textContent=msg;}
+
+ document.querySelectorAll('.movement-seg button').forEach(b=>{
+ b.addEventListener('click',()=>setMovement(+b.dataset.move));
+ });
+
+ const isoBoth=document.getElementById('isoBoth'),
+ isoH=document.getElementById('isoHumans'),
+ isoA=document.getElementById('isoAnimals');
+ function setIso(mode){
+ state.show.human = (mode==='both'||mode==='human');
+ state.show.animal= (mode==='both'||mode==='animal');
+ isoBoth.setAttribute('aria-pressed',mode==='both');
+ isoH.setAttribute('aria-pressed',mode==='human');
+ isoA.setAttribute('aria-pressed',mode==='animal');
+ announce(mode==='both'?'Showing all choreography':mode==='human'?'Isolated human choreography':'Isolated animal choreography');
+ }
+ isoBoth.addEventListener('click',()=>setIso('both'));
+ isoH.addEventListener('click',()=>setIso('human'));
+ isoA.addEventListener('click',()=>setIso('animal'));
+
+ const pauseBtn=document.getElementById('pauseBtn');
+ pauseBtn.addEventListener('click',()=>{
+ state.paused=!state.paused;
+ pauseBtn.setAttribute('aria-pressed',state.paused);
+ pauseBtn.textContent=state.paused?'Resume everything':'Pause everything';
+ document.getElementById('tagState').textContent=state.paused?'Paused':(state.reduce?'Reduced':'Playing');
+ announce(state.paused?'Everything paused':'Resumed');
+ });
+
+ const reduceBtn=document.getElementById('reduceBtn');
+ function setReduce(v){
+ state.reduce=v;
+ reduceBtn.setAttribute('aria-pressed',v);
+ reduceBtn.textContent=v?'Full motion':'Reduced motion';
+ document.getElementById('tagState').textContent=state.paused?'Paused':(v?'Reduced':'Playing');
+ announce(v?'Reduced motion on — only slow lighting changes':'Full motion restored');
+ }
+ reduceBtn.addEventListener('click',()=>setReduce(!state.reduce));
+
+ const tempo=document.getElementById('tempo'),tempoVal=document.getElementById('tempoVal');
+ tempo.addEventListener('input',()=>{
+ state.tempo=+tempo.value; tempoVal.textContent=(+tempo.value).toFixed(1)+'×';
+ });
+
+ // Respect OS reduced-motion preference on load
+ if(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches){
+ setReduce(true);
+ }
+
+ // keyboard: space toggles pause when not on a control
+ document.addEventListener('keydown',(ev)=>{
+ if(ev.code==='Space' && !/BUTTON|INPUT|SUMMARY|DETAILS/.test(document.activeElement.tagName)){
+ ev.preventDefault();pauseBtn.click();
+ }
+ });
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1c80df76af60/qwen25-7b.html b/data/artifacts/1c80df76af60/qwen25-7b.html
new file mode 100644
index 0000000..8272226
--- /dev/null
+++ b/data/artifacts/1c80df76af60/qwen25-7b.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Midnight Woodland Jam</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ background: #12002b;
+ color: #f3e9ff;
+ font-family: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+ }
+ .scene {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ width: 80vw;
+ height: 80vh;
+ overflow: hidden;
+ }
+ .group {
+ display: grid;
+ gap: 24px;
+ padding: 96px;
+ }
+ .character, .animal, .light-source {
+ animation-duration: 1.5s;
+ animation-fill-mode: both;
+ will-change: transform, opacity;
+ }
+ @keyframes fadeUp {
+ from { opacity: 0; transform: translateY(28px); }
+ to { opacity: 1; transform: none; }
+ }
+ .gathering {
+ background-size: 350% auto;
+ animation-name: gatherAnimation, ambientGradientDrift;
+ animation-duration: 60s, 18s;
+ animation-iteration-count: infinite, linear;
+ }
+ @keyframes gatherAnimation {
+ 0% { transform: scale(1); opacity: 1; }
+ 50% { transform: scale(.95); opacity: .75; }
+ 100% { transform: scale(1); opacity: 1; }
+ }
+ </style>
+</head>
+<body>
+ <div class="scene">
+ <div id="controls" style="position: absolute; top: 24px; left: 24px;">
+ <button onclick="setMovement('gathering')">Gathering</button>
+ <button onclick="setMovement('deepJam')">Deep Jam</button>
+ <button onclick="setMovement('dawnReturn')">Dawn Return</button>
+ <label for="tempo">Tempo:</label><input type="range" id="tempo" min="50" max="150" value="100">
+ <label for="motionType">Motion Type:</label><select id="motionType">
+ <option value="normal">Normal</option>
+ <option value="reduced">Reduced</option>
+ </select>
+ </div>
+ <svg viewBox="-50 -50 100 100" class="light-source">
+ <circle cx="0" cy="0" r="20" fill="#ff2e88"/>
+ </svg>
+ <div id="gathering" class="group gathering">
+ <!-- Characters and Animals -->
+ <div class="character"></div><div class="character"></div><div class="character"></div>
+ <div class="animal deer"></div><div class="animal bear"></div><div class="animal wolf"></div>
+ <div class="light-source"></div>
+ </div>
+ </div>
+ <script>
+ function setMovement(movement) {
+ document.getElementById('gathering').classList.remove('deepJam', 'dawnReturn');
+ document.getElementById('gathering').classList.add(movement);
+ updateControls();
+ }
+ function updateControls() {
+ const tempo = document.getElementById('tempo');
+ const motionType = document.getElementById('motionType');
+ if (['deepJam', 'dawnReturn'].includes(document.getElementById('gathering').className)) {
+ tempo.disabled = true;
+ motionType.value = 'reduced';
+ motionType.disabled = true;
+ } else {
+ tempo.disabled = false;
+ motionType.value = 'normal';
+ motionType.disabled = false;
+ }
+ }
+ window.onload = function() {
+ setMovement('gathering');
+ updateControls();
+ };
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/1c80df76af60/qwen3-14b.html b/data/artifacts/1c80df76af60/qwen3-14b.html
new file mode 100644
index 0000000..b4e50f8
--- /dev/null
+++ b/data/artifacts/1c80df76af60/qwen3-14b.html
@@ -0,0 +1,448 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Midnight Woodland Jam</title>
+ <style>
+ :root {
+ --bg: #0d0d0f;
+ --surface: #17171c;
+ --text: #f5f2ea;
+ --muted: #8a8578;
+ --accent: #c9a961;
+ --accent2: #7d6a45;
+ --shadow: 0 8px 24px rgba(0,0,0,0.12);
+ --glow: 0 0 24px var(--accent)35%;
+ --card-radius: 14px;
+ --card-gap: 24px;
+ --font-display: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+ --font-body: -apple-system, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
+ --scale-h1: 50px;
+ --scale-h2: 38px;
+ --scale-h3: 28px;
+ --scale-lead: 21px;
+ --scale-body: 16px;
+ --scale-caption: 12px;
+ }
+
+ * {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+
+ html, body {
+ background: var(--bg);
+ color: var(--text);
+ font-family: var(--font-body);
+ font-size: var(--scale-body);
+ height: 100%;
+ overflow: hidden;
+ scroll-behavior: smooth;
+ }
+
+ body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+ position: relative;
+ }
+
+ .canvas {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ }
+
+ .scene {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ }
+
+ .group {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .element {
+ position: absolute;
+ animation: fadeUp 0.7s cubic-bezier(.22,1,.36,1) both;
+ }
+
+ .speaker {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 120px;
+ height: 120px;
+ background: linear-gradient(135deg, var(--accent), var(--accent2));
+ border-radius: 50%;
+ box-shadow: var(--glow);
+ transform: translate(-50%, -50%);
+ }
+
+ .light {
+ position: absolute;
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ background: radial-gradient(circle, var(--accent) 0%, var(--accent2) 100%);
+ box-shadow: var(--glow);
+ }
+
+ .controls {
+ position: absolute;
+ top: 24px;
+ left: 24px;
+ z-index: 10;
+ font-family: var(--font-body);
+ font-size: var(--scale-caption);
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ color: var(--muted);
+ }
+
+ .controls label {
+ display: block;
+ margin-bottom: 8px;
+ }
+
+ .controls select, .controls input[type="range"], .controls input[type="checkbox"] {
+ margin-bottom: 16px;
+ width: 100%;
+ background: var(--surface);
+ color: var(--text);
+ border: 1px solid var(--muted);
+ border-radius: var(--card-radius);
+ padding: 8px 12px;
+ font-size: var(--scale-body);
+ }
+
+ .curator-note {
+ position: absolute;
+ bottom: 24px;
+ left: 24px;
+ background: var(--surface);
+ padding: 24px;
+ border-radius: var(--card-radius);
+ box-shadow: var(--shadow);
+ font-family: var(--font-display);
+ font-size: var(--scale-h3);
+ color: var(--accent);
+ max-width: 600px;
+ }
+
+ @keyframes fadeUp {
+ from { opacity: 0; transform: translateY(28px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+
+ @keyframes drift {
+ 0% { background-position: 0% 50%; }
+ 100% { background-position: 100% 50%; }
+ }
+
+ .bg {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(120deg, var(--accent) 0%, var(--accent2) 100%);
+ background-size: 400% 400%;
+ animation: drift 18s linear infinite;
+ z-index: 0;
+ }
+
+ .particle {
+ position: absolute;
+ width: 6px;
+ height: 6px;
+ background: var(--accent);
+ border-radius: 50%;
+ opacity: 0.6;
+ animation: pulse 2s infinite ease-in-out;
+ }
+
+ @keyframes pulse {
+ 0%, 100% { transform: scale(1); opacity: 0.6; }
+ 50% { transform: scale(1.2); opacity: 0.3; }
+ }
+ </style>
+</head>
+<body>
+ <div class="canvas">
+ <div class="bg"></div>
+ <div class="scene" id="scene">
+ <div class="group" id="group0"></div>
+ <div class="group" id="group1"></div>
+ <div class="group" id="group2"></div>
+ <div class="speaker" id="speaker"></div>
+ </div>
+ </div>
+
+ <div class="controls">
+ <label for="movement">Movement</label>
+ <select id="movement">
+ <option value="gathering">Gathering</option>
+ <option value="deep-jam">Deep Jam</option>
+ <option value="dawn-return">Dawn Return</option>
+ </select>
+
+ <label for="tempo">Visual Tempo</label>
+ <input type="range" id="tempo" min="0.5" max="2" step="0.1" value="1">
+
+ <label for="isolate">Isolate</label>
+ <select id="isolate">
+ <option value="all">All</option>
+ <option value="humans">Humans</option>
+ <option value="animals">Animals</option>
+ </select>
+
+ <label for="pause">Pause</label>
+ <input type="checkbox" id="pause">
+
+ <label for="reduce-motion">Reduce Motion</label>
+ <input type="checkbox" id="reduce-motion">
+ </div>
+
+ <div class="curator-note">
+ <h3>Curator's Note</h3>
+ <p>This is an artistic rhythm simulation. It responds to visual tempo and movement, not actual audio input. Explore the choreography of midnight in a woodland jam.</p>
+ </div>
+
+ <script>
+ const scene = document.getElementById('scene');
+ const speaker = document.getElementById('speaker');
+ const movementSelect = document.getElementById('movement');
+ const tempoSlider = document.getElementById('tempo');
+ const isolateSelect = document.getElementById('isolate');
+ const pauseCheckbox = document.getElementById('pause');
+ const reduceMotionCheckbox = document.getElementById('reduce-motion');
+
+ const groups = [document.getElementById('group0'), document.getElementById('group1'), document.getElementById('group2')];
+ const elements = [];
+
+ const createHuman = () => {
+ const el = document.createElement('div');
+ el.classList.add('element', 'human');
+ el.style.left = `${Math.random() * 100}%`;
+ el.style.top = `${Math.random() * 100}%`;
+ el.style.animationDuration = `${Math.random() * 1 + 0.5}s`;
+ el.style.animationDelay = `${Math.random() * 1}s`;
+ el.style.animationName = `spin, bend, clap, drift`;
+ el.style.animationIterationCount = 'infinite';
+ el.style.animationTimingFunction = 'ease-in-out';
+ el.style.animationDirection = 'alternate';
+ el.style.opacity = Math.random();
+ el.style.transform = `scale(${Math.random() * 0.4 + 0.3})`;
+ el.style.zIndex = Math.floor(Math.random() * 10) + 1;
+ return el;
+ };
+
+ const createAnimal = () => {
+ const el = document.createElement('div');
+ el.classList.add('element', 'animal');
+ el.style.left = `${Math.random() * 100}%`;
+ el.style.top = `${Math.random() * 100}%`;
+ el.style.animationDuration = `${Math.random() * 2 + 1}s`;
+ el.style.animationDelay = `${Math.random() * 2}s`;
+ el.style.animationName = `step, head-turn, rock, loop-run`;
+ el.style.animationIterationCount = 'infinite';
+ el.style.animationTimingFunction = 'ease-in-out';
+ el.style.animationDirection = 'alternate';
+ el.style.opacity = Math.random();
+ el.style.transform = `scale(${Math.random() * 0.3 + 0.2})`;
+ el.style.zIndex = Math.floor(Math.random() * 10) + 1;
+ return el;
+ };
+
+ const createFrog = () => {
+ const el = document.createElement('div');
+ el.classList.add('element', 'frog');
+ el.style.left = `${Math.random() * 100}%`;
+ el.style.top = `${Math.random() * 100}%`;
+ el.style.animationDuration = `${Math.random() * 1 + 0.5}s`;
+ el.style.animationDelay = `${Math.random() * 1}s`;
+ el.style.animationName = `bounce`;
+ el.style.animationIterationCount = 'infinite';
+ el.style.animationTimingFunction = 'ease-in-out';
+ el.style.animationDirection = 'alternate';
+ el.style.opacity = Math.random();
+ el.style.transform = `scale(${Math.random() * 0.2 + 0.1})`;
+ el.style.zIndex = Math.floor(Math.random() * 10) + 1;
+ return el;
+ };
+
+ const createMushroom = () => {
+ const el = document.createElement('div');
+ el.classList.add('element', 'mushroom');
+ el.style.left = `${Math.random() * 100}%`;
+ el.style.top = `${Math.random() * 100}%`;
+ el.style.animationDuration = `${Math.random() * 2 + 1}s`;
+ el.style.animationDelay = `${Math.random() * 1}s`;
+ el.style.animationName = `glow`;
+ el.style.animationIterationCount = 'infinite';
+ el.style.animationTimingFunction = 'ease-in-out';
+ el.style.opacity = Math.random();
+ el.style.transform = `scale(${Math.random() * 0.4 + 0.2})`;
+ el.style.zIndex = Math.floor(Math.random() * 10) + 1;
+ return el;
+ };
+
+ const createFirefly = () => {
+ const el = document.createElement('div');
+ el.classList.add('element', 'firefly');
+ el.style.left = `${Math.random() * 100}%`;
+ el.style.top = `${Math.random() * 100}%`;
+ el.style.animationDuration = `${Math.random() * 1 + 0.5}s`;
+ el.style.animationDelay = `${Math.random() * 1}s`;
+ el.style.animationName = `fly, spiral`;
+ el.style.animationIterationCount = 'infinite';
+ el.style.animationTimingFunction = 'ease-in-out';
+ el.style.opacity = Math.random();
+ el.style.transform = `scale(${Math.random() * 0.2 + 0.1})`;
+ el.style.zIndex = Math.floor(Math.random() * 10) + 1;
+ return el;
+ };
+
+ const addElements = (group, count, creator) => {
+ for (let i = 0; i < count; i++) {
+ const el = creator();
+ group.appendChild(el);
+ elements.push(el);
+ }
+ };
+
+ addElements(groups[0], 10, createHuman);
+ addElements(groups[1], 8, createAnimal);
+ addElements(groups[1], 2, createFrog);
+ addElements(groups[2], 4, createMushroom);
+ addElements(groups[2], 10, createFirefly);
+
+ const createParticle = (x, y) => {
+ const el = document.createElement('div');
+ el.classList.add('particle');
+ el.style.left = `${x}%`;
+ el.style.top = `${y}%`;
+ scene.appendChild(el);
+ return el;
+ };
+
+ const particles = [];
+ for (let i = 0; i < 50; i++) {
+ const x = Math.random() * 100;
+ const y = Math.random() * 100;
+ const el = createParticle(x, y);
+ particles.push(el);
+ }
+
+ const animations = {
+ 'gathering': {
+ palette: '#c9a961 #7d6a45',
+ density: 0.8,
+ particleSpeed: 0.5
+ },
+ 'deep-jam': {
+ palette: '#ff2e88 #00e5ff',
+ density: 1.2,
+ particleSpeed: 1.2
+ },
+ 'dawn-return': {
+ palette: '#e0a458 #c65f38',
+ density: 0.6,
+ particleSpeed: 0.3
+ }
+ };
+
+ let currentMovement = 'gathering';
+ let currentDensity = 0.8;
+ let currentParticleSpeed = 0.5;
+ let isPaused = false;
+ let isReducedMotion = false;
+
+ const update = () => {
+ if (isPaused || isReducedMotion) {
+ document.querySelectorAll('.element').forEach(el => {
+ el.style.animationPlayState = 'paused';
+ el.style.pointerEvents = 'none';
+ });
+ return;
+ }
+
+ document.querySelectorAll('.element').forEach(el => {
+ el.style.animationPlayState = 'running';
+ el.style.pointerEvents = 'auto';
+ });
+
+ const density = animations[currentMovement].density;
+ const particleSpeed = animations[currentMovement].particleSpeed;
+
+ elements.forEach(el => {
+ const opacity = Math.random() * density;
+ el.style.opacity = opacity;
+ });
+
+ particles.forEach(p => {
+ p.style.animationDuration = `${particleSpeed}s`;
+ });
+ };
+
+ const applyIsolation = (type) => {
+ elements.forEach(el => {
+ if (type === 'all') {
+ el.style.display = 'block';
+ } else if (type === 'humans' && el.classList.contains('human')) {
+ el.style.display = 'block';
+ } else if (type === 'animals' && el.classList.contains('animal')) {
+ el.style.display = 'block';
+ } else {
+ el.style.display = 'none';
+ }
+ });
+ };
+
+ movementSelect.addEventListener('change', () => {
+ currentMovement = movementSelect.value;
+ update();
+ });
+
+ tempoSlider.addEventListener('input', () => {
+ const speed = tempoSlider.value;
+ document.querySelectorAll('.element').forEach(el => {
+ el.style.animationDuration = `${speed}s`;
+ });
+ });
+
+ isolateSelect.addEventListener('change', () => {
+ applyIsolation(isolateSelect.value);
+ });
+
+ pauseCheckbox.addEventListener('change', () => {
+ isPaused = pauseCheckbox.checked;
+ update();
+ });
+
+ reduceMotionCheckbox.addEventListener('change', () => {
+ isReducedMotion = reduceMotionCheckbox.checked;
+ update();
+ });
+
+ // Initial setup
+ update();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/246049758201/claude-code.html b/data/artifacts/246049758201/claude-code.html
new file mode 100644
index 0000000..6022db2
--- /dev/null
+++ b/data/artifacts/246049758201/claude-code.html
@@ -0,0 +1,773 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Wall of Sound — Street Dance</title>
+<style>
+ :root{
+ --bg:#12002b; --surface:#1e0b3d; --text:#f3e9ff; --muted:#8d7bb0;
+ --hot:#ff2e88; --cyan:#00e5ff;
+ --amp-orange:#ff7a1a; --amp-red:#e8202a; --amp-yellow:#ffcf33;
+ --amp-green:#2fbf5f; --amp-blue:#2f6dff; --amp-cream:#efe4c8;
+ --grille:#0a0018;
+ --panel:rgba(20,4,44,.82);
+ --hair:rgba(255,255,255,.12);
+ --shadow:0 8px 24px rgba(0,0,0,.45);
+ }
+ *{box-sizing:border-box}
+ html,body{margin:0;height:100%;background:var(--bg);color:var(--text);
+ font-family:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;overflow:hidden}
+ #stage{position:fixed;inset:0}
+ canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
+
+ #titleplate{position:fixed;top:16px;left:50%;transform:translateX(-50%);
+ text-align:center;pointer-events:none;z-index:5;mix-blend-mode:screen}
+ #titleplate .kicker{font-size:12px;letter-spacing:.28em;color:var(--cyan);
+ text-transform:uppercase;margin:0 0 4px}
+ #titleplate h1{margin:0;font-family:'Didot','Bodoni MT','Playfair Display',Georgia,serif;
+ font-weight:700;font-size:clamp(24px,4.4vw,50px);line-height:.95;
+ color:var(--text);text-shadow:0 0 22px rgba(255,46,136,.55),0 2px 0 rgba(0,0,0,.6)}
+ #titleplate h1 em{font-style:italic;color:var(--amp-orange)}
+
+ #dock{position:fixed;left:16px;bottom:16px;z-index:8;
+ width:min(340px,calc(100vw - 32px));
+ background:var(--panel);backdrop-filter:blur(12px);
+ border:1px solid var(--hair);border-radius:14px;box-shadow:var(--shadow);
+ padding:14px 14px 12px;transition:transform .3s cubic-bezier(.22,1,.36,1),opacity .3s}
+ #dock.collapsed{transform:translateY(calc(100% - 46px))}
+ #dock header{display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none}
+ #dock header .dot{width:12px;height:12px;border-radius:50%;
+ background:radial-gradient(circle at 30% 30%,var(--amp-yellow),var(--amp-orange));
+ box-shadow:0 0 10px var(--amp-orange)}
+ #dock header h2{margin:0;font-size:12px;letter-spacing:.18em;text-transform:uppercase;
+ color:var(--text);flex:1}
+ #dock header .chev{color:var(--muted);font-size:12px;transition:transform .3s}
+ #dock.collapsed header .chev{transform:rotate(180deg)}
+ .row{margin-top:12px}
+ .lbl{font-size:12px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted);
+ display:block;margin-bottom:6px}
+ .grp{display:flex;flex-wrap:wrap;gap:6px}
+ button.pill{background:var(--surface);color:var(--text);border:1px solid var(--hair);
+ border-radius:10px;padding:7px 10px;font-size:12px;letter-spacing:.04em;cursor:pointer;
+ transition:all .15s ease}
+ button.pill:hover{border-color:var(--cyan);color:var(--cyan)}
+ button.pill:focus-visible{outline:2px solid var(--cyan);outline-offset:2px}
+ button.pill.on{background:linear-gradient(135deg,var(--hot),var(--amp-orange));
+ color:#fff;border-color:transparent;box-shadow:0 0 16px rgba(255,46,136,.45)}
+ .toggles{display:flex;flex-wrap:wrap;gap:6px}
+ .toggles button{flex:0 0 auto}
+ input[type=range]{width:100%;-webkit-appearance:none;height:6px;border-radius:6px;
+ background:linear-gradient(90deg,var(--hot),var(--cyan));outline:none;margin:2px 0}
+ input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:18px;height:18px;
+ border-radius:50%;background:var(--amp-cream);border:2px solid var(--amp-orange);
+ cursor:pointer;box-shadow:0 2px 6px rgba(0,0,0,.5)}
+ input[type=range]::-moz-range-thumb{width:18px;height:18px;border-radius:50%;
+ background:var(--amp-cream);border:2px solid var(--amp-orange);cursor:pointer}
+ .val{float:right;color:var(--cyan);font-variant-numeric:tabular-nums}
+ .two{display:flex;gap:8px}
+ .two>div{flex:1}
+ #legend{position:fixed;right:16px;top:16px;z-index:7;
+ background:var(--panel);backdrop-filter:blur(10px);border:1px solid var(--hair);
+ border-radius:14px;padding:12px 14px;box-shadow:var(--shadow);font-size:12px;
+ max-width:230px;opacity:0;transform:translateY(-8px);pointer-events:none;
+ transition:opacity .35s,transform .35s}
+ #legend.show{opacity:1;transform:none}
+ #legend h3{margin:0 0 8px;font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--cyan)}
+ #legend .item{display:flex;align-items:center;gap:8px;margin:5px 0;color:var(--text)}
+ #legend .sw{width:14px;height:14px;border-radius:4px;flex:0 0 auto}
+ #legend .note{margin-top:10px;color:var(--muted);font-size:11px;line-height:1.4}
+ .credit{position:fixed;right:14px;bottom:12px;z-index:6;color:var(--muted);
+ font-size:11px;letter-spacing:.14em;text-transform:uppercase;pointer-events:none}
+ @media (max-width:640px){
+ #legend{display:none}
+ #dock{width:calc(100vw - 24px);left:12px;bottom:12px}
+ }
+</style>
+</head>
+<body>
+<div id="stage">
+ <canvas id="cv"></canvas>
+</div>
+
+<div id="titleplate">
+ <p class="kicker">Monumental Public Art · Night Plaza</p>
+ <h1>Wall of <em>Sound</em> — Street Dance</h1>
+</div>
+
+<div id="legend" aria-hidden="true">
+ <h3>Architectural Diagram</h3>
+ <div class="item"><span class="sw" style="background:var(--hot)"></span>Central spinning circle</div>
+ <div class="item"><span class="sw" style="background:var(--cyan)"></span>Freestyle crossers (curved paths)</div>
+ <div class="item"><span class="sw" style="background:var(--amp-yellow)"></span>Mirroring pairs (edges)</div>
+ <div class="item"><span class="sw" style="background:var(--amp-green)"></span>Slow street procession</div>
+ <div class="item"><span class="sw" style="background:var(--amp-orange)"></span>Animal companions</div>
+ <p class="note">Rings are a <b>designed rhythm visualization</b> — a choreographed pulse, not audio analysis.</p>
+</div>
+
+<div id="dock">
+ <header id="dockHead" role="button" tabindex="0" aria-expanded="true">
+ <span class="dot"></span>
+ <h2>Wall of Sound — Console</h2>
+ <span class="chev">▾</span>
+ </header>
+
+ <div class="row">
+ <span class="lbl">Camera Position</span>
+ <div class="grp" id="camGrp">
+ <button class="pill on" data-cam="plaza">Plaza Wide</button>
+ <button class="pill" data-cam="circle">The Circle</button>
+ <button class="pill" data-cam="wall">Speaker Wall</button>
+ <button class="pill" data-cam="street">Street Entry</button>
+ <button class="pill" data-cam="aerial">Aerial</button>
+ </div>
+ </div>
+
+ <div class="row two">
+ <div>
+ <span class="lbl">Tempo <span class="val" id="tempoV">108</span></span>
+ <input type="range" id="tempo" min="60" max="150" value="108" aria-label="Tempo BPM">
+ </div>
+ <div>
+ <span class="lbl">Crowd <span class="val" id="crowdV">100%</span></span>
+ <input type="range" id="crowd" min="20" max="100" value="100" aria-label="Crowd density">
+ </div>
+ </div>
+
+ <div class="row">
+ <span class="lbl">Trail Intensity <span class="val" id="trailV">45%</span></span>
+ <input type="range" id="trail" min="0" max="100" value="45" aria-label="Trail intensity">
+ </div>
+
+ <div class="row">
+ <span class="lbl">Animal Companions</span>
+ <div class="toggles" id="animGrp">
+ <button class="pill on" data-anim="bears">🐻 Bears</button>
+ <button class="pill on" data-anim="dogs">🐕 Dogs</button>
+ <button class="pill on" data-anim="pigeons">🕊 Pigeons</button>
+ <button class="pill on" data-anim="tortoise">🐢 Tortoise</button>
+ </div>
+ </div>
+
+ <div class="row">
+ <span class="lbl">Controls</span>
+ <div class="grp">
+ <button class="pill" id="pauseBtn">⏸ Pause</button>
+ <button class="pill" id="restartBtn">↺ Restart</button>
+ <button class="pill" id="diagBtn">◫ Diagram</button>
+ <button class="pill" id="motionBtn">🌙 Reduced Motion</button>
+ </div>
+ </div>
+</div>
+
+<div class="credit">FPS <span id="fps">—</span></div>
+
+<script>
+"use strict";
+(function(){
+ const cv = document.getElementById('cv');
+ const ctx = cv.getContext('2d');
+ let W=0,H=0,DPR=1;
+
+ const state = {
+ tempo: 108,
+ crowd: 1.0,
+ trail: 0.45,
+ paused: false,
+ diagram: false,
+ reduced: false,
+ cam: 'plaza',
+ animals: { bears:true, dogs:true, pigeons:true, tortoise:true }
+ };
+ try{ if(matchMedia('(prefers-reduced-motion: reduce)').matches){ state.reduced=true; } }catch(e){}
+
+ const CAMS = {
+ plaza: {cx:0.50, cy:0.56, zoom:1.00, name:'Plaza Wide'},
+ circle: {cx:0.50, cy:0.66, zoom:1.55, name:'The Circle'},
+ wall: {cx:0.50, cy:0.30, zoom:1.30, name:'Speaker Wall'},
+ street: {cx:0.74, cy:0.44, zoom:1.40, name:'Street Entry'},
+ aerial: {cx:0.50, cy:0.50, zoom:0.86, name:'Aerial'}
+ };
+ const cam = {cx:0.5, cy:0.56, zoom:1.0};
+
+ function resize(){
+ DPR = Math.min(window.devicePixelRatio||1, 2);
+ W = cv.clientWidth; H = cv.clientHeight;
+ cv.width = Math.floor(W*DPR); cv.height = Math.floor(H*DPR);
+ ctx.setTransform(DPR,0,0,DPR,0,0);
+ buildWall();
+ }
+ window.addEventListener('resize', resize);
+
+ function project(wx, wy){
+ const horizon = H*0.30;
+ const groundBottom = H*1.02;
+ const depth = wy;
+ const persp = 0.34 + depth*0.66;
+ const sxCenterWorld = 0.5;
+ let sx = W*0.5 + (wx - sxCenterWorld)*W*persp;
+ let sy = horizon + (groundBottom-horizon)*Math.pow(depth,1.06);
+ sx = (sx - W*cam.cx) * cam.zoom + W*cam.cx;
+ sy = (sy - H*cam.cy) * cam.zoom + H*cam.cy;
+ return {x:sx, y:sy, scale:persp*cam.zoom};
+ }
+
+ let wallCanvas=null;
+ function buildWall(){
+ wallCanvas = document.createElement('canvas');
+ wallCanvas.width = Math.max(2,Math.floor(W*DPR));
+ wallCanvas.height = Math.max(2,Math.floor(H*0.42*DPR));
+ const g = wallCanvas.getContext('2d');
+ g.setTransform(DPR,0,0,DPR,0,0);
+ const ww = W, wh = H*0.42;
+ const sky = g.createLinearGradient(0,0,0,wh);
+ sky.addColorStop(0,'#0a0016');
+ sky.addColorStop(1,'#1a0538');
+ g.fillStyle = sky; g.fillRect(0,0,ww,wh);
+
+ const baseY = wh;
+ const wallTop = wh*0.10;
+ const cabW = Math.max(34, ww*0.052);
+ const cols = Math.ceil(ww/cabW)+1;
+ const engColors = ['#ff7a1a','#e8202a','#ffcf33','#2fbf5f','#2f6dff','#efe4c8'];
+
+ for(let c=0;c<cols;c++){
+ const x = c*cabW;
+ const dc = Math.abs((x+cabW/2) - ww/2)/(ww/2);
+ const colH = (wh - wallTop) * (0.55 + (1-dc)*0.45);
+ const topY = baseY - colH;
+ const rows = Math.floor(colH/ (cabW*0.62));
+ for(let r=0;r<rows;r++){
+ const cy = baseY - r*(colH/rows) - (colH/rows);
+ const ch = colH/rows;
+ const shade = 8 + ((c*3+r*5)%5)*4;
+ g.fillStyle = `rgb(${shade+6},${shade},${shade+14})`;
+ g.fillRect(x+1, cy+1, cabW-2, ch-2);
+ g.strokeStyle='rgba(255,255,255,.06)';g.lineWidth=1;
+ g.strokeRect(x+1.5,cy+1.5,cabW-3,ch-3);
+ const coneColor = engColors[(c+r)%engColors.length];
+ const big = ((c+r)%3===0);
+ if(big){
+ drawCone(g, x+cabW/2, cy+ch/2, Math.min(cabW,ch)*0.34, coneColor);
+ }else{
+ const s = Math.min(cabW,ch)*0.17;
+ drawCone(g, x+cabW*0.33, cy+ch*0.34, s, coneColor);
+ drawCone(g, x+cabW*0.67, cy+ch*0.34, s, engColors[(c+r+2)%engColors.length]);
+ drawCone(g, x+cabW*0.33, cy+ch*0.68, s, engColors[(c+r+4)%engColors.length]);
+ drawCone(g, x+cabW*0.67, cy+ch*0.68, s, engColors[(c+r+1)%engColors.length]);
+ }
+ }
+ g.fillStyle = engColors[c%engColors.length];
+ g.globalAlpha=0.85;
+ g.fillRect(x+2, topY-3, cabW-4, 3);
+ g.globalAlpha=1;
+ }
+ g.strokeStyle='rgba(239,228,200,.10)';g.lineWidth=2;
+ for(let i=0;i<=6;i++){const x=ww*i/6;g.beginPath();g.moveTo(x,wallTop*0.4);g.lineTo(x,baseY);g.stroke();}
+ }
+ function drawCone(g,cx,cy,r,color){
+ g.beginPath();g.arc(cx,cy,r,0,Math.PI*2);
+ g.fillStyle='#050008';g.fill();
+ g.beginPath();g.arc(cx,cy,r*0.92,0,Math.PI*2);
+ g.strokeStyle=color;g.globalAlpha=.55;g.lineWidth=1.4;g.stroke();g.globalAlpha=1;
+ g.beginPath();g.arc(cx,cy,r*0.34,0,Math.PI*2);
+ g.fillStyle=color;g.globalAlpha=.9;g.fill();g.globalAlpha=1;
+ g.beginPath();g.arc(cx-r*0.1,cy-r*0.1,r*0.14,0,Math.PI*2);
+ g.fillStyle='rgba(255,255,255,.5)';g.fill();
+ }
+
+ const rings=[];
+ function updateRings(dt, beatPhase, beatHit){
+ if(beatHit && !state.reduced){
+ rings.push({t:0, life:2.6});
+ }
+ for(let i=rings.length-1;i>=0;i--){
+ rings[i].t += dt;
+ if(rings[i].t>rings[i].life) rings.splice(i,1);
+ }
+ }
+ function drawRings(){
+ const originX = W*0.5, originY = H*0.30;
+ for(const r of rings){
+ const p = r.t/r.life;
+ const alpha = (1-p)*0.5;
+ const spread = p;
+ const proj = project(0.5, spread);
+ const radius = 40 + spread*Math.max(W,H)*0.9;
+ ctx.beginPath();
+ ctx.ellipse(originX, originY+ (proj.y-originY)*0.5, radius, radius*0.34, 0, 0, Math.PI*2);
+ ctx.strokeStyle=`rgba(0,229,255,${alpha})`;
+ ctx.lineWidth=2+ (1-p)*2;
+ ctx.stroke();
+ ctx.strokeStyle=`rgba(255,46,136,${alpha*0.6})`;
+ ctx.lineWidth=1;
+ ctx.beginPath();
+ ctx.ellipse(originX, originY+ (proj.y-originY)*0.5, radius*0.96, radius*0.33, 0, 0, Math.PI*2);
+ ctx.stroke();
+ }
+ }
+
+ function makeDancer(kind, wx, wy, hue, extra){
+ return Object.assign({
+ kind, wx, wy, hue,
+ off: Math.random()*Math.PI*2,
+ wobble: 0.6+Math.random()*0.8,
+ size: 1
+ }, extra||{});
+ }
+
+ let circleDancers=[], freestyle=[], pairs=[], procession=[];
+ let bears=[], dogs=[], pigeons=[], tortoise=null;
+
+ function buildCrowd(){
+ circleDancers=[];freestyle=[];pairs=[];procession=[];
+ bears=[];dogs=[];pigeons=[];tortoise=null;
+
+ const N=14;
+ for(let i=0;i<N;i++){
+ circleDancers.push(makeDancer('circle',0,0,`hsl(${335+ (i*4)},90%,62%)`,{idx:i, count:N}));
+ }
+ for(let i=0;i<10;i++){
+ freestyle.push(makeDancer('freestyle', Math.random(), 0.55+Math.random()*0.4,
+ `hsl(${185+Math.random()*20},90%,60%)`,
+ {phase:Math.random(), speed:0.05+Math.random()*0.05, dir:Math.random()<0.5?1:-1,
+ amp:0.05+Math.random()*0.08, baseY:0.55+Math.random()*0.42}));
+ }
+ for(let i=0;i<4;i++){
+ const left = i<2;
+ const ex = left ? 0.10+Math.random()*0.06 : 0.84+Math.random()*0.06;
+ const ey = 0.60+Math.random()*0.30;
+ pairs.push({wx:ex, wy:ey, hue:`hsl(${48+i*6},95%,60%)`, off:Math.random()*6.28, gap:0.045+Math.random()*0.02, side:left?1:-1});
+ }
+ for(let i=0;i<9;i++){
+ procession.push(makeDancer('proc', 0.9, 0.34, `hsl(${140+Math.random()*30},60%,58%)`,
+ {t:i*0.11, speed:0.018+Math.random()*0.006}));
+ }
+ for(let i=0;i<3;i++){
+ bears.push({wx:0.24+i*0.26, wy:0.72+Math.random()*0.1, off:Math.random()*6.28, step:0});
+ }
+ for(let i=0;i<3;i++){
+ dogs.push({phase:Math.random(), speed:0.09+Math.random()*0.04, baseY:0.66+Math.random()*0.22,
+ amp:0.09+Math.random()*0.05, dir:Math.random()<.5?1:-1, hue:'#ff9a3c'});
+ }
+ for(let i=0;i<16;i++){
+ pigeons.push({bx:0.3+Math.random()*0.4, by:0.5+Math.random()*0.2,
+ off:i*0.35, jitter:Math.random()*0.02});
+ }
+ tortoise={wx:0.05, wy:0.82, dir:1};
+ }
+
+ let beatClock=0;
+ let lastBeatInt=-1;
+
+ function figure(sx, sy, scale, phase, hue, opts){
+ opts=opts||{};
+ const s = scale* (opts.size||1) * 42;
+ if(s<1) return;
+ const bob = Math.sin(phase)*s*0.10;
+ const sway = Math.sin(phase*0.5)*s*0.08;
+ const armA = Math.sin(phase)*0.9;
+ const armB = Math.sin(phase+Math.PI)*0.9;
+ const legA = Math.sin(phase)*0.5;
+ const y = sy - bob;
+
+ ctx.save();
+ ctx.translate(sx, y);
+ ctx.lineCap='round';
+ if(state.diagram){
+ ctx.globalAlpha=0.9;
+ ctx.strokeStyle = opts.diagColor||hue;
+ ctx.fillStyle = opts.diagColor||hue;
+ }else{
+ ctx.strokeStyle = hue;
+ ctx.fillStyle = hue;
+ }
+ ctx.lineWidth = Math.max(1.4, s*0.11);
+
+ const headR = s*0.16;
+ const torso = s*0.5;
+ ctx.beginPath();
+ ctx.moveTo(0,0);
+ ctx.lineTo(sway - s*0.18*(1+legA), s*0.5);
+ ctx.moveTo(0,0);
+ ctx.lineTo(sway + s*0.18*(1-legA), s*0.5);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.moveTo(sway,0);
+ ctx.lineTo(sway*0.6, -torso);
+ ctx.stroke();
+ const shoulder=-torso*0.82;
+ ctx.beginPath();
+ ctx.moveTo(sway*0.6, shoulder);
+ ctx.lineTo(sway*0.6 - s*0.30, shoulder - s*0.30*armA);
+ ctx.moveTo(sway*0.6, shoulder);
+ ctx.lineTo(sway*0.6 + s*0.30, shoulder - s*0.30*armB);
+ ctx.stroke();
+ ctx.beginPath();
+ ctx.arc(sway*0.55, -torso-headR*0.7, headR, 0, Math.PI*2);
+ ctx.fill();
+ ctx.restore();
+ }
+
+ function drawCircle(t){
+ const cxW=0.5, cyW=0.66;
+ const rW = 0.15;
+ const spin = state.reduced ? 0 : t*0.35;
+ const pts=[];
+ const n=circleDancers.length;
+ const useN = Math.max(4, Math.round(n*state.crowd));
+ for(let i=0;i<useN;i++){
+ const a = spin + (i/useN)*Math.PI*2;
+ const wx = cxW + Math.cos(a)*rW;
+ const wy = cyW + Math.sin(a)*rW*0.5;
+ pts.push(project(wx, wy));
+ }
+ if(!state.diagram){
+ ctx.strokeStyle='rgba(255,207,51,.30)';ctx.lineWidth=1.5;
+ ctx.beginPath();
+ for(let i=0;i<pts.length;i++){
+ const p=pts[i];const q=pts[(i+1)%pts.length];
+ ctx.moveTo(p.x,p.y-6);ctx.lineTo(q.x,q.y-6);
+ }
+ ctx.stroke();
+ }else{
+ ctx.strokeStyle='rgba(255,46,136,.6)';ctx.setLineDash([4,4]);ctx.lineWidth=1.5;
+ ctx.beginPath();
+ for(let i=0;i<pts.length;i++){const p=pts[i];i?ctx.lineTo(p.x,p.y):ctx.moveTo(p.x,p.y);}
+ ctx.closePath();ctx.stroke();ctx.setLineDash([]);
+ }
+ const order=pts.map((p,i)=>({p,i})).sort((a,b)=>a.p.y-b.p.y);
+ for(const o of order){
+ const d=circleDancers[o.i];
+ figure(o.p.x, o.p.y, o.p.scale, beatClock*Math.PI + d.off, d.hue, {diagColor:'#ff2e88'});
+ }
+ }
+
+ function drawFreestyle(t){
+ const useN = Math.max(2, Math.round(freestyle.length*state.crowd));
+ for(let i=0;i<useN;i++){
+ const d=freestyle[i];
+ let px = (d.phase + t*d.speed*d.dir);
+ px = ((px%1)+1)%1;
+ const wy = d.baseY + Math.sin(px*Math.PI*2 + d.off)*d.amp;
+ const wx = px;
+ const pr = project(wx, wy);
+ if(state.diagram){
+ ctx.strokeStyle='rgba(0,229,255,.5)';ctx.setLineDash([3,5]);ctx.lineWidth=1.2;
+ ctx.beginPath();
+ for(let s=0;s<=1;s+=0.05){
+ const yy=d.baseY+Math.sin(s*Math.PI*2+d.off)*d.amp;
+ const q=project(s,yy);s?ctx.lineTo(q.x,q.y):ctx.moveTo(q.x,q.y);
+ }
+ ctx.stroke();ctx.setLineDash([]);
+ }
+ figure(pr.x, pr.y, pr.scale, beatClock*Math.PI+d.off, d.hue, {diagColor:'#00e5ff'});
+ }
+ }
+
+ function drawPairs(t){
+ for(const p of pairs){
+ const ph = beatClock*Math.PI + p.off;
+ const c1 = project(p.wx - p.gap, p.wy);
+ const c2 = project(p.wx + p.gap, p.wy);
+ if(state.diagram){
+ ctx.strokeStyle='rgba(255,207,51,.6)';ctx.setLineDash([2,4]);ctx.lineWidth=1.2;
+ ctx.beginPath();ctx.moveTo(c1.x,c1.y);ctx.lineTo(c2.x,c2.y);ctx.stroke();ctx.setLineDash([]);
+ }
+ figure(c1.x,c1.y,c1.scale, ph, p.hue, {diagColor:'#ffcf33'});
+ figure(c2.x,c2.y,c2.scale, Math.PI-ph, p.hue, {diagColor:'#ffcf33'});
+ }
+ }
+
+ function drawProcession(t){
+ const useN = Math.max(2, Math.round(procession.length*state.crowd));
+ for(let i=0;i<useN;i++){
+ const d=procession[i];
+ let tt = (d.t + t*d.speed);
+ tt = ((tt%1)+1)%1;
+ const sx=0.94, sy=0.30, ex=0.5, ey=0.70, mx=0.72, my=0.40;
+ const u=tt;
+ const wx=(1-u)*(1-u)*sx + 2*(1-u)*u*mx + u*u*ex;
+ const wy=(1-u)*(1-u)*sy + 2*(1-u)*u*my + u*u*ey;
+ const pr=project(wx,wy);
+ if(state.diagram && i===0){
+ ctx.strokeStyle='rgba(47,191,95,.6)';ctx.setLineDash([5,5]);ctx.lineWidth=1.4;
+ ctx.beginPath();
+ for(let s=0;s<=1;s+=0.04){
+ const xx=(1-s)*(1-s)*sx+2*(1-s)*s*mx+s*s*ex;
+ const yy=(1-s)*(1-s)*sy+2*(1-s)*s*my+s*s*ey;
+ const q=project(xx,yy);s?ctx.lineTo(q.x,q.y):ctx.moveTo(q.x,q.y);
+ }
+ ctx.stroke();ctx.setLineDash([]);
+ }
+ figure(pr.x,pr.y,pr.scale*0.95, beatClock*Math.PI*0.5+d.off, d.hue, {diagColor:'#2fbf5f', size:0.9});
+ }
+ }
+
+ function drawBear(sx,sy,scale,phase){
+ const s=scale*46;
+ if(s<1)return;
+ const step=Math.sin(phase);
+ const shift=step*s*0.12;
+ ctx.save();ctx.translate(sx,sy);
+ ctx.fillStyle = state.diagram? '#ff7a1a' : '#7a4a2a';
+ ctx.strokeStyle=ctx.fillStyle;
+ ctx.beginPath();ctx.ellipse(shift*0.3,-s*0.5,s*0.32,s*0.42,0,0,6.28);ctx.fill();
+ ctx.beginPath();ctx.arc(shift*0.4,-s*1.0,s*0.24,0,6.28);ctx.fill();
+ ctx.beginPath();ctx.arc(shift*0.4-s*0.18,-s*1.18,s*0.09,0,6.28);
+ ctx.arc(shift*0.4+s*0.18,-s*1.18,s*0.09,0,6.28);ctx.fill();
+ ctx.lineWidth=s*0.18;ctx.lineCap='round';
+ ctx.beginPath();
+ ctx.moveTo(-s*0.16,-s*0.1);ctx.lineTo(-s*0.16+step*s*0.14,0);
+ ctx.moveTo(s*0.16,-s*0.1);ctx.lineTo(s*0.16-step*s*0.14,0);
+ ctx.stroke();
+ if(!state.diagram){ctx.fillStyle='#caa';ctx.beginPath();ctx.arc(shift*0.4,-s*0.94,s*0.08,0,6.28);ctx.fill();}
+ ctx.restore();
+ }
+ function drawDog(sx,sy,scale,phase){
+ const s=scale*30;if(s<1)return;
+ const trot=Math.sin(phase*2);
+ ctx.save();ctx.translate(sx,sy);
+ ctx.strokeStyle= state.diagram? '#ff7a1a':'#ff9a3c';
+ ctx.fillStyle=ctx.strokeStyle;ctx.lineWidth=s*0.16;ctx.lineCap='round';
+ ctx.beginPath();ctx.moveTo(-s*0.4,-s*0.4);ctx.lineTo(s*0.4,-s*0.4);ctx.stroke();
+ ctx.beginPath();ctx.arc(s*0.5,-s*0.5,s*0.16,0,6.28);ctx.fill();
+ ctx.beginPath();ctx.moveTo(-s*0.4,-s*0.4);ctx.lineTo(-s*0.62,-s*0.7+trot*s*0.1);ctx.stroke();
+ ctx.lineWidth=s*0.1;
+ ctx.beginPath();
+ ctx.moveTo(-s*0.3,-s*0.4);ctx.lineTo(-s*0.3+trot*s*0.14,0);
+ ctx.moveTo(s*0.3,-s*0.4);ctx.lineTo(s*0.3-trot*s*0.14,0);
+ ctx.moveTo(-s*0.1,-s*0.4);ctx.lineTo(-s*0.1-trot*s*0.14,0);
+ ctx.moveTo(s*0.1,-s*0.4);ctx.lineTo(s*0.1+trot*s*0.14,0);
+ ctx.stroke();
+ ctx.restore();
+ }
+ function drawTortoise(sx,sy,scale,phase){
+ const s=scale*34;if(s<1)return;
+ const plod=Math.sin(phase*1.5)*s*0.04;
+ ctx.save();ctx.translate(sx,sy);
+ ctx.fillStyle= state.diagram? '#ff7a1a':'#4a7a3a';
+ ctx.beginPath();ctx.ellipse(0,-s*0.3,s*0.4,s*0.26,0,Math.PI,0);ctx.fill();
+ if(!state.diagram){ctx.strokeStyle='#2f5a26';ctx.lineWidth=1.4;
+ for(let i=-2;i<=2;i++){ctx.beginPath();ctx.moveTo(i*s*0.13,-s*0.3);ctx.lineTo(i*s*0.16,-s*0.52);ctx.stroke();}}
+ ctx.fillStyle= state.diagram?'#ff7a1a':'#5a8a48';
+ ctx.beginPath();ctx.arc(s*0.44,-s*0.22,s*0.1,0,6.28);ctx.fill();
+ ctx.fillRect(-s*0.3+plod,-s*0.12,s*0.1,s*0.14);
+ ctx.fillRect(s*0.18-plod,-s*0.12,s*0.1,s*0.14);
+ ctx.restore();
+ }
+ function drawPigeon(sx,sy,scale,flap,lift){
+ const s=scale*10*(1+lift*0.4);if(s<0.6)return;
+ ctx.save();ctx.translate(sx,sy);
+ ctx.strokeStyle= state.diagram? '#ff7a1a':'#cfd6e6';ctx.lineWidth=Math.max(1,s*0.16);ctx.lineCap='round';
+ const w=Math.sin(flap)*s*1.1;
+ ctx.beginPath();
+ ctx.moveTo(-s, -Math.abs(w)*0.5);ctx.lineTo(0,0);ctx.lineTo(s,-Math.abs(w)*0.5);
+ ctx.stroke();
+ ctx.restore();
+ }
+
+ function drawAnimals(t){
+ if(state.animals.pigeons){
+ for(let i=0;i<pigeons.length;i++){
+ const pg=pigeons[i];
+ const wave = Math.sin(t*1.6 - pg.off);
+ const lift = Math.max(0, wave);
+ const flap = t*10 + pg.off*3;
+ const wy = pg.by - lift*0.28;
+ const wx = pg.bx + Math.sin(t*0.5+pg.off)*0.03;
+ const pr=project(wx, wy);
+ const yLift = pr.y - lift*80*pr.scale;
+ drawPigeon(pr.x, yLift, pr.scale, flap, lift);
+ }
+ }
+ if(state.animals.tortoise && tortoise){
+ tortoise.wx += (state.reduced?0.00003:0.00009)* (state.paused?0:1);
+ if(tortoise.wx>1.05) tortoise.wx=-0.05;
+ const pr=project(tortoise.wx, tortoise.wy);
+ if(state.diagram){
+ ctx.strokeStyle='rgba(255,122,26,.5)';ctx.setLineDash([4,6]);ctx.lineWidth=1.2;
+ const a=project(-0.05,tortoise.wy),b=project(1.05,tortoise.wy);
+ ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.stroke();ctx.setLineDash([]);
+ }
+ drawTortoise(pr.x,pr.y,pr.scale, beatClock*Math.PI);
+ }
+ if(state.animals.bears){
+ for(const b of bears){
+ const pr=project(b.wx,b.wy);
+ drawBear(pr.x,pr.y,pr.scale, beatClock*Math.PI*0.5 + b.off);
+ }
+ }
+ if(state.animals.dogs){
+ for(const dg of dogs){
+ let px=(dg.phase + t*dg.speed*dg.dir);px=((px%1)+1)%1;
+ const wy=dg.baseY + Math.sin(px*Math.PI*4+dg.phase)*dg.amp;
+ const pr=project(px,wy);
+ if(state.diagram){
+ ctx.strokeStyle='rgba(255,122,26,.45)';ctx.setLineDash([2,6]);ctx.lineWidth=1;
+ ctx.beginPath();
+ for(let s=0;s<=1;s+=0.05){const yy=dg.baseY+Math.sin(s*Math.PI*4+dg.phase)*dg.amp;const q=project(s,yy);s?ctx.lineTo(q.x,q.y):ctx.moveTo(q.x,q.y);}
+ ctx.stroke();ctx.setLineDash([]);
+ }
+ drawDog(pr.x,pr.y,pr.scale, beatClock*Math.PI + dg.phase*6);
+ }
+ }
+ }
+
+ function drawGround(){
+ const horizon=H*0.30;
+ const gg=ctx.createLinearGradient(0,horizon,0,H);
+ gg.addColorStop(0,'#170a30');
+ gg.addColorStop(1,'#0b0420');
+ ctx.fillStyle=gg;ctx.fillRect(0,horizon,W,H-horizon);
+ const glow=ctx.createRadialGradient(W*0.5,horizon,20,W*0.5,horizon,W*0.7);
+ glow.addColorStop(0,'rgba(255,122,26,.16)');
+ glow.addColorStop(1,'rgba(255,122,26,0)');
+ ctx.fillStyle=glow;ctx.fillRect(0,horizon-40,W,H*0.6);
+ ctx.strokeStyle='rgba(0,229,255,.10)';ctx.lineWidth=1;
+ for(let i=-6;i<=6;i++){
+ const a=project(0.5+i*0.14, 0.02), b=project(0.5+i*0.5, 1.0);
+ ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.stroke();
+ }
+ for(let d=0.1; d<=1; d+=0.13){
+ const a=project(0,d), b=project(1,d);
+ ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.stroke();
+ }
+ }
+
+ let last=performance.now();
+ let fpsAcc=0, fpsN=0, fpsT=0;
+
+ function frame(now){
+ let dt=(now-last)/1000; last=now;
+ if(dt>0.1) dt=0.1;
+ fpsAcc+=1/Math.max(dt,0.0001);fpsN++;fpsT+=dt;
+ if(fpsT>0.5){document.getElementById('fps').textContent=Math.round(fpsAcc/fpsN);fpsAcc=0;fpsN=0;fpsT=0;}
+
+ const tgt=CAMS[state.cam];
+ cam.cx += (tgt.cx-cam.cx)*Math.min(1,dt*3);
+ cam.cy += (tgt.cy-cam.cy)*Math.min(1,dt*3);
+ cam.zoom += (tgt.zoom-cam.zoom)*Math.min(1,dt*3);
+
+ const active = !state.paused;
+ const speed = state.reduced ? 0.4 : 1;
+ if(active){
+ const beatsPerSec = state.tempo/60;
+ beatClock += dt*beatsPerSec*speed;
+ }
+ const t = beatClock;
+ const beatInt=Math.floor(beatClock);
+ const beatHit = beatInt!==lastBeatInt;
+ if(beatHit) lastBeatInt=beatInt;
+ if(active) updateRings(dt, beatClock%1, beatHit);
+
+ if(state.trail>0 && !state.reduced && !state.diagram){
+ ctx.globalCompositeOperation='source-over';
+ ctx.fillStyle=`rgba(18,0,43,${1-state.trail*0.92})`;
+ ctx.fillRect(0,0,W,H);
+ }else{
+ ctx.clearRect(0,0,W,H);
+ ctx.fillStyle='#12002b';ctx.fillRect(0,0,W,H);
+ }
+
+ if(wallCanvas){
+ ctx.save();
+ const pulse=state.reduced?1:(1+Math.max(0,Math.sin(beatClock*Math.PI*2))*0.08);
+ const wallH=H*0.42;
+ ctx.translate(W*0.5,H*0.30);
+ ctx.scale(cam.zoom,cam.zoom);
+ ctx.translate(-W*0.5, -H*0.30);
+ ctx.globalAlpha=Math.min(1,0.85*pulse);
+ ctx.drawImage(wallCanvas,0,0,W,wallH);
+ ctx.restore();
+ }
+
+ drawGround();
+ if(!state.reduced) drawRings();
+
+ drawProcession(t);
+ drawPairs(t);
+ drawFreestyle(t);
+ drawAnimals(t);
+ drawCircle(t);
+
+ const vg=ctx.createRadialGradient(W*0.5,H*0.5,H*0.3,W*0.5,H*0.5,H*0.85);
+ vg.addColorStop(0,'rgba(0,0,0,0)');
+ vg.addColorStop(1,'rgba(0,0,0,.55)');
+ ctx.fillStyle=vg;ctx.fillRect(0,0,W,H);
+
+ requestAnimationFrame(frame);
+ }
+
+ document.querySelectorAll('#camGrp button').forEach(b=>{
+ b.addEventListener('click',()=>{
+ document.querySelectorAll('#camGrp button').forEach(x=>x.classList.remove('on'));
+ b.classList.add('on'); state.cam=b.dataset.cam;
+ });
+ });
+ document.querySelectorAll('#animGrp button').forEach(b=>{
+ b.addEventListener('click',()=>{
+ const k=b.dataset.anim; state.animals[k]=!state.animals[k];
+ b.classList.toggle('on',state.animals[k]);
+ });
+ });
+ const tempo=document.getElementById('tempo'), tempoV=document.getElementById('tempoV');
+ tempo.addEventListener('input',()=>{state.tempo=+tempo.value;tempoV.textContent=tempo.value;});
+ const crowd=document.getElementById('crowd'), crowdV=document.getElementById('crowdV');
+ crowd.addEventListener('input',()=>{state.crowd=+crowd.value/100;crowdV.textContent=crowd.value+'%';});
+ const trail=document.getElementById('trail'), trailV=document.getElementById('trailV');
+ trail.addEventListener('input',()=>{state.trail=+trail.value/100;trailV.textContent=trail.value+'%';});
+
+ const pauseBtn=document.getElementById('pauseBtn');
+ pauseBtn.addEventListener('click',()=>{
+ state.paused=!state.paused;
+ pauseBtn.textContent=state.paused?'▶ Play':'⏸ Pause';
+ pauseBtn.classList.toggle('on',state.paused);
+ });
+ document.getElementById('restartBtn').addEventListener('click',()=>{
+ beatClock=0;lastBeatInt=-1;rings.length=0;buildCrowd();
+ state.paused=false;pauseBtn.textContent='⏸ Pause';pauseBtn.classList.remove('on');
+ });
+ const diagBtn=document.getElementById('diagBtn');
+ const legend=document.getElementById('legend');
+ diagBtn.addEventListener('click',()=>{
+ state.diagram=!state.diagram;
+ diagBtn.classList.toggle('on',state.diagram);
+ legend.classList.toggle('show',state.diagram);
+ legend.setAttribute('aria-hidden', state.diagram?'false':'true');
+ });
+ const motionBtn=document.getElementById('motionBtn');
+ if(state.reduced) motionBtn.classList.add('on');
+ motionBtn.addEventListener('click',()=>{
+ state.reduced=!state.reduced;
+ motionBtn.classList.toggle('on',state.reduced);
+ });
+
+ const dock=document.getElementById('dock'), dockHead=document.getElementById('dockHead');
+ function toggleDock(){dock.classList.toggle('collapsed');
+ dockHead.setAttribute('aria-expanded', dock.classList.contains('collapsed')?'false':'true');}
+ dockHead.addEventListener('click',toggleDock);
+ dockHead.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();toggleDock();}});
+
+ window.addEventListener('keydown',e=>{
+ if(e.target.tagName==='INPUT')return;
+ if(e.key===' '){e.preventDefault();pauseBtn.click();}
+ else if(e.key==='d'||e.key==='D')diagBtn.click();
+ else if(e.key==='r'||e.key==='R')document.getElementById('restartBtn').click();
+ else if(e.key==='m'||e.key==='M')motionBtn.click();
+ else if('12345'.includes(e.key)){
+ const map=['plaza','circle','wall','street','aerial'];
+ const b=document.querySelector(`#camGrp button[data-cam="${map[+e.key-1]}"]`);
+ if(b)b.click();
+ }
+ });
+
+ document.addEventListener('visibilitychange',()=>{ last=performance.now(); });
+
+ resize();
+ buildCrowd();
+ requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/246049758201/qwen25-7b.html b/data/artifacts/246049758201/qwen25-7b.html
new file mode 100644
index 0000000..b3fd1cf
--- /dev/null
+++ b/data/artifacts/246049758201/qwen25-7b.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Wall of Sound Street Dance</title>
+ <style>
+ body { font-family: 'Didot', serif; background-color: #12002b; color: #f3e9ff; padding: 96px; }
+ .card { border-radius: 14px; overflow: hidden; margin-bottom: 24px; }
+ h1, h2, h3 { font-weight: normal; text-align: center; }
+ h1 { font-size: 50px; letter-spacing: .15em; color: #c65f38; }
+ p { font-size: 16px; line-height: 1.5; margin-bottom: 24px; }
+ button, input { border: none; background-color: #b0472e; padding: 12px 24px; color: white; cursor: pointer; margin-right: 8px; font-size: 16px; letter-spacing: .15em; }
+ button:hover { background-color: #ff2e88; }
+ input[type="range"] { width: 300px; margin-bottom: 16px; }
+ @keyframes fadeUp {
+ from { opacity: 0; transform: translateY(28px); }
+ to { opacity: 1; transform: none; }
+ }
+ .card section { padding: 24px; background-color: #1e0b3d; margin-bottom: 24px; }
+ </style>
+</head>
+<body>
+ <h1>Wall of Sound Street Dance</h1>
+ <section>
+ <p><strong>Controls:</strong></p>
+ <div style="display: flex; justify-content: space-between;">
+ <input type="range" min="0" max="200" value="100" step="1" id="tempoSlider">
+ <label for="tempoSlider">Tempo</label>
+ <button onclick="toggleAnimals()">Toggle Animals</button>
+ <label>Trail Intensity: </label><input type="range" min="0" max="255" value="64" step="1" id="trailSlider">
+ </div>
+ <p><button onclick="pauseAnimation()">Pause</button> <button onclick="restartAnimation()">Restart</button></p>
+ </section>
+
+ <section>
+ <h3>Architectural Diagram Mode</h3>
+ <div class="card" id="diagram">
+ <img src="" alt="Diagram" style="width: 100%; height: auto; object-fit: cover;">
+ </div>
+ </section>
+
+ <script>
+ let animalsVisible = true;
+ const diagramImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjRK3AaaaCAED3Am8igCGAIAAAASUVORK5CYII=";
+
+ function toggleAnimals() {
+ animalsVisible = !animalsVisible;
+ const diagram = document.getElementById("diagram");
+ if (animalsVisible) {
+ diagram.style.backgroundImage = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjRK3AaaaCAED3Am8igCGAIAAAASUVORK5CYII=')";
+ } else {
+ diagram.style.backgroundImage = "";
+ }
+ }
+
+ function pauseAnimation() {
+ const elements = document.querySelectorAll('.animate');
+ elements.forEach(element => element.classList.add('paused'));
+ }
+
+ function restartAnimation() {
+ const elements = document.querySelectorAll('.animate.paused');
+ elements.forEach(element => element.classList.remove('paused'));
+ }
+
+ const tempoSlider = document.getElementById("tempoSlider");
+ const trailSlider = document.getElementById("trailSlider");
+
+ let animation;
+ tempoSlider.oninput = () => {
+ if (animation) clearInterval(animation);
+ animation = setInterval(() => {
+ updateAnimation();
+ }, tempoSlider.value);
+ };
+
+ function updateAnimation() {
+ // Placeholder for updating the animation based on tempo and trail slider values
+ }
+
+ window.addEventListener('resize', () => {
+ updateAnimation();
+ });
+
+ document.body.style.animation = "ambient 18s infinite linear";
+
+ const animals = [
+ { name: 'Bear', path: "M250,179.6c-34.8,0,-63.8,28.9,-63.8,63.9v0c0,35.1,29,63.9,63.8,63.9s63.8,-28.7,63.8,-63.9V179.6z" },
+ { name: 'Dog', path: "M400,250c-15.6,0,-28.4,12.8,-28.4,28.4v0c0,15.6,12.7,28.3,28.4,28.3s28.4,-12.7,28.4,-28.3V250z" },
+ { name: 'Pigeon', path: "M300,300c-10.6,0,-20.4,9.3,-20.4,20.4v0c0,10.6,9.8,20.4,20.4,20.4s20.4,-9.8,20.4,-20.4V300z" },
+ { name: 'Tortoise', path: "M500,350c-10,0,-20,10,-20,20v0c0,10,10,20,20,20s20,-10,20,-20V350z" }
+ ];
+
+ function drawAnimals() {
+ const stage = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ stage.setAttribute("width", "600");
+ stage.setAttribute("height", "400");
+ animals.forEach(animal => {
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+ path.setAttribute("d", animal.path);
+ path.style.fill = "#e0a458";
+ path.style.stroke = "#b0472e";
+ stage.appendChild(path);
+ });
+ const diagram = document.getElementById("diagram");
+ diagram.innerHTML = '';
+ diagram.appendChild(stage);
+ }
+
+ drawAnimals();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/246049758201/qwen3-14b.html b/data/artifacts/246049758201/qwen3-14b.html
new file mode 100644
index 0000000..969d81f
--- /dev/null
+++ b/data/artifacts/246049758201/qwen3-14b.html
@@ -0,0 +1,488 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Wall of Sound Street Dance</title>
+ <style>
+ :root {
+ --bg: #0d0d0f;
+ --surface: #17171c;
+ --text: #f5f2ea;
+ --muted: #8a8578;
+ --accent: #c9a961;
+ --spacing-4: 4px;
+ --spacing-8: 8px;
+ --spacing-12: 12px;
+ --spacing-16: 16px;
+ --spacing-24: 24px;
+ --spacing-32: 32px;
+ --spacing-48: 48px;
+ --spacing-64: 64px;
+ --card-radius: 14px;
+ --font-display: 67px;
+ --font-h1: 50px;
+ --font-h2: 38px;
+ --font-h3: 28px;
+ --font-lead: 21px;
+ --font-body: 16px;
+ --font-caption: 12px;
+ --shadow-card: 0 8px 24px rgba(0,0,0,0.12);
+ --shadow-hover: 0 16px 48px rgba(0,0,0,0.18);
+ --glow: 0 0 24px rgba(201, 169, 97, 0.35);
+ --transition: 150ms ease-in-out;
+ }
+
+ * {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+
+ body {
+ background: var(--bg);
+ color: var(--text);
+ font-family: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+ overflow: hidden;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ position: relative;
+ }
+
+ h1 {
+ font-size: var(--font-h1);
+ font-weight: bold;
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ margin-bottom: var(--spacing-24);
+ }
+
+ .controls {
+ position: absolute;
+ top: var(--spacing-32);
+ left: var(--spacing-32);
+ z-index: 10;
+ background: var(--surface);
+ padding: var(--spacing-24);
+ border-radius: var(--card-radius);
+ box-shadow: var(--shadow-card);
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--spacing-16);
+ }
+
+ .controls button,
+ .controls input[type="range"],
+ .controls input[type="checkbox"] {
+ background: var(--surface);
+ border: 1px solid var(--muted);
+ color: var(--text);
+ padding: var(--spacing-8) var(--spacing-16);
+ border-radius: var(--card-radius);
+ cursor: pointer;
+ transition: var(--transition);
+ font-size: var(--font-body);
+ }
+
+ .controls button:hover {
+ background: var(--accent);
+ color: var(--bg);
+ box-shadow: var(--glow);
+ }
+
+ .controls input[type="range"] {
+ width: 200px;
+ }
+
+ .controls input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: var(--accent);
+ cursor: pointer;
+ }
+
+ .controls input[type="range"]::-moz-range-thumb {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: var(--accent);
+ cursor: pointer;
+ }
+
+ .scene {
+ position: relative;
+ width: 100%;
+ height: 100vh;
+ overflow: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: flex-start;
+ flex-direction: column;
+ background: linear-gradient(270deg, #1e0b3d, #12002b);
+ background-size: 400% 400%;
+ animation: gradient-drift 18s linear infinite;
+ }
+
+ .speaker-wall {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 200px;
+ background: linear-gradient(to top, #c9a961, #7d6a45);
+ box-shadow: 0 0 60px 10px rgba(201, 169, 97, 0.3);
+ z-index: 1;
+ }
+
+ .rhythm-visual {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ pointer-events: none;
+ z-index: 0;
+ }
+
+ .rhythm-ring {
+ position: absolute;
+ border-radius: 50%;
+ background: rgba(201, 169, 97, 0.3);
+ opacity: 0.6;
+ animation: pulse 1s ease-out infinite;
+ }
+
+ .dance-floor {
+ position: relative;
+ width: 80vw;
+ height: 80vh;
+ margin: auto;
+ overflow: hidden;
+ background: linear-gradient(135deg, #1e0b3d 25%, #12002b 75%);
+ }
+
+ .group {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ }
+
+ .group.circle {
+ animation: spin 10s linear infinite;
+ }
+
+ .group.freestyle {
+ animation: curve 15s linear infinite;
+ }
+
+ .group.mirror {
+ animation: mirror 12s linear infinite;
+ }
+
+ .group.procession {
+ animation: enter 20s linear infinite;
+ }
+
+ .animal {
+ position: absolute;
+ width: 40px;
+ height: 40px;
+ border-radius: 50%;
+ background: #7d6a45;
+ box-shadow: 0 0 8px rgba(125, 106, 69, 0.5);
+ animation: animalMove 10s linear infinite;
+ }
+
+ .animal.bear {
+ background: #c9a961;
+ }
+
+ .animal.dog {
+ background: #ff2e88;
+ }
+
+ .animal.pigeon {
+ background: #00e5ff;
+ }
+
+ .animal.tortoise {
+ background: #e0a458;
+ }
+
+ .icon {
+ font-family: 'Arial', sans-serif;
+ font-size: 20px;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ color: var(--bg);
+ }
+
+ .diagram-mode {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.2);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: var(--text);
+ font-size: var(--font-body);
+ z-index: 5;
+ pointer-events: auto;
+ opacity: 0;
+ transition: opacity 0.3s;
+ }
+
+ .diagram-mode.active {
+ opacity: 1;
+ }
+
+ .diagram-label {
+ margin: var(--spacing-16) 0;
+ text-align: center;
+ }
+
+ @keyframes gradient-drift {
+ 0% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+ 100% { background-position: 0% 50%; }
+ }
+
+ @keyframes pulse {
+ 0% { transform: scale(0.8); opacity: 0.6; }
+ 50% { transform: scale(1.2); opacity: 0.8; }
+ 100% { transform: scale(0.8); opacity: 0.6; }
+ }
+
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
+
+ @keyframes curve {
+ 0% { transform: translateX(0%) translateY(0%); }
+ 25% { transform: translateX(50%) translateY(-25%); }
+ 50% { transform: translateX(100%) translateY(0%); }
+ 75% { transform: translateX(100%) translateY(25%); }
+ 100% { transform: translateX(0%) translateY(0%); }
+ }
+
+ @keyframes mirror {
+ 0% { transform: translateX(10%) rotate(0deg); }
+ 50% { transform: translateX(50%) rotate(180deg); }
+ 100% { transform: translateX(10%) rotate(0deg); }
+ }
+
+ @keyframes enter {
+ 0% { transform: translateX(-100%); opacity: 0; }
+ 50% { opacity: 1; }
+ 100% { transform: translateX(100%); opacity: 0; }
+ }
+
+ @keyframes animalMove {
+ 0% { transform: translateX(0%) translateY(0%); }
+ 100% { transform: translateX(100%) translateY(0%); }
+ }
+
+ @keyframes fadeUp {
+ from { opacity: 0; transform: translateY(28px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+ </style>
+</head>
+<body>
+ <h1>WALL OF SOUND STREET DANCE</h1>
+ <div class="controls">
+ <button id="pause">Pause</button>
+ <button id="restart">Restart</button>
+ <button id="toggle-diagram">Diagram Mode</button>
+ <label for="tempo">Tempo</label>
+ <input type="range" id="tempo" min="40" max="200" value="120">
+ <label for="crowd">Crowd Density</label>
+ <input type="range" id="crowd" min="10" max="50" value="25">
+ <label for="animal-visibility">Animal Visibility</label>
+ <input type="range" id="animal-visibility" min="0" max="100" value="100">
+ <label for="trail-intensity">Trail Intensity</label>
+ <input type="range" id="trail-intensity" min="0" max="100" value="50">
+ <label><input type="checkbox" id="reduced-motion" checked> Reduced Motion</label>
+ </div>
+ <div class="scene">
+ <div class="speaker-wall"></div>
+ <div class="rhythm-visual"></div>
+ <div class="dance-floor">
+ <div class="group circle">
+ <div class="icon">•</div>
+ </div>
+ <div class="group freestyle"></div>
+ <div class="group mirror"></div>
+ <div class="group procession"></div>
+ <div class="animal bear"></div>
+ <div class="animal dog"></div>
+ <div class="animal pigeon"></div>
+ <div class="animal tortoise"></div>
+ </div>
+ </div>
+ <div class="diagram-mode" id="diagram-mode">
+ <h2>Architectural Diagram</h2>
+ <div class="diagram-label">• Central Circle - Spinning Hand-in-Hand</div>
+ <div class="diagram-label">• Freestyle Dancers - Curved Paths</div>
+ <div class="diagram-label">• Mirror Pairs - Edges</div>
+ <div class="diagram-label">• Procession - Entering from Street</div>
+ <div class="diagram-label">• Animal Companions - Bears, Dogs, Pigeons, Tortoise</div>
+ </div>
+ <script>
+ const tempoSlider = document.getElementById('tempo');
+ const crowdSlider = document.getElementById('crowd');
+ const animalVisibilitySlider = document.getElementById('animal-visibility');
+ const trailIntensitySlider = document.getElementById('trail-intensity');
+ const pauseButton = document.getElementById('pause');
+ const restartButton = document.getElementById('restart');
+ const diagramMode = document.getElementById('diagram-mode');
+ const toggleDiagramButton = document.getElementById('toggle-diagram');
+ const reducedMotionCheckbox = document.getElementById('reduced-motion');
+
+ let isPlaying = true;
+ let tempo = 120;
+ let crowd = 25;
+ let animalVisibility = 100;
+ let trailIntensity = 50;
+ let isReducedMotion = true;
+
+ // Rhythm rings
+ const rhythmVisual = document.querySelector('.rhythm-visual');
+
+ function createRhythmRing() {
+ const ring = document.createElement('div');
+ ring.classList.add('rhythm-ring');
+ ring.style.left = `${Math.random() * 100}%`;
+ ring.style.top = `${Math.random() * 100}%`;
+ ring.style.width = `${Math.random() * 100 + 50}px`;
+ ring.style.height = ring.style.width;
+ rhythmVisual.appendChild(ring);
+ ring.style.animationDuration = `${1000 / (tempo / 60)}ms`;
+ ring.style.opacity = trailIntensity / 100;
+ setTimeout(() => {
+ ring.remove();
+ }, 10000);
+ }
+
+ function updateRhythmVisual() {
+ rhythmVisual.innerHTML = '';
+ for (let i = 0; i < 20; i++) {
+ createRhythmRing();
+ }
+ }
+
+ // Dance group management
+ function spawnDancers() {
+ const circle = document.querySelector('.group.circle');
+ circle.style.animationDuration = `${60000 / (tempo / 60)}ms`;
+ circle.style.animationTimingFunction = 'linear';
+
+ const freestyle = document.querySelector('.group.freestyle');
+ freestyle.style.animationDuration = `${15000 / (tempo / 60)}ms`;
+
+ const mirror = document.querySelector('.group.mirror');
+ mirror.style.animationDuration = `${12000 / (tempo / 60)}ms`;
+
+ const procession = document.querySelector('.group.procession');
+ procession.style.animationDuration = `${20000 / (tempo / 60)}ms`;
+ }
+
+ function updateDancers() {
+ const circle = document.querySelector('.group.circle');
+ circle.style.opacity = crowd / 50;
+ circle.style.animationDuration = `${60000 / (tempo / 60)}ms`;
+
+ const freestyle = document.querySelector('.group.freestyle');
+ freestyle.style.opacity = crowd / 50;
+ freestyle.style.animationDuration = `${15000 / (tempo / 60)}ms`;
+
+ const mirror = document.querySelector('.group.mirror');
+ mirror.style.opacity = crowd / 50;
+ mirror.style.animationDuration = `${12000 / (tempo / 60)}ms`;
+
+ const procession = document.querySelector('.group.procession');
+ procession.style.opacity = crowd / 50;
+ procession.style.animationDuration = `${20000 / (tempo / 60)}ms`;
+
+ const animals = document.querySelectorAll('.animal');
+ animals.forEach(animal => {
+ animal.style.opacity = animalVisibility / 100;
+ });
+ }
+
+ function toggleDiagramMode() {
+ diagramMode.classList.toggle('active');
+ }
+
+ function pauseAnimation() {
+ isPlaying = !isPlaying;
+ if (isPlaying) {
+ pauseButton.textContent = 'Pause';
+ } else {
+ pauseButton.textContent = 'Resume';
+ }
+ }
+
+ function restartAnimation() {
+ isPlaying = true;
+ pauseButton.textContent = 'Pause';
+ updateDancers();
+ updateRhythmVisual();
+ }
+
+ function init() {
+ updateRhythmVisual();
+ updateDancers();
+ spawnDancers();
+ }
+
+ // Controls
+ tempoSlider.addEventListener('input', () => {
+ tempo = parseInt(tempoSlider.value);
+ updateDancers();
+ updateRhythmVisual();
+ });
+
+ crowdSlider.addEventListener('input', () => {
+ crowd = parseInt(crowdSlider.value);
+ updateDancers();
+ });
+
+ animalVisibilitySlider.addEventListener('input', () => {
+ animalVisibility = parseInt(animalVisibilitySlider.value);
+ updateDancers();
+ });
+
+ trailIntensitySlider.addEventListener('input', () => {
+ trailIntensity = parseInt(trailIntensitySlider.value);
+ updateRhythmVisual();
+ });
+
+ pauseButton.addEventListener('click', pauseAnimation);
+
+ restartButton.addEventListener('click', restartAnimation);
+
+ toggleDiagramButton.addEventListener('click', toggleDiagramMode);
+
+ reducedMotionCheckbox.addEventListener('change', (e) => {
+ isReducedMotion = e.target.checked;
+ });
+
+ init();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9682a8139b09/claude-code.html b/data/artifacts/9682a8139b09/claude-code.html
new file mode 100644
index 0000000..fee6fcf
--- /dev/null
+++ b/data/artifacts/9682a8139b09/claude-code.html
@@ -0,0 +1,685 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Orbit of the Cosmic Dancers</title>
+<style>
+ :root{
+ --bg:#0d0d0f; --surface:#17171c; --text:#f5f2ea; --muted:#8a8578;
+ --accent:#c9a961; --accent-2:#7d6a45; --champagne:#e8d6a8;
+ --line:rgba(201,169,97,.28);
+ --serif:'Didot','Bodoni MT','Playfair Display',Georgia,serif;
+ --sans:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
+ }
+ *{box-sizing:border-box}
+ html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font-family:var(--sans);overflow:hidden}
+ #stage{position:fixed;inset:0;display:block}
+ /* museum vignette + frame */
+ .vignette{position:fixed;inset:0;pointer-events:none;
+ background:radial-gradient(130% 100% at 50% 42%,transparent 55%,rgba(0,0,0,.55) 100%);}
+ .edge{position:fixed;inset:14px;pointer-events:none;border:1px solid rgba(201,169,97,.14);border-radius:4px}
+
+ .overlay{position:fixed;inset:0;pointer-events:none;padding:clamp(18px,3vw,40px);
+ display:flex;flex-direction:column;justify-content:space-between}
+ .overlay > *{pointer-events:auto}
+
+ .masthead{max-width:min(560px,60vw)}
+ .kicker{font-size:12px;letter-spacing:.28em;text-transform:uppercase;color:var(--accent);margin:0 0 8px}
+ h1{font-family:var(--serif);font-weight:500;font-size:clamp(28px,4.6vw,50px);line-height:1.02;margin:0;letter-spacing:.01em}
+ .byline{font-family:var(--serif);font-style:italic;color:var(--muted);font-size:clamp(14px,1.6vw,18px);margin:10px 0 0}
+
+ /* Score panel */
+ .score{position:fixed;top:clamp(18px,3vw,40px);right:clamp(18px,3vw,40px);
+ width:min(320px,74vw);background:linear-gradient(180deg,rgba(23,23,28,.92),rgba(13,13,15,.9));
+ border:1px solid rgba(201,169,97,.22);border-radius:14px;
+ padding:18px 20px;backdrop-filter:blur(9px);box-shadow:0 8px 24px rgba(0,0,0,.35)}
+ .score h2{font-size:12px;letter-spacing:.2em;text-transform:uppercase;color:var(--accent);margin:0 0 12px;font-weight:600}
+ .mv-num{font-family:var(--serif);font-size:13px;letter-spacing:.22em;color:var(--muted);text-transform:uppercase}
+ .mv-title{font-family:var(--serif);font-size:22px;line-height:1.1;margin:2px 0 8px}
+ .mv-desc{font-size:13.5px;line-height:1.5;color:#d9d3c4;margin:0}
+ .score .now{margin-top:12px;padding-top:12px;border-top:1px solid rgba(201,169,97,.16);font-size:12.5px;line-height:1.5;color:#cfc8b7}
+ .score .now b{color:var(--champagne);font-weight:600}
+ .score .silent{margin-top:12px;font-size:11px;letter-spacing:.04em;color:var(--muted);font-style:italic}
+
+ /* Controls */
+ .controls{margin:0 auto;display:flex;flex-wrap:wrap;gap:10px 12px;align-items:center;justify-content:center;
+ background:linear-gradient(180deg,rgba(23,23,28,.9),rgba(13,13,15,.92));
+ border:1px solid rgba(201,169,97,.2);border-radius:14px;padding:12px 16px;
+ backdrop-filter:blur(9px);box-shadow:0 8px 24px rgba(0,0,0,.35);max-width:960px}
+ .ctl{display:flex;flex-direction:column;gap:5px}
+ .lbl{font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted)}
+ .row{display:flex;align-items:center;gap:8px}
+ button,select{font-family:var(--sans);color:var(--text);background:rgba(245,242,234,.05);
+ border:1px solid rgba(201,169,97,.3);border-radius:9px;padding:8px 12px;font-size:13px;cursor:pointer;
+ transition:background .18s,border-color .18s,transform .12s}
+ button:hover,select:hover{background:rgba(201,169,97,.14);border-color:var(--accent)}
+ button:active{transform:translateY(1px)}
+ button[aria-pressed="true"]{background:rgba(201,169,97,.22);border-color:var(--accent);color:var(--champagne);
+ box-shadow:inset 0 0 0 1px rgba(201,169,97,.35)}
+ button .ico{font-family:var(--sans);margin-right:6px;font-size:13px}
+ button .st{font-variant:small-caps;letter-spacing:.06em}
+ select{padding-right:10px}
+ input[type=range]{width:130px;accent-color:var(--accent);cursor:pointer}
+ .val{font-size:12px;color:var(--champagne);min-width:38px;text-align:right;font-variant-numeric:tabular-nums}
+ :focus-visible{outline:2px solid var(--accent);outline-offset:2px}
+
+ .about{position:fixed;left:clamp(18px,3vw,40px);bottom:calc(clamp(18px,3vw,40px) + 96px)}
+ details{background:rgba(23,23,28,.85);border:1px solid rgba(201,169,97,.18);border-radius:10px;
+ max-width:320px;backdrop-filter:blur(8px)}
+ summary{cursor:pointer;padding:8px 12px;font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--accent)}
+ details p{margin:0;padding:0 12px 12px;font-size:12.5px;line-height:1.55;color:#cfc8b7}
+ details kbd{background:rgba(245,242,234,.1);border:1px solid rgba(201,169,97,.3);border-radius:4px;
+ padding:1px 6px;font-family:var(--sans);font-size:11px;color:var(--champagne)}
+ .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}
+
+ @media (max-width:720px){
+ .score{position:static;width:auto;margin-top:14px}
+ .masthead{max-width:none}
+ .about{display:none}
+ .overlay{justify-content:flex-start;gap:14px}
+ .controls{margin-top:auto}
+ }
+</style>
+</head>
+<body>
+<canvas id="stage" aria-label="Animated projection: groups of dancers orbit a radiant flower-like star while animal constellations gather and dissolve."></canvas>
+<div class="vignette"></div>
+<div class="edge"></div>
+
+<div class="overlay">
+ <div class="masthead">
+ <p class="kicker">A Celestial Ballroom · Projection</p>
+ <h1>Orbit of the Cosmic Dancers</h1>
+ <p class="byline">Figures turning in elliptical light around a blossoming star.</p>
+
+ <section class="score" aria-live="polite" aria-label="Score panel describing the current visual movement">
+ <h2>The Score — a notation of motion</h2>
+ <div class="mv-num" id="mvNum">Movement I</div>
+ <div class="mv-title" id="mvTitle">Grand Ensemble</div>
+ <p class="mv-desc" id="mvDesc"></p>
+ <p class="now" id="nowLine"></p>
+ <p class="silent">This projection is silent — it does not hear or react to sound.</p>
+ </section>
+ </div>
+
+ <div class="about">
+ <details>
+ <summary>Keys & about</summary>
+ <p><kbd>Space</kbd> pause · <kbd>R</kbd> reset · <kbd>T</kbd> trails · <kbd>L</kbd> labels · <kbd>M</kbd> reduced motion · <kbd>←</kbd>/<kbd>→</kbd> orbit speed. A responsive, single-file study in choreographed orbital motion.</p>
+ </details>
+ </div>
+
+ <div class="controls" role="group" aria-label="Projection controls">
+ <div class="ctl">
+ <span class="lbl" id="spdLbl">Orbit speed</span>
+ <div class="row">
+ <input type="range" id="speed" min="0" max="200" value="60" aria-labelledby="spdLbl" aria-describedby="spdVal">
+ <span class="val" id="spdVal">60%</span>
+ </div>
+ </div>
+ <div class="ctl">
+ <label class="lbl" for="mode">Choreography mode</label>
+ <select id="mode" aria-label="Choreography mode">
+ <option value="ensemble">Ⅰ · Grand Ensemble</option>
+ <option value="solo">Ⅱ · Solo Variations</option>
+ <option value="couples">Ⅲ · Pas de Deux</option>
+ <option value="ring">Ⅳ · Breathing Ring</option>
+ <option value="spiral">Ⅴ · Spiral Procession</option>
+ </select>
+ </div>
+ <div class="ctl">
+ <span class="lbl">Display</span>
+ <div class="row">
+ <button id="trails" aria-pressed="true"><span class="ico" aria-hidden="true">▦</span>Trails: <span class="st" id="trailsSt">on</span></button>
+ <button id="labels" aria-pressed="true"><span class="ico" aria-hidden="true">✦</span>Labels: <span class="st" id="labelsSt">on</span></button>
+ </div>
+ </div>
+ <div class="ctl">
+ <span class="lbl">Transport</span>
+ <div class="row">
+ <button id="pause" aria-pressed="false"><span class="ico" aria-hidden="true">❚❚</span><span class="st" id="pauseSt">pause</span></button>
+ <button id="reset"><span class="ico" aria-hidden="true">↺</span><span class="st">reset</span></button>
+ <button id="reduce" aria-pressed="false"><span class="ico" aria-hidden="true">〜</span>Reduced motion: <span class="st" id="reduceSt">off</span></button>
+ </div>
+ </div>
+ </div>
+</div>
+<div class="sr-only" id="announce" aria-live="polite"></div>
+
+<script>
+(function(){
+"use strict";
+const canvas=document.getElementById('stage');
+const ctx=canvas.getContext('2d');
+let trail,tctx,neb,starfield; // offscreen layers
+let W=0,H=0,dpr=1,U=1;
+let cx=0,cy=0,camx=0,camy=0,zoom=1;
+
+// ---- state ----
+const media=window.matchMedia('(prefers-reduced-motion: reduce)');
+const state={
+ running:true, T:0, speed:0.6,
+ mode:'ensemble', trails:true, labels:true, reduced:media.matches
+};
+if(state.reduced) state.trails=false;
+
+// ---- palette ----
+const GOLD='#c9a961', CHAMP='#e8d6a8';
+const prism=['#ff4d7d','#ffb454','#ffe66d','#6de5b0','#5ec8ff','#b07cff'];
+
+// ---- easing ----
+const eio=t=>t<.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2;
+const clamp=(v,a,b)=>v<a?a:v>b?b:v;
+const rnd=(a,b)=>a+Math.random()*(b-a);
+
+// world->screen
+function tx(wx,wy){return {x:cx+camx+wx*zoom, y:cy+camy+wy*zoom};}
+
+// ---- dancer groups ----
+let groups=[];
+function buildGroups(){
+ groups=[
+ {kind:'solo', a:0.30, b:0.18, rot:0.25, n:9, base:seq(9), alpha:1, tgt:1},
+ {kind:'solo', a:0.185,b:0.125,rot:-0.6, n:6, base:seq(6), alpha:1, tgt:1},
+ {kind:'couples', a:0.245,b:0.155,rot:-0.35, pairs:6, alpha:1, tgt:1},
+ {kind:'ring', orbit:0.30, n:14, alpha:1, tgt:1},
+ {kind:'spiral', n:20, alpha:1, tgt:1}
+ ];
+}
+function seq(n){const a=[];for(let i=0;i<n;i++)a.push(i/n*Math.PI*2);return a;}
+buildGroups();
+
+// ---- figure glyph ----
+function figure(c,px,py,s,rot,swing,color,alpha){
+ c.save();
+ c.globalAlpha=alpha;
+ c.translate(px,py);
+ c.rotate(rot);
+ c.strokeStyle=color; c.fillStyle=color;
+ c.lineWidth=Math.max(1,s*0.15); c.lineCap='round';
+ // torso
+ c.beginPath();c.moveTo(0,-s*0.18);c.lineTo(0,s*0.42);c.stroke();
+ // head
+ c.beginPath();c.arc(0,-s*0.4,s*0.19,0,7);c.fill();
+ // arms
+ const a=Math.sin(swing)*0.55;
+ c.beginPath();
+ c.moveTo(0,-s*0.02);c.lineTo(Math.cos(Math.PI*0.92+a)*s*0.55,-s*0.02+Math.sin(Math.PI*0.92+a)*s*0.55);
+ c.moveTo(0,-s*0.02);c.lineTo(Math.cos(Math.PI*0.08-a)*s*0.55,-s*0.02+Math.sin(Math.PI*0.08-a)*s*0.55);
+ c.stroke();
+ // legs
+ const l=Math.sin(swing+1)*0.32;
+ c.beginPath();
+ c.moveTo(0,s*0.42);c.lineTo(Math.sin(l)*s*0.3,s*0.42+Math.cos(l)*s*0.52);
+ c.moveTo(0,s*0.42);c.lineTo(Math.sin(-l)*s*0.3,s*0.42+Math.cos(l)*s*0.52);
+ c.stroke();
+ c.restore();
+}
+
+function dancerColor(i){ return i%5===0 ? prism[i%prism.length] : CHAMP; }
+
+// draw all dancers onto trail layer (c=tctx)
+function drawDancers(c){
+ const T=state.T, spin=state.reduced?0:1, minWH=Math.min(W,H);
+ c.globalCompositeOperation='lighter';
+ for(const g of groups){
+ if(g.alpha<0.02) continue;
+ const A=g.alpha;
+ if(g.kind==='solo'){
+ const a=g.a*minWH, b=g.b*minWH, cr=Math.cos(g.rot), sr=Math.sin(g.rot);
+ for(let i=0;i<g.n;i++){
+ const th=g.base[i]+T*0.5;
+ let wx=a*Math.cos(th), wy=b*Math.sin(th);
+ const rx=wx*cr-wy*sr, ry=wx*sr+wy*cr;
+ const p=tx(rx,ry);
+ figure(c,p.x,p.y,12*U,T*2*spin+i, T*3+i, dancerColor(i+g.n), A);
+ }
+ } else if(g.kind==='couples'){
+ const a=g.a*minWH,b=g.b*minWH,cr=Math.cos(g.rot),sr=Math.sin(g.rot),R=26*U;
+ for(let pi=0;pi<g.pairs;pi++){
+ const th=pi/g.pairs*Math.PI*2+T*0.35;
+ let wx=a*Math.cos(th), wy=b*Math.sin(th);
+ const rx=wx*cr-wy*sr, ry=wx*sr+wy*cr;
+ const spinA=T*1.7*spin;
+ for(let k=0;k<2;k++){
+ const ang=spinA+k*Math.PI;
+ const p=tx(rx+Math.cos(ang)*R, ry+Math.sin(ang)*R);
+ figure(c,p.x,p.y,11.5*U, ang+Math.PI/2, T*2.4+pi+k, k?CHAMP:dancerColor(pi), A);
+ }
+ }
+ } else if(g.kind==='ring'){
+ const orb=g.orbit*minWH, ca=T*0.25;
+ const ox=Math.cos(ca)*orb, oy=Math.sin(ca)*orb*0.62;
+ const Rr=(0.075+0.045*(0.5+0.5*Math.sin(T*0.6)))*minWH;
+ for(let i=0;i<g.n;i++){
+ const ang=i/g.n*Math.PI*2+T*0.3;
+ const p=tx(ox+Math.cos(ang)*Rr, oy+Math.sin(ang)*Rr);
+ figure(c,p.x,p.y,11*U, ang+Math.PI/2, T*3+i, dancerColor(i+3), A);
+ }
+ } else if(g.kind==='spiral'){
+ const w=state.reduced?0.4:(1.4*Math.sin(T*0.12)); // reverses direction
+ const glob=T*0.15*spin;
+ for(let i=0;i<g.n;i++){
+ const rad=(0.05+i*0.021)*minWH;
+ const ang=i*0.52*w+glob;
+ const p=tx(Math.cos(ang)*rad, Math.sin(ang)*rad*0.9);
+ figure(c,p.x,p.y,11*U, ang+Math.PI/2, T*2.6+i, dancerColor(i+1), A*0.96);
+ }
+ }
+ }
+ c.globalCompositeOperation='source-over';
+}
+
+// ---- star (flower) ----
+function drawStar(dim){
+ const T=state.T, minWH=Math.min(W,H), R=minWH*0.058;
+ const p=tx(0,0);
+ const bright=1-dim*0.72;
+ // outer glow
+ let gr=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,R*7);
+ gr.addColorStop(0,`rgba(255,236,190,${0.5*bright})`);
+ gr.addColorStop(0.25,`rgba(230,180,120,${0.22*bright})`);
+ gr.addColorStop(0.6,`rgba(120,90,150,${0.10*bright})`);
+ gr.addColorStop(1,'rgba(0,0,0,0)');
+ ctx.save(); ctx.globalCompositeOperation='lighter';
+ ctx.fillStyle=gr; ctx.beginPath();ctx.arc(p.x,p.y,R*7,0,7);ctx.fill();
+ // petals
+ const petals=10, rot=state.reduced?0:T*0.15;
+ for(let i=0;i<petals;i++){
+ const a=rot+i/petals*Math.PI*2;
+ ctx.save();ctx.translate(p.x,p.y);ctx.rotate(a);
+ const pg=ctx.createLinearGradient(0,0,0,-R*2.4);
+ pg.addColorStop(0,`rgba(255,224,170,${0.5*bright})`);
+ pg.addColorStop(1,'rgba(201,120,150,0)');
+ ctx.fillStyle=pg;
+ ctx.beginPath();
+ ctx.moveTo(0,0);
+ ctx.quadraticCurveTo(R*0.9,-R*1.3, 0,-R*2.4);
+ ctx.quadraticCurveTo(-R*0.9,-R*1.3, 0,0);
+ ctx.fill();
+ ctx.restore();
+ }
+ // inner second layer
+ for(let i=0;i<petals;i++){
+ const a=-rot*1.4+i/petals*Math.PI*2+0.3;
+ ctx.save();ctx.translate(p.x,p.y);ctx.rotate(a);
+ ctx.fillStyle=`rgba(255,246,220,${0.4*bright})`;
+ ctx.beginPath();
+ ctx.moveTo(0,0);
+ ctx.quadraticCurveTo(R*0.5,-R*0.8,0,-R*1.4);
+ ctx.quadraticCurveTo(-R*0.5,-R*0.8,0,0);
+ ctx.fill();ctx.restore();
+ }
+ // core
+ const cg=ctx.createRadialGradient(p.x,p.y,0,p.x,p.y,R*0.9);
+ cg.addColorStop(0,`rgba(255,255,248,${bright})`);
+ cg.addColorStop(1,`rgba(255,214,150,0)`);
+ ctx.fillStyle=cg;ctx.beginPath();ctx.arc(p.x,p.y,R*0.9,0,7);ctx.fill();
+ ctx.restore();
+}
+
+// ---- orbit paths ----
+function drawOrbits(){
+ const minWH=Math.min(W,H);
+ ctx.save();ctx.lineWidth=1;
+ const paths=[[0.30,0.18,0.25],[0.185,0.125,-0.6],[0.245,0.155,-0.35]];
+ for(const[a0,b0,r]of paths){
+ const a=a0*minWH*zoom,b=b0*minWH*zoom;
+ ctx.strokeStyle='rgba(201,169,97,0.12)';
+ ctx.beginPath();
+ ctx.ellipse(cx+camx,cy+camy,a,b,r,0,7);
+ ctx.stroke();
+ }
+ ctx.restore();
+}
+
+// ---- eclipse body ----
+const eclipse={active:false};
+function drawEclipse(){
+ const T=state.T, minWH=Math.min(W,H);
+ const orb=minWH*0.34, ea=T*0.13;
+ const wx=Math.cos(ea)*orb, wy=Math.sin(ea)*orb*0.5;
+ const p=tx(wx,wy);
+ const star=tx(0,0);
+ const d=Math.hypot(p.x-star.x,p.y-star.y);
+ const R=minWH*0.05;
+ const near=minWH*0.10;
+ const amt=clamp(1-d/near,0,1);
+ eclipse.active=amt>0.15;
+ eclipse.amt=amt;
+ // dark disk
+ ctx.save();
+ ctx.fillStyle='#0a0a10';
+ ctx.beginPath();ctx.arc(p.x,p.y,R,0,7);ctx.fill();
+ ctx.strokeStyle='rgba(30,26,34,0.9)';ctx.lineWidth=2;ctx.stroke();
+ // prismatic corona when eclipsing
+ if(amt>0.15){
+ ctx.globalCompositeOperation='lighter';
+ for(let i=0;i<6;i++){
+ ctx.strokeStyle=prism[i];ctx.globalAlpha=amt*0.5;
+ ctx.lineWidth=1.4;
+ ctx.beginPath();ctx.arc(p.x,p.y,R+3+i*2.4,0,7);ctx.stroke();
+ }
+ ctx.globalAlpha=1;
+ }
+ ctx.restore();
+ return amt;
+}
+
+// ---- prismatic burst ----
+let burst=null, nextBurst=9;
+function updateBurst(){
+ if(!state.reduced && state.running && state.T>nextBurst && !burst){
+ burst={t0:state.T}; nextBurst=state.T+rnd(13,20);
+ }
+ if(burst){
+ const life=1.4, e=(state.T-burst.t0)/life;
+ if(e>=1){burst=null;return;}
+ const star=tx(0,0), minWH=Math.min(W,H), maxR=minWH*0.42*eio(clamp(e,0,1));
+ ctx.save();ctx.globalCompositeOperation='lighter';
+ const rays=16;
+ for(let i=0;i<rays;i++){
+ const a=i/rays*Math.PI*2+burst.t0;
+ ctx.strokeStyle=prism[i%prism.length];
+ ctx.globalAlpha=(1-e)*0.5;
+ ctx.lineWidth=1.2;
+ ctx.beginPath();
+ ctx.moveTo(star.x+Math.cos(a)*minWH*0.06, star.y+Math.sin(a)*minWH*0.06);
+ ctx.lineTo(star.x+Math.cos(a)*maxR, star.y+Math.sin(a)*maxR);
+ ctx.stroke();
+ }
+ ctx.restore();
+ }
+}
+
+// ---- constellations ----
+const SHAPES={
+ bear:{label:'The Bear',pts:[[-0.92,-0.1],[-0.72,-0.55],[-0.58,-0.38],[-0.36,-0.16],[0.1,-0.4],[0.66,-0.22],[0.86,-0.05],[-0.3,0.04],[-0.32,0.62],[0.54,0.06],[0.54,0.62],[0.12,0.24]],
+ bones:[[0,2],[2,1],[2,3],[3,4],[4,5],[5,6],[3,7],[7,8],[5,9],[9,10],[3,11],[11,4]]},
+ turtle:{label:'The Turtle',pts:[[0.92,0.0],[0.5,-0.24],[0.0,-0.52],[-0.62,-0.16],[-0.94,0.06],[0.46,0.26],[-0.55,0.26],[0.34,0.58],[-0.4,0.58]],
+ bones:[[1,0],[1,2],[2,3],[3,4],[1,5],[3,6],[5,6],[5,7],[6,8]]},
+ hare:{label:'The Hare',pts:[[-0.92,0.12],[-0.58,-0.82],[-0.34,-0.8],[-0.6,-0.1],[0.12,-0.16],[0.72,0.02],[0.88,0.06],[-0.42,0.62],[0.56,0.62]],
+ bones:[[0,3],[3,1],[3,2],[3,4],[4,5],[5,6],[4,7],[5,8]]},
+ fox:{label:'The Fox',pts:[[-0.96,0.06],[-0.6,-0.62],[-0.4,-0.56],[-0.56,-0.12],[0.2,-0.22],[0.6,-0.05],[0.9,0.1],[1.06,0.36],[-0.36,0.56],[0.55,0.56]],
+ bones:[[0,3],[3,1],[3,2],[3,4],[4,5],[5,6],[6,7],[3,8],[5,9]]},
+ birds:makeBirds()
+};
+function makeBirds(){
+ const pts=[],bones=[];
+ const centers=[[-0.82,-0.5],[-0.36,-0.16],[0.02,0.12],[0.42,-0.12],[0.82,-0.44]];
+ centers.forEach((c,k)=>{
+ const b=pts.length, w=0.2-(k%2?0.03:0);
+ pts.push([c[0]-w,c[1]-w*0.6]);pts.push([c[0],c[1]]);pts.push([c[0]+w,c[1]-w*0.6]);
+ bones.push([b,b+1],[b+1,b+2]);
+ });
+ return {label:'Flock of Birds',pts,bones};
+}
+const ORDER=['bear','turtle','hare','fox','birds'];
+const SPOTS=[
+ {name:'the north-east',x:0.74,y:0.30},{name:'the south-east',x:0.76,y:0.72},
+ {name:'the south-west',x:0.26,y:0.74},{name:'the north-west',x:0.26,y:0.30},
+ {name:'the east',x:0.82,y:0.5},{name:'the west',x:0.2,y:0.52}
+];
+let orderI=0, spotI=1, con=null, nextSpawn=3.5;
+const DUR={gather:2.6,dance:5.6,dissolve:2.6};
+
+function spawnConstellation(){
+ const key=ORDER[orderI%ORDER.length]; orderI++;
+ const spot=SPOTS[spotI%SPOTS.length]; spotI++;
+ const shp=SHAPES[key];
+ const minWH=Math.min(W,H), scale=minWH*(key==='birds'?0.15:0.15);
+ const ccx=spot.x*W, ccy=spot.y*H;
+ const pts=shp.pts.map(([nx,ny])=>{
+ const txp=ccx+nx*scale, typ=ccy+ny*scale;
+ const oa=rnd(0,Math.PI*2), orr=scale*rnd(2.5,4);
+ const da=rnd(0,Math.PI*2), dr=scale*rnd(3,5);
+ return {tx:txp,ty:typ,ox:ccx+Math.cos(oa)*orr,oy:ccy+Math.sin(oa)*orr,
+ dx:ccx+Math.cos(da)*dr,dy:ccy+Math.sin(da)*dr,x:0,y:0,ph:rnd(0,6.28)};
+ });
+ con={key,label:shp.label,bones:shp.bones,pts,scale,ccx,ccy,dir:spot.name,
+ t0:state.T,phase:'gather'};
+ announce(shp.label+' gathers in '+spot.name+'.');
+}
+
+function updateConstellation(){
+ if(!con){
+ if(state.running && state.T>nextSpawn) spawnConstellation();
+ return;
+ }
+ const el=state.T-con.t0;
+ let phase, p, alpha=1, breathe=0, sway=0;
+ if(el<DUR.gather){phase='gather';p=eio(el/DUR.gather);alpha=p;}
+ else if(el<DUR.gather+DUR.dance){phase='dance';p=1;const de=(el-DUR.gather)/DUR.dance;breathe=Math.sin(de*Math.PI*3)*0.04;sway=Math.sin(de*Math.PI*2)*0.05;}
+ else if(el<DUR.gather+DUR.dance+DUR.dissolve){phase='dissolve';const de=(el-DUR.gather-DUR.dance)/DUR.dissolve;p=eio(de);alpha=1-de;}
+ else {con=null;nextSpawn=state.T+rnd(3.5,6);return;}
+ const prev=con.phase; con.phase=phase; con.alpha=alpha;
+ if(prev!==phase && phase==='dissolve') announce(con.label+' dissolves back into stars.');
+
+ for(const pt of con.pts){
+ if(phase==='gather'){pt.x=pt.ox+(pt.tx-pt.ox)*p;pt.y=pt.oy+(pt.ty-pt.oy)*p;}
+ else if(phase==='dance'){
+ const s=con.scale;
+ pt.x=pt.tx+Math.sin((state.T+pt.ph)*1.6)*s*(0.03+breathe)+sway*s*(pt.tx-con.ccx)/s;
+ pt.y=pt.ty+Math.cos((state.T+pt.ph)*1.4)*s*(0.03+breathe)+Math.sin(state.T*0.9)*s*0.02;
+ } else {pt.x=pt.tx+(pt.dx-pt.tx)*p;pt.y=pt.ty+(pt.dy-pt.ty)*p;}
+ }
+}
+
+function drawConstellation(){
+ if(!con) return;
+ const A=con.alpha;
+ ctx.save();ctx.globalCompositeOperation='lighter';
+ // bones
+ ctx.strokeStyle=`rgba(201,169,97,${0.35*A})`;ctx.lineWidth=1;
+ ctx.beginPath();
+ for(const[i,j]of con.bones){const a=con.pts[i],b=con.pts[j];ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);}
+ ctx.stroke();
+ // points
+ for(const pt of con.pts){
+ const r=3.4*U;
+ const g=ctx.createRadialGradient(pt.x,pt.y,0,pt.x,pt.y,r*4);
+ g.addColorStop(0,`rgba(255,248,224,${0.95*A})`);
+ g.addColorStop(0.4,`rgba(232,214,168,${0.5*A})`);
+ g.addColorStop(1,'rgba(232,214,168,0)');
+ ctx.fillStyle=g;ctx.beginPath();ctx.arc(pt.x,pt.y,r*4,0,7);ctx.fill();
+ ctx.fillStyle=`rgba(255,255,250,${A})`;ctx.beginPath();ctx.arc(pt.x,pt.y,r*0.7,0,7);ctx.fill();
+ }
+ ctx.restore();
+ // label
+ if(state.labels && A>0.2){
+ ctx.save();
+ ctx.font=`${14*Math.max(1,U)}px 'Didot','Bodoni MT','Playfair Display',Georgia,serif`;
+ ctx.textAlign='center';
+ ctx.fillStyle=`rgba(201,169,97,${A})`;
+ ctx.fillText(con.label.toUpperCase(), con.ccx, con.ccy-con.scale*1.25);
+ ctx.restore();
+ }
+}
+
+// ---- background ----
+function buildStarfield(){
+ starfield=document.createElement('canvas');starfield.width=W;starfield.height=H;
+ const c=starfield.getContext('2d');
+ const n=Math.round(W*H/2600);
+ for(let i=0;i<n;i++){
+ const x=Math.random()*W,y=Math.random()*H,r=Math.random()*1.3+0.2;
+ const a=Math.random()*0.6+0.15;
+ c.fillStyle=`rgba(${230+Math.random()*25|0},${220+Math.random()*30|0},${200+Math.random()*40|0},${a})`;
+ c.beginPath();c.arc(x,y,r,0,7);c.fill();
+ }
+}
+function buildNebula(){
+ neb=document.createElement('canvas');neb.width=W;neb.height=H;
+ const c=neb.getContext('2d');
+ const blobs=[
+ {x:0.2,y:0.3,r:0.55,col:'70,54,110'},
+ {x:0.8,y:0.25,r:0.5,col:'120,70,90'},
+ {x:0.7,y:0.8,r:0.6,col:'40,80,95'},
+ {x:0.3,y:0.75,r:0.5,col:'110,86,50'},
+ {x:0.5,y:0.5,r:0.45,col:'60,50,80'}
+ ];
+ c.globalCompositeOperation='screen';
+ for(const b of blobs){
+ const R=b.r*Math.min(W,H);
+ // cut-paper edge (offset darker layer)
+ let ge=c.createRadialGradient(b.x*W+8,b.y*H+8,0,b.x*W+8,b.y*H+8,R*1.02);
+ ge.addColorStop(0,'rgba(0,0,0,0)');ge.addColorStop(0.78,'rgba(10,8,14,0.0)');
+ ge.addColorStop(0.82,'rgba(5,4,8,0.25)');ge.addColorStop(1,'rgba(0,0,0,0)');
+ c.fillStyle=ge;c.beginPath();c.arc(b.x*W+8,b.y*H+8,R*1.02,0,7);c.fill();
+ let g=c.createRadialGradient(b.x*W,b.y*H,0,b.x*W,b.y*H,R);
+ g.addColorStop(0,`rgba(${b.col},0.34)`);
+ g.addColorStop(0.5,`rgba(${b.col},0.14)`);
+ g.addColorStop(0.82,`rgba(${b.col},0.05)`);
+ g.addColorStop(1,'rgba(0,0,0,0)');
+ c.fillStyle=g;c.beginPath();c.arc(b.x*W,b.y*H,R,0,7);c.fill();
+ }
+ // grain
+ c.globalCompositeOperation='source-over';
+ for(let i=0;i<W*H/1400;i++){
+ c.fillStyle=`rgba(255,240,210,${Math.random()*0.03})`;
+ c.fillRect(Math.random()*W,Math.random()*H,1,1);
+ }
+}
+
+// ---- resize ----
+function resize(){
+ W=window.innerWidth;H=window.innerHeight;
+ dpr=Math.min(window.devicePixelRatio||1,2);
+ canvas.width=W*dpr;canvas.height=H*dpr;
+ canvas.style.width=W+'px';canvas.style.height=H+'px';
+ ctx.setTransform(dpr,0,0,dpr,0,0);
+ cx=W/2;cy=H/2;U=clamp(Math.min(W,H)/850,0.7,1.7);
+ trail=document.createElement('canvas');trail.width=W;trail.height=H;
+ tctx=trail.getContext('2d');
+ buildStarfield();buildNebula();
+}
+window.addEventListener('resize',resize);
+resize();
+
+// ---- main loop ----
+let last=performance.now();
+function frame(now){
+ let dt=(now-last)/1000; last=now;
+ dt=clamp(dt,0,0.05);
+ if(state.running){
+ const sf=state.speed*(state.reduced?0.45:1);
+ state.T+=dt*sf;
+ }
+ // camera
+ if(state.reduced){camx=0;camy=0;zoom=1;}
+ else{
+ const T=state.T;
+ camx=Math.sin(T*0.05)*W*0.02;
+ camy=Math.cos(T*0.043)*H*0.02;
+ zoom=1+Math.sin(T*0.03)*0.03;
+ }
+ // group alpha targets from mode
+ for(const g of groups){
+ g.tgt = (state.mode==='ensemble'||state.mode===g.kind)?1:0;
+ g.alpha+=(g.tgt-g.alpha)*clamp(dt*3.2,0,1);
+ }
+
+ // ---- render ----
+ ctx.fillStyle='#0d0d0f';ctx.fillRect(0,0,W,H);
+ // nebula + stars with slight parallax
+ ctx.drawImage(neb, camx*0.4-6, camy*0.4-6);
+ ctx.drawImage(starfield, camx*0.7, camy*0.7);
+ drawOrbits();
+ const dim = eclipse.active?eclipse.amt:0;
+ drawStar(dim);
+
+ // dancers -> trail layer
+ if(state.trails){tctx.globalCompositeOperation='source-over';tctx.fillStyle='rgba(9,9,13,0.11)';tctx.fillRect(0,0,W,H);}
+ else{tctx.clearRect(0,0,W,H);}
+ drawDancers(tctx);
+ ctx.save();ctx.globalCompositeOperation='lighter';ctx.drawImage(trail,0,0);ctx.restore();
+
+ const eAmt=drawEclipse();
+ updateBurst();
+ updateConstellation();
+ drawConstellation();
+
+ refreshScore(eAmt);
+ requestAnimationFrame(frame);
+}
+requestAnimationFrame(frame);
+
+// ---- score panel ----
+const MODES={
+ ensemble:{num:'Movement I',title:'Grand Ensemble',desc:'Every company turns at once — soloists rotating as they travel, couples pivoting on shared centres, the ring breathing wide then narrow, the spiral line reversing its winding.'},
+ solo:{num:'Movement II',title:'Solo Variations',desc:'Single figures spin upon their own axes while gliding along elliptical paths of light.'},
+ couples:{num:'Movement III',title:'Pas de Deux',desc:'Dancers pair off and revolve about the still point held between them as each pair drifts around the star.'},
+ ring:{num:'Movement IV',title:'Breathing Ring',desc:'A circle of dancers expands and contracts in slow succession, widening outward then gathering close.'},
+ spiral:{num:'Movement V',title:'Spiral Procession',desc:'A single coiling line winds inward, unwinds, and reverses direction — never crossing itself.'}
+};
+const elNum=document.getElementById('mvNum'),elTitle=document.getElementById('mvTitle'),
+ elDesc=document.getElementById('mvDesc'),elNow=document.getElementById('nowLine');
+let lastNow='';
+function refreshScore(eAmt){
+ const m=MODES[state.mode];
+ if(elNum.textContent!==m.num){elNum.textContent=m.num;elTitle.textContent=m.title;elDesc.textContent=m.desc;}
+ let now='';
+ if(con && con.alpha>0.15){
+ now+=`<b>${con.label}</b> ${con.phase==='dissolve'?'scatters back into stars':con.phase==='gather'?'assembles from drifting points':'performs its turn'} in ${con.dir}. `;
+ } else { now+='The constellations rest as scattered stars. '; }
+ if(eAmt>0.2) now+='A dark moon crosses the star — an eclipse rings it in prismatic light. ';
+ if(burst) now+='A prism of colour scatters briefly from the star.';
+ if(!now.trim()) now='The ensemble turns in seamless, repeating light.';
+ if(now!==lastNow){elNow.innerHTML=now;lastNow=now;}
+}
+
+const anEl=document.getElementById('announce');
+let anT=0;
+function announce(msg){const n=performance.now();if(n-anT>1200){anEl.textContent=msg;anT=n;}}
+
+// ---- controls ----
+const speed=document.getElementById('speed'),spdVal=document.getElementById('spdVal');
+speed.addEventListener('input',()=>{state.speed=speed.value/100;spdVal.textContent=speed.value+'%';});
+document.getElementById('mode').addEventListener('change',e=>{state.mode=e.target.value;announce(MODES[state.mode].title+' begins.');});
+
+function toggle(id,stId,prop,onWord,offWord){
+ const btn=document.getElementById(id),st=document.getElementById(stId);
+ const apply=()=>{btn.setAttribute('aria-pressed',state[prop]?'true':'false');st.textContent=state[prop]?onWord:offWord;};
+ btn.addEventListener('click',()=>{state[prop]=!state[prop];apply();});
+ apply();return apply;
+}
+const applyTrails=toggle('trails','trailsSt','trails','on','off');
+const applyLabels=toggle('labels','labelsSt','labels','on','off');
+const applyReduce=toggle('reduce','reduceSt','reduced','on','off');
+
+const pauseBtn=document.getElementById('pause'),pauseSt=document.getElementById('pauseSt');
+function applyPause(){pauseBtn.setAttribute('aria-pressed',state.running?'false':'true');pauseSt.textContent=state.running?'pause':'play';}
+pauseBtn.addEventListener('click',()=>{state.running=!state.running;applyPause();});
+applyPause();
+
+function doReset(){
+ state.T=0;state.speed=0.6;state.mode='ensemble';state.trails=!state.reduced;state.labels=true;state.running=true;
+ speed.value=60;spdVal.textContent='60%';document.getElementById('mode').value='ensemble';
+ buildGroups();con=null;orderI=0;spotI=1;nextSpawn=3.5;burst=null;nextBurst=9;
+ if(tctx)tctx.clearRect(0,0,W,H);
+ applyTrails();applyLabels();applyPause();
+ announce('Projection reset to the Grand Ensemble.');
+}
+document.getElementById('reset').addEventListener('click',doReset);
+
+// keyboard
+window.addEventListener('keydown',e=>{
+ const tag=(e.target.tagName||'').toLowerCase();
+ const inForm=tag==='select'||tag==='input';
+ const k=e.key.toLowerCase();
+ if(e.code==='Space' && !inForm){e.preventDefault();state.running=!state.running;applyPause();return;}
+ if(inForm && (k==='arrowleft'||k==='arrowright')) return;
+ if(k==='r'){doReset();}
+ else if(k==='t'){state.trails=!state.trails;applyTrails();}
+ else if(k==='l'){state.labels=!state.labels;applyLabels();}
+ else if(k==='m'){state.reduced=!state.reduced;applyReduce();}
+ else if(k==='arrowright'){speed.value=clamp(+speed.value+10,0,200);speed.dispatchEvent(new Event('input'));}
+ else if(k==='arrowleft'){speed.value=clamp(+speed.value-10,0,200);speed.dispatchEvent(new Event('input'));}
+});
+
+media.addEventListener&&media.addEventListener('change',ev=>{state.reduced=ev.matches;applyReduce();});
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9682a8139b09/claude-code.png b/data/artifacts/9682a8139b09/claude-code.png
new file mode 100644
index 0000000..8a24bd9
Binary files /dev/null and b/data/artifacts/9682a8139b09/claude-code.png differ
diff --git a/data/artifacts/9682a8139b09/qwen25-7b.html b/data/artifacts/9682a8139b09/qwen25-7b.html
new file mode 100644
index 0000000..fb22c06
--- /dev/null
+++ b/data/artifacts/9682a8139b09/qwen25-7b.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Orbit of the Cosmic Dancers</title>
+ <style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ body, html { font-family: 'Didot', serif; background: #12002b; color: #f3e9ff; overflow-x: hidden; }
+ .container { display: grid; place-items: center; height: 100vh; }
+ .gallery { width: 80%; max-width: 640px; padding: 24px; background: #1e0b3d; border-radius: 14px; box-shadow: 0 8px 24px rgba(0,0,0,.12); transition: box-shadow .7s cubic-bezier(.22,1,.36,1) both; }
+ .controls { display: flex; justify-content: space-between; margin-bottom: 24px; }
+ input[type="range"] { width: 80%; height: 5px; background: #9b948a; border-radius: 3px; cursor: pointer; }
+ label { font-size: 12px; letter-spacing: .15em; color: #8d7bb0; }
+ .score { margin-top: 24px; text-align: center; font-size: 16px; }
+ @keyframes fadeUp { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: none; } }
+ </style>
+</head>
+<body>
+ <div class="container">
+ <div id="gallery" class="gallery"></div>
+ <div class="controls">
+ <label>Orbit Speed</label>
+ <input type="range" min="0.1" max="5" step="0.1" value="2" id="orbit-speed">
+ <label>Choreography Mode</label>
+ <input type="checkbox" checked id="choreo-mode">
+ <label>Trails</label>
+ <input type="checkbox" checked id="trail-check">
+ <label Constellation Labels</label>
+ <input type="checkbox" checked id="constell-labels">
+ </div>
+ <div class="score">Orbiting Dancers, Eclipses, and Constellations Dancing in the Stars</div>
+ </div>
+ <script>
+ const gallery = document.getElementById('gallery');
+ const orbitSpeed = document.getElementById('orbit-speed').value;
+ const choreoMode = document.getElementById('choreo-mode').checked;
+ const trailCheck = document.getElementById('trail-check').checked;
+ const constellLabels = document.getElementById('constell-labels').checked;
+
+ function updateGallery() {
+ gallery.innerHTML = `
+ <div style="background: radial-gradient(circle, rgba(255,142,136,0.9) 0%, rgba(255,78,73,0.9) 100%); animation: orbit ${orbitSpeed}s linear infinite; position: relative;">
+ <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) rotate(45deg); width: 200px; height: 100px; background: #ff2e88;"></div>
+ </div>
+ <div style="background: radial-gradient(circle, rgba(236,71,69,0.7) 0%, rgba(154,47,46,0.7) 100%); animation: orbit ${orbitSpeed}s linear infinite; position: relative;">
+ <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) rotate(30deg); width: 200px; height: 100px; background: #b0472e;"></div>
+ </div>
+ `;
+ }
+
+ updateGallery();
+
+ document.getElementById('orbit-speed').addEventListener('input', () => {
+ orbitSpeed = parseFloat(document.getElementById('orbit-speed').value);
+ updateGallery();
+ });
+
+ document.getElementById('choreo-mode').addEventListener('change', () => {
+ choreoMode = document.getElementById('choreo-mode').checked;
+ // Simulate choreography mode logic
+ updateGallery();
+ });
+
+ document.getElementById('trail-check').addEventListener('change', () => {
+ trailCheck = document.getElementById('trail-check').checked;
+ // Simulate trails logic
+ updateGallery();
+ });
+
+ document.getElementById('constell-labels').addEventListener('change', () => {
+ constellLabels = document.getElementById('constell-labels').checked;
+ // Simulate constellation labels logic
+ updateGallery();
+ });
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9682a8139b09/qwen25-7b.png b/data/artifacts/9682a8139b09/qwen25-7b.png
index 30972cb..4d6a61d 100644
Binary files a/data/artifacts/9682a8139b09/qwen25-7b.png and b/data/artifacts/9682a8139b09/qwen25-7b.png differ
diff --git a/data/artifacts/9682a8139b09/qwen3-14b.html b/data/artifacts/9682a8139b09/qwen3-14b.html
new file mode 100644
index 0000000..1f080de
--- /dev/null
+++ b/data/artifacts/9682a8139b09/qwen3-14b.html
@@ -0,0 +1,438 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Orbit of the Cosmic Dancers</title>
+ <style>
+ :root {
+ --bg: #0d0d0f;
+ --surface: #17171c;
+ --text: #f5f2ea;
+ --muted: #8a8578;
+ --accent: #c9a961;
+ --accent2: #7d6a45;
+ --card-radius: 14px;
+ --spacing-4: 4px;
+ --spacing-8: 8px;
+ --spacing-12: 12px;
+ --spacing-16: 16px;
+ --spacing-24: 24px;
+ --spacing-32: 32px;
+ --spacing-48: 48px;
+ --spacing-64: 64px;
+ --spacing-96: 96px;
+ --font-display: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+ --font-body: -apple-system, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
+ --caption: 12px;
+ --body: 16px;
+ --lead: 21px;
+ --h3: 28px;
+ --h2: 38px;
+ --h1: 50px;
+ --display: 67px;
+ }
+
+ * {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+
+ html, body {
+ height: 100%;
+ width: 100%;
+ background: var(--bg);
+ color: var(--text);
+ font-family: var(--font-body);
+ overflow: hidden;
+ }
+
+ body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ }
+
+ canvas {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 0;
+ touch-action: none;
+ }
+
+ .score-panel {
+ position: absolute;
+ top: var(--spacing-24);
+ left: var(--spacing-24);
+ background: var(--surface);
+ padding: var(--spacing-16);
+ border-radius: var(--card-radius);
+ box-shadow: 0 8px 24px rgba(0,0,0,.12);
+ font-family: var(--font-display);
+ font-size: var(--h3);
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ color: var(--muted);
+ z-index: 1;
+ }
+
+ .controls {
+ position: absolute;
+ bottom: var(--spacing-24);
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1;
+ font-family: var(--font-body);
+ font-size: var(--body);
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--spacing-16);
+ }
+
+ .control {
+ background: var(--surface);
+ padding: var(--spacing-8) var(--spacing-16);
+ border-radius: var(--card-radius);
+ cursor: pointer;
+ transition: background 150ms ease-in-out;
+ }
+
+ .control:hover {
+ background: var(--muted);
+ }
+
+ .control:active {
+ background: var(--accent2);
+ }
+
+ .control:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px var(--accent);
+ }
+
+ @keyframes drift {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 100% {
+ background-position: 100% 50%;
+ }
+ }
+
+ .bg-ambient {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(45deg, #1f003a, #2e004c, #45005c, #5a006b, #6e007a, #820089, #960098, #aa00a7, #bd00b6, #d000c5, #e400d4, #f700e3);
+ background-size: 800% 800%;
+ animation: drift 18s linear infinite;
+ z-index: -1;
+ }
+ </style>
+</head>
+<body>
+ <div class="bg-ambient"></div>
+ <canvas id="canvas"></canvas>
+ <div class="score-panel" id="scorePanel">Orbit Speed: Normal | Choreography: Free | Trails: On | Constellations: On</div>
+ <div class="controls">
+ <div class="control" data-action="orbitSpeed" data-value="slow">Slow Orbit</div>
+ <div class="control" data-action="orbitSpeed" data-value="normal">Normal Orbit</div>
+ <div class="control" data-action="orbitSpeed" data-value="fast">Fast Orbit</div>
+ <div class="control" data-action="choreography" data-value="free">Free</div>
+ <div class="control" data-action="choreography" data-value="couple">Couple</div>
+ <div class="control" data-action="choreography" data-value="ring">Ring</div>
+ <div class="control" data-action="trails" data-value="toggle">Toggle Trails</div>
+ <div class="control" data-action="constellations" data-value="toggle">Toggle Constellations</div>
+ <div class="control" data-action="labels" data-value="toggle">Toggle Labels</div>
+ <div class="control" data-action="pause" data-value="toggle">Pause</div>
+ <div class="control" data-action="reset">Reset</div>
+ <div class="control" data-action="reducedMotion" data-value="toggle">Reduced Motion</div>
+ </div>
+
+ <script>
+ const canvas = document.getElementById('canvas');
+ const ctx = canvas.getContext('2d');
+ const scorePanel = document.getElementById('scorePanel');
+
+ let orbitSpeed = 1;
+ let choreographyMode = 'free';
+ let trailsEnabled = true;
+ let constellationsEnabled = true;
+ let labelsEnabled = false;
+ let isPaused = false;
+ let reducedMotion = false;
+
+ let dancers = [];
+ let constellations = [];
+
+ const star = {
+ x: canvas.width / 2,
+ y: canvas.height / 2,
+ radius: 40,
+ color: 'rgba(201, 169, 97, 0.9)',
+ };
+
+ const constellationData = {
+ bear: { points: [[0, -10], [3, -5], [5, 0], [3, 5], [0, 10], [-3, 5], [-5, 0], [-3, -5]], color: 'rgba(255, 46, 136, 0.8)', name: 'Bear' },
+ turtle: { points: [[0, -10], [3, -5], [5, 0], [3, 5], [0, 10], [-3, 5], [-5, 0], [-3, -5], [0, -5], [2, -7], [-2, -7]], color: 'rgba(0, 229, 255, 0.8)', name: 'Turtle' },
+ hare: { points: [[-5, -5], [-10, 0], [-5, 5], [5, 5], [10, 0], [5, -5], [0, -10]], color: 'rgba(255, 149, 0, 0.8)', name: 'Hare' },
+ fox: { points: [[-5, -5], [-10, 0], [-5, 5], [5, 5], [10, 0], [5, -5], [0, -10], [0, -15]], color: 'rgba(255, 87, 34, 0.8)', name: 'Fox' },
+ birds: { points: [[-5, 5], [-10, 0], [-5, -5], [5, -5], [10, 0], [5, 5], [0, 10], [0, 15], [-5, 10], [5, 10]], color: 'rgba(0, 255, 255, 0.8)', name: 'Birds' },
+ };
+
+ function createDancer(x, y, radius, color, orbit, angle, speed, type) {
+ return {
+ x, y, radius, color, orbit, angle, speed, type,
+ trail: [],
+ update() {
+ this.angle += this.speed * orbitSpeed;
+ this.x = star.x + Math.cos(this.angle) * orbit;
+ this.y = star.y + Math.sin(this.angle) * orbit;
+ if (trailsEnabled) {
+ this.trail.push({ x: this.x, y: this.y });
+ if (this.trail.length > 50) this.trail.shift();
+ }
+ },
+ draw() {
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
+ ctx.fillStyle = this.color;
+ ctx.fill();
+ if (trailsEnabled) {
+ ctx.strokeStyle = this.color;
+ ctx.lineWidth = 1;
+ ctx.globalAlpha = 0.3;
+ for (let i = 0; i < this.trail.length - 1; i++) {
+ ctx.beginPath();
+ ctx.moveTo(this.trail[i].x, this.trail[i].y);
+ ctx.lineTo(this.trail[i + 1].x, this.trail[i + 1].y);
+ ctx.stroke();
+ }
+ ctx.globalAlpha = 1;
+ }
+ }
+ };
+ }
+
+ function createConstellation(name, points, color, orbit, angle, speed) {
+ return {
+ name,
+ points,
+ color,
+ orbit,
+ angle,
+ speed,
+ active: false,
+ draw() {
+ if (!this.active) return;
+ ctx.strokeStyle = this.color;
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ for (let i = 0; i < this.points.length; i++) {
+ const [px, py] = this.points[i];
+ const x = this.orbit * Math.cos(this.angle + px * 0.1);
+ const y = this.orbit * Math.sin(this.angle + py * 0.1);
+ if (i === 0) ctx.moveTo(x + star.x, y + star.y);
+ else ctx.lineTo(x + star.x, y + star.y);
+ }
+ ctx.stroke();
+ if (labelsEnabled) {
+ ctx.fillStyle = this.color;
+ ctx.font = '12px sans-serif';
+ ctx.textAlign = 'center';
+ ctx.fillText(this.name, star.x, star.y - this.orbit - 10);
+ }
+ },
+ update() {
+ this.angle += this.speed * orbitSpeed;
+ this.active = Math.sin(this.angle) > 0.5;
+ }
+ };
+ }
+
+ function initDancers() {
+ dancers = [];
+ const numDancers = 30;
+ for (let i = 0; i < numDancers; i++) {
+ const orbit = Math.random() * 200 + 100;
+ const angle = Math.random() * Math.PI * 2;
+ const speed = Math.random() * 0.02 + 0.01;
+ dancers.push(createDancer(
+ star.x + Math.cos(angle) * orbit,
+ star.y + Math.sin(angle) * orbit,
+ 4,
+ 'rgba(201, 169, 97, 0.7)',
+ orbit,
+ angle,
+ speed,
+ 'individual'
+ ));
+ }
+
+ // Couples
+ for (let i = 0; i < 8; i++) {
+ const orbit = Math.random() * 150 + 100;
+ const angle = Math.random() * Math.PI * 2;
+ const speed = Math.random() * 0.015 + 0.005;
+ dancers.push(createDancer(
+ star.x + Math.cos(angle) * orbit,
+ star.y + Math.sin(angle) * orbit,
+ 4,
+ 'rgba(255, 46, 136, 0.7)',
+ orbit,
+ angle,
+ speed,
+ 'couple'
+ ));
+ dancers.push(createDancer(
+ star.x + Math.cos(angle + Math.PI) * orbit,
+ star.y + Math.sin(angle + Math.PI) * orbit,
+ 4,
+ 'rgba(0, 229, 255, 0.7)',
+ orbit,
+ angle + Math.PI,
+ speed,
+ 'couple'
+ ));
+ }
+
+ // Ring
+ for (let i = 0; i < 12; i++) {
+ const orbit = 250 + Math.sin(i * 0.5) * 30;
+ const angle = i * (Math.PI * 2) / 12;
+ const speed = Math.random() * 0.01 + 0.005;
+ dancers.push(createDancer(
+ star.x + Math.cos(angle) * orbit,
+ star.y + Math.sin(angle) * orbit,
+ 4,
+ 'rgba(255, 149, 0, 0.7)',
+ orbit,
+ angle,
+ speed,
+ 'ring'
+ ));
+ }
+
+ // Spiral
+ for (let i = 0; i < 10; i++) {
+ const orbit = 200 + i * 10;
+ const angle = (Math.sin(i * 0.5) + 1) * Math.PI;
+ const speed = Math.random() * 0.01 + 0.005;
+ dancers.push(createDancer(
+ star.x + Math.cos(angle) * orbit,
+ star.y + Math.sin(angle) * orbit,
+ 4,
+ 'rgba(0, 255, 255, 0.7)',
+ orbit,
+ angle,
+ speed,
+ 'spiral'
+ ));
+ }
+ }
+
+ function initConstellations() {
+ constellations = [];
+ for (const [name, data] of Object.entries(constellationData)) {
+ const orbit = 300 + Math.random() * 50;
+ const angle = Math.random() * Math.PI * 2;
+ const speed = Math.random() * 0.005 + 0.001;
+ constellations.push(createConstellation(name, data.points, data.color, orbit, angle, speed));
+ }
+ }
+
+ function drawStar() {
+ ctx.beginPath();
+ ctx.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
+ ctx.fillStyle = star.color;
+ ctx.fill();
+ }
+
+ function draw() {
+ if (isPaused) return;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ drawStar();
+
+ dancers.forEach(d => {
+ d.update();
+ d.draw();
+ });
+
+ if (constellationsEnabled) {
+ constellations.forEach(c => {
+ c.update();
+ c.draw();
+ });
+ }
+ }
+
+ function resize() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ }
+
+ window.addEventListener('resize', resize);
+ resize();
+
+ initDancers();
+ initConstellations();
+
+ function updateScorePanel() {
+ scorePanel.textContent = `Orbit Speed: ${orbitSpeed === 1 ? 'Normal' : orbitSpeed === 0.5 ? 'Slow' : 'Fast'} | Choreography: ${choreographyMode} | Trails: ${trailsEnabled ? 'On' : 'Off'} | Constellations: ${constellationsEnabled ? 'On' : 'Off'}`;
+ }
+
+ document.querySelectorAll('.control').forEach(el => {
+ el.addEventListener('click', function() {
+ const action = this.getAttribute('data-action');
+ const value = this.getAttribute('data-value');
+
+ if (action === 'orbitSpeed') {
+ if (value === 'slow') orbitSpeed = 0.5;
+ else if (value === 'normal') orbitSpeed = 1;
+ else if (value === 'fast') orbitSpeed = 2;
+ } else if (action === 'choreography') {
+ choreographyMode = value;
+ } else if (action === 'trails') {
+ trailsEnabled = !trailsEnabled;
+ } else if (action === 'constellations') {
+ constellationsEnabled = !constellationsEnabled;
+ } else if (action === 'labels') {
+ labelsEnabled = !labelsEnabled;
+ } else if (action === 'pause') {
+ isPaused = !isPaused;
+ } else if (action === 'reset') {
+ initDancers();
+ initConstellations();
+ } else if (action === 'reducedMotion') {
+ reducedMotion = !reducedMotion;
+ if (reducedMotion) {
+ orbitSpeed = 1;
+ choreographyMode = 'free';
+ trailsEnabled = false;
+ constellationsEnabled = false;
+ labelsEnabled = false;
+ isPaused = false;
+ }
+ }
+
+ updateScorePanel();
+ });
+ });
+
+ function animate() {
+ draw();
+ requestAnimationFrame(animate);
+ }
+
+ animate();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/9682a8139b09/qwen3-14b.png b/data/artifacts/9682a8139b09/qwen3-14b.png
index 5a7973f..bb63050 100644
Binary files a/data/artifacts/9682a8139b09/qwen3-14b.png and b/data/artifacts/9682a8139b09/qwen3-14b.png differ
diff --git a/data/artifacts/c5c20cf0ae13/claude-code.html b/data/artifacts/c5c20cf0ae13/claude-code.html
new file mode 100644
index 0000000..079b01b
--- /dev/null
+++ b/data/artifacts/c5c20cf0ae13/claude-code.html
@@ -0,0 +1,896 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Sunwheel Meadow Dance</title>
+<style>
+ :root{
+ --bg:#0d0b14; --surface:rgba(24,18,34,.72); --text:#f6efe4; --muted:#b3a68f;
+ --accent:#e0a458; --accent2:#c65f38; --line:rgba(246,239,228,.16);
+ --serif:'Didot','Bodoni MT','Playfair Display',Georgia,serif;
+ --sans:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
+ }
+ *{box-sizing:border-box}
+ html,body{height:100%;margin:0}
+ body{
+ background:var(--bg); color:var(--text); font-family:var(--sans);
+ overflow:hidden; -webkit-font-smoothing:antialiased;
+ }
+ #stage{position:fixed; inset:0}
+ canvas{position:absolute; inset:0; width:100%; height:100%; display:block}
+
+ /* ---- Title + rhythm ---- */
+ .titlewrap{
+ position:absolute; top:clamp(16px,3vw,40px); left:clamp(16px,3vw,44px);
+ z-index:5; max-width:min(60vw,520px);
+ animation:fadeUp .9s cubic-bezier(.22,1,.36,1) both;
+ text-shadow:0 2px 18px rgba(0,0,0,.55);
+ pointer-events:none;
+ }
+ .titlewrap h1{
+ font-family:var(--serif); font-weight:500; letter-spacing:.01em;
+ font-size:clamp(30px,5.4vw,58px); line-height:1.02; margin:0;
+ }
+ .titlewrap .sub{
+ margin:.5em 0 0; color:var(--text); opacity:.82;
+ font-style:italic; font-family:var(--serif); font-size:clamp(14px,1.7vw,20px);
+ }
+ .rhythm{
+ margin-top:16px; display:flex; align-items:center; gap:12px;
+ pointer-events:none;
+ }
+ .rlabel{
+ font-size:12px; letter-spacing:.15em; text-transform:uppercase; color:var(--muted);
+ }
+ .bars{display:flex; align-items:flex-end; gap:3px; height:26px}
+ .bars i{
+ width:4px; height:6px; background:linear-gradient(180deg,var(--accent),var(--accent2));
+ border-radius:2px; display:block; box-shadow:0 0 10px rgba(224,164,88,.4);
+ transition:height .06s linear, opacity .06s linear;
+ }
+ .rnote{
+ margin-top:6px; font-size:11px; color:var(--muted); letter-spacing:.02em; max-width:340px;
+ }
+
+ /* ---- Controls ---- */
+ .panel{
+ position:absolute; left:50%; bottom:clamp(14px,2.6vw,28px); transform:translateX(-50%);
+ z-index:6; display:flex; flex-wrap:wrap; align-items:center; justify-content:center;
+ gap:clamp(10px,1.6vw,22px); padding:14px clamp(16px,2vw,26px);
+ background:var(--surface); backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
+ border:1px solid var(--line); border-radius:16px;
+ box-shadow:0 16px 48px rgba(0,0,0,.42);
+ max-width:min(96vw,940px);
+ animation:fadeUp .9s .1s cubic-bezier(.22,1,.36,1) both;
+ }
+ .ctrl{display:flex; flex-direction:column; gap:6px; min-width:96px}
+ .ctrl label,.seg-label{
+ font-size:12px; letter-spacing:.15em; text-transform:uppercase; color:var(--muted);
+ }
+ .ctrl input[type=range]{
+ -webkit-appearance:none; appearance:none; width:100%; height:4px; border-radius:4px;
+ background:linear-gradient(90deg,var(--accent2),var(--accent)); outline:none; cursor:pointer;
+ }
+ .ctrl input[type=range]::-webkit-slider-thumb{
+ -webkit-appearance:none; width:16px; height:16px; border-radius:50%;
+ background:var(--text); border:2px solid var(--accent); box-shadow:0 0 10px rgba(224,164,88,.5); cursor:pointer;
+ }
+ .ctrl input[type=range]::-moz-range-thumb{
+ width:15px; height:15px; border-radius:50%; background:var(--text);
+ border:2px solid var(--accent); box-shadow:0 0 10px rgba(224,164,88,.5); cursor:pointer;
+ }
+ .valrow{display:flex; justify-content:space-between; font-size:11px; color:var(--text); opacity:.7}
+ button.btn{
+ font-family:var(--sans); font-size:13px; letter-spacing:.06em;
+ color:var(--text); background:rgba(246,239,228,.06); border:1px solid var(--line);
+ padding:10px 16px; border-radius:12px; cursor:pointer; min-width:104px;
+ transition:background .18s ease, border-color .18s ease, transform .12s ease;
+ }
+ button.btn:hover{background:rgba(246,239,228,.13)}
+ button.btn:active{transform:translateY(1px)}
+ button.btn[aria-pressed=true]{
+ background:linear-gradient(180deg,rgba(224,164,88,.28),rgba(198,95,56,.24));
+ border-color:var(--accent);
+ }
+ .toggles{display:flex; flex-direction:column; gap:8px}
+ :focus-visible{outline:2px solid var(--accent); outline-offset:3px; border-radius:8px}
+ .sr-only{position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); border:0}
+ .hint{
+ position:absolute; right:clamp(14px,2vw,26px); bottom:6px; z-index:5;
+ font-size:11px; color:var(--muted); letter-spacing:.04em; pointer-events:none; opacity:.75;
+ }
+ @keyframes fadeUp{from{opacity:0; transform:translateY(28px)} to{opacity:1; transform:none}}
+ @media (max-width:640px){
+ .panel{gap:12px; padding:12px}
+ .ctrl{min-width:76px}
+ button.btn{min-width:0; padding:9px 12px}
+ .titlewrap{max-width:78vw}
+ }
+</style>
+</head>
+<body>
+<div id="stage">
+ <canvas id="c" role="img"
+ aria-label="An animated twilight meadow where diverse stylized figures dance in concentric circles around a glowing central sunwheel, surrounded by rabbits, foxes, owls, and butterflies. A generated visual composition, not derived from any audio."></canvas>
+
+ <div class="titlewrap">
+ <h1>Sunwheel Meadow Dance</h1>
+ <p class="sub">a twilight round for roses, spirals & spectral light</p>
+ <div class="rhythm" aria-hidden="true">
+ <span class="rlabel">Visual Rhythm</span>
+ <div class="bars" id="bars"></div>
+ </div>
+ <p class="rnote">A generated visual cadence that drives the choreography — this artwork does not listen to, record, or analyze any audio.</p>
+ </div>
+
+ <div class="panel" role="group" aria-label="Artwork controls">
+ <button id="play" class="btn" aria-pressed="true">⏸ Pause</button>
+
+ <div class="ctrl">
+ <label for="tempo">Tempo</label>
+ <input id="tempo" type="range" min="0.4" max="1.9" step="0.05" value="1" aria-describedby="tempoval">
+ <div class="valrow"><span>slow</span><span id="tempoval">1.00×</span></div>
+ </div>
+
+ <div class="ctrl">
+ <label for="density">Crowd Density</label>
+ <input id="density" type="range" min="0.6" max="1.9" step="0.1" value="1.1" aria-describedby="densval">
+ <div class="valrow"><span>sparse</span><span id="densval">full</span></div>
+ </div>
+
+ <div class="ctrl">
+ <label for="night">Sunset → Night</label>
+ <input id="night" type="range" min="0" max="1" step="0.02" value="0.5" aria-describedby="nightval">
+ <div class="valrow"><span>dusk</span><span id="nightval">evening</span></div>
+ </div>
+
+ <div class="toggles">
+ <button id="reduce" class="btn" aria-pressed="false">Reduced Motion</button>
+ </div>
+ </div>
+
+ <p class="hint">Space = play/pause · M = reduced motion</p>
+</div>
+
+<script>
+(() => {
+"use strict";
+const canvas = document.getElementById('c');
+const ctx = canvas.getContext('2d');
+const TAU = Math.PI*2;
+const BASE_BPS = 0.82; // baseline "beats" per second
+const SQUASH = 0.52; // ground-plane ellipse squash for the rings
+
+let W=0,H=0,DPR=1;
+let cx=0, cy=0, maxR=0, unit=1;
+
+const state = {
+ playing:true, tempo:1, density:1.1, night:0.5, reduced:false,
+ beat:0, t:0
+};
+
+/* deterministic pseudo-random so layout is stable across resizes */
+function rnd(i){ const x=Math.sin(i*127.1+311.7)*43758.5453; return x-Math.floor(x); }
+const lerp=(a,b,t)=>a+(b-a)*t;
+const clamp=(v,a,b)=>v<a?a:v>b?b:v;
+const easeIO=t=>0.5-0.5*Math.cos(clamp(t,0,1)*Math.PI);
+
+/* ------------------------------------------------------------------ */
+/* Scene population */
+/* ------------------------------------------------------------------ */
+let dancers=[], butterflies=[], fireflies=[], rabbits=[], foxes=[], owls=[], flowers=[];
+
+function build(){
+ const rect = canvas.getBoundingClientRect();
+ W=rect.width; H=rect.height;
+ DPR=Math.min(2, window.devicePixelRatio||1);
+ canvas.width=Math.round(W*DPR); canvas.height=Math.round(H*DPR);
+ ctx.setTransform(DPR,0,0,DPR,0,0);
+
+ cx=W*0.5; cy=H*0.54;
+ maxR=Math.min(W,H)*0.40;
+ unit=Math.min(W,H)/860;
+
+ buildDancers();
+ buildFlora();
+ buildCreatures();
+}
+
+function buildDancers(){
+ dancers=[];
+ const ringFrac=[1.0,0.79,0.585,0.36];
+ const speed =[0.052,0.062,0.078,0.10];
+ let id=0;
+ for(let ri=0; ri<ringFrac.length; ri++){
+ const radius = maxR*ringFrac[ri];
+ const dir = (ri%2===0)? 1 : -1; // outer clockwise, then alternate
+ const figW = 30*unit;
+ const spacing = (figW*1.55)/state.density;
+ const count = Math.max(5, Math.floor((TAU*radius)/spacing));
+ for(let s=0; s<count; s++){
+ const seed=ri*911+s*57+7;
+ const baseAngle = s/count*TAU;
+ const gap = TAU/count;
+ const pairRole = (s%4<2) ? ((s%2)===0?1:-1) : 0; // pair up two of every four slots
+ dancers.push({
+ id:id++, ri, radius, dir, count, baseAngle,
+ stepAngle:speed[ri]*dir,
+ off:rnd(seed),
+ hue:( (s/count)*300 + ri*38 + rnd(seed)*20 )%360,
+ accentHue:( (s/count)*300 + ri*38 + 150 )%360,
+ style: rnd(seed+3)<0.5 ? 'skirt':'tunic',
+ spinEvery: 7 + Math.floor(rnd(seed+5)*8),
+ spinOff: rnd(seed+9)*10,
+ impEvery: 9 + Math.floor(rnd(seed+11)*10),
+ impOff: rnd(seed+13)*12,
+ pairSign: pairRole,
+ pairPhase: rnd(seed+2)*TAU,
+ tradeAmp: gap*0.5*Math.abs(pairRole),
+ bright: 0.85+rnd(seed+17)*0.3
+ });
+ }
+ }
+}
+
+function buildFlora(){
+ flowers=[];
+ const n=Math.round(26*Math.min(1.4,(W*H)/(1280*720)));
+ for(let i=0;i<n;i++){
+ const seed=i*13+3;
+ const ang=rnd(seed)*TAU;
+ // scatter across meadow, biased to foreground
+ const rr = maxR*(0.5+rnd(seed+1)*1.25);
+ const x = cx + Math.cos(ang)*rr*(0.8+rnd(seed+7)*0.5);
+ const y = cy + Math.sin(ang)*rr*SQUASH + rnd(seed+2)*40*unit;
+ if(y < cy-maxR*0.6) continue;
+ flowers.push({
+ x, y, r:(3+rnd(seed+4)*4)*unit,
+ hue:(rnd(seed+5)*360),
+ kind: rnd(seed+6)<0.62?'rose':'star',
+ ph:rnd(seed+8)*TAU
+ });
+ }
+}
+
+function buildCreatures(){
+ butterflies=[]; fireflies=[]; rabbits=[]; foxes=[]; owls=[];
+ for(let i=0;i<6;i++){
+ const seed=i*29+5;
+ butterflies.push({
+ cxr:cx+(rnd(seed)-0.5)*W*0.7,
+ cyr:cy+(rnd(seed+1)-0.5)*H*0.45,
+ ax:(60+rnd(seed+2)*130)*unit, ay:(40+rnd(seed+3)*90)*unit,
+ sp:0.25+rnd(seed+4)*0.4, ph:rnd(seed+5)*TAU,
+ hue:rnd(seed+6)*360, sz:(6+rnd(seed+7)*5)*unit
+ });
+ }
+ const fn=Math.round(46*Math.min(1.5,(W*H)/(1280*720)));
+ for(let i=0;i<fn;i++){
+ const seed=i*7+1;
+ fireflies.push({x:rnd(seed)*W,y:cy-maxR*0.4+rnd(seed+1)*(H-(cy-maxR*0.4)),
+ ph:rnd(seed+2)*TAU, sp:0.3+rnd(seed+3)*0.6, drift:(rnd(seed+4)-0.5)*10*unit,
+ hue:40+rnd(seed+5)*40});
+ }
+ // rabbits hop along the front meadow
+ for(let i=0;i<3;i++){
+ const seed=i*41+9;
+ rabbits.push({baseY:cy+maxR*(0.62+i*0.16)*SQUASH+30*unit,
+ x0:W*(0.15+rnd(seed)*0.6), span:W*0.28, sp:0.5+rnd(seed+1)*0.4,
+ ph:rnd(seed+2)*TAU, dir:rnd(seed+3)<0.5?1:-1, sz:unit*(0.9+rnd(seed+4)*0.3)});
+ }
+ // foxes turn and bow at the meadow edges
+ foxes.push({x:W*0.16, y:cy+maxR*0.86*SQUASH+34*unit, ph:0.0, sz:unit*1.15, face:1});
+ foxes.push({x:W*0.86, y:cy+maxR*0.72*SQUASH+18*unit, ph:2.4, sz:unit*1.0, face:-1});
+ // owls blink from tree branches (upper corners)
+ owls.push({x:W*0.085, y:H*0.30, sz:unit*1.1, ph:0.6, side:1});
+ owls.push({x:W*0.915, y:H*0.24, sz:unit*0.95, ph:2.2, side:-1});
+}
+
+/* ------------------------------------------------------------------ */
+/* Background */
+/* ------------------------------------------------------------------ */
+function mix(c1,c2,t){
+ return [Math.round(lerp(c1[0],c2[0],t)),Math.round(lerp(c1[1],c2[1],t)),Math.round(lerp(c1[2],c2[2],t))];
+}
+const rgb=a=>`rgb(${a[0]},${a[1]},${a[2]})`;
+
+function drawSky(){
+ const n=state.night;
+ // top colour: dusk violet -> deep night indigo
+ const top = mix([58,36,72],[8,9,26], n);
+ const midC = mix([150,78,86],[24,20,54], n);
+ const horiz = mix([232,150,86],[54,40,78], n);
+ const g=ctx.createLinearGradient(0,0,0,cy+maxR*0.4);
+ g.addColorStop(0,rgb(top));
+ g.addColorStop(0.55,rgb(midC));
+ g.addColorStop(1,rgb(horiz));
+ ctx.fillStyle=g; ctx.fillRect(0,0,W,cy+maxR*0.5);
+
+ // stars (grow with night)
+ const starA=clamp((n-0.15)/0.7,0,1);
+ if(starA>0){
+ for(let i=0;i<130;i++){
+ const s=i*3.3+1;
+ const x=rnd(s)*W, y=rnd(s+1)*(cy-maxR*0.2);
+ const tw=0.5+0.5*Math.sin(state.t*1.5+rnd(s+2)*TAU);
+ ctx.globalAlpha=starA*(0.3+0.7*tw)*(0.5+rnd(s+3)*0.5);
+ ctx.fillStyle= rnd(s+4)<0.15 ? '#ffe9c2' : '#f4f0ff';
+ const r=(rnd(s+5)*1.3+0.3);
+ ctx.fillRect(x,y,r,r);
+ }
+ ctx.globalAlpha=1;
+ }
+
+ // sun / moon orb near horizon
+ const orbY = lerp(cy-maxR*0.16, H*0.14, n);
+ const orbX = lerp(W*0.5, W*0.72, n);
+ const orbR = lerp(70,42,n)*unit*1.1;
+ const orbCol = mix([255,214,150],[226,224,244], n);
+ const halo=ctx.createRadialGradient(orbX,orbY,0,orbX,orbY,orbR*5);
+ halo.addColorStop(0,`rgba(${orbCol[0]},${orbCol[1]},${orbCol[2]},${lerp(0.5,0.32,n)})`);
+ halo.addColorStop(1,'rgba(0,0,0,0)');
+ ctx.fillStyle=halo; ctx.fillRect(0,0,W,cy+maxR*0.4);
+ ctx.beginPath(); ctx.arc(orbX,orbY,orbR,0,TAU);
+ ctx.fillStyle=rgb(orbCol); ctx.fill();
+ if(n>0.55){ // moon craters
+ ctx.fillStyle='rgba(150,150,190,.35)';
+ for(let k=0;k<4;k++){const s=k*5+2;ctx.beginPath();
+ ctx.arc(orbX+(rnd(s)-0.5)*orbR,orbY+(rnd(s+1)-0.5)*orbR,orbR*(0.1+rnd(s+2)*0.14),0,TAU);ctx.fill();}
+ }
+
+ // layered distant hills
+ const hills=[
+ {y:cy-maxR*0.05, c:mix([70,52,78],[16,18,44],n), amp:26*unit},
+ {y:cy+maxR*0.10, c:mix([52,44,70],[10,13,36],n), amp:40*unit}
+ ];
+ hills.forEach((h,idx)=>{
+ ctx.beginPath(); ctx.moveTo(0,H);
+ for(let x=0;x<=W;x+=14){
+ const y=h.y + Math.sin(x*0.006+idx*2)*h.amp + Math.sin(x*0.017+idx)*h.amp*0.4;
+ ctx.lineTo(x,y);
+ }
+ ctx.lineTo(W,H); ctx.closePath(); ctx.fillStyle=rgb(h.c); ctx.fill();
+ });
+}
+
+function drawGround(){
+ const n=state.night;
+ const gTop=mix([46,58,40],[14,20,26],n);
+ const gBot=mix([24,32,22],[6,10,16],n);
+ const g=ctx.createLinearGradient(0,cy-maxR*0.1,0,H);
+ g.addColorStop(0,rgb(gTop)); g.addColorStop(1,rgb(gBot));
+ ctx.fillStyle=g; ctx.fillRect(0,cy-maxR*0.12,W,H-(cy-maxR*0.12));
+
+ // grass fleck texture
+ ctx.strokeStyle=`rgba(${lerp(120,40,n)|0},${lerp(150,70,n)|0},${lerp(90,50,n)|0},.16)`;
+ ctx.lineWidth=1;
+ for(let i=0;i<170;i++){
+ const s=i*2.7+11;
+ const x=rnd(s)*W;
+ const y=cy-maxR*0.1 + rnd(s+1)*(H-(cy-maxR*0.1));
+ const h=(2+rnd(s+2)*5)*unit*(0.6+(y-cy)/H);
+ ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(x+(rnd(s+3)-0.5)*3,y-h); ctx.stroke();
+ }
+}
+
+/* roses & stars scattered in the meadow */
+function drawFlowers(){
+ const n=state.night;
+ flowers.forEach(f=>{
+ const pulse=0.6+0.4*Math.sin(state.beat*Math.PI+f.ph);
+ if(f.kind==='rose'){
+ for(let p=3;p>=1;p--){
+ ctx.beginPath();
+ ctx.fillStyle=`hsla(${f.hue},${60}%,${lerp(64,54,n)}%,${0.85})`;
+ ctx.arc(f.x,f.y,f.r*(p/3),0,TAU); ctx.fill();
+ ctx.fillStyle=`hsla(${(f.hue+20)%360},70%,${lerp(72,58,n)}%,.9)`;
+ ctx.beginPath(); ctx.arc(f.x-f.r*0.12*p,f.y-f.r*0.12*p,f.r*(p/3)*0.55,0,TAU); ctx.fill();
+ }
+ } else {
+ star(f.x,f.y,f.r*1.2,f.r*0.5,5, -Math.PI/2);
+ ctx.fillStyle=`hsla(${f.hue},80%,${lerp(80,66,n)}%,${0.55+0.35*pulse})`;
+ ctx.fill();
+ }
+ // soft glow at night
+ if(n>0.4){
+ ctx.beginPath(); ctx.arc(f.x,f.y,f.r*2.4,0,TAU);
+ ctx.fillStyle=`hsla(${f.hue},80%,70%,${0.06*(n)*pulse})`; ctx.fill();
+ }
+ });
+}
+
+function star(x,y,ro,ri,pts,rot){
+ ctx.beginPath();
+ for(let i=0;i<pts*2;i++){
+ const r=i%2?ri:ro; const a=rot+i*Math.PI/pts;
+ const px=x+Math.cos(a)*r, py=y+Math.sin(a)*r;
+ i?ctx.lineTo(px,py):ctx.moveTo(px,py);
+ }
+ ctx.closePath();
+}
+
+/* ------------------------------------------------------------------ */
+/* Central sunwheel — original abstract emblem */
+/* ------------------------------------------------------------------ */
+function drawSunwheel(){
+ const beat=state.beat, n=state.night;
+ const pulse=1+0.035*Math.sin(beat*Math.PI);
+ const R=maxR*0.19*pulse;
+ ctx.save(); ctx.translate(cx,cy);
+
+ // outer spectral glow
+ const glow=ctx.createRadialGradient(0,0,R*0.2,0,0,R*3.4);
+ const gi=0.55+0.2*Math.sin(beat*Math.PI);
+ glow.addColorStop(0,`rgba(255,224,150,${0.5*gi})`);
+ glow.addColorStop(0.35,`rgba(232,120,70,${0.28*gi})`);
+ glow.addColorStop(0.7,`rgba(120,70,180,${0.14*gi})`);
+ glow.addColorStop(1,'rgba(0,0,0,0)');
+ ctx.fillStyle=glow;
+ ctx.beginPath(); ctx.arc(0,0,R*3.4,0,TAU); ctx.fill();
+
+ // rotating spectral rays
+ const rays=14;
+ ctx.rotate(beat*0.14);
+ for(let i=0;i<rays;i++){
+ const a=i/rays*TAU;
+ const hue=(i/rays*360 + beat*30)%360;
+ ctx.save(); ctx.rotate(a);
+ ctx.beginPath();
+ ctx.moveTo(0,-R*0.55);
+ ctx.lineTo(R*0.12,-R*2.3);
+ ctx.lineTo(-R*0.12,-R*2.3);
+ ctx.closePath();
+ ctx.fillStyle=`hsla(${hue},85%,${lerp(66,58,n)}%,.5)`;
+ ctx.fill();
+ ctx.restore();
+ }
+ ctx.rotate(-beat*0.14);
+
+ // concentric spectral ring
+ ctx.lineWidth=R*0.09;
+ for(let i=0;i<48;i++){
+ const a0=i/48*TAU, a1=(i+1)/48*TAU;
+ ctx.beginPath(); ctx.arc(0,0,R*1.15,a0,a1);
+ ctx.strokeStyle=`hsl(${(i/48*360+beat*40)%360},80%,60%)`; ctx.stroke();
+ }
+
+ // inner disc
+ const disc=ctx.createRadialGradient(0,-R*0.2,0,0,0,R);
+ disc.addColorStop(0,'#fff4d6'); disc.addColorStop(0.6,'#f0b45a'); disc.addColorStop(1,'#c65f38');
+ ctx.fillStyle=disc; ctx.beginPath(); ctx.arc(0,0,R*0.82,0,TAU); ctx.fill();
+
+ // rose medallion (layered petals) + star — the abstract sun-rose emblem
+ ctx.save(); ctx.rotate(-beat*0.09);
+ for(let layer=0; layer<3; layer++){
+ const petals=6+layer*2;
+ const pr=R*(0.7-layer*0.18);
+ for(let i=0;i<petals;i++){
+ const a=i/petals*TAU + layer*0.4;
+ ctx.save(); ctx.rotate(a);
+ ctx.beginPath();
+ ctx.ellipse(0,-pr*0.5,pr*0.26,pr*0.5,0,0,TAU);
+ ctx.fillStyle=`hsla(${(layer*40+beat*20)%360},70%,${68-layer*6}%,.55)`;
+ ctx.fill(); ctx.restore();
+ }
+ }
+ ctx.restore();
+
+ // central star + core
+ star(0,0,R*0.42,R*0.17,8,-Math.PI/2 + beat*0.2);
+ ctx.fillStyle='#fff8e6'; ctx.fill();
+ ctx.beginPath(); ctx.arc(0,0,R*0.14,0,TAU);
+ ctx.fillStyle='#fffdf5'; ctx.fill();
+
+ ctx.restore();
+}
+
+/* ------------------------------------------------------------------ */
+/* Dancer */
+/* ------------------------------------------------------------------ */
+function computeDancer(d){
+ const beat=state.beat;
+ const fb=beat-Math.floor(beat);
+ const stepE=easeIO(fb);
+ const stepIdx=Math.floor(beat);
+
+ // travel: plant-and-glide one stepAngle per beat (reads as authored stepping)
+ let ang = d.baseAngle + d.stepAngle*(stepIdx+stepE);
+ // pairs trade places: slow crossing oscillation, partners mirror each other
+ if(d.pairSign) ang += d.pairSign*Math.sin(beat*0.22 + d.pairPhase)*d.tradeAmp;
+
+ const x = cx + Math.cos(ang)*d.radius;
+ const front = Math.sin(ang); // 1 = nearest viewer
+ const y = cy + front*d.radius*SQUASH;
+ const depth = 0.82 + 0.30*(front*0.5+0.5);
+ const scale = unit*depth*1.15;
+
+ // stepping bob (rise mid-step)
+ const bob = -Math.abs(Math.sin(fb*Math.PI))*3*scale;
+ const legSwing = Math.sin(fb*Math.PI)*d.dir;
+
+ // gentle body sway
+ const tilt = Math.sin(beat*Math.PI + d.off*TAU)*0.10;
+
+ // arms wave, occasionally both raised (improvisation)
+ const wave=Math.sin(beat*Math.PI*0.55 + d.off*TAU);
+ let armL=0.35+0.35*wave, armR=0.35-0.35*wave;
+ const impP=((beat+d.impOff)%d.impEvery);
+ let hop=0, handsUp=0;
+ if(impP<1.4){
+ const u=easeIO(impP/1.4)*Math.sin(impP/1.4*Math.PI); // up then down
+ armL=Math.max(armL,u); armR=Math.max(armR,u);
+ hop=-u*6*scale; handsUp=u;
+ }
+
+ // full spin (vertical-axis turn illusion via horizontal squash)
+ let spin=0;
+ const spP=((beat+d.spinOff)%d.spinEvery);
+ if(spP<1){ spin=easeIO(spP)*TAU*d.dir; armL=Math.max(armL,0.7); armR=Math.max(armR,0.7); }
+
+ // secondary motion: cloth & hair lag the body sway
+ const clothSway=Math.sin(beat*Math.PI + d.off*TAU - 0.7)*0.9 + Math.sin(spP<1?spP*Math.PI:0)*1.2;
+ const hairSway =Math.sin(beat*Math.PI + d.off*TAU - 1.1)*0.8;
+
+ return {d,x,y,scale,bob,hop,tilt,legSwing,armL,armR,spin,clothSway,hairSway,handsUp};
+}
+
+function drawDancer(a){
+ const d=a.d, n=state.night;
+ const L=state.night, lite=lerp(58,50,L);
+ const body=`hsl(${d.hue},60%,${lite}%)`;
+ const bodyDk=`hsl(${d.hue},55%,${lite-22}%)`;
+ const accent=`hsl(${d.accentHue},65%,${lite+6}%)`;
+ const skin=`hsl(${28+ (d.hue%40)},32%,${lerp(66,50,L)}%)`;
+ const hair=`hsl(${20+d.hue%30},40%,${lerp(30,22,L)}%)`;
+
+ // ground shadow
+ ctx.save();
+ ctx.globalAlpha=0.28;
+ ctx.fillStyle='#000';
+ ctx.beginPath();
+ ctx.ellipse(a.x, a.y, 9*a.scale, 3.2*a.scale, 0, 0, TAU);
+ ctx.fill();
+ ctx.restore();
+
+ ctx.save();
+ ctx.translate(a.x, a.y + a.hop + a.bob);
+ ctx.scale(a.scale, a.scale);
+ ctx.rotate(a.tilt);
+ const sx=Math.cos(a.spin); // turn illusion
+ ctx.save();
+ ctx.scale(sx>=0?Math.max(0.12,sx):Math.min(-0.12,sx),1);
+
+ ctx.lineCap='round'; ctx.lineJoin='round';
+
+ // legs
+ ctx.strokeStyle=bodyDk; ctx.lineWidth=2.6;
+ const lf=-2.6+a.legSwing*4.2, rf=2.6-a.legSwing*4.2;
+ ctx.beginPath(); ctx.moveTo(-1.6,-13); ctx.lineTo(lf,0); ctx.stroke();
+ ctx.beginPath(); ctx.moveTo(1.6,-13); ctx.lineTo(rf,0); ctx.stroke();
+
+ // skirt / tunic (swaying hem = secondary motion)
+ if(d.style==='skirt'){
+ ctx.beginPath();
+ ctx.moveTo(-4.2,-14); ctx.lineTo(4.2,-14);
+ ctx.lineTo(6.4+a.clothSway*3,-2.5);
+ ctx.quadraticCurveTo(a.clothSway*2,-0.5,-6.4+a.clothSway*3,-2.5);
+ ctx.closePath();
+ ctx.fillStyle=body; ctx.fill();
+ ctx.strokeStyle=accent; ctx.lineWidth=0.8; ctx.stroke();
+ } else {
+ ctx.strokeStyle=body; ctx.lineWidth=5;
+ ctx.beginPath(); ctx.moveTo(0,-13); ctx.lineTo(0,-6+a.clothSway*0.4); ctx.stroke();
+ ctx.strokeStyle=accent; ctx.lineWidth=1.4;
+ ctx.beginPath(); ctx.moveTo(-3.5,-9); ctx.lineTo(3.5,-9); ctx.stroke();
+ }
+
+ // torso
+ ctx.strokeStyle=body; ctx.lineWidth=4.4;
+ ctx.beginPath(); ctx.moveTo(0,-13.5); ctx.lineTo(0,-23.5); ctx.stroke();
+ // scarf accent
+ ctx.strokeStyle=accent; ctx.lineWidth=1.6;
+ ctx.beginPath(); ctx.moveTo(-2.4,-22); ctx.lineTo(2.4,-22); ctx.stroke();
+
+ // arms
+ ctx.strokeStyle=body; ctx.lineWidth=2.4;
+ const shoulder=-22.5;
+ drawArm(-1.4,shoulder,-1,a.armL);
+ drawArm( 1.4,shoulder, 1,a.armR);
+
+ // head
+ ctx.beginPath(); ctx.arc(0,-26.6,2.9,0,TAU); ctx.fillStyle=skin; ctx.fill();
+ // hair (lags)
+ ctx.strokeStyle=hair; ctx.lineWidth=1.5;
+ for(let k=-1;k<=1;k++){
+ ctx.beginPath(); ctx.moveTo(k*1.6,-28.4);
+ ctx.quadraticCurveTo(k*2.4+a.hairSway,-25.5, k*2.2+a.hairSway*1.6,-22.6);
+ ctx.stroke();
+ }
+
+ ctx.restore(); // sx
+ ctx.restore(); // translate
+
+ function drawArm(ox,oy,dir,raise){
+ const a1=-Math.PI/2 + dir*(Math.PI*0.55) - raise*dir*Math.PI*0.9 - raise*Math.PI*0.1;
+ const ex=ox+Math.cos(a1)*7.5, ey=oy+Math.sin(a1)*7.5;
+ ctx.beginPath(); ctx.moveTo(ox,oy); ctx.lineTo(ex,ey); ctx.stroke();
+ ctx.beginPath(); ctx.arc(ex,ey,1.3,0,TAU); ctx.fillStyle=skin; ctx.fill();
+ if(raise>0.65){ // spectral spark in raised hand
+ ctx.beginPath(); ctx.arc(ex,ey,2.6,0,TAU);
+ ctx.fillStyle=`hsla(${d.accentHue},90%,75%,${0.4*raise})`; ctx.fill();
+ }
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Creatures */
+/* ------------------------------------------------------------------ */
+function drawRabbits(){
+ const beat=state.beat;
+ rabbits.forEach(rb=>{
+ const p=(beat*rb.sp+rb.ph);
+ const cyc=p%2; // hop cycle
+ const along=(Math.sin(p*0.5))*0.5+0.5;
+ const x=rb.x0 + rb.dir*rb.span*along;
+ const hopY=-Math.abs(Math.sin(cyc*Math.PI))*22*rb.sz; // rhythmic arc
+ const y=rb.baseY+hopY;
+ const s=rb.sz;
+ ctx.save(); ctx.translate(x,y); ctx.scale(rb.dir,1);
+ // shadow
+ ctx.globalAlpha=0.25; ctx.fillStyle='#000';
+ ctx.beginPath(); ctx.ellipse(0,-hopY+2,8*s,2.4*s,0,0,TAU); ctx.fill(); ctx.globalAlpha=1;
+ const fur=`hsl(30,18%,${lerp(70,52,state.night)}%)`;
+ ctx.fillStyle=fur;
+ ctx.beginPath(); ctx.ellipse(0,0,7*s,5*s,0,0,TAU); ctx.fill(); // body
+ ctx.beginPath(); ctx.arc(6*s,-3*s,3*s,0,TAU); ctx.fill(); // head
+ ctx.strokeStyle=fur; ctx.lineWidth=2*s; // ears
+ ctx.beginPath(); ctx.moveTo(6*s,-5*s); ctx.lineTo(5*s,-11*s); ctx.stroke();
+ ctx.beginPath(); ctx.moveTo(7*s,-5*s); ctx.lineTo(8*s,-11*s); ctx.stroke();
+ ctx.beginPath(); ctx.arc(-6*s,-1*s,2*s,0,TAU); ctx.fillStyle='#fff'; ctx.fill(); // tail
+ ctx.restore();
+ });
+}
+
+function drawFoxes(){
+ const beat=state.beat;
+ foxes.forEach(fx=>{
+ const p=beat*0.5+fx.ph;
+ const turn=Math.sin(p*0.5); // turning
+ const bow=Math.max(0,Math.sin(p*0.5 - 0.6)); // periodic bow
+ const s=fx.sz;
+ ctx.save(); ctx.translate(fx.x,fx.y); ctx.scale(fx.face*Math.sign(turn||1),1);
+ ctx.globalAlpha=0.25; ctx.fillStyle='#000';
+ ctx.beginPath(); ctx.ellipse(0,3*s,11*s,2.6*s,0,0,TAU); ctx.fill(); ctx.globalAlpha=1;
+ const col=`hsl(20,65%,${lerp(52,40,state.night)}%)`;
+ ctx.fillStyle=col;
+ // body
+ ctx.beginPath(); ctx.ellipse(0,-4*s,10*s,5*s,0,0,TAU); ctx.fill();
+ // legs
+ ctx.strokeStyle=col; ctx.lineWidth=2.4*s;
+ for(const lx of [-6,-2,3,7]){ctx.beginPath();ctx.moveTo(lx*s,-2*s);ctx.lineTo(lx*s,3*s);ctx.stroke();}
+ // head lowers on bow
+ ctx.save(); ctx.translate(9*s,-6*s); ctx.rotate(bow*0.7);
+ ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(9*s,2*s); ctx.lineTo(0,5*s); ctx.closePath(); ctx.fill(); // snout
+ ctx.beginPath(); ctx.arc(0,0,4*s,0,TAU); ctx.fill();
+ ctx.fillStyle='#2a1408';
+ ctx.beginPath(); ctx.moveTo(-2*s,-3*s); ctx.lineTo(-4*s,-7*s); ctx.lineTo(0,-4*s); ctx.closePath(); ctx.fill(); //ear
+ ctx.restore();
+ // bushy tail lifts opposite the bow
+ ctx.fillStyle=col;
+ ctx.save(); ctx.translate(-9*s,-4*s); ctx.rotate(-0.5-turn*0.3);
+ ctx.beginPath(); ctx.ellipse(-5*s,0,7*s,3.2*s,0,0,TAU); ctx.fill();
+ ctx.fillStyle='#f4ead8'; ctx.beginPath(); ctx.arc(-11*s,0,2.4*s,0,TAU); ctx.fill();
+ ctx.restore();
+ ctx.restore();
+ });
+}
+
+function drawTreesAndOwls(){
+ const n=state.night;
+ owls.forEach(o=>{
+ // simple tree silhouette + branch anchoring the owl
+ ctx.save();
+ ctx.strokeStyle=`hsl(28,25%,${lerp(26,16,n)}%)`;
+ ctx.lineWidth=8*o.sz; ctx.lineCap='round';
+ const tx=o.x - o.side*30*o.sz;
+ ctx.beginPath(); ctx.moveTo(tx, -10); ctx.lineTo(tx, o.y+70*o.sz); ctx.stroke();
+ ctx.lineWidth=4*o.sz;
+ ctx.beginPath(); ctx.moveTo(tx,o.y+14*o.sz); ctx.lineTo(o.x+o.side*10*o.sz,o.y+16*o.sz); ctx.stroke();
+ // foliage blobs
+ ctx.fillStyle=`hsla(120,20%,${lerp(30,18,n)}%,.9)`;
+ for(let k=0;k<5;k++){const s=k*4+o.side*7;
+ ctx.beginPath(); ctx.arc(tx+(rnd(s)-0.5)*40*o.sz, (rnd(s+1)*70), (16+rnd(s+2)*14)*o.sz,0,TAU); ctx.fill();}
+
+ // owl
+ const beat=state.beat;
+ const blink = ((beat+o.ph)%5) < 0.25 ? 0 : 1; // eyes blink
+ const s=o.sz;
+ ctx.translate(o.x,o.y);
+ const ocol=`hsl(30,22%,${lerp(58,40,n)}%)`;
+ ctx.fillStyle=ocol;
+ ctx.beginPath(); ctx.ellipse(0,0,9*s,12*s,0,0,TAU); ctx.fill(); // body
+ // ear tufts
+ ctx.beginPath(); ctx.moveTo(-6*s,-9*s); ctx.lineTo(-8*s,-15*s); ctx.lineTo(-3*s,-11*s); ctx.closePath(); ctx.fill();
+ ctx.beginPath(); ctx.moveTo(6*s,-9*s); ctx.lineTo(8*s,-15*s); ctx.lineTo(3*s,-11*s); ctx.closePath(); ctx.fill();
+ // face disc
+ ctx.fillStyle=`hsl(34,26%,${lerp(70,52,n)}%)`;
+ ctx.beginPath(); ctx.ellipse(0,-3*s,7*s,7*s,0,0,TAU); ctx.fill();
+ // eyes (blink)
+ ctx.fillStyle='#2a1a08';
+ if(blink){
+ ctx.beginPath(); ctx.arc(-3*s,-4*s,2.4*s,0,TAU); ctx.arc(3*s,-4*s,2.4*s,0,TAU); ctx.fill();
+ ctx.fillStyle=`hsl(48,90%,70%)`;
+ ctx.beginPath(); ctx.arc(-3*s,-4*s,1.1*s,0,TAU); ctx.arc(3*s,-4*s,1.1*s,0,TAU); ctx.fill();
+ } else {
+ ctx.strokeStyle='#2a1a08'; ctx.lineWidth=1.4*s;
+ ctx.beginPath(); ctx.moveTo(-5*s,-4*s); ctx.lineTo(-1*s,-4*s);
+ ctx.moveTo(1*s,-4*s); ctx.lineTo(5*s,-4*s); ctx.stroke();
+ }
+ // beak
+ ctx.fillStyle='#d8952f';
+ ctx.beginPath(); ctx.moveTo(0,-2*s); ctx.lineTo(-1.4*s,1*s); ctx.lineTo(1.4*s,1*s); ctx.closePath(); ctx.fill();
+ ctx.restore();
+ });
+}
+
+function drawButterflies(){
+ const beat=state.beat;
+ butterflies.forEach(b=>{
+ const p=beat*b.sp+b.ph;
+ // looping lissajous path
+ const x=b.cxr+Math.cos(p)*b.ax + Math.cos(p*2.1)*b.ax*0.3;
+ const y=b.cyr+Math.sin(p*1.3)*b.ay + Math.sin(p*0.7)*b.ay*0.3;
+ const flap=Math.abs(Math.sin(beat*Math.PI*2 + b.ph));
+ const s=b.sz;
+ ctx.save(); ctx.translate(x,y);
+ const dir=Math.cos(p)>=0?1:-1; ctx.scale(dir,1);
+ for(const side of [-1,1]){
+ ctx.save(); ctx.scale(1, side);
+ ctx.fillStyle=`hsla(${b.hue},80%,68%,.85)`;
+ ctx.beginPath();
+ ctx.ellipse(2*s, 3*s*(0.4+flap*0.9), s*(0.6+flap*0.5), s*1.1, 0.5,0,TAU);
+ ctx.fill();
+ ctx.fillStyle=`hsla(${(b.hue+40)%360},80%,72%,.75)`;
+ ctx.beginPath();
+ ctx.ellipse(-1*s, 1.5*s*(0.4+flap*0.9), s*0.5, s*0.8, -0.4,0,TAU);
+ ctx.fill();
+ ctx.restore();
+ }
+ ctx.strokeStyle='#20142a'; ctx.lineWidth=0.8;
+ ctx.beginPath(); ctx.moveTo(0,-2*s); ctx.lineTo(0,3*s); ctx.stroke();
+ ctx.restore();
+ });
+}
+
+function drawFireflies(){
+ const n=state.night;
+ if(n<0.2) return;
+ fireflies.forEach(f=>{
+ const g=0.5+0.5*Math.sin(state.t*f.sp*4+f.ph);
+ const x=f.x+Math.sin(state.t*0.4+f.ph)*f.drift;
+ const y=f.y+Math.cos(state.t*0.3+f.ph)*f.drift*0.6;
+ ctx.beginPath(); ctx.arc(x,y,1.6*unit+g*1.4*unit,0,TAU);
+ ctx.fillStyle=`hsla(${f.hue},95%,72%,${0.15*n + g*0.5*n})`; ctx.fill();
+ });
+}
+
+/* ------------------------------------------------------------------ */
+/* Master render */
+/* ------------------------------------------------------------------ */
+function render(){
+ ctx.clearRect(0,0,W,H);
+ drawSky();
+ drawTreesAndOwls();
+ drawGround();
+ drawFlowers();
+ drawSunwheel();
+
+ // depth-sorted stage: dancers + foreground creatures
+ const items=[];
+ for(const d of dancers){ const a=computeDancer(d); items.push({y:a.y, kind:'dancer', a}); }
+ items.push({y:-1, kind:'rabbits'}); // rabbits/foxes have own y handling; draw them within band
+ items.sort((p,q)=>p.y-q.y);
+ // draw dancers behind center first, interleave nothing else for simplicity
+ for(const it of items){ if(it.kind==='dancer') drawDancer(it.a); }
+
+ drawRabbits();
+ drawFoxes();
+ drawButterflies();
+ drawFireflies();
+
+ // vignette
+ const vg=ctx.createRadialGradient(cx,cy*0.9,maxR*0.5,cx,cy,Math.max(W,H)*0.75);
+ vg.addColorStop(0,'rgba(0,0,0,0)');
+ vg.addColorStop(1,`rgba(0,0,0,${0.28+state.night*0.28})`);
+ ctx.fillStyle=vg; ctx.fillRect(0,0,W,H);
+}
+
+/* ------------------------------------------------------------------ */
+/* Visual-rhythm indicator (drives choreography; not audio) */
+/* ------------------------------------------------------------------ */
+const barsEl=document.getElementById('bars');
+const barNodes=[];
+for(let i=0;i<7;i++){const el=document.createElement('i');barsEl.appendChild(el);barNodes.push(el);}
+function updateRhythm(){
+ for(let i=0;i<barNodes.length;i++){
+ const ph=state.beat*Math.PI + i*0.55;
+ const v=0.5+0.5*Math.sin(ph);
+ barNodes[i].style.height=(6+v*20)+'px';
+ barNodes[i].style.opacity=(0.4+v*0.6).toFixed(2);
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Loop + controls */
+/* ------------------------------------------------------------------ */
+let last=performance.now(), raf=0;
+function frame(now){
+ const dt=Math.min(0.05,(now-last)/1000); last=now;
+ if(state.playing && !state.reduced){
+ state.t+=dt;
+ state.beat+=dt*BASE_BPS*state.tempo;
+ }
+ render();
+ updateRhythm();
+ raf=requestAnimationFrame(frame);
+}
+
+const playBtn=document.getElementById('play');
+const reduceBtn=document.getElementById('reduce');
+const tempoEl=document.getElementById('tempo');
+const densEl=document.getElementById('density');
+const nightEl=document.getElementById('night');
+const tempoVal=document.getElementById('tempoval');
+const densVal=document.getElementById('densval');
+const nightVal=document.getElementById('nightval');
+
+function setPlaying(p){
+ state.playing=p;
+ playBtn.setAttribute('aria-pressed', String(p));
+ playBtn.textContent = p ? '⏸ Pause' : '▶ Play';
+}
+playBtn.addEventListener('click',()=>setPlaying(!state.playing));
+
+reduceBtn.addEventListener('click',()=>{
+ state.reduced=!state.reduced;
+ reduceBtn.setAttribute('aria-pressed',String(state.reduced));
+ if(state.reduced){ setPlaying(false); }
+});
+
+tempoEl.addEventListener('input',()=>{
+ state.tempo=parseFloat(tempoEl.value);
+ tempoVal.textContent=state.tempo.toFixed(2)+'×';
+});
+densEl.addEventListener('input',()=>{
+ state.density=parseFloat(densEl.value);
+ densVal.textContent = state.density<0.85?'sparse':state.density>1.5?'crowded':'full';
+ buildDancers();
+});
+nightEl.addEventListener('input',()=>{
+ state.night=parseFloat(nightEl.value);
+ nightVal.textContent = state.night<0.25?'dusk':state.night<0.55?'evening':state.night<0.8?'night':'deep night';
+});
+
+document.addEventListener('keydown',e=>{
+ const tag=(e.target.tagName||'').toLowerCase();
+ if(e.code==='Space' && tag!=='input'){ e.preventDefault(); if(!state.reduced) setPlaying(!state.playing); }
+ if(e.key.toLowerCase()==='m'){ reduceBtn.click(); }
+});
+
+/* respect OS reduced-motion preference on load */
+if(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches){
+ state.reduced=true; state.playing=false; state.beat=0.62;
+ reduceBtn.setAttribute('aria-pressed','true');
+ playBtn.setAttribute('aria-pressed','false'); playBtn.textContent='▶ Play';
+}
+
+let resizeT;
+window.addEventListener('resize',()=>{clearTimeout(resizeT);resizeT=setTimeout(build,140);});
+
+build();
+raf=requestAnimationFrame(frame);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/c5c20cf0ae13/qwen25-7b.html b/data/artifacts/c5c20cf0ae13/qwen25-7b.html
new file mode 100644
index 0000000..fb939f9
--- /dev/null
+++ b/data/artifacts/c5c20cf0ae13/qwen25-7b.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Sunwheel Meadow Dance</title>
+ <style>
+ body {
+ margin: 0;
+ overflow: hidden;
+ background-color: #12002b;
+ color: #f3e9ff;
+ font-family: 'Didot', Georgia, serif;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ }
+ canvas {
+ width: 100%;
+ height: 100%;
+ background-color: #1e0b3d;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="animation"></canvas>
+ <script>
+ const canvas = document.getElementById('animation');
+ const ctx = canvas.getContext('2d');
+
+ let width = window.innerWidth, height = window.innerHeight;
+ canvas.width = width; canvas.height = height;
+
+ class Person {
+ constructor(x, y, r, dir) {
+ this.x = x;
+ this.y = y;
+ this.r = r;
+ this.dir = dir;
+ this.path = [];
+ this.armAngle = 0;
+ this.swing = false;
+ }
+ update() {
+ if (this.path.length === 0) {
+ const angleStep = Math.PI * 2 / 360;
+ for (let i = 0; i < 360; i++) {
+ this.path.push({ x: this.x + Math.cos(i * angleStep) * this.r, y: this.y + Math.sin(i * angleStep) * this.r });
+ }
+ }
+
+ const [x, y] = this.path[Math.floor(this.path.length * (Date.now() / 1000 % 360) / 360)];
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.rotate(Date.now() / 500 + this.armAngle);
+
+ if (this.swing && Date.now() - this.swingStart < 2000) {
+ ctx.beginPath();
+ ctx.moveTo(0, -10);
+ ctx.lineTo(-20, 10);
+ ctx.stroke();
+ }
+
+ ctx.restore();
+
+ this.armAngle += Math.PI / 50;
+ }
+ }
+
+ class Rabbit {
+ constructor(x, y) {
+ this.x = x;
+ this.y = y;
+ this.path = [];
+ }
+ update() {
+ if (this.path.length === 0) {
+ const angleStep = Math.PI * 2 / 360;
+ for (let i = 0; i < 360; i++) {
+ this.path.push({ x: this.x + Math.cos(i * angleStep) * 50, y: this.y + Math.sin(i * angleStep) * 50 });
+ }
+ }
+
+ const [x, y] = this.path[Math.floor(this.path.length * (Date.now() / 1000 % 360) / 360)];
+ ctx.save();
+ ctx.translate(x, y);
+ ctx.rotate(Date.now() / 1500);
+
+ ctx.beginPath();
+ ctx.arc(0, 0, 20, 0, Math.PI * 2);
+ ctx.fillStyle = '#fff';
+ ctx.fill();
+
+ ctx.restore();
+ }
+ }
+
+ const people = [];
+ for (let i = 0; i < 36; i++) {
+ const r = width / 4;
+ people.push(new Person(width / 2 + Math.cos(i * 15) * r, height / 2 + Math.sin(i * 15) * r, r, Date.now() % 720 > 360 ? -1 : 1));
+ }
+
+ const rabbits = [];
+ for (let i = 0; i < 12; i++) {
+ const x = Math.random() * width;
+ const y = height / 2 + 50 * Math.sin(Date.now() / 360);
+ rabbits.push(new Rabbit(x, y));
+ }
+
+ function animate() {
+ ctx.clearRect(0, 0, width, height);
+
+ people.forEach(p => p.update());
+ rabbits.forEach(r => r.update());
+
+ requestAnimationFrame(animate);
+ }
+ animate();
+
+ document.body.style.overflow = 'hidden';
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/c5c20cf0ae13/qwen25-7b.png b/data/artifacts/c5c20cf0ae13/qwen25-7b.png
index 5ba2a81..e7b81bd 100644
Binary files a/data/artifacts/c5c20cf0ae13/qwen25-7b.png and b/data/artifacts/c5c20cf0ae13/qwen25-7b.png differ
diff --git a/data/artifacts/c5c20cf0ae13/qwen3-14b.html b/data/artifacts/c5c20cf0ae13/qwen3-14b.html
new file mode 100644
index 0000000..1bd4d3c
--- /dev/null
+++ b/data/artifacts/c5c20cf0ae13/qwen3-14b.html
@@ -0,0 +1,324 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <title>Sunwheel Meadow Dance</title>
+ <style>
+ :root {
+ --bg: #f7efe6;
+ --surface: #fff9f1;
+ --text: #3a2c22;
+ --muted: #a08c7a;
+ --accent: #c65f38;
+ --accent-2: #e0a458;
+ --shadow-glow: 0 0 24px rgba(198, 95, 56, 0.35);
+ --spacing-4: 4px;
+ --spacing-8: 8px;
+ --spacing-12: 12px;
+ --spacing-16: 16px;
+ --spacing-24: 24px;
+ --spacing-32: 32px;
+ --spacing-48: 48px;
+ --spacing-64: 64px;
+ --font-display: 67px;
+ --font-h1: 50px;
+ --font-h2: 38px;
+ --font-h3: 28px;
+ --font-lead: 21px;
+ --font-body: 16px;
+ --font-caption: 12px;
+ }
+
+ * {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+
+ html, body {
+ height: 100%;
+ width: 100%;
+ font-family: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+ background: var(--bg);
+ color: var(--text);
+ overflow: hidden;
+ }
+
+ .container {
+ height: 100vh;
+ width: 100vw;
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ padding: var(--spacing-48) var(--spacing-32);
+ overflow: hidden;
+ }
+
+ .meadow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #fff9f1 0%, #f7efe6 100%);
+ background-size: 400% 400%;
+ animation: driftGradient 18s ease-in-out infinite;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .sunwheel {
+ width: 150px;
+ height: 150px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 30% 30%, #e0a458, #c65f38, #b24a2a);
+ box-shadow: var(--shadow-glow);
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 2;
+ }
+
+ .dancer {
+ position: absolute;
+ width: 40px;
+ height: 60px;
+ background: radial-gradient(circle, #fff9f1, #f7efe6);
+ border-radius: 50%;
+ box-shadow: 0 0 10px #fff9f1;
+ animation: dance 4s infinite ease-in-out;
+ }
+
+ .dancer:nth-child(1) { top: 30%; left: 10%; animation-delay: 0s; }
+ .dancer:nth-child(2) { top: 40%; left: 30%; animation-delay: 0.1s; }
+ .dancer:nth-child(3) { top: 50%; left: 50%; animation-delay: 0.2s; }
+ .dancer:nth-child(4) { top: 60%; left: 70%; animation-delay: 0.3s; }
+ .dancer:nth-child(5) { top: 70%; left: 90%; animation-delay: 0.4s; }
+ .dancer:nth-child(6) { top: 80%; left: 80%; animation-delay: 0.5s; }
+ .dancer:nth-child(7) { top: 90%; left: 60%; animation-delay: 0.6s; }
+ .dancer:nth-child(8) { top: 100%; left: 40%; animation-delay: 0.7s; }
+ .dancer:nth-child(9) { top: 110%; left: 20%; animation-delay: 0.8s; }
+ .dancer:nth-child(10) { top: 120%; left: 10%; animation-delay: 0.9s; }
+
+ .dancer--inner {
+ width: 30px;
+ height: 50px;
+ background: radial-gradient(circle, #e0a458, #c65f38);
+ box-shadow: 0 0 12px #e0a458;
+ }
+
+ .dancer--inner:nth-child(1) { top: 40%; left: 50%; animation-delay: 0s; }
+ .dancer--inner:nth-child(2) { top: 60%; left: 50%; animation-delay: 0.1s; }
+
+ @keyframes dance {
+ 0%, 100% { transform: translate(0, 0) rotate(0deg); }
+ 25% { transform: translate(5px, -5px) rotate(15deg); }
+ 50% { transform: translate(-5px, -5px) rotate(-15deg); }
+ 75% { transform: translate(5px, 5px) rotate(15deg); }
+ }
+
+ .animal {
+ position: absolute;
+ width: 30px;
+ height: 30px;
+ background: radial-gradient(circle, #fff9f1, #f7efe6);
+ border-radius: 50%;
+ box-shadow: 0 0 10px #fff9f1;
+ animation: hop 3s infinite ease-in-out;
+ }
+
+ .animal:nth-child(1) { top: 20%; left: 20%; animation-delay: 0s; }
+ .animal:nth-child(2) { top: 30%; left: 40%; animation-delay: 0.1s; }
+ .animal:nth-child(3) { top: 50%; left: 60%; animation-delay: 0.2s; }
+ .animal:nth-child(4) { top: 70%; left: 80%; animation-delay: 0.3s; }
+
+ @keyframes hop {
+ 0%, 100% { transform: translateY(0) scale(1); }
+ 25% { transform: translateY(-20px) scale(1.2); }
+ 50% { transform: translateY(0) scale(1); }
+ 75% { transform: translateY(-10px) scale(1.1); }
+ }
+
+ .controls {
+ position: absolute;
+ bottom: var(--spacing-32);
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ gap: var(--spacing-16);
+ font-size: var(--font-caption);
+ text-transform: uppercase;
+ letter-spacing: 0.15em;
+ color: var(--muted);
+ }
+
+ .controls button {
+ background: var(--surface);
+ border: 1px solid var(--muted);
+ border-radius: var(--card-radius);
+ padding: var(--spacing-12) var(--spacing-24);
+ cursor: pointer;
+ transition: all 0.2s ease;
+ }
+
+ .controls button:hover {
+ background: #fff9f1;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
+ }
+
+ .visual-rhythm {
+ position: absolute;
+ top: var(--spacing-32);
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: var(--font-caption);
+ color: var(--muted);
+ text-transform: uppercase;
+ letter-spacing: 0.15em;
+ }
+
+ @keyframes driftGradient {
+ 0% { background-position: 0% 50%; }
+ 50% { background-position: 100% 50%; }
+ 100% { background-position: 0% 50%; }
+ }
+
+ .overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 1;
+ pointer-events: none;
+ }
+
+ .overlay::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(circle at 50% 50%, rgba(255,255,255,0.05), rgba(0,0,0,0.02));
+ filter: blur(100px);
+ }
+ </style>
+</head>
+<body>
+ <div class="container">
+ <div class="meadow">
+ <div class="sunwheel"></div>
+ <div class="overlay"></div>
+ <!-- Dancers -->
+ <div class="dancer"></div>
+ <div class="dancer"></div>
+ <div class="dancer"></div>
+ <div class="dancer"></div>
+ <div class="dancer"></div>
+ <div class="dancer"></div>
+ <div class="dancer"></div>
+ <div class="dancer"></div>
+ <div class="dancer"></div>
+ <div class="dancer"></div>
+ <div class="dancer--inner"></div>
+ <div class="dancer--inner"></div>
+ <!-- Animals -->
+ <div class="animal"></div>
+ <div class="animal"></div>
+ <div class="animal"></div>
+ <div class="animal"></div>
+ </div>
+ <div class="controls">
+ <button id="playpause">▶️ Play/Pause</button>
+ <button id="tempo">🎵 Tempo</button>
+ <button id="crowd">👥 Crowd</button>
+ <button id="sunset">🌅 Sunset</button>
+ <button id="reduce">🚫 Reduce Motion</button>
+ </div>
+ <div class="visual-rhythm">Visual Rhythm: Stable</div>
+ </div>
+
+ <script>
+ const playPauseBtn = document.getElementById('playpause');
+ const tempoBtn = document.getElementById('tempo');
+ const crowdBtn = document.getElementById('crowd');
+ const sunsetBtn = document.getElementById('sunset');
+ const reduceBtn = document.getElementById('reduce');
+
+ let isPlaying = true;
+ let dancers = document.querySelectorAll('.dancer, .dancer--inner');
+ let isReduced = false;
+
+ function togglePlayPause() {
+ isPlaying = !isPlaying;
+ dancers.forEach(d => {
+ d.style.animationPlayState = isPlaying ? 'running' : 'paused';
+ });
+ playPauseBtn.textContent = isPlaying ? '⏸️ Pause' : '▶️ Play';
+ }
+
+ function adjustTempo() {
+ // Placeholder for tempo controls
+ alert('Tempo controls not implemented in this version.');
+ }
+
+ function adjustCrowd() {
+ // Placeholder for crowd density controls
+ alert('Crowd density controls not implemented in this version.');
+ }
+
+ function toggleSunset() {
+ const meadow = document.querySelector('.meadow');
+ const sunwheel = document.querySelector('.sunwheel');
+ const bg = document.documentElement.style;
+ const currentBg = bg.getPropertyValue('--bg');
+
+ if (currentBg === '#f7efe6') {
+ bg.setProperty('--bg', '#0d0d0f');
+ bg.setProperty('--surface', '#17171c');
+ bg.setProperty('--text', '#f5f2ea');
+ bg.setProperty('--muted', '#8a8578');
+ bg.setProperty('--accent', '#c9a961');
+ bg.setProperty('--accent-2', '#7d6a45');
+ sunwheel.style.background = 'radial-gradient(circle at 30% 30%, #c9a961, #7d6a45, #4b402c)';
+ sunwheel.style.boxShadow = '0 0 24px #c9a961';
+ } else {
+ bg.setProperty('--bg', '#f7efe6');
+ bg.setProperty('--surface', '#fff9f1');
+ bg.setProperty('--text', '#3a2c22');
+ bg.setProperty('--muted', '#a08c7a');
+ bg.setProperty('--accent', '#c65f38');
+ bg.setProperty('--accent-2', '#e0a458');
+ sunwheel.style.background = 'radial-gradient(circle at 30% 30%, #e0a458, #c65f38, #b24a2a)';
+ sunwheel.style.boxShadow = '0 0 24px #e0a458';
+ }
+ }
+
+ function toggleReduceMotion() {
+ isReduced = !isReduced;
+ if (isReduced) {
+ dancers.forEach(d => {
+ d.style.animation = 'none';
+ });
+ reduceBtn.textContent = '🔁 Restore Motion';
+ } else {
+ dancers.forEach(d => {
+ d.style.animation = 'dance 4s infinite ease-in-out';
+ });
+ reduceBtn.textContent = '🚫 Reduce Motion';
+ }
+ }
+
+ playPauseBtn.addEventListener('click', togglePlayPause);
+ tempoBtn.addEventListener('click', adjustTempo);
+ crowdBtn.addEventListener('click', adjustCrowd);
+ sunsetBtn.addEventListener('click', toggleSunset);
+ reduceBtn.addEventListener('click', toggleReduceMotion);
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ee75e7535787/claude-code.html b/data/artifacts/ee75e7535787/claude-code.html
new file mode 100644
index 0000000..0af3880
--- /dev/null
+++ b/data/artifacts/ee75e7535787/claude-code.html
@@ -0,0 +1,813 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
+<title>The Dancing Bear Procession</title>
+<style>
+ :root{
+ --bg:#12002b; --surface:#1e0b3d; --surface2:#2a1152; --text:#f3e9ff; --muted:#8d7bb0;
+ --a1:#ff2e88; --a2:#00e5ff; --gold:#ffb020; --violet:#a06bff; --mint:#3ef0a0;
+ --line:#3a1f66;
+ --sp:12px; --r:14px;
+ --disp:'Didot','Bodoni MT','Playfair Display',Georgia,serif;
+ --body:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
+ }
+ *{box-sizing:border-box}
+ html,body{margin:0}
+ body{
+ background:var(--bg); color:var(--text); font-family:var(--body);
+ min-height:100vh; overflow-x:hidden;
+ -webkit-font-smoothing:antialiased;
+ }
+ .app{ --spd:1; max-width:1500px; margin:0 auto; padding:clamp(8px,2vw,20px); }
+
+ /* ---------- Header ---------- */
+ header.top{
+ display:flex; align-items:flex-end; justify-content:space-between; gap:16px;
+ flex-wrap:wrap; margin-bottom:12px;
+ }
+ .brand h1{
+ font-family:var(--disp); font-weight:600; margin:0;
+ font-size:clamp(24px,5.2vw,50px); line-height:.98; letter-spacing:.5px;
+ background:linear-gradient(92deg,var(--a1),var(--gold) 45%,var(--a2));
+ -webkit-background-clip:text; background-clip:text; color:transparent;
+ text-shadow:0 0 30px rgba(255,46,136,.18);
+ }
+ .brand .sub{
+ margin-top:4px; font-size:12px; letter-spacing:.15em; text-transform:uppercase; color:var(--muted);
+ }
+ .status{
+ font-size:12px; color:var(--muted); text-align:right; line-height:1.5;
+ background:var(--surface); border:1px solid var(--line); border-radius:var(--r);
+ padding:8px 14px; min-width:190px;
+ }
+ .status b{ color:var(--text); font-weight:600; letter-spacing:.02em; }
+ .status .hood{ color:var(--gold); }
+
+ /* ---------- Stage / film ---------- */
+ .stage{
+ position:relative; width:100%;
+ height:clamp(360px,64vh,600px);
+ border-radius:18px; overflow:hidden;
+ border:1px solid var(--line);
+ background:
+ radial-gradient(120% 90% at 78% -10%, #37105e 0%, #1a0538 42%, #0c0022 100%);
+ box-shadow:0 18px 60px rgba(0,0,0,.55), inset 0 0 90px rgba(0,0,0,.5);
+ background-size:200% 200%;
+ animation:skyDrift 22s ease-in-out infinite alternate;
+ }
+ @keyframes skyDrift{ from{background-position:0% 50%} to{background-position:100% 50%} }
+
+ .layer{ position:absolute; left:0; bottom:0; width:100%; overflow:hidden; pointer-events:none; }
+ .track{ display:flex; width:max-content; will-change:transform; }
+ .track svg{ display:block; }
+ @keyframes scroll{ to{ transform:translateX(-1200px); } }
+ .app.dir-rev .track{ animation-direction:reverse; }
+
+ .l-far{ height:62%; z-index:1; opacity:.9 }
+ .l-mid{ height:54%; z-index:2 }
+ .l-lan{ height:46%; z-index:3 }
+ .l-ban{ top:0; bottom:auto; height:78px; z-index:7 }
+ .l-rev{ height:31%; z-index:5 }
+ .l-fg{ height:26%; z-index:8; opacity:.97 }
+
+ .l-far .track{ animation:scroll linear infinite; animation-duration:calc(96s / var(--spd)); }
+ .l-mid .track{ animation:scroll linear infinite; animation-duration:calc(60s / var(--spd)); }
+ .l-lan .track{ animation:scroll linear infinite; animation-duration:calc(42s / var(--spd)); }
+ .l-ban .track{ animation:scroll linear infinite; animation-duration:calc(50s / var(--spd)); }
+ .l-rev .track{ animation:scroll linear infinite; animation-duration:calc(30s / var(--spd)); }
+ .l-fg .track{ animation:scroll linear infinite; animation-duration:calc(20s / var(--spd)); }
+
+ /* Ground / street */
+ .ground{
+ position:absolute; left:0; bottom:0; width:100%; height:clamp(64px,16%,108px); z-index:4;
+ background:
+ radial-gradient(50% 120% at 16% 0%, rgba(255,176,32,.22), transparent 60%),
+ radial-gradient(46% 120% at 46% 0%, rgba(0,229,255,.16), transparent 60%),
+ radial-gradient(50% 120% at 78% 0%, rgba(255,46,136,.20), transparent 60%),
+ linear-gradient(#241043,#140628 70%,#0c0320);
+ border-top:1px solid rgba(255,255,255,.08);
+ box-shadow:inset 0 8px 24px rgba(0,0,0,.5);
+ pointer-events:none;
+ }
+
+ /* Stars & moon */
+ .sky{ position:absolute; inset:0; z-index:0; pointer-events:none; }
+ .star{ position:absolute; width:2px; height:2px; border-radius:50%; background:#fff; opacity:.8; animation:tw 3.5s ease-in-out infinite; }
+ @keyframes tw{ 0%,100%{opacity:.25;transform:scale(.7)} 50%{opacity:.95;transform:scale(1)} }
+ .moon{
+ position:absolute; right:9%; top:9%; width:clamp(46px,9vw,86px); height:clamp(46px,9vw,86px);
+ border-radius:50%;
+ background:radial-gradient(circle at 35% 32%, #fff6e2, #ffd98a 55%, #e0a458 100%);
+ box-shadow:0 0 60px rgba(255,214,120,.5), 0 0 120px rgba(255,176,32,.28);
+ z-index:0;
+ }
+
+ /* Bears band */
+ .bears{
+ position:absolute; left:0; right:0; bottom:clamp(30px,9%,72px); z-index:6;
+ display:flex; justify-content:center; align-items:flex-end;
+ gap:clamp(2px,1.6vw,26px); padding:0 8px; pointer-events:none;
+ }
+ .bear-slot{ position:relative; display:flex; flex-direction:column; align-items:center; }
+ .tag{
+ font-size:10px; letter-spacing:.06em; text-transform:uppercase; color:var(--text);
+ background:rgba(20,6,40,.82); border:1px solid var(--line); border-radius:20px;
+ padding:3px 9px; margin-bottom:6px; white-space:nowrap;
+ opacity:0; transform:translateY(4px); transition:opacity .25s, transform .25s;
+ box-shadow:0 4px 14px rgba(0,0,0,.4);
+ }
+ .tag span{ display:block; font-size:8px; color:var(--muted); letter-spacing:.12em; }
+ .app.show-labels .tag{ opacity:1; transform:none; }
+ .bear{ width:clamp(52px,13.5vw,116px); height:auto; overflow:visible; transition:transform .35s ease; }
+ .app.dir-rev .bear{ transform:scaleX(-1); }
+
+ .b-shadow{ fill:rgba(0,0,0,.35); transform-box:fill-box; transform-origin:50% 50%; }
+ .b-body{ transform-box:fill-box; transform-origin:50% 100%; }
+ .b-turn{ transform-box:fill-box; transform-origin:50% 100%; }
+ .b-leg{ transform-box:fill-box; transform-origin:50% 8%; }
+ .b-arm{ transform-box:fill-box; transform-origin:50% 12%; }
+ .b-head{ transform-box:fill-box; transform-origin:50% 92%; }
+
+ /* ===== dance cycles ===== */
+ /* High step */
+ @keyframes hsBody{0%,100%{transform:translateY(0) rotate(-2deg)}25%{transform:translateY(-5px) rotate(0)}50%{transform:translateY(0) rotate(2deg)}75%{transform:translateY(-5px) rotate(0)}}
+ @keyframes hsLegL{0%,100%{transform:rotate(8deg)}50%{transform:rotate(-42deg)}}
+ @keyframes hsLegR{0%,100%{transform:rotate(-42deg)}50%{transform:rotate(8deg)}}
+ @keyframes hsArmL{0%,100%{transform:rotate(-26deg)}50%{transform:rotate(24deg)}}
+ @keyframes hsArmR{0%,100%{transform:rotate(24deg)}50%{transform:rotate(-26deg)}}
+ @keyframes hsHead{0%,100%{transform:rotate(-3deg)}50%{transform:rotate(3deg)}}
+ .bear.hs .b-body{animation:hsBody calc(.95s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.hs .b-leg-l{animation:hsLegL calc(.95s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.hs .b-leg-r{animation:hsLegR calc(.95s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.hs .b-arm-l{animation:hsArmL calc(.95s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.hs .b-arm-r{animation:hsArmR calc(.95s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.hs .b-head{animation:hsHead calc(1.9s/var(--spd)) ease-in-out infinite var(--dl)}
+
+ /* Shoulder bounce */
+ @keyframes sbBody{0%,100%{transform:translateX(-3px) rotate(-4.5deg)}50%{transform:translateX(3px) rotate(4.5deg)}}
+ @keyframes sbLegL{0%,100%{transform:rotate(-13deg)}50%{transform:rotate(7deg)}}
+ @keyframes sbLegR{0%,100%{transform:rotate(7deg)}50%{transform:rotate(-13deg)}}
+ @keyframes sbArmL{0%,100%{transform:rotate(40deg) translateY(0)}50%{transform:rotate(16deg) translateY(-6px)}}
+ @keyframes sbArmR{0%,100%{transform:rotate(-16deg) translateY(-6px)}50%{transform:rotate(-40deg) translateY(0)}}
+ @keyframes sbHead{0%,100%{transform:translateX(-2px) rotate(4deg)}50%{transform:translateX(2px) rotate(-4deg)}}
+ .bear.sb .b-body{animation:sbBody calc(1.15s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sb .b-leg-l{animation:sbLegL calc(1.15s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sb .b-leg-r{animation:sbLegR calc(1.15s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sb .b-arm-l{animation:sbArmL calc(1.15s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sb .b-arm-r{animation:sbArmR calc(1.15s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sb .b-head{animation:sbHead calc(1.15s/var(--spd)) ease-in-out infinite var(--dl)}
+
+ /* Heel turn */
+ @keyframes htTurn{0%,40%{transform:scaleX(1)}46%{transform:scaleX(.12)}52%,90%{transform:scaleX(-1)}96%{transform:scaleX(.12)}100%{transform:scaleX(1)}}
+ @keyframes htBody{0%,100%{transform:translateY(0)}25%{transform:translateY(-4px)}50%{transform:translateY(0)}75%{transform:translateY(-4px)}}
+ @keyframes htLegL{0%,100%{transform:rotate(-7deg)}50%{transform:rotate(15deg)}}
+ @keyframes htLegR{0%,100%{transform:rotate(15deg)}50%{transform:rotate(-7deg)}}
+ @keyframes htArmL{0%,100%{transform:rotate(-30deg)}50%{transform:rotate(-58deg)}}
+ @keyframes htArmR{0%,100%{transform:rotate(30deg)}50%{transform:rotate(58deg)}}
+ .bear.ht .b-turn{animation:htTurn calc(2.7s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.ht .b-body{animation:htBody calc(.82s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.ht .b-leg-l{animation:htLegL calc(.82s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.ht .b-leg-r{animation:htLegR calc(.82s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.ht .b-arm-l{animation:htArmL calc(.82s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.ht .b-arm-r{animation:htArmR calc(.82s/var(--spd)) ease-in-out infinite var(--dl)}
+
+ /* Slow twirl */
+ @keyframes twTurn{0%{transform:scaleX(1)}25%{transform:scaleX(.08)}50%{transform:scaleX(-1)}75%{transform:scaleX(.08)}100%{transform:scaleX(1)}}
+ @keyframes twBody{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
+ @keyframes twArmL{0%,100%{transform:rotate(-72deg)}50%{transform:rotate(-58deg)}}
+ @keyframes twArmR{0%,100%{transform:rotate(72deg)}50%{transform:rotate(58deg)}}
+ @keyframes twLeg{0%,100%{transform:rotate(7deg)}50%{transform:rotate(-7deg)}}
+ .bear.tw .b-turn{animation:twTurn calc(3.4s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.tw .b-body{animation:twBody calc(1.7s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.tw .b-arm-l{animation:twArmL calc(1.7s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.tw .b-arm-r{animation:twArmR calc(1.7s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.tw .b-leg-l{animation:twLeg calc(1.7s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.tw .b-leg-r{animation:twLeg calc(1.7s/var(--spd)) ease-in-out infinite calc(var(--dl) - .3s)}
+
+ /* Skipping hop */
+ @keyframes skBody{0%{transform:translateY(0)}15%{transform:translateY(-16px)}30%{transform:translateY(-2px)}45%{transform:translateY(-9px)}60%{transform:translateY(0)}100%{transform:translateY(0)}}
+ @keyframes skLegL{0%,60%,100%{transform:rotate(12deg)}15%{transform:rotate(-32deg)}45%{transform:rotate(-20deg)}}
+ @keyframes skLegR{0%,60%,100%{transform:rotate(-12deg)}15%{transform:rotate(32deg)}45%{transform:rotate(20deg)}}
+ @keyframes skArmL{0%,100%{transform:rotate(-42deg)}30%{transform:rotate(-74deg)}}
+ @keyframes skArmR{0%,100%{transform:rotate(42deg)}30%{transform:rotate(74deg)}}
+ @keyframes skShadow{0%,60%,100%{transform:scaleX(1);opacity:.35}15%{transform:scaleX(.55);opacity:.18}}
+ .bear.sk .b-body{animation:skBody calc(1s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sk .b-leg-l{animation:skLegL calc(1s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sk .b-leg-r{animation:skLegR calc(1s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sk .b-arm-l{animation:skArmL calc(1s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sk .b-arm-r{animation:skArmR calc(1s/var(--spd)) ease-in-out infinite var(--dl)}
+ .bear.sk .b-shadow{animation:skShadow calc(1s/var(--spd)) ease-in-out infinite var(--dl)}
+
+ /* Town inner motion */
+ .flick{ animation:flick 5s steps(1) infinite; }
+ @keyframes flick{0%,90%,100%{opacity:1}92%{opacity:.2}94%{opacity:1}96%{opacity:.4}}
+ .bird{ animation:birdfly calc(9s/var(--spd)) ease-in-out infinite; transform-box:fill-box; }
+ .bird .wing{ animation:flap calc(.35s/var(--spd)) ease-in-out infinite; transform-box:fill-box; transform-origin:50% 100%; }
+ @keyframes birdfly{0%{transform:translate(0,0)}50%{transform:translate(150px,-34px)}100%{transform:translate(0,0)}}
+ @keyframes flap{0%,100%{transform:scaleY(1)}50%{transform:scaleY(.4)}}
+ .lamp{ animation:lampGlow 4s ease-in-out infinite; transform-box:fill-box; }
+ @keyframes lampGlow{0%,100%{opacity:.9}50%{opacity:.55}}
+
+ /* Revelers */
+ .rev{ transform-box:fill-box; transform-origin:50% 100%; }
+ .rev-spin{ animation:revspin calc(4.4s/var(--spd)) linear infinite; }
+ @keyframes revspin{to{transform:rotate(360deg)}}
+ .rev-sway{ animation:revsway calc(2.2s/var(--spd)) ease-in-out infinite; }
+ @keyframes revsway{0%,100%{transform:rotate(-6deg)}50%{transform:rotate(6deg)}}
+ .rev-weave{ animation:revweave calc(3.2s/var(--spd)) ease-in-out infinite; }
+ @keyframes revweave{0%,100%{transform:translateX(-15px) rotate(-3deg)}50%{transform:translateX(15px) rotate(3deg)}}
+ .rev-arch{ animation:revarch calc(3s/var(--spd)) ease-in-out infinite; }
+ @keyframes revarch{0%,100%{transform:translateY(0) rotate(-2deg)}50%{transform:translateY(-3px) rotate(2deg)}}
+ .dogbob{ animation:dogbob calc(.5s/var(--spd)) ease-in-out infinite; transform-box:fill-box; }
+ @keyframes dogbob{0%,100%{transform:translateY(0)}50%{transform:translateY(-2px)}}
+ .dog{ animation:dogshake 6s ease-in-out infinite; transform-box:fill-box; transform-origin:50% 100%; }
+ @keyframes dogshake{0%,78%,100%{transform:rotate(0)}82%{transform:rotate(-7deg)}88%{transform:rotate(7deg)}94%{transform:rotate(-4deg)}}
+ .dleg{ transform-box:fill-box; transform-origin:50% 0; }
+ .dleg.a{ animation:dlegA calc(.42s/var(--spd)) ease-in-out infinite; }
+ .dleg.b{ animation:dlegB calc(.42s/var(--spd)) ease-in-out infinite; }
+ @keyframes dlegA{0%,100%{transform:rotate(18deg)}50%{transform:rotate(-18deg)}}
+ @keyframes dlegB{0%,100%{transform:rotate(-18deg)}50%{transform:rotate(18deg)}}
+ .racarm{ transform-box:fill-box; transform-origin:50% 0; }
+ .racarm.l{ animation:racL calc(.34s/var(--spd)) ease-in-out infinite; }
+ .racarm.r{ animation:racR calc(.34s/var(--spd)) ease-in-out infinite; }
+ @keyframes racL{0%,100%{transform:rotate(-6deg)}50%{transform:rotate(-46deg)}}
+ @keyframes racR{0%,100%{transform:rotate(-46deg)}50%{transform:rotate(-6deg)}}
+
+ /* Banners */
+ .flags{ animation:flagsway calc(3.6s/var(--spd)) ease-in-out infinite; transform-box:fill-box; }
+ @keyframes flagsway{0%,100%{transform:translateY(0) rotate(-.6deg)}50%{transform:translateY(3px) rotate(.6deg)}}
+
+ /* Paper flowers */
+ .flowers{ position:absolute; inset:0; z-index:9; pointer-events:none; overflow:hidden; }
+ .flower{ position:absolute; top:-8%; font-size:16px; will-change:transform;
+ animation:fall linear infinite; animation-duration:calc(var(--fd,14s)/var(--spd)); animation-delay:var(--fdl,0s);
+ filter:drop-shadow(0 2px 4px rgba(0,0,0,.4)); }
+ @keyframes fall{
+ 0%{ transform:translate(0,-10%) rotate(0deg); opacity:0 }
+ 10%{opacity:.95}
+ 50%{ transform:translate(var(--fx,20px),55vh) rotate(180deg) }
+ 90%{opacity:.9}
+ 100%{ transform:translate(calc(var(--fx,20px)*-1),115vh) rotate(360deg); opacity:0 }
+ }
+
+ /* Vignette */
+ .vig{ position:absolute; inset:0; z-index:10; pointer-events:none;
+ background:radial-gradient(130% 100% at 50% 40%, transparent 55%, rgba(6,0,18,.55) 100%);
+ box-shadow:inset 0 0 0 1px rgba(255,255,255,.03); border-radius:18px; }
+
+ /* ---------- Ribbon ---------- */
+ .ribbon{ margin:16px 0 6px; }
+ .ribbon .lbl{ font-size:11px; letter-spacing:.15em; text-transform:uppercase; color:var(--muted); margin-bottom:8px; display:flex; justify-content:space-between; }
+ .hoods{ display:flex; gap:8px; }
+ .hood-seg{
+ flex:1; position:relative; background:var(--surface); border:1px solid var(--line);
+ border-radius:12px; padding:9px 8px 10px; overflow:hidden; text-align:center; transition:.3s;
+ }
+ .hood-seg .n{ font-size:9px; color:var(--muted); letter-spacing:.1em; }
+ .hood-seg .t{ font-size:clamp(10px,1.6vw,13px); font-weight:600; color:var(--text); margin-top:2px; font-family:var(--disp); letter-spacing:.02em; }
+ .hood-seg .fill{ position:absolute; left:0; bottom:0; height:3px; width:0%; background:linear-gradient(90deg,var(--a1),var(--a2)); }
+ .hood-seg.active{ border-color:var(--a2); box-shadow:0 0 22px rgba(0,229,255,.28), inset 0 0 30px rgba(255,46,136,.10); transform:translateY(-2px); }
+ .hood-seg.active .t{ color:#fff; }
+ .hood-seg.done .fill{ width:100%!important; opacity:.5; }
+
+ /* ---------- Controls ---------- */
+ .controls{
+ display:flex; flex-wrap:wrap; align-items:center; gap:10px;
+ background:var(--surface); border:1px solid var(--line); border-radius:var(--r);
+ padding:12px; margin-top:14px;
+ }
+ .btn{
+ font-family:var(--body); font-size:13px; font-weight:600; color:var(--text);
+ background:var(--surface2); border:1px solid var(--line); border-radius:10px;
+ padding:9px 13px; cursor:pointer; display:inline-flex; align-items:center; gap:7px;
+ transition:transform .15s, background .2s, border-color .2s, box-shadow .2s; line-height:1;
+ }
+ .btn:hover{ background:#351765; transform:translateY(-1px); }
+ .btn:active{ transform:translateY(0); }
+ .btn .ic{ font-size:14px; }
+ .btn[aria-pressed="true"]{ border-color:var(--a2); background:#2b1a5c; box-shadow:0 0 18px rgba(0,229,255,.25); color:#fff; }
+ .btn.primary{ background:linear-gradient(92deg,var(--a1),#c81f6d); border-color:transparent; }
+ .btn.primary:hover{ box-shadow:0 0 22px rgba(255,46,136,.4); }
+ :focus-visible{ outline:2px solid var(--a2); outline-offset:2px; border-radius:10px; }
+
+ .tempo{ display:flex; align-items:center; gap:8px; margin-left:auto; }
+ .tempo label{ font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); }
+ .tempo input[type=range]{ width:clamp(90px,18vw,150px); accent-color:var(--a1); cursor:pointer; }
+ .tempo .val{ font-variant-numeric:tabular-nums; font-weight:700; font-size:13px; min-width:44px; text-align:right; color:var(--a2); }
+ .sep{ width:1px; align-self:stretch; background:var(--line); margin:2px 2px; }
+
+ .keys{ font-size:11px; color:var(--muted); margin-top:8px; line-height:1.6; }
+ .keys kbd{ background:var(--surface2); border:1px solid var(--line); border-bottom-width:2px; border-radius:5px; padding:1px 6px; font-family:var(--body); font-size:10px; color:var(--text); }
+
+ /* ---------- Guide ---------- */
+ .guide{ margin-top:16px; }
+ .guide h2{ font-family:var(--disp); font-weight:600; font-size:18px; margin:0 0 10px; letter-spacing:.02em; }
+ .cards{ display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:12px; }
+ .card{
+ background:var(--surface); border:1px solid var(--line); border-radius:12px; padding:12px;
+ display:flex; gap:11px; align-items:center;
+ }
+ .card .swk{ width:40px; height:40px; border-radius:10px; flex:0 0 auto; box-shadow:0 0 16px currentColor; }
+ .card .nm{ font-weight:700; font-size:14px; }
+ .card .dn{ font-size:11px; color:var(--muted); margin-top:2px; }
+ .card .ds{ font-size:11px; color:var(--muted); margin-top:4px; line-height:1.4; }
+
+ footer.legal{ margin:18px 2px 6px; font-size:10.5px; color:var(--muted); letter-spacing:.03em; line-height:1.5; }
+
+ /* ---------- State ---------- */
+ .app.paused .stage *{ animation-play-state:paused !important; }
+ .app.paused .stage{ animation-play-state:paused !important; }
+ .app.reduced .stage *, .app.reduced .flower{ animation:none !important; }
+ .app.reduced .stage{ animation:none !important; }
+ .app.reduced .bear.tw .b-turn, .app.reduced .bear.ht .b-turn{ transform:scaleX(1) !important; }
+
+ @media (max-width:560px){
+ .tempo{ margin-left:0; width:100%; justify-content:space-between; }
+ .status{ min-width:0; flex:1; text-align:left; }
+ }
+ @media (prefers-reduced-motion: reduce){
+ .stage, .stage *{ animation-duration:.001ms !important; animation-iteration-count:1 !important; }
+ }
+ .sr{ position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); }
+</style>
+</head>
+<body>
+<div class="app" id="app">
+
+ <header class="top">
+ <div class="brand">
+ <h1>The Dancing Bear Procession</h1>
+ <div class="sub">A kinetic festival film · one HTML reel</div>
+ </div>
+ <div class="status" role="status" aria-live="polite" id="status">
+ <div>Now passing <span class="hood" id="stHood">Lantern Row</span></div>
+ <div><b id="stPlay">Playing</b> · tempo <b id="stTempo">1.0×</b> · <b id="stDir">eastbound →</b></div>
+ </div>
+ </header>
+
+ <div class="stage" id="stage" role="img" aria-label="A night parade of five illustrated dancing bears leading revelers, dogs, raccoons and birds through a neon concert town.">
+ <div class="sky" id="sky"><div class="moon"></div></div>
+
+ <div class="layer l-far"><div class="track" id="tkFar"></div></div>
+ <div class="layer l-mid"><div class="track" id="tkMid"></div></div>
+ <div class="layer l-lan"><div class="track" id="tkLan"></div></div>
+ <div class="ground"></div>
+ <div class="layer l-rev"><div class="track" id="tkRev"></div></div>
+ <div class="layer l-ban"><div class="track" id="tkBan"></div></div>
+
+ <div class="bears" id="bears"></div>
+
+ <div class="layer l-fg"><div class="track" id="tkFg"></div></div>
+ <div class="flowers" id="flowers"></div>
+ <div class="vig"></div>
+ </div>
+
+ <div class="ribbon">
+ <div class="lbl"><span>The route · five neighborhoods</span><span id="loopCount">loop 1</span></div>
+ <div class="hoods" id="hoods"></div>
+ </div>
+
+ <div class="controls" role="group" aria-label="Parade controls">
+ <button class="btn primary" id="btnPlay" aria-pressed="false" title="Pause / play (Space)"><span class="ic" id="playIc">⏸</span><span id="playTx">Pause</span></button>
+ <button class="btn" id="btnDir" title="Reverse parade direction (← / →)"><span class="ic">⇄</span> Direction</button>
+ <button class="btn" id="btnRestart" title="Restart the loop (R)"><span class="ic">↺</span> Restart</button>
+ <div class="sep"></div>
+ <button class="btn" id="btnLabels" aria-pressed="false" title="Show character labels (L)"><span class="ic">🏷</span> Labels</button>
+ <button class="btn" id="btnReduce" aria-pressed="false" title="Reduced motion (M)"><span class="ic">☾</span> Reduced motion</button>
+ <div class="tempo">
+ <label for="tempo">Tempo</label>
+ <button class="btn" id="btnSlow" title="Slower (−)" aria-label="Slower">−</button>
+ <input type="range" id="tempo" min="0.5" max="2" step="0.1" value="1" aria-label="Parade tempo">
+ <button class="btn" id="btnFast" title="Faster (+)" aria-label="Faster">+</button>
+ <span class="val" id="tempoVal">1.0×</span>
+ </div>
+ </div>
+ <div class="keys">
+ Keyboard: <kbd>Space</kbd> pause · <kbd>←</kbd><kbd>→</kbd> direction · <kbd>+</kbd><kbd>−</kbd> tempo · <kbd>L</kbd> labels · <kbd>R</kbd> restart · <kbd>M</kbd> reduced motion
+ </div>
+
+ <div class="guide">
+ <h2>Character Guide</h2>
+ <div class="cards" id="cards"></div>
+ </div>
+
+ <footer class="legal">
+ Five newly-illustrated bears — original characters drawn for this piece — lead the procession in the playful, tie-dyed spirit of festival culture, without tracing any protected mark. Built as a single self-contained HTML reel: no images, no fonts, no network.
+ </footer>
+
+ <div class="sr" aria-live="polite" id="live"></div>
+</div>
+
+<script>
+(function(){
+ "use strict";
+ var app = document.getElementById('app');
+
+ /* ---------- Bear roster ---------- */
+ var BEARS = [
+ {name:'Juniper', dance:'High Step', cls:'hs', body:'#ff2e88', dark:'#a4004f', belly:'#ffd6ea', desc:'Knees high, chin proud — leads the pack with a marching lift.', dl:'0s'},
+ {name:'Cosmo', dance:'Shoulder Bounce', cls:'sb', body:'#00e5ff', dark:'#00707f', belly:'#d6faff', desc:'Rocks shoulder to shoulder, weight rolling side to side.', dl:'-0.45s'},
+ {name:'Marigold',dance:'Heel Turn', cls:'ht', body:'#ffb020', dark:'#a86300', belly:'#fff0cf', desc:'Struts, then pivots on a heel to flip and face back.', dl:'-0.95s'},
+ {name:'Tango', dance:'Slow Twirl', cls:'tw', body:'#a06bff', dark:'#5227a3', belly:'#e7dcff', desc:'Arms outstretched, spinning slow and dreamy.', dl:'-1.35s'},
+ {name:'Pip', dance:'Skipping Hop', cls:'sk', body:'#3ef0a0', dark:'#12915a', belly:'#d6ffe9', desc:'A buoyant skip — up on the beat, ground on the off.', dl:'-0.2s'}
+ ];
+
+ function bearSVG(c){
+ return '<div class="bear-slot">'
+ + '<div class="tag">'+c.name+'<span>'+c.dance+'</span></div>'
+ + '<svg class="bear '+c.cls+'" viewBox="0 0 100 140" style="--dl:'+c.dl+'" role="img" aria-label="'+c.name+', a bear performing the '+c.dance+'">'
+ + '<ellipse class="b-shadow" cx="50" cy="135" rx="27" ry="5"/>'
+ + '<g class="b-turn"><g class="b-body">'
+ + '<g class="b-leg b-leg-l"><rect x="39" y="95" width="10" height="36" rx="5" fill="'+c.dark+'"/><ellipse cx="44" cy="130" rx="8" ry="4" fill="'+c.dark+'"/></g>'
+ + '<g class="b-leg b-leg-r"><rect x="51" y="95" width="10" height="36" rx="5" fill="'+c.body+'"/><ellipse cx="56" cy="130" rx="8" ry="4" fill="'+c.body+'"/></g>'
+ + '<g class="b-arm b-arm-l"><rect x="25" y="57" width="10" height="31" rx="5" fill="'+c.dark+'"/></g>'
+ + '<rect class="b-torso" x="31" y="51" width="38" height="54" rx="18" fill="'+c.body+'"/>'
+ + '<ellipse cx="50" cy="82" rx="13" ry="17" fill="'+c.belly+'"/>'
+ + '<path d="M53 63 L45 80 L51 80 L47 92 L58 74 L52 74 Z" fill="'+c.dark+'" opacity=".55"/>'
+ + '<g class="b-head">'
+ + '<circle cx="34" cy="19" r="9.5" fill="'+c.body+'"/><circle cx="34" cy="19" r="4.4" fill="'+c.dark+'"/>'
+ + '<circle cx="66" cy="19" r="9.5" fill="'+c.body+'"/><circle cx="66" cy="19" r="4.4" fill="'+c.dark+'"/>'
+ + '<circle cx="50" cy="34" r="21" fill="'+c.body+'"/>'
+ + '<ellipse cx="50" cy="42" rx="10.5" ry="8.5" fill="'+c.belly+'"/>'
+ + '<circle cx="50" cy="39" r="3.1" fill="#20112e"/>'
+ + '<circle cx="42.5" cy="31" r="2.7" fill="#20112e"/><circle cx="57.5" cy="31" r="2.7" fill="#20112e"/>'
+ + '<circle cx="43.4" cy="30" r=".9" fill="#fff"/><circle cx="58.4" cy="30" r=".9" fill="#fff"/>'
+ + '<path d="M45 45 Q50 49 55 45" stroke="'+c.dark+'" stroke-width="1.4" fill="none" stroke-linecap="round"/>'
+ + '</g>'
+ + '<g class="b-arm b-arm-r"><rect x="65" y="57" width="10" height="31" rx="5" fill="'+c.body+'"/></g>'
+ + '</g></g>'
+ + '</svg></div>';
+ }
+ document.getElementById('bears').innerHTML = BEARS.map(bearSVG).join('');
+
+ /* Guide cards */
+ document.getElementById('cards').innerHTML = BEARS.map(function(c){
+ return '<div class="card"><div class="swk" style="background:'+c.body+';color:'+c.body+'"></div>'
+ +'<div><div class="nm">'+c.name+'</div><div class="dn">'+c.dance+'</div><div class="ds">'+c.desc+'</div></div></div>';
+ }).join('');
+
+ /* ---------- Neighborhoods / ribbon ---------- */
+ var HOODS = ['Lantern Row','Marquee Mile','Drum Circle Green','Firefly Commons','Encore Plaza'];
+ var hoodsEl = document.getElementById('hoods');
+ hoodsEl.innerHTML = HOODS.map(function(h,i){
+ return '<div class="hood-seg" data-i="'+i+'"><div class="n">STOP '+(i+1)+'</div><div class="t">'+h+'</div><div class="fill"></div></div>';
+ }).join('');
+ var segEls = Array.prototype.slice.call(hoodsEl.children);
+
+ /* ---------- Stars ---------- */
+ (function(){
+ var sky = document.getElementById('sky'), s='';
+ for(var i=0;i<70;i++){
+ var x=Math.random()*100, y=Math.random()*62, d=(Math.random()*4+2).toFixed(2), dl=(-Math.random()*4).toFixed(2), sz=(Math.random()<.15?3:Math.random()<.5?2:1);
+ s+='<span class="star" style="left:'+x.toFixed(1)+'%;top:'+y.toFixed(1)+'%;width:'+sz+'px;height:'+sz+'px;animation-duration:'+d+'s;animation-delay:'+dl+'s"></span>';
+ }
+ sky.insertAdjacentHTML('beforeend', s);
+ })();
+
+ /* ---------- Paper flowers ---------- */
+ (function(){
+ var fl = document.getElementById('flowers'), s='', glyphs=['✿','❁','✾','❀','⚘'];
+ var cols=['#ff2e88','#00e5ff','#ffb020','#a06bff','#3ef0a0','#ff7ab6'];
+ for(var i=0;i<16;i++){
+ var left=(Math.random()*100).toFixed(1), fd=(Math.random()*10+11).toFixed(1), fdl=(-Math.random()*14).toFixed(1),
+ fx=((Math.random()*70+20)*(Math.random()<.5?-1:1)).toFixed(0), sz=(Math.random()*14+12).toFixed(0),
+ g=glyphs[i%glyphs.length], c=cols[i%cols.length];
+ s+='<span class="flower" style="left:'+left+'%;--fd:'+fd+'s;--fdl:'+fdl+'s;--fx:'+fx+'px;font-size:'+sz+'px;color:'+c+'">'+g+'</span>';
+ }
+ fl.innerHTML=s;
+ })();
+
+ /* ---------- Town tile builders ---------- */
+ function tri(){ return; }
+ function rep(str){ return str+str+str; } // 3 identical tiles -> seamless -1200px scroll
+
+ // FAR: skyline + venue
+ function farTile(){
+ var W=1200,H=372, s='<svg width="'+W+'" height="'+H+'" viewBox="0 0 '+W+' '+H+'" xmlns="http://www.w3.org/2000/svg">';
+ var shades=['#1a0838','#230c48','#2c1058'];
+ var x=0, seed=7;
+ while(x<W){
+ var w=64+((seed*37)%56), h=120+((seed*53)%180); if(x+w>W) w=W-x;
+ var y=H-h; var col=shades[seed%3];
+ s+='<rect x="'+x+'" y="'+y+'" width="'+w+'" height="'+h+'" fill="'+col+'"/>';
+ for(var wy=y+14; wy<H-16; wy+=18){
+ for(var wx=x+8; wx<x+w-8; wx+=16){
+ if((wx+wy+seed)%3===0){
+ var lit=((wx*7+wy*3)%9===0);
+ s+='<rect class="'+(lit?'flick':'')+'" x="'+wx+'" y="'+wy+'" width="5" height="7" rx="1" fill="'+(lit?'#ffd98a':'#0d0322')+'" opacity="'+(lit?'.9':'.5')+'"/>';
+ }
+ }
+ }
+ x+=w+8; seed=(seed*7+13)%97;
+ }
+ // Venue amphitheater around x 480-760
+ var vx=470,vw=270,vy=H-210;
+ s+='<path d="M'+vx+' '+H+' L'+vx+' '+(vy+40)+' Q'+(vx+vw/2)+' '+(vy-70)+' '+(vx+vw)+' '+(vy+40)+' L'+(vx+vw)+' '+H+' Z" fill="#301056"/>';
+ s+='<path d="M'+(vx+18)+' '+(vy+40)+' Q'+(vx+vw/2)+' '+(vy-46)+' '+(vx+vw-18)+' '+(vy+40)+'" fill="none" stroke="#00e5ff" stroke-width="3" opacity=".8"/>';
+ s+='<path d="M'+(vx+40)+' '+(vy+54)+' Q'+(vx+vw/2)+' '+(vy-18)+' '+(vx+vw-40)+' '+(vy+54)+'" fill="none" stroke="#ff2e88" stroke-width="3" opacity=".7"/>';
+ s+='<rect x="'+(vx+vw/2-58)+'" y="'+(vy+62)+'" width="116" height="26" rx="5" fill="#12002b" stroke="#ffb020" stroke-width="1.5"/>';
+ s+='<text class="flick" x="'+(vx+vw/2)+'" y="'+(vy+80)+'" text-anchor="middle" font-family="Georgia,serif" font-size="15" fill="#ffb020" letter-spacing="2">TONIGHT</text>';
+ // pillars
+ for(var p=0;p<5;p++){ var px=vx+26+p*((vw-52)/4); s+='<rect x="'+px+'" y="'+(vy+90)+'" width="7" height="'+(H-vy-90)+'" fill="#3a1668"/>'; }
+ s+='</svg>';
+ return s;
+ }
+
+ // MID: buildings + neon signs + birds
+ function midTile(){
+ var W=1200,H=300, s='<svg width="'+W+'" height="'+H+'" viewBox="0 0 '+W+' '+H+'" xmlns="http://www.w3.org/2000/svg">';
+ var cols=['#20093f','#2a0f52'];
+ var x=0,seed=3;
+ while(x<W){
+ var w=90+((seed*29)%70), h=90+((seed*61)%150); if(x+w>W)w=W-x;
+ s+='<rect x="'+x+'" y="'+(H-h)+'" width="'+w+'" height="'+h+'" fill="'+cols[seed%2]+'"/>';
+ x+=w; seed=(seed*11+7)%89;
+ }
+ // neon signs
+ var signs=[[120,80,'#ff2e88','MUSIC'],[360,110,'#00e5ff','LIVE'],[600,70,'#ffb020','DANCE'],[820,120,'#a06bff','GLOW'],[1010,90,'#3ef0a0','JAM']];
+ signs.forEach(function(sg,i){
+ var sx=sg[0],sy=sg[1],c=sg[2],t=sg[3];
+ s+='<g style="filter:drop-shadow(0 0 8px '+c+')"><rect class="'+(i%2?'flick':'')+'" x="'+sx+'" y="'+sy+'" width="'+(t.length*13+16)+'" height="30" rx="6" fill="#0e0326" stroke="'+c+'" stroke-width="2"/>'
+ +'<text x="'+(sx+ (t.length*13+16)/2)+'" y="'+(sy+21)+'" text-anchor="middle" font-family="Georgia,serif" font-size="16" fill="'+c+'" letter-spacing="1.5">'+t+'</text></g>';
+ });
+ // birds fluttering between signs
+ var birds=[[180,50,'0s'],[540,44,'-3s'],[900,58,'-5.5s']];
+ birds.forEach(function(b){
+ s+='<g class="bird" style="animation-delay:'+b[2]+'"><g class="wing" transform="translate('+b[0]+','+b[1]+')">'
+ +'<path d="M0 0 Q7 -7 14 0 Q21 -7 28 0" fill="none" stroke="#f3e9ff" stroke-width="2.4" stroke-linecap="round"/></g></g>';
+ });
+ s+='</svg>';
+ return s;
+ }
+
+ // LANTERN: string lights + poles + lamps
+ function lanTile(){
+ var W=1200,H=250, s='<svg width="'+W+'" height="'+H+'" viewBox="0 0 '+W+' '+H+'" xmlns="http://www.w3.org/2000/svg">';
+ // string lights swag across top
+ s+='<path d="M0 34 Q100 66 200 34 T400 34 T600 34 T800 34 T1000 34 T1200 34" fill="none" stroke="#3a1f66" stroke-width="2"/>';
+ var bc=['#ff2e88','#00e5ff','#ffb020','#3ef0a0','#a06bff'];
+ for(var lx=20;lx<W;lx+=40){
+ var yy=34+Math.abs(Math.sin(lx/63.6))*30*0; // approx
+ var sag=34 + (Math.sin(lx/100)*0);
+ var y=34 + (18*Math.abs(Math.sin((lx%200)/200*Math.PI)));
+ var c=bc[(lx/40)%bc.length|0];
+ s+='<circle class="lamp" cx="'+lx+'" cy="'+y+'" r="4" fill="'+c+'" style="filter:drop-shadow(0 0 5px '+c+');animation-delay:'+(-(lx%9)*0.3).toFixed(1)+'s"/>';
+ }
+ // lantern poles + streetlamps
+ var poles=[80,320,560,800,1040];
+ poles.forEach(function(px,i){
+ var c=bc[i%bc.length];
+ s+='<rect x="'+(px-3)+'" y="90" width="6" height="'+(H-90)+'" fill="#31145c"/>';
+ s+='<path d="M'+px+' 92 q22 0 22 18" fill="none" stroke="#31145c" stroke-width="5"/>';
+ s+='<circle class="lamp" cx="'+(px+22)+'" cy="116" r="8" fill="'+c+'" style="filter:drop-shadow(0 0 12px '+c+')"/>';
+ // ground pool
+ s+='<ellipse cx="'+(px+22)+'" cy="'+(H-8)+'" rx="60" ry="14" fill="'+c+'" opacity=".12"/>';
+ });
+ s+='</svg>';
+ return s;
+ }
+
+ // BANNERS: triangular flags
+ function banTile(){
+ var W=1200,H=78, s='<svg width="'+W+'" height="'+H+'" viewBox="0 0 '+W+' '+H+'" xmlns="http://www.w3.org/2000/svg">';
+ s+='<path d="M0 14 Q300 30 600 14 T1200 14" fill="none" stroke="#4a2f78" stroke-width="2"/>';
+ var bc=['#ff2e88','#00e5ff','#ffb020','#3ef0a0','#a06bff','#ff7ab6'];
+ s+='<g class="flags">';
+ for(var fx=16;fx<W;fx+=34){
+ var y=14 + 10*Math.abs(Math.sin((fx%200)/200*Math.PI));
+ var c=bc[(fx/34)%bc.length|0];
+ s+='<path d="M'+fx+' '+y+' L'+(fx+24)+' '+y+' L'+(fx+12)+' '+(y+26)+' Z" fill="'+c+'" opacity=".92"/>';
+ }
+ s+='</g></svg>';
+ return s;
+ }
+
+ // Human figure helper
+ function person(x, col, cls, dl, armsUp){
+ var g='<g class="rev '+cls+'" transform="translate('+x+',0)" style="animation-delay:'+dl+'">';
+ g+='<path d="M0 150 L-13 150 L-7 92 Q0 80 7 92 L13 150 Z" fill="'+col+'"/>';
+ g+='<circle cx="0" cy="72" r="10" fill="'+col+'"/>';
+ if(armsUp){
+ g+='<path d="M-6 96 Q-20 76 -13 60" stroke="'+col+'" stroke-width="6" fill="none" stroke-linecap="round"/>';
+ g+='<path d="M6 96 Q20 76 13 60" stroke="'+col+'" stroke-width="6" fill="none" stroke-linecap="round"/>';
+ }else{
+ g+='<path d="M-6 98 Q-16 108 -12 120" stroke="'+col+'" stroke-width="6" fill="none" stroke-linecap="round"/>';
+ g+='<path d="M6 98 Q16 108 12 120" stroke="'+col+'" stroke-width="6" fill="none" stroke-linecap="round"/>';
+ }
+ // glowstick accent
+ g+='<circle cx="'+(armsUp?-13:12)+'" cy="'+(armsUp?60:120)+'" r="3.4" fill="#00e5ff" style="filter:drop-shadow(0 0 5px #00e5ff)"/>';
+ g+='</g>';
+ return g;
+ }
+
+ // REVELERS tile
+ function revTile(){
+ var W=1200,H=155, s='<svg width="'+W+'" height="'+H+'" viewBox="0 0 '+W+' '+H+'" xmlns="http://www.w3.org/2000/svg">';
+ var C='#5a3a92', C2='#4a2f78';
+ // spinner
+ s+=person(90,C,'rev-spin','0s',true);
+ // weaver between two poles
+ s+='<rect x="196" y="70" width="4" height="80" fill="#3a1f66"/><rect x="266" y="70" width="4" height="80" fill="#3a1f66"/>';
+ s+=person(232,C2,'rev-weave','-0.6s',false);
+ // dog trotting
+ s+='<g class="dog" transform="translate(340,0)"><g class="dogbob">'
+ +'<ellipse cx="0" cy="132" rx="20" ry="9" fill="#4a2f78"/>'
+ +'<circle cx="18" cy="123" r="8" fill="#4a2f78"/><path d="M22 116 l6 -7 l2 8 z" fill="#4a2f78"/>'
+ +'<ellipse cx="25" cy="126" rx="5" ry="3" fill="#5a3a92"/>'
+ +'<path d="M-18 130 q-10 -4 -12 -12" stroke="#4a2f78" stroke-width="4" fill="none" stroke-linecap="round"/>'
+ +'<rect class="dleg a" x="-12" y="138" width="4" height="12" fill="#4a2f78"/>'
+ +'<rect class="dleg b" x="-4" y="138" width="4" height="12" fill="#4a2f78"/>'
+ +'<rect class="dleg b" x="8" y="138" width="4" height="12" fill="#4a2f78"/>'
+ +'<rect class="dleg a" x="14" y="138" width="4" height="12" fill="#4a2f78"/>'
+ +'</g></g>';
+ // link-hands row of three
+ s+=person(430,C,'rev-sway','0s',false)+person(468,C2,'rev-sway','-0.4s',false)+person(506,C,'rev-sway','-0.8s',false);
+ // raccoon drumming on a crate
+ s+='<g transform="translate(600,0)">'
+ +'<rect x="-16" y="122" width="32" height="26" rx="3" fill="#241043" stroke="#4a2f78" stroke-width="1.5"/>'
+ +'<circle cx="0" cy="98" r="12" fill="#6b6b7d"/><circle cx="0" cy="108" r="9" fill="#6b6b7d"/>'
+ +'<path d="M-6 94 q-3 -8 2 -12 M6 94 q3 -8 -2 -12" stroke="#6b6b7d" stroke-width="5" fill="none" stroke-linecap="round"/>'
+ +'<rect x="-9" y="93" width="18" height="6" rx="3" fill="#2a2a38"/>'
+ +'<circle cx="-4" cy="96" r="1.6" fill="#fff"/><circle cx="4" cy="96" r="1.6" fill="#fff"/>'
+ +'<rect class="racarm l" x="-13" y="106" width="5" height="20" rx="2.5" fill="#6b6b7d"/>'
+ +'<rect class="racarm r" x="8" y="106" width="5" height="20" rx="2.5" fill="#6b6b7d"/>'
+ +'</g>';
+ // arch pair
+ s+='<g transform="translate(720,0)">'+person(0,C2,'rev-arch','0s',true)+person(46,C2,'rev-arch','-1.5s',true)
+ +'<path d="M8 62 Q46 34 84 62" fill="none" stroke="#a06bff" stroke-width="2.5" opacity=".6"/></g>';
+ // second spinner + weaver
+ s+=person(880,C,'rev-spin','-2.1s',true);
+ s+='<rect x="960" y="70" width="4" height="80" fill="#3a1f66"/><rect x="1030" y="70" width="4" height="80" fill="#3a1f66"/>';
+ s+=person(996,C2,'rev-weave','-1.3s',false);
+ s+=person(1110,C,'rev-sway','-0.3s',false);
+ s+='</svg>';
+ return s;
+ }
+
+ // FOREGROUND crowd heads
+ function fgTile(){
+ var W=1200,H=130, s='<svg width="'+W+'" height="'+H+'" viewBox="0 0 '+W+' '+H+'" xmlns="http://www.w3.org/2000/svg">';
+ var x=0,seed=5;
+ while(x<W){
+ var r=26+((seed*17)%20);
+ s+='<circle cx="'+x+'" cy="'+(H-6)+'" r="'+r+'" fill="#0a0018"/>';
+ s+='<rect x="'+(x-r-10)+'" y="'+(H-6)+'" width="'+(2*r+20)+'" height="'+r+'" fill="#0a0018"/>';
+ // occasional raised glowstick
+ if(seed%3===0){ s+='<rect x="'+(x-3)+'" y="'+(H-6-r-30)+'" width="4" height="30" rx="2" fill="'+(['#ff2e88','#00e5ff','#3ef0a0','#ffb020'][seed%4])+'" opacity=".85" style="filter:drop-shadow(0 0 5px #fff)"/>'; }
+ x+=r+18; seed=(seed*13+9)%101;
+ }
+ s+='</svg>';
+ return s;
+ }
+
+ document.getElementById('tkFar').innerHTML = rep(farTile());
+ document.getElementById('tkMid').innerHTML = rep(midTile());
+ document.getElementById('tkLan').innerHTML = rep(lanTile());
+ document.getElementById('tkBan').innerHTML = rep(banTile());
+ document.getElementById('tkRev').innerHTML = rep(revTile());
+ document.getElementById('tkFg').innerHTML = rep(fgTile());
+
+ /* ---------- State + controls ---------- */
+ var state = { paused:false, dir:1, tempo:1, labels:false, reduced:false, loop:1 };
+ var live = document.getElementById('live');
+
+ function setSpd(){ app.style.setProperty('--spd', state.tempo); }
+ function fmtT(){ return state.tempo.toFixed(1)+'×'; }
+
+ function updateStatus(){
+ document.getElementById('stPlay').textContent = state.reduced ? 'Reduced' : (state.paused ? 'Paused' : 'Playing');
+ document.getElementById('stTempo').textContent = fmtT();
+ document.getElementById('stDir').textContent = state.dir===1 ? 'eastbound →' : '← westbound';
+ document.getElementById('tempoVal').textContent = fmtT();
+ }
+
+ var btnPlay=document.getElementById('btnPlay'), playIc=document.getElementById('playIc'), playTx=document.getElementById('playTx');
+ function togglePlay(force){
+ state.paused = (typeof force==='boolean') ? force : !state.paused;
+ app.classList.toggle('paused', state.paused);
+ btnPlay.setAttribute('aria-pressed', state.paused ? 'true':'false');
+ playIc.textContent = state.paused ? '▶' : '⏸';
+ playTx.textContent = state.paused ? 'Play' : 'Pause';
+ updateStatus();
+ announce(state.paused ? 'Parade paused' : 'Parade playing');
+ }
+ btnPlay.addEventListener('click', function(){ togglePlay(); });
+
+ document.getElementById('btnDir').addEventListener('click', function(){
+ state.dir *= -1;
+ app.classList.toggle('dir-rev', state.dir===-1);
+ updateStatus();
+ announce('Parade now '+(state.dir===1?'eastbound':'westbound'));
+ });
+
+ var btnLabels=document.getElementById('btnLabels');
+ function toggleLabels(){
+ state.labels=!state.labels;
+ app.classList.toggle('show-labels', state.labels);
+ btnLabels.setAttribute('aria-pressed', state.labels?'true':'false');
+ announce(state.labels?'Character labels shown':'Character labels hidden');
+ }
+ btnLabels.addEventListener('click', toggleLabels);
+
+ var btnReduce=document.getElementById('btnReduce');
+ function toggleReduce(force){
+ state.reduced = (typeof force==='boolean') ? force : !state.reduced;
+ app.classList.toggle('reduced', state.reduced);
+ btnReduce.setAttribute('aria-pressed', state.reduced?'true':'false');
+ updateStatus();
+ announce(state.reduced?'Reduced motion on':'Reduced motion off');
+ }
+ btnReduce.addEventListener('click', function(){ toggleReduce(); });
+
+ var tempo=document.getElementById('tempo');
+ function setTempo(v){
+ state.tempo = Math.min(2, Math.max(0.5, Math.round(v*10)/10));
+ tempo.value = state.tempo; setSpd(); updateStatus();
+ }
+ tempo.addEventListener('input', function(){ setTempo(parseFloat(tempo.value)); });
+ document.getElementById('btnSlow').addEventListener('click', function(){ setTempo(state.tempo-0.1); announce('Tempo '+fmtT()); });
+ document.getElementById('btnFast').addEventListener('click', function(){ setTempo(state.tempo+0.1); announce('Tempo '+fmtT()); });
+
+ var stage=document.getElementById('stage');
+ function restart(){
+ // reset progress
+ pos = 0; lastHood = -1;
+ // hard-restart every CSS animation in the stage
+ stage.style.display='none';
+ void stage.offsetHeight;
+ stage.style.display='';
+ state.loop = 1;
+ document.getElementById('loopCount').textContent='loop 1';
+ updateRibbon(true);
+ announce('Procession restarted from the top');
+ }
+ document.getElementById('btnRestart').addEventListener('click', restart);
+
+ function announce(msg){ live.textContent=msg; }
+
+ /* Keyboard */
+ document.addEventListener('keydown', function(e){
+ var tag=(e.target.tagName||'').toLowerCase();
+ if(tag==='input' && e.key!==' ') { /* let range handle arrows */ }
+ if(e.key===' '){ if(tag==='button'){return;} e.preventDefault(); togglePlay(); }
+ else if(e.key==='ArrowRight'){ e.preventDefault(); if(state.dir!==1) document.getElementById('btnDir').click(); }
+ else if(e.key==='ArrowLeft'){ e.preventDefault(); if(state.dir!==-1) document.getElementById('btnDir').click(); }
+ else if(e.key==='+'||e.key==='='){ e.preventDefault(); setTempo(state.tempo+0.1); announce('Tempo '+fmtT()); }
+ else if(e.key==='-'||e.key==='_'){ e.preventDefault(); setTempo(state.tempo-0.1); announce('Tempo '+fmtT()); }
+ else if(e.key==='l'||e.key==='L'){ toggleLabels(); }
+ else if(e.key==='r'||e.key==='R'){ restart(); }
+ else if(e.key==='m'||e.key==='M'){ toggleReduce(); }
+ });
+
+ /* ---------- Progress engine (drives ribbon + neighborhood) ---------- */
+ var pos=0; // virtual seconds along the route
+ var SEG=8; // seconds per neighborhood
+ var PERIOD=SEG*HOODS.length;
+ var lastHood=-1, lastT=0;
+
+ function updateRibbon(force){
+ var f = ((pos % PERIOD) + PERIOD) % PERIOD;
+ var idx = Math.floor(f/SEG) % HOODS.length;
+ var within = (f - idx*SEG)/SEG;
+ for(var i=0;i<segEls.length;i++){
+ var seg=segEls[i], fill=seg.querySelector('.fill');
+ seg.classList.toggle('active', i===idx);
+ if(i<idx){ seg.classList.add('done'); fill.style.width='100%'; }
+ else if(i===idx){ seg.classList.remove('done'); fill.style.width=(within*100).toFixed(1)+'%'; }
+ else { seg.classList.remove('done'); fill.style.width='0%'; }
+ }
+ if(idx!==lastHood || force){
+ lastHood=idx;
+ document.getElementById('stHood').textContent = HOODS[idx];
+ announce('Now passing '+HOODS[idx]);
+ }
+ }
+
+ function tick(t){
+ if(!lastT) lastT=t;
+ var dt=(t-lastT)/1000; lastT=t;
+ if(dt>0.25) dt=0.25;
+ if(!state.paused && !state.reduced){
+ var prev=pos;
+ pos += dt * state.tempo * state.dir;
+ // loop counter when crossing a full period forward
+ var pl=Math.floor(prev/PERIOD), cl=Math.floor(pos/PERIOD);
+ if(cl>pl){ state.loop++; document.getElementById('loopCount').textContent='loop '+state.loop; }
+ else if(cl<pl && state.loop>1){ state.loop--; document.getElementById('loopCount').textContent='loop '+state.loop; }
+ updateRibbon(false);
+ }
+ requestAnimationFrame(tick);
+ }
+
+ /* ---------- Init ---------- */
+ setSpd(); updateStatus(); updateRibbon(true);
+ if(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches){
+ toggleReduce(true);
+ }
+ requestAnimationFrame(tick);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ee75e7535787/qwen25-7b.html b/data/artifacts/ee75e7535787/qwen25-7b.html
new file mode 100644
index 0000000..1d6e3b9
--- /dev/null
+++ b/data/artifacts/ee75e7535787/qwen25-7b.html
@@ -0,0 +1,211 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>The Dancing Bear Procession</title>
+ <style>
+ body {
+ font-family: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+ background-color: #12002b;
+ color: #f3e9ff;
+ margin: 0;
+ padding: 0;
+ overflow-x: hidden;
+ }
+ canvas {
+ display: block;
+ width: 100%;
+ height: auto;
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: -1;
+ }
+ #controls {
+ position: fixed;
+ bottom: 24px;
+ right: 24px;
+ display: flex;
+ gap: 8px;
+ background-color: rgba(255, 255, 255, 0.3);
+ border-radius: 14px;
+ padding: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, .12), hover 0 16px 48px rgba(0, 0, 0, .18);
+ }
+ #controls label {
+ font-size: 12px;
+ letter-spacing: .15em;
+ color: #f3e9ff;
+ }
+ .progress-ribbon {
+ position: fixed;
+ bottom: 48px;
+ left: 24px;
+ padding: 4px 16px;
+ background-color: rgba(255, 255, 255, 0.3);
+ border-radius: 14px;
+ font-size: 12px;
+ letter-spacing: .15em;
+ color: #f3e9ff;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="paradeCanvas"></canvas>
+ <div id="controls">
+ <label for="direction">Direction:</label>
+ <input type="range" id="direction" min="-100" max="100" value="0">
+ <label for="tempo">Tempo:</label>
+ <input type="range" id="tempo" min="50" max="250" value="150">
+ <button onclick="loopRestart()">Loop Restart</button>
+ <button onclick="pausePlay()">Pause/Play</button>
+ <button onclick="reduceMotion()">Reduce Motion</button>
+ </div>
+ <div class="progress-ribbon">Neighborhood 1 / 5</div>
+
+ <script>
+ const canvas = document.getElementById('paradeCanvas');
+ const ctx = canvas.getContext('2d');
+
+ let direction = 0;
+ let tempo = 150;
+ let loopCount = 0;
+
+ function setup() {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
+ drawBears();
+ drawRevelers();
+ drawDogs();
+ drawRaccoons();
+ drawBirds();
+ drawBackground();
+ }
+
+ function drawBears() {
+ const bearSteps = [
+ { cycle: 'highStep', time: 120, delay: 0 },
+ { cycle: 'shoulderBounce', time: 60, delay: 60 },
+ { cycle: 'heelTurn', time: 90, delay: 180 },
+ { cycle: 'slowTwirl', time: 75, delay: 270 },
+ { cycle: 'skippingHop', time: 45, delay: 345 }
+ ];
+
+ bearSteps.forEach(step => {
+ step.delay = (step.delay + loopCount * tempo) % tempo;
+ ctx.beginPath();
+ // Bear drawing logic here
+ ctx.closePath();
+ setTimeout(() => {
+ ctx.fill();
+ }, step.delay);
+ });
+ }
+
+ function drawRevelers() {
+ const revelers = [
+ { shape: 'circle', size: 20, offset: 15 },
+ { shape: 'handHold', size: 30, offset: 60 }
+ ];
+
+ revelers.forEach(rev => {
+ ctx.beginPath();
+ if (rev.shape === 'circle') {
+ ctx.arc(200 + direction * 4, 400 + loopCount * tempo / 15, rev.size, 0, Math.PI * 2);
+ } else {
+ ctx.moveTo(200 + direction * 4, 380 + loopCount * tempo / 15 - rev.offset);
+ ctx.lineTo(210 + direction * 4, 390 + loopCount * tempo / 15 - rev.offset / 2);
+ ctx.lineTo(220 + direction * 4, 380 + loopCount * tempo / 15 - rev.offset);
+ }
+ ctx.closePath();
+ setTimeout(() => {
+ ctx.fill();
+ }, (loopCount + revelers.indexOf(rev)) * 60 % tempo);
+ });
+ }
+
+ function drawDogs() {
+ const dogs = [
+ { x: 230, y: 420 },
+ { x: 250, y: 410 }
+ ];
+
+ dogs.forEach(dog => {
+ ctx.beginPath();
+ ctx.arc(dog.x + direction * 4, dog.y + loopCount * tempo / 15, 20, 0, Math.PI * 2);
+ ctx.closePath();
+ setTimeout(() => {
+ ctx.fill();
+ }, (loopCount + dogs.indexOf(dog)) * 60 % tempo);
+ });
+ }
+
+ function drawRaccoons() {
+ const raccoons = [
+ { x: 240, y: 390 },
+ { x: 260, y: 400 }
+ ];
+
+ raccoons.forEach(raccoon => {
+ ctx.beginPath();
+ ctx.arc(raccoon.x + direction * 4, raccoon.y + loopCount * tempo / 15, 20, 0, Math.PI * 2);
+ ctx.closePath();
+ setTimeout(() => {
+ ctx.fill();
+ }, (loopCount + raccoons.indexOf(raccoon)) * 60 % tempo);
+ });
+ }
+
+ function drawBirds() {
+ const birds = [
+ { x: 250, y: 370 },
+ { x: 270, y: 380 }
+ ];
+
+ birds.forEach(bird => {
+ ctx.beginPath();
+ ctx.arc(bird.x + direction * 4, bird.y + loopCount * tempo / 15, 10, 0, Math.PI * 2);
+ ctx.closePath();
+ setTimeout(() => {
+ ctx.fill();
+ }, (loopCount + birds.indexOf(bird)) * 60 % tempo);
+ });
+ }
+
+ function drawBackground() {
+ const parallaxLayer = [
+ { x: 512, y: 0 },
+ { x: 768, y: 300 }
+ ];
+
+ parallaxLayer.forEach(layer => {
+ ctx.beginPath();
+ ctx.arc(layer.x + direction * 4, layer.y + loopCount * tempo / 15, 50, 0, Math.PI * 2);
+ ctx.closePath();
+ setTimeout(() => {
+ ctx.fill();
+ }, (loopCount + parallaxLayer.indexOf(layer)) * 60 % tempo);
+ });
+ }
+
+ function update() {
+ loopCount++;
+ drawBears();
+ drawRevelers();
+ drawDogs();
+ drawRaccoons();
+ drawBirds();
+ drawBackground();
+ requestAnimationFrame(update);
+ }
+
+ setup();
+ update();
+
+ document.getElementById('direction').addEventListener('input', (e) => direction = e.target.value / 100);
+ document.getElementById('tempo').addEventListener('input', (e) => tempo = e.target.value);
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ee75e7535787/qwen3-14b.html b/data/artifacts/ee75e7535787/qwen3-14b.html
new file mode 100644
index 0000000..c56cb22
--- /dev/null
+++ b/data/artifacts/ee75e7535787/qwen3-14b.html
@@ -0,0 +1,386 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>The Dancing Bear Procession</title>
+ <style>
+ :root {
+ --bg: #12002b;
+ --surface: #1e0b3d;
+ --text: #f3e9ff;
+ --muted: #8d7bb0;
+ --accent: #ff2e88;
+ --card-radius: 14px;
+ --spacing-8: 8px;
+ --spacing-12: 12px;
+ --spacing-16: 16px;
+ --spacing-24: 24px;
+ --spacing-32: 32px;
+ --spacing-48: 48px;
+ --card-shadow: 0 8px 24px rgba(0,0,0,.12);
+ --glow: 0 0 24px rgba(255,46,136,0.35);
+ --label-font: 12px;
+ --label-letter-spacing: 0.15em;
+ --label-text: #8d7bb0;
+ --progress-ribbon-height: 4px;
+ --progress-ribbon-color: #ff2e88;
+ --progress-ribbon-background: #1e0b3d;
+ }
+
+ body {
+ margin: 0;
+ padding: 0;
+ background: var(--bg);
+ color: var(--text);
+ font-family: -apple-system, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
+ overflow: hidden;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+ }
+
+ .container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .parade {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ background: linear-gradient(135deg, #12002b, #1e0b3d);
+ animation: drift 18s linear infinite;
+ }
+
+ .parade-layer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ pointer-events: none;
+ }
+
+ .parade-layer.foreground {
+ z-index: 5;
+ }
+
+ .parade-layer.middle {
+ z-index: 3;
+ }
+
+ .parade-layer.background {
+ z-index: 1;
+ }
+
+ .bear {
+ position: absolute;
+ width: 50px;
+ height: 50px;
+ background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50"><circle cx="25" cy="25" r="20" fill="#c9a961"/><circle cx="20" cy="20" r="5" fill="#7d6a45"/><circle cx="30" cy="20" r="5" fill="#7d6a45"/><circle cx="25" cy="35" r="8" fill="#c9a961"/></svg>') no-repeat center/contain;
+ animation: move 5s linear infinite;
+ will-change: transform;
+ }
+
+ .bear.bear-1 {
+ top: 20%;
+ left: 20%;
+ animation-name: high-step;
+ }
+
+ .bear.bear-2 {
+ top: 30%;
+ left: 40%;
+ animation-name: shoulder-bounce;
+ }
+
+ .bear.bear-3 {
+ top: 40%;
+ left: 60%;
+ animation-name: heel-turn;
+ }
+
+ .bear.bear-4 {
+ top: 50%;
+ left: 80%;
+ animation-name: slow-twirl;
+ }
+
+ .bear.bear-5 {
+ top: 60%;
+ left: 100%;
+ animation-name: skipping-hop;
+ }
+
+ @keyframes high-step {
+ 0% { transform: translateX(0) translateY(0); }
+ 25% { transform: translateX(10px) translateY(-10px); }
+ 50% { transform: translateX(20px) translateY(0); }
+ 75% { transform: translateX(10px) translateY(10px); }
+ 100% { transform: translateX(0) translateY(0); }
+ }
+
+ @keyframes shoulder-bounce {
+ 0% { transform: translateX(0) translateY(0) rotate(0deg); }
+ 25% { transform: translateX(10px) translateY(-10px) rotate(10deg); }
+ 50% { transform: translateX(20px) translateY(0) rotate(20deg); }
+ 75% { transform: translateX(10px) translateY(10px) rotate(10deg); }
+ 100% { transform: translateX(0) translateY(0) rotate(0deg); }
+ }
+
+ @keyframes heel-turn {
+ 0% { transform: translateX(0) translateY(0) rotate(0deg); }
+ 25% { transform: translateX(10px) translateY(0) rotate(10deg); }
+ 50% { transform: translateX(20px) translateY(0) rotate(20deg); }
+ 75% { transform: translateX(10px) translateY(0) rotate(10deg); }
+ 100% { transform: translateX(0) translateY(0) rotate(0deg); }
+ }
+
+ @keyframes slow-twirl {
+ 0% { transform: translateX(0) translateY(0) rotate(0deg); }
+ 50% { transform: translateX(0) translateY(0) rotate(180deg); }
+ 100% { transform: translateX(0) translateY(0) rotate(360deg); }
+ }
+
+ @keyframes skipping-hop {
+ 0% { transform: translateX(0) translateY(0); }
+ 25% { transform: translateX(10px) translateY(-10px); }
+ 50% { transform: translateX(20px) translateY(0); }
+ 75% { transform: translateX(10px) translateY(10px); }
+ 100% { transform: translateX(0) translateY(0); }
+ }
+
+ @keyframes move {
+ 0% { left: -100%; }
+ 100% { left: 100%; }
+ }
+
+ .revellers {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ animation: move 5s linear infinite;
+ }
+
+ .revellers img {
+ width: 30px;
+ height: 30px;
+ }
+
+ .controls {
+ position: absolute;
+ top: var(--spacing-8);
+ left: var(--spacing-8);
+ z-index: 10;
+ background: var(--surface);
+ padding: var(--spacing-12);
+ border-radius: var(--card-radius);
+ box-shadow: var(--card-shadow);
+ font-size: var(--label-font);
+ letter-spacing: var(--label-letter-spacing);
+ color: var(--label-text);
+ }
+
+ .controls button {
+ margin: var(--spacing-4) var(--spacing-8);
+ padding: var(--spacing-8) var(--spacing-12);
+ border: none;
+ background: var(--accent);
+ color: var(--text);
+ border-radius: var(--card-radius);
+ cursor: pointer;
+ transition: background 0.2s ease;
+ }
+
+ .controls button:hover {
+ background: rgba(255,46,136,0.8);
+ }
+
+ .progress-ribbon {
+ position: absolute;
+ bottom: var(--spacing-16);
+ left: 0;
+ right: 0;
+ height: var(--progress-ribbon-height);
+ background: var(--progress-ribbon-background);
+ overflow: hidden;
+ }
+
+ .progress-ribbon::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ height: 100%;
+ width: 100%;
+ background: var(--progress-ribbon-color);
+ animation: progress 5s linear infinite;
+ }
+
+ @keyframes progress {
+ 0% { width: 0%; }
+ 100% { width: 100%; }
+ }
+
+ .character-guide {
+ position: absolute;
+ top: var(--spacing-16);
+ right: var(--spacing-16);
+ background: var(--surface);
+ padding: var(--spacing-12);
+ border-radius: var(--card-radius);
+ box-shadow: var(--card-shadow);
+ font-size: var(--label-font);
+ letter-spacing: var(--label-letter-spacing);
+ color: var(--label-text);
+ }
+
+ .character-guide h3 {
+ margin: 0 0 var(--spacing-8) 0;
+ color: var(--text);
+ font-size: 14px;
+ }
+
+ .character-guide ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ }
+
+ .character-guide li {
+ margin: var(--spacing-4) 0;
+ }
+
+ @keyframes drift {
+ 0% { background-position: 0% 50%; }
+ 100% { background-position: 100% 50%; }
+ }
+ </style>
+</head>
+<body>
+ <div class="container">
+ <div class="parade">
+ <div class="parade-layer foreground">
+ <div class="bear bear-1"></div>
+ <div class="bear bear-2"></div>
+ <div class="bear bear-3"></div>
+ <div class="bear bear-4"></div>
+ <div class="bear bear-5"></div>
+ <div class="revellers">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 1">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 2">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 3">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 4">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 5">
+ </div>
+ </div>
+ <div class="parade-layer middle">
+ <div class="bear bear-1"></div>
+ <div class="bear bear-2"></div>
+ <div class="bear bear-3"></div>
+ <div class="bear bear-4"></div>
+ <div class="bear bear-5"></div>
+ <div class="revellers">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 1">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 2">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 3">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 4">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 5">
+ </div>
+ </div>
+ <div class="parade-layer background">
+ <div class="bear bear-1"></div>
+ <div class="bear bear-2"></div>
+ <div class="bear bear-3"></div>
+ <div class="bear bear-4"></div>
+ <div class="bear bear-5"></div>
+ <div class="revellers">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 1">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 2">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 3">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 4">
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='30'><circle cx='15' cy='15' r='12' fill='%23ff2e88'/></svg>" alt="Reveller 5">
+ </div>
+ </div>
+ </div>
+ <div class="controls">
+ <button onclick="toggleDirection()">Toggle Direction</button>
+ <button onclick="toggleTempo()">Toggle Tempo</button>
+ <button onclick="toggleLabels()">Toggle Labels</button>
+ <button onclick="restartLoop()">Restart</button>
+ <button onclick="pause()">Pause</button>
+ <button onclick="toggleReducedMotion()">Reduced Motion</button>
+ </div>
+ <div class="progress-ribbon"></div>
+ <div class="character-guide">
+ <h3>Character Guide</h3>
+ <ul>
+ <li><strong>High Step</strong> – Bear 1</li>
+ <li><strong>Shoulder Bounce</strong> – Bear 2</li>
+ <li><strong>Heel Turn</strong> – Bear 3</li>
+ <li><strong>Slow Twirl</strong> – Bear 4</li>
+ <li><strong>Skipping Hop</strong> – Bear 5</li>
+ </ul>
+ </div>
+ </div>
+ <script>
+ let direction = 1;
+ let tempo = 5;
+ let isPaused = false;
+ let isLabelsOn = true;
+ let isReducedMotion = false;
+
+ function toggleDirection() {
+ direction *= -1;
+ document.querySelectorAll('.bear').forEach(bear => {
+ bear.style.animationDirection = direction === 1 ? 'normal' : 'reverse';
+ });
+ }
+
+ function toggleTempo() {
+ tempo = tempo === 5 ? 10 : 5;
+ document.querySelectorAll('.bear').forEach(bear => {
+ bear.style.animationDuration = `${tempo}s`;
+ });
+ }
+
+ function toggleLabels() {
+ isLabelsOn = !isLabelsOn;
+ const labels = document.querySelectorAll('[data-label]');
+ labels.forEach(label => {
+ label.style.display = isLabelsOn ? 'block' : 'none';
+ });
+ }
+
+ function restartLoop() {
+ document.querySelectorAll('.bear').forEach(bear => {
+ bear.style.animationIterationCount = 'infinite';
+ });
+ }
+
+ function pause() {
+ isPaused = !isPaused;
+ document.querySelectorAll('.bear').forEach(bear => {
+ bear.style.animationPlayState = isPaused ? 'paused' : 'running';
+ });
+ }
+
+ function toggleReducedMotion() {
+ isReducedMotion = !isReducedMotion;
+ document.querySelectorAll('.bear').forEach(bear => {
+ bear.style.animation = isReducedMotion ? 'none' : '';
+ });
+ }
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/challenges.json b/data/challenges.json
index e174329..032e7bc 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -34512,12 +34512,12 @@
"model": "qwen25-7b",
"status": "done",
"error": null,
- "seconds": 51,
+ "seconds": 26,
"cost": 0,
- "started_at": "2026-09-01T20:42:04.768Z",
- "finished_at": "2026-09-01T20:42:55.742Z",
- "queued_at": "2026-09-01T20:42:04.755Z",
- "bytes": 6910,
+ "started_at": "2026-09-01T21:31:03.233Z",
+ "finished_at": "2026-09-01T21:31:29.631Z",
+ "queued_at": "2026-09-01T21:28:39.426Z",
+ "bytes": 3913,
"thumb": true,
"aiScore": 7.3,
"aiReason": "The artwork is visually appealing and fulfills the challenge's requirements well, but lacks some of the dynamic elements like dancing figures and wildlife.",
@@ -34706,34 +34706,34 @@
"model": "qwen3-14b",
"status": "done",
"error": null,
- "seconds": 97,
+ "seconds": 90,
"cost": 0,
- "started_at": "2026-09-01T20:46:01.022Z",
- "finished_at": "2026-09-01T20:47:38.467Z",
- "queued_at": "2026-09-01T20:42:04.822Z",
- "bytes": 14826,
+ "started_at": "2026-09-01T21:35:18.371Z",
+ "finished_at": "2026-09-01T21:36:48.670Z",
+ "queued_at": "2026-09-01T21:35:18.364Z",
+ "bytes": 13169,
"thumb": true,
- "aiScore": 5.3,
- "aiReason": "The interface is functional but lacks the depth and complexity of a museum projection.",
+ "aiScore": 6.3,
+ "aiReason": "The model effectively fulfills the challenge by creating an orbiting celestial dance scene with clear controls and a visually engaging score panel.",
"aiScores": {
- "qwen2.5vl:7b": 7,
+ "qwen2.5vl:7b": 9,
"minicpm-v:latest": 3.5
},
- "aiSpread": 3.5
+ "aiSpread": 5.5
},
{
"model": "qwen25-7b",
"status": "done",
"error": null,
- "seconds": 42,
+ "seconds": 27,
"cost": 0,
- "started_at": "2026-09-01T20:44:07.450Z",
- "finished_at": "2026-09-01T20:44:49.435Z",
- "queued_at": "2026-09-01T20:42:04.827Z",
- "bytes": 5381,
+ "started_at": "2026-09-01T21:38:55.332Z",
+ "finished_at": "2026-09-01T21:39:22.755Z",
+ "queued_at": "2026-09-01T21:38:55.326Z",
+ "bytes": 4078,
"thumb": true,
"aiScore": 6.8,
- "aiReason": "The work shows promise but lacks depth and interactivity as described in the challenge.",
+ "aiReason": "The work shows promise but lacks the depth and complexity required by the challenge.",
"aiScores": {
"qwen2.5vl:7b": 7,
"minicpm-v:latest": 6.5
@@ -34742,19 +34742,27 @@
},
{
"model": "claude-code",
- "status": "running",
+ "status": "done",
"error": null,
- "seconds": 256,
+ "seconds": 421,
"cost": 0,
"started_at": "2026-09-01T21:14:14.780Z",
- "finished_at": null,
+ "finished_at": "2026-09-01T21:21:15.303Z",
"queued_at": "2026-09-01T21:14:14.775Z",
- "bytes": 31147
+ "bytes": 29802,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The image effectively fulfills the challenge by creating a surreal animated gallery work that is both legible and beautiful, with clear controls and a compact score panel.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
}
],
"judging": false,
- "aiPick": "qwen25-7b",
- "judged_at": "2026-09-01T21:07:34.262Z"
+ "aiPick": "claude-code",
+ "judged_at": "2026-09-01T21:22:58.405Z"
},
{
"id": "246049758201",
@@ -34826,5 +34834,699 @@
"judging": false,
"aiPick": "claude-code",
"judged_at": "2026-09-01T21:13:07.571Z"
+ },
+ {
+ "id": "c8e81379ec15",
+ "title": "Sortable Product Catalog — Luxury Wallpaper",
+ "prompt": "Build a single self-contained HTML file: a luxury wallcovering product catalog grid of 12 sample products (CSS-drawn swatch, name, collection, price). Include a working sort <select> (Newest, Price ↑↓, Name A-Z, Color) AND a density slider that changes the grid column count, both persisted in localStorage. Elegant editorial card design, hover lift. Output ONLY the HTML.",
+ "category": "Real Work",
+ "designTools": true,
+ "created_at": "2026-09-01T21:23:58.298Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 57,
+ "cost": 0,
+ "started_at": "2026-09-01T21:23:58.317Z",
+ "finished_at": "2026-09-01T21:24:54.894Z",
+ "queued_at": "2026-09-01T21:23:58.303Z",
+ "bytes": 8093,
+ "thumb": true,
+ "aiScore": 8,
+ "aiReason": "The design is visually appealing and fulfills the requirements of a product catalog grid. However, it lacks some interactive elements like sorting and density slider functionality.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 9
+ },
+ "aiSpread": 2
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 30,
+ "cost": 0,
+ "started_at": "2026-09-01T21:23:58.322Z",
+ "finished_at": "2026-09-01T21:24:28.534Z",
+ "queued_at": "2026-09-01T21:23:58.308Z",
+ "bytes": 3665,
+ "thumb": true,
+ "aiScore": 8,
+ "aiReason": "The HTML fulfills the challenge requirements but lacks visual quality and editorial card design.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 9
+ },
+ "aiSpread": 2
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 143,
+ "cost": 0,
+ "started_at": "2026-09-01T21:23:58.327Z",
+ "finished_at": "2026-09-01T21:26:21.503Z",
+ "queued_at": "2026-09-01T21:23:58.313Z",
+ "bytes": 16134,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The design is elegant and fulfills the requirements well, but lacks some visual polish in terms of color contrast and hover effects.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 2.5
+ }
+ ],
+ "judging": false,
+ "aiPick": "claude-code",
+ "judged_at": "2026-09-01T21:32:19.285Z"
+ },
+ {
+ "id": "370279e038b0",
+ "title": "Luxury Product Page — Wallpaper Detail",
+ "prompt": "Build a single self-contained HTML file: a luxury wallpaper product page — hero CSS pattern swatch, product title, collection, price, colorway dots, add-to-cart button, pattern repeat info, and a row of related swatches. Dark luxe editorial layout with serif type. Output ONLY the HTML.",
+ "category": "Real Work",
+ "designTools": true,
+ "created_at": "2026-09-01T21:23:58.635Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 31,
+ "cost": 0,
+ "started_at": "2026-09-01T21:24:54.899Z",
+ "finished_at": "2026-09-01T21:25:26.150Z",
+ "queued_at": "2026-09-01T21:23:58.644Z",
+ "bytes": 4687,
+ "thumb": true,
+ "aiScore": 9,
+ "aiReason": "The HTML fulfills the challenge requirements and has a polished dark luxe editorial layout.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9
+ },
+ "aiSpread": 0
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 38,
+ "cost": 0,
+ "started_at": "2026-09-01T21:24:28.539Z",
+ "finished_at": "2026-09-01T21:25:06.471Z",
+ "queued_at": "2026-09-01T21:23:58.651Z",
+ "bytes": 3996,
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The HTML fulfills the challenge requirements but lacks some visual elements like colorway dots and related swatches.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 0
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 87,
+ "cost": 0,
+ "started_at": "2026-09-01T21:26:21.508Z",
+ "finished_at": "2026-09-01T21:27:48.370Z",
+ "queued_at": "2026-09-01T21:23:58.655Z",
+ "bytes": 17401,
+ "thumb": true,
+ "aiScore": 9,
+ "aiReason": "The HTML file effectively fulfills the challenge requirements and has a polished visual quality.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9
+ },
+ "aiSpread": 0
+ }
+ ],
+ "judging": false,
+ "aiPick": "qwen3-14b",
+ "judged_at": "2026-09-01T21:32:19.290Z"
+ },
+ {
+ "id": "4312ba790371",
+ "title": "Room Repaint Visualizer",
+ "prompt": "Build a single self-contained HTML file: an interactive room visualizer. A CSS/canvas-drawn living room with a large feature wall; a side palette of 8 wallcovering swatches (CSS-generated damask/grasscloth/geometric textures); clicking a swatch instantly re-skins the feature wall with a smooth crossfade. Include a day/night lighting toggle. Luxury interior aesthetic. Output ONLY the HTML.",
+ "category": "Real Work",
+ "designTools": true,
+ "created_at": "2026-09-01T21:23:58.967Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 48,
+ "cost": 0,
+ "started_at": "2026-09-01T21:25:26.155Z",
+ "finished_at": "2026-09-01T21:26:14.220Z",
+ "queued_at": "2026-09-01T21:23:58.973Z",
+ "bytes": 7552,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The design is visually appealing and fulfills the requirements well, but lacks some interactivity in terms of lighting toggle.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 29,
+ "cost": 0,
+ "started_at": "2026-09-01T21:25:06.476Z",
+ "finished_at": "2026-09-01T21:25:35.917Z",
+ "queued_at": "2026-09-01T21:23:58.979Z",
+ "bytes": 4259,
+ "thumb": true,
+ "aiScore": 6.5,
+ "aiReason": "The room visualizer is interactive and fulfills the challenge requirements, but lacks some polish in terms of design and lighting.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 1
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 103,
+ "cost": 0,
+ "started_at": "2026-09-01T21:27:48.375Z",
+ "finished_at": "2026-09-01T21:29:31.577Z",
+ "queued_at": "2026-09-01T21:23:58.993Z",
+ "bytes": 17937,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The visual is clear and the functionality meets all requirements.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
+ }
+ ],
+ "judging": false,
+ "aiPick": "claude-code",
+ "judged_at": "2026-09-01T21:32:19.294Z"
+ },
+ {
+ "id": "93852a2b65ff",
+ "title": "Seamless Pattern Studio",
+ "prompt": "Build a single self-contained HTML file: a generative seamless-wallpaper pattern maker. Sliders control motif type (dots/leaves/diamonds/waves), scale, rotation, and a 5-color luxury palette; the canvas renders a live tiling repeat that visibly seams correctly. A 'Randomize' and 'Download PNG' button. 60fps. Output ONLY the HTML.",
+ "category": "Real Work",
+ "designTools": true,
+ "created_at": "2026-09-01T21:23:59.304Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 67,
+ "cost": 0,
+ "started_at": "2026-09-01T21:26:14.225Z",
+ "finished_at": "2026-09-01T21:27:20.907Z",
+ "queued_at": "2026-09-01T21:23:59.309Z",
+ "bytes": 10931,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The image shows a seamless wallpaper generator interface but lacks the sliders and live tiling repeat functionality described in the challenge.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 34,
+ "cost": 0,
+ "started_at": "2026-09-01T21:25:35.922Z",
+ "finished_at": "2026-09-01T21:26:10.421Z",
+ "queued_at": "2026-09-01T21:23:59.314Z",
+ "bytes": 5090,
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The HTML file is functional but lacks the luxury palette and does not display a live tiling repeat.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 0
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 169,
+ "cost": 0,
+ "started_at": "2026-09-01T21:29:31.582Z",
+ "finished_at": "2026-09-01T21:32:20.935Z",
+ "queued_at": "2026-09-01T21:23:59.319Z",
+ "bytes": 14508,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The HTML file effectively fulfills the challenge requirements and has a clean, polished visual quality.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
+ }
+ ],
+ "judging": false,
+ "aiPick": "claude-code",
+ "judged_at": "2026-09-01T21:32:37.972Z"
+ },
+ {
+ "id": "9a879cc61947",
+ "title": "Wallpaper Calculator",
+ "prompt": "Build a single self-contained HTML file: a wallpaper-quantity calculator. Inputs for wall width, height, and number of walls, plus roll dimensions and pattern-repeat; it computes rolls needed (with 10% waste), total coverage, and estimated cost. Clean luxury form UI, live recompute, print-friendly result card. Output ONLY the HTML.",
+ "category": "Real Work",
+ "designTools": true,
+ "created_at": "2026-09-01T21:23:59.629Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 38,
+ "cost": 0,
+ "started_at": "2026-09-01T21:27:20.912Z",
+ "finished_at": "2026-09-01T21:27:59.339Z",
+ "queued_at": "2026-09-01T21:23:59.636Z",
+ "bytes": 6541,
+ "thumb": true,
+ "aiScore": 8,
+ "aiReason": "The HTML file is clean and fulfills the challenge requirements effectively.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 2
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 28,
+ "cost": 0,
+ "started_at": "2026-09-01T21:26:10.426Z",
+ "finished_at": "2026-09-01T21:26:38.643Z",
+ "queued_at": "2026-09-01T21:23:59.642Z",
+ "bytes": 4107,
+ "thumb": true,
+ "aiScore": 9,
+ "aiReason": "The HTML file is clean and fulfills the challenge requirements effectively.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9
+ },
+ "aiSpread": 0
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 94,
+ "cost": 0,
+ "started_at": "2026-09-01T21:32:20.941Z",
+ "finished_at": "2026-09-01T21:33:55.038Z",
+ "queued_at": "2026-09-01T21:23:59.647Z",
+ "bytes": 19880,
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The HTML file is clean and fulfills the challenge requirements effectively.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
+ }
+ ],
+ "judging": false,
+ "aiPick": "claude-code",
+ "judged_at": "2026-09-01T21:34:10.010Z"
+ },
+ {
+ "id": "e5e301be79a9",
+ "title": "Fleet Defender — Agent Command Arcade",
+ "prompt": "Build a single self-contained HTML file: a playable arcade game FLEET DEFENDER themed as an AI-agent command center. Player pilots a glowing command ship at the bottom (arrow keys move, spacebar fires) defending the fleet from descending 'bug' invaders. Neon command-center look (dark navy, cyan/amber HUD, agent-node grid), score, wave number, lives, rising difficulty, particle explosions, game-over + restart. 60fps canvas, keyboard controls, no external assets. Output ONLY the HTML.",
+ "category": "Games",
+ "designTools": true,
+ "created_at": "2026-09-01T21:23:59.955Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 41,
+ "cost": 0,
+ "started_at": "2026-09-01T21:27:59.344Z",
+ "finished_at": "2026-09-01T21:28:40.374Z",
+ "queued_at": "2026-09-01T21:23:59.960Z",
+ "bytes": 7416,
+ "thumb": true,
+ "aiScore": 7,
+ "aiReason": "The game is functional but lacks the neon command-center look and particle explosions.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 0
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 159,
+ "cost": 0,
+ "started_at": "2026-09-01T21:26:38.648Z",
+ "finished_at": "2026-09-01T21:29:18.031Z",
+ "queued_at": "2026-09-01T21:23:59.965Z",
+ "bytes": 7101,
+ "thumb": true,
+ "aiScore": 5.3,
+ "aiReason": "The HTML file is functional but lacks visual elements and detailed design.",
+ "aiScores": {
+ "qwen2.5vl:7b": 4,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 2.5
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 103,
+ "cost": 0,
+ "started_at": "2026-09-01T21:33:55.043Z",
+ "finished_at": "2026-09-01T21:35:37.838Z",
+ "queued_at": "2026-09-01T21:23:59.970Z",
+ "bytes": 19023,
+ "thumb": true,
+ "aiScore": 8.3,
+ "aiReason": "The HTML file successfully fulfills the challenge requirements and has a polished visual quality.",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 1.5
+ }
+ ],
+ "judging": false,
+ "aiPick": "claude-code",
+ "judged_at": "2026-09-01T21:37:15.443Z"
+ },
+ {
+ "id": "e2f4ae36f757",
+ "title": "Playable Asteroids",
+ "prompt": "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.",
+ "category": "Games",
+ "designTools": true,
+ "created_at": "2026-09-01T21:24:00.279Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 52,
+ "cost": 0,
+ "started_at": "2026-09-01T21:28:40.379Z",
+ "finished_at": "2026-09-01T21:29:32.126Z",
+ "queued_at": "2026-09-01T21:24:00.284Z",
+ "bytes": 9244,
+ "thumb": true,
+ "aiScore": 6.5,
+ "aiReason": "The game is functional but lacks visual polish and some features like asteroid splitting are not fully implemented.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6
+ },
+ "aiSpread": 1
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 31,
+ "cost": 0,
+ "started_at": "2026-09-01T21:29:18.036Z",
+ "finished_at": "2026-09-01T21:29:49.464Z",
+ "queued_at": "2026-09-01T21:24:00.288Z",
+ "bytes": 4293,
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The game is functional but lacks detailed graphics and animations.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 67,
+ "cost": 0,
+ "started_at": "2026-09-01T21:35:37.844Z",
+ "finished_at": "2026-09-01T21:36:44.673Z",
+ "queued_at": "2026-09-01T21:24:00.293Z",
+ "bytes": 12421,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The HTML file successfully implements the Asteroids clone with basic functionality and visual elements.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ }
+ ],
+ "judging": false,
+ "aiPick": "claude-code",
+ "judged_at": "2026-09-01T21:37:15.447Z"
+ },
+ {
+ "id": "a3f76984cd9c",
+ "title": "Pattern Runner — Paint Roller",
+ "prompt": "Build a single self-contained HTML file: an endless-runner where a paint-roller rolls along a wall collecting wallpaper rolls (colored by design style) and dodging obstacles. Tap/space to jump, rising speed, score + best in localStorage, game-over + restart. Playful luxe palette, 60fps canvas. Output ONLY the HTML.",
+ "category": "Games",
+ "designTools": true,
+ "created_at": "2026-09-01T21:24:00.604Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 42,
+ "cost": 0,
+ "started_at": "2026-09-01T21:29:32.131Z",
+ "finished_at": "2026-09-01T21:30:14.032Z",
+ "queued_at": "2026-09-01T21:24:00.609Z",
+ "bytes": 7521,
+ "thumb": true,
+ "aiScore": 6.8,
+ "aiReason": "The game is functional but lacks visual appeal and detail.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 33,
+ "cost": 0,
+ "started_at": "2026-09-01T21:29:49.469Z",
+ "finished_at": "2026-09-01T21:30:22.671Z",
+ "queued_at": "2026-09-01T21:24:00.614Z",
+ "bytes": 4176,
+ "thumb": true,
+ "aiScore": 3.3,
+ "aiReason": "The image is completely blank and does not display any content related to the endless runner game.",
+ "aiScores": {
+ "qwen2.5vl:7b": 0,
+ "minicpm-v:latest": 6.5
+ },
+ "aiSpread": 6.5
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 102,
+ "cost": 0,
+ "started_at": "2026-09-01T21:36:44.678Z",
+ "finished_at": "2026-09-01T21:38:26.265Z",
+ "queued_at": "2026-09-01T21:24:00.619Z",
+ "bytes": 18437,
+ "thumb": true,
+ "aiScore": 7.3,
+ "aiReason": "The game concept is clear and the visual design is playful, but lacks some polish in terms of animation and interactivity.",
+ "aiScores": {
+ "qwen2.5vl:7b": 7,
+ "minicpm-v:latest": 7.5
+ },
+ "aiSpread": 0.5
+ }
+ ],
+ "judging": false,
+ "aiPick": "claude-code",
+ "judged_at": "2026-09-01T21:38:42.042Z"
+ },
+ {
+ "id": "b2babfd5dbc5",
+ "title": "Color Story Carousel — Instagram 1080x1080",
+ "prompt": "Build a single self-contained HTML file: an Instagram carousel (1080x1080, swipe/dots) presenting a seasonal wallcovering COLOR STORY. Slide 1 cover with the palette name (e.g. 'Terracotta & Sage'), slides 2-4 each feature one hue with its designer name (Oatmeal, Celadon, Terracotta) over a CSS-drawn swatch + a room-use tip, slide 5 CTA. Muted luxe type. Editable palette. Output ONLY the HTML.",
+ "category": "Real Work",
+ "designTools": true,
+ "created_at": "2026-09-01T21:24:00.929Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 43,
+ "cost": 0,
+ "started_at": "2026-09-01T21:30:14.037Z",
+ "finished_at": "2026-09-01T21:30:56.790Z",
+ "queued_at": "2026-09-01T21:24:00.944Z",
+ "bytes": 7223,
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The HTML file successfully fulfills the challenge by presenting a seasonal wallcovering color story in an Instagram carousel format. The visual quality is polished and adheres to the specified design ",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 19,
+ "cost": 0,
+ "started_at": "2026-09-01T21:30:22.676Z",
+ "finished_at": "2026-09-01T21:30:41.917Z",
+ "queued_at": "2026-09-01T21:24:00.951Z",
+ "bytes": 2629,
+ "thumb": true,
+ "aiScore": 9.3,
+ "aiReason": "The HTML file effectively fulfills the challenge by presenting a seasonal wallcovering color story in an Instagram carousel format. The design is visually appealing and adheres to the specified aesthe",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9.5
+ },
+ "aiSpread": 0.5
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 75,
+ "cost": 0,
+ "started_at": "2026-09-01T21:38:26.271Z",
+ "finished_at": "2026-09-01T21:39:40.862Z",
+ "queued_at": "2026-09-01T21:24:00.956Z",
+ "bytes": 13370,
+ "thumb": true,
+ "aiScore": 9,
+ "aiReason": "The HTML file effectively fulfills the challenge by presenting a seasonal wallcovering color story in an Instagram carousel format. The design is visually appealing and adheres to the specified aesthe",
+ "aiScores": {
+ "qwen2.5vl:7b": 9,
+ "minicpm-v:latest": 9
+ },
+ "aiSpread": 0
+ }
+ ],
+ "judging": false,
+ "aiPick": "qwen3-14b",
+ "judged_at": "2026-09-01T21:39:56.743Z"
+ },
+ {
+ "id": "f3bef7cf51a6",
+ "title": "Before/After Room Slider",
+ "prompt": "Build a single self-contained HTML file: a draggable before/after image slider for a wallcovering brand's social posts. Two CSS-illustrated room states (plain wall vs papered feature wall) with a draggable vertical handle to wipe between them, plus a caption and share bar. Smooth drag, touch support. Output ONLY the HTML.",
+ "category": "Real Work",
+ "designTools": true,
+ "created_at": "2026-09-01T21:24:01.268Z",
+ "winner": null,
+ "runs": [
+ {
+ "model": "qwen3-14b",
+ "status": "done",
+ "error": null,
+ "seconds": 30,
+ "cost": 0,
+ "started_at": "2026-09-01T21:30:56.795Z",
+ "finished_at": "2026-09-01T21:31:26.960Z",
+ "queued_at": "2026-09-01T21:24:01.273Z",
+ "bytes": 4600,
+ "thumb": true,
+ "aiScore": 6.5,
+ "aiReason": "The image shows a basic before/after slider concept but lacks the interactive elements and CSS styling required for smooth dragging and touch support.",
+ "aiScores": {
+ "qwen2.5vl:7b": 6,
+ "minicpm-v:latest": 7
+ },
+ "aiSpread": 1
+ },
+ {
+ "model": "qwen25-7b",
+ "status": "done",
+ "error": null,
+ "seconds": 21,
+ "cost": 0,
+ "started_at": "2026-09-01T21:30:41.923Z",
+ "finished_at": "2026-09-01T21:31:03.227Z",
+ "queued_at": "2026-09-01T21:24:01.277Z",
+ "bytes": 3202,
+ "thumb": true
+ },
+ {
+ "model": "claude-code",
+ "status": "done",
+ "error": null,
+ "seconds": 111,
+ "cost": 0,
+ "started_at": "2026-09-01T21:39:40.867Z",
+ "finished_at": "2026-09-01T21:41:32.352Z",
+ "queued_at": "2026-09-01T21:24:01.282Z",
+ "bytes": 19195,
+ "thumb": true
+ }
+ ],
+ "judging": true
}
]
\ No newline at end of file
diff --git a/verification/grateful-dead-dance-five-live.png b/verification/grateful-dead-dance-five-live.png
new file mode 100644
index 0000000..85159dc
Binary files /dev/null and b/verification/grateful-dead-dance-five-live.png differ
diff --git a/verification/grateful-dead-dance-five-proof.json b/verification/grateful-dead-dance-five-proof.json
new file mode 100644
index 0000000..2a798a8
--- /dev/null
+++ b/verification/grateful-dead-dance-five-proof.json
@@ -0,0 +1,44 @@
+{
+ "intent": "Create and test five new Grateful Dead-themed animated worlds featuring dancing people, spinning circles, and choreographed animals.",
+ "riskTier": "R4 production content mutation",
+ "environment": "https://modelarena.agentabrams.com/",
+ "timestamp": "2026-09-01T21:35:00-07:00",
+ "promptCommit": "2ce0937",
+ "challengeIds": ["c5c20cf0ae13", "ee75e7535787", "1c80df76af60", "9682a8139b09", "246049758201"],
+ "prompts": {
+ "count": 5,
+ "characters": [1190, 1203, 1278, 1248, 1256],
+ "themes": ["spinning meadow", "dancing-bear procession", "woodland jam", "cosmic ballroom", "Wall of Sound street dance"]
+ },
+ "generation": {
+ "models": ["qwen3-14b", "qwen25-7b", "claude-code"],
+ "requestedRuns": 15,
+ "completedRuns": 15,
+ "renderedThumbnails": 15,
+ "refereeScores": 15,
+ "meteredSpendUsd": 0,
+ "aiPickEveryChallenge": "claude-code"
+ },
+ "animationE2E": {
+ "command": "ARENA_URL=https://modelarena.agentabrams.com/ node verification/grateful-dead-dance-five.e2e.js",
+ "verdict": "PASS",
+ "renderedArtifacts": 15,
+ "motionVerifiedArtifacts": 12,
+ "browserErrorsOutsideArtifacts": 0,
+ "acceptance": "Each idea has at least two working animated implementations and at least one working implementation with controls.",
+ "weakCompetitors": [
+ { "challenge": "Sunwheel Meadow Dance", "model": "qwen25-7b", "result": "canvas renders but animation stops on an object-destructuring runtime error and has no controls" },
+ { "challenge": "Orbit of the Cosmic Dancers", "model": "qwen25-7b", "result": "controls render but no executable animation loop is present" },
+ { "challenge": "Wall of Sound Street Dance", "model": "qwen25-7b", "result": "static SVG/control composition with no live animation loop" }
+ ]
+ },
+ "checks": [
+ { "boundary": "production API and queue", "verdict": "PASS", "assertions": ["five unique challenges persisted", "15/15 runs reached done", "automatic queues advanced without service restart", "zero metered spend"] },
+ { "boundary": "rendering and scoring", "verdict": "PASS", "assertions": ["15/15 thumbnails exist", "15/15 numeric referee scores", "all five AI picks present"] },
+ { "boundary": "runtime animation", "verdict": "PASS", "assertions": ["all 15 artifacts visibly render", "12 artifacts execute CSS animation or requestAnimationFrame ticks", "every idea has two working animations", "every idea has at least one controlled working animation", "individual model failures are retained and disclosed"] },
+ { "boundary": "live browser", "verdict": "PASS", "assertions": ["all five challenge titles visible", "all 15 artifact routes exercised", "zero Model Arena shell errors", "screenshot captured"] }
+ ],
+ "evidence": ["verification/grateful-dead-dance-five.e2e.js", "verification/grateful-dead-dance-five-live.png", "data/grateful-dead-dance-challenges.json"],
+ "cleanup": "Five production challenges and all comparison artifacts intentionally retained. Retries were limited to broken zero-cost artifacts; no service restart or metered model was used.",
+ "verdict": "PASS_WITH_DISCLOSED_MODEL_FAILURES"
+}
diff --git a/verification/grateful-dead-dance-five.e2e.js b/verification/grateful-dead-dance-five.e2e.js
new file mode 100644
index 0000000..f3b8f6a
--- /dev/null
+++ b/verification/grateful-dead-dance-five.e2e.js
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+const { chromium } = require('/Users/macstudio3/Projects/Designer-Wallcoverings/node_modules/playwright');
+
+const challenges = [
+ ['c5c20cf0ae13', 'Sunwheel Meadow Dance'],
+ ['ee75e7535787', 'The Dancing Bear Procession'],
+ ['1c80df76af60', 'Midnight Woodland Jam'],
+ ['9682a8139b09', 'Orbit of the Cosmic Dancers'],
+ ['246049758201', 'Wall of Sound Street Dance'],
+];
+const models = ['qwen3-14b', 'qwen25-7b', 'claude-code'];
+
+(async () => {
+ const base = process.env.ARENA_URL || 'https://modelarena.agentabrams.com/';
+ const browser = await chromium.launch({ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', headless: true });
+ const context = await browser.newContext({
+ viewport: { width: 1280, height: 900 },
+ httpCredentials: { username: process.env.ARENA_USER || 'admin', password: process.env.ARENA_PASS || 'DW2024!' },
+ });
+ const page = await context.newPage();
+ await page.addInitScript(() => {
+ window.__arenaRafTicks = 0;
+ const nativeRaf = window.requestAnimationFrame.bind(window);
+ window.requestAnimationFrame = callback => nativeRaf(time => {
+ window.__arenaRafTicks += 1;
+ callback(time);
+ });
+ });
+ const errors = [];
+ const artifactErrors = [];
+ let activeArtifact = null;
+ page.on('pageerror', error => (activeArtifact ? artifactErrors : errors).push(activeArtifact ? { artifact: activeArtifact, error: String(error) } : String(error)));
+ page.on('console', message => {
+ const value = message.text();
+ if (message.type() !== 'error' || /^Failed to load resource:/.test(value) || /static\.cloudflareinsights\.com\/beacon\.min\.js/.test(value)) return;
+ (activeArtifact ? artifactErrors : errors).push(activeArtifact ? { artifact: activeArtifact, error: value } : value);
+ });
+ await page.goto(base, { waitUntil: 'domcontentloaded' });
+ await page.waitForSelector('#list .card');
+ const listText = await page.locator('#list').innerText();
+ for (const [, title] of challenges) if (!listText.includes(title)) throw new Error(`live list missing ${title}`);
+
+ const records = await page.evaluate(async ids => Promise.all(ids.map(id => fetch('/api/challenges/' + id).then(response => response.json()))), challenges.map(([id]) => id));
+ for (const record of records) {
+ if (record.prompt.length < 1000) throw new Error(`${record.title} prompt too short`);
+ if (record.runs.length !== 3 || record.runs.some(run => run.status !== 'done' || !run.thumb || typeof run.aiScore !== 'number')) {
+ throw new Error(`${record.title} is not complete/rendered/scored`);
+ }
+ }
+
+ const motion = [];
+ for (const [id, title] of challenges) {
+ for (const model of models) {
+ activeArtifact = `${id}/${model}`;
+ const errorStart = artifactErrors.length;
+ await page.goto(new URL(`artifact/${id}/${model}`, base).href, { waitUntil: 'domcontentloaded' });
+ await page.waitForTimeout(350);
+ const evidence = await page.evaluate(() => {
+ const html = document.documentElement.outerHTML;
+ return {
+ bodyChars: (document.body.innerText || '').trim().length,
+ cssAnimations: document.getAnimations().length,
+ canvasCount: document.querySelectorAll('canvas').length,
+ svgCount: document.querySelectorAll('svg').length,
+ rafTicks: window.__arenaRafTicks || 0,
+ motionMarkers: (html.match(/@keyframes|requestAnimationFrame|\.animate\s*\(/g) || []).length,
+ controls: document.querySelectorAll('button,input,select').length,
+ };
+ });
+ if (evidence.bodyChars < 20 && evidence.canvasCount < 1 && evidence.svgCount < 1) throw new Error(`${title}/${model} rendered empty`);
+ const runtimeErrors = artifactErrors.slice(errorStart);
+ const motionWorking = runtimeErrors.length === 0 && evidence.motionMarkers > 0 && (evidence.cssAnimations > 0 || evidence.rafTicks > 0);
+ motion.push({ id, model, motionWorking, ...evidence });
+ activeArtifact = null;
+ }
+ }
+
+ for (const [id, title] of challenges) {
+ const candidates = motion.filter(item => item.id === id);
+ if (candidates.filter(item => item.motionWorking).length < 2 || candidates.filter(item => item.motionWorking && item.controls > 0).length < 1) {
+ throw new Error(`${title} needs two working animations and one controlled implementation: ${JSON.stringify(candidates)}`);
+ }
+ }
+
+ await page.goto(base, { waitUntil: 'domcontentloaded' });
+ await page.screenshot({ path: 'verification/grateful-dead-dance-five-live.png', fullPage: true });
+ if (errors.length) throw new Error(`browser errors: ${errors.join(' | ')}`);
+ console.log(JSON.stringify({ pass: true, challenges: 5, artifacts: 15, motionVerified: motion.filter(item => item.motionWorking).length, motion, artifactErrors, browserErrors: 0 }));
+ await browser.close();
+})().catch(error => { console.error(error.stack || error); process.exit(1); });
← 3ac1126 auto-data-snapshot: 2026-09-01T14:14:21 (15 data files) — da
·
back to Model Arena
·
auto-data-snapshot: 2026-09-01T14:52:01 (32 data files) — da 8923fc9 →