[object Object]

← back to Model Arena

verify five new Grateful Dead gallery builds

37b006700f868d7d493189b82d769728ac3961f6 · 2026-09-01 17:12:33 -0700 · Steve Abrams

Files touched

Diff

commit 37b006700f868d7d493189b82d769728ac3961f6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 1 17:12:33 2026 -0700

    verify five new Grateful Dead gallery builds
---
 data/artifacts/127317180daf/claude-code.html      | 918 ++++++++++++++++++++
 data/artifacts/127317180daf/qwen25-7b.html        | 176 ++++
 data/artifacts/127317180daf/qwen3-14b.html        | 524 ++++++++++++
 data/artifacts/77fc58739877/claude-code.html      | 973 ++++++++++++++++++++++
 data/artifacts/77fc58739877/qwen25-7b.html        | 128 +++
 data/artifacts/77fc58739877/qwen25-7b.png         | Bin 6930 -> 2731 bytes
 data/artifacts/77fc58739877/qwen3-14b.html        | 423 ++++++++++
 data/artifacts/77fc58739877/qwen3-14b.png         | Bin 21564 -> 25509 bytes
 data/artifacts/97621618f31c/claude-code.html      | 865 +++++++++++++++++++
 data/artifacts/97621618f31c/qwen25-7b.html        | 146 ++++
 data/artifacts/97621618f31c/qwen3-14b.html        | 511 ++++++++++++
 data/artifacts/cd83de847d24/claude-code.html      | 887 ++++++++++++++++++++
 data/artifacts/cd83de847d24/claude-code.png       | Bin 57097 -> 76250 bytes
 data/artifacts/cd83de847d24/qwen25-7b.html        | 182 ++++
 data/artifacts/cd83de847d24/qwen25-7b.png         | Bin 32233 -> 79849 bytes
 data/artifacts/cd83de847d24/qwen3-14b.html        | 384 +++++++++
 data/artifacts/ec9e68a64f43/claude-code.html      | 794 ++++++++++++++++++
 data/artifacts/ec9e68a64f43/qwen25-7b.html        |  71 ++
 data/artifacts/ec9e68a64f43/qwen3-14b.html        | 400 +++++++++
 data/challenges.json                              |  56 +-
 verification/grateful-dead-poster-five-live.png   | Bin 0 -> 90055 bytes
 verification/grateful-dead-poster-five-proof.json |  39 +
 verification/grateful-dead-poster-five.e2e.js     |  79 ++
 23 files changed, 7528 insertions(+), 28 deletions(-)

diff --git a/data/artifacts/127317180daf/claude-code.html b/data/artifacts/127317180daf/claude-code.html
new file mode 100644
index 0000000..0ab0589
--- /dev/null
+++ b/data/artifacts/127317180daf/claude-code.html
@@ -0,0 +1,918 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Rose City Rooftop Solstice</title>
+<style>
+  :root{
+    --ink:#f6ecff;
+    --ink-dim:#c9bce0;
+    --panel:rgba(18,10,38,.72);
+    --panel-solid:#160a2e;
+    --edge:rgba(255,214,170,.28);
+    --gold:#f7c56b;
+    --rose:#ff7fae;
+    --violet:#b48bff;
+    --focus:#7ff0e6;
+    --shadow:0 18px 50px rgba(0,0,0,.55);
+    font-synthesis:none;
+  }
+  *{box-sizing:border-box}
+  html,body{height:100%;margin:0}
+  body{
+    background:#0a0618;
+    color:var(--ink);
+    font-family:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,"Times New Roman",serif;
+    overflow:hidden;
+    -webkit-font-smoothing:antialiased;
+  }
+  #stage-wrap{position:fixed;inset:0}
+  canvas{display:block;width:100%;height:100%}
+
+  /* Title */
+  .masthead{
+    position:fixed;top:0;left:0;right:0;padding:18px 22px 40px;
+    pointer-events:none;
+    background:linear-gradient(180deg,rgba(6,3,16,.55),rgba(6,3,16,0));
+    z-index:3;
+  }
+  .masthead h1{
+    margin:0;font-weight:500;letter-spacing:.11em;
+    font-size:clamp(19px,3.1vw,34px);
+    text-transform:uppercase;
+    text-shadow:0 2px 22px rgba(0,0,0,.6);
+  }
+  .masthead h1 .rose{color:var(--rose)}
+  .masthead p{
+    margin:.35em 0 0;font-size:clamp(11px,1.5vw,14px);
+    color:var(--ink-dim);letter-spacing:.16em;text-transform:uppercase;
+    font-family:ui-sans-serif,system-ui,"Helvetica Neue",Arial,sans-serif;
+  }
+  .scene-flag{
+    display:inline-block;margin-top:.5em;padding:.28em .8em;
+    border:1px solid var(--edge);border-radius:999px;
+    font-family:ui-sans-serif,system-ui,sans-serif;font-size:12px;
+    letter-spacing:.22em;color:var(--gold);background:rgba(20,10,40,.4);
+  }
+
+  /* Controls */
+  .controls{
+    position:fixed;top:14px;right:14px;z-index:6;
+    width:min(320px,90vw);max-height:calc(100vh - 28px);overflow:auto;
+    background:var(--panel);backdrop-filter:blur(11px);
+    border:1px solid var(--edge);border-radius:16px;
+    box-shadow:var(--shadow);
+    font-family:ui-sans-serif,system-ui,"Helvetica Neue",Arial,sans-serif;
+    scrollbar-width:thin;
+  }
+  .controls header{
+    display:flex;align-items:center;justify-content:space-between;
+    padding:12px 14px;position:sticky;top:0;background:var(--panel-solid);
+    border-bottom:1px solid var(--edge);z-index:2;border-radius:16px 16px 0 0;
+  }
+  .controls header h2{margin:0;font-size:13px;letter-spacing:.16em;text-transform:uppercase;color:var(--gold);font-weight:600}
+  .toggle-panel{
+    background:none;border:1px solid var(--edge);color:var(--ink);
+    border-radius:8px;padding:4px 9px;cursor:pointer;font-size:12px;
+  }
+  .controls .body{padding:6px 14px 16px}
+  fieldset{border:none;border-top:1px solid rgba(255,255,255,.08);margin:0;padding:14px 0 4px}
+  fieldset:first-of-type{border-top:none}
+  legend{
+    padding:0;font-size:11px;letter-spacing:.2em;text-transform:uppercase;
+    color:var(--violet);font-weight:600;margin-bottom:8px;
+  }
+  .btnrow{display:flex;flex-wrap:wrap;gap:6px}
+  .seg{
+    flex:1 1 auto;min-width:64px;text-align:center;
+    background:rgba(255,255,255,.05);border:1px solid transparent;
+    color:var(--ink);border-radius:9px;padding:8px 6px;cursor:pointer;
+    font-size:12px;letter-spacing:.04em;transition:background .18s,border-color .18s;
+  }
+  .seg:hover{background:rgba(255,255,255,.12)}
+  .seg[aria-pressed="true"],.seg.active{
+    background:linear-gradient(180deg,rgba(255,127,174,.35),rgba(180,139,255,.28));
+    border-color:var(--edge);color:#fff;font-weight:600;
+  }
+  .slider{margin:10px 0}
+  .slider label{display:flex;justify-content:space-between;font-size:12px;color:var(--ink-dim);margin-bottom:4px}
+  .slider output{color:var(--gold);font-variant-numeric:tabular-nums}
+  input[type="range"]{width:100%;accent-color:var(--rose);height:22px;cursor:pointer}
+  .checks{display:grid;grid-template-columns:1fr 1fr;gap:6px 10px}
+  .chk{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--ink);cursor:pointer;user-select:none}
+  .chk input{accent-color:var(--violet);width:15px;height:15px;cursor:pointer}
+  .transport{display:flex;gap:6px}
+  .transport .seg{font-weight:600}
+  .hint{font-size:11px;color:var(--ink-dim);margin:10px 0 0;line-height:1.5}
+  kbd{
+    font-family:ui-monospace,Menlo,Consolas,monospace;font-size:10.5px;
+    background:rgba(255,255,255,.1);border:1px solid var(--edge);
+    border-radius:4px;padding:1px 5px;color:var(--ink);
+  }
+  .note{
+    position:fixed;left:14px;bottom:12px;z-index:5;max-width:min(430px,72vw);
+    font-family:ui-sans-serif,system-ui,sans-serif;font-size:11.5px;line-height:1.5;
+    color:var(--ink-dim);background:rgba(10,5,24,.5);backdrop-filter:blur(6px);
+    border:1px solid rgba(255,255,255,.08);border-left:2px solid var(--gold);
+    border-radius:0 10px 10px 0;padding:9px 13px;
+  }
+  .note b{color:var(--gold);font-weight:600}
+
+  /* Focus states */
+  a:focus-visible,button:focus-visible,input:focus-visible,.chk:focus-within,[tabindex]:focus-visible{
+    outline:3px solid var(--focus);outline-offset:2px;border-radius:8px;
+  }
+  .skip{position:fixed;left:-999px;top:0;z-index:20;background:var(--panel-solid);color:#fff;padding:10px 16px;border-radius:0 0 10px 0}
+  .skip:focus{left:0}
+
+  .controls.collapsed .body{display:none}
+  @media (max-width:560px){
+    .controls{width:calc(100vw - 20px);left:10px;right:10px}
+    .note{max-width:calc(100vw - 28px)}
+  }
+  @media (prefers-reduced-motion:reduce){
+    *{scroll-behavior:auto}
+  }
+</style>
+</head>
+<body>
+<a class="skip" href="#controls">Skip to controls</a>
+
+<div id="stage-wrap">
+  <canvas id="scene" role="img"
+    aria-label="An animated urban-fantasy rooftop garden at solstice sunset: dancers spin around a rose-shaped light sculpture, couples cross narrow bridges, freestyle dancers trail curves near water towers, and a quiet procession climbs fire escapes while bears, cats, pigeons, dogs and moths move through the scene."></canvas>
+</div>
+
+<header class="masthead" aria-hidden="false">
+  <h1>Rose City <span class="rose">Rooftop</span> Solstice</h1>
+  <p>An authored dance-garden above the glowing city</p><br>
+  <span class="scene-flag" id="sceneFlag">Arrival</span>
+</header>
+
+<form class="controls" id="controls" aria-label="Composition controls">
+  <header>
+    <h2>Direct the Solstice</h2>
+    <button type="button" class="toggle-panel" id="panelToggle" aria-expanded="true" aria-controls="ctlBody">Hide</button>
+  </header>
+  <div class="body" id="ctlBody">
+
+    <fieldset>
+      <legend>Camera</legend>
+      <div class="btnrow" role="group" aria-label="Camera view">
+        <button type="button" class="seg" data-cam="skyline"  aria-pressed="false">Skyline</button>
+        <button type="button" class="seg" data-cam="rooftop"  aria-pressed="true">Rooftop</button>
+        <button type="button" class="seg" data-cam="birdseye" aria-pressed="false">Bird’s-eye</button>
+      </div>
+    </fieldset>
+
+    <fieldset>
+      <legend>Scene</legend>
+      <div class="btnrow" role="group" aria-label="Scene chapter">
+        <button type="button" class="seg" data-scene="0" aria-pressed="false">Arrival</button>
+        <button type="button" class="seg" data-scene="1" aria-pressed="false">Long Light</button>
+        <button type="button" class="seg" data-scene="2" aria-pressed="false">Midnight Bloom</button>
+        <button type="button" class="seg active" data-scene="auto" aria-pressed="true">Auto ↻</button>
+      </div>
+    </fieldset>
+
+    <fieldset>
+      <legend>Tempo &amp; Crowd</legend>
+      <div class="slider">
+        <label for="tempo">Visual tempo <output id="tempoOut">1.0×</output></label>
+        <input type="range" id="tempo" min="0" max="2" step="0.05" value="1">
+      </div>
+      <div class="slider">
+        <label for="density">Crowd density <output id="densityOut">70%</output></label>
+        <input type="range" id="density" min="0" max="1" step="0.02" value="0.7">
+      </div>
+    </fieldset>
+
+    <fieldset>
+      <legend>Isolate Choreography</legend>
+      <div class="checks">
+        <label class="chk"><input type="checkbox" data-grp="circle" checked> Rose circle</label>
+        <label class="chk"><input type="checkbox" data-grp="couples" checked> Bridge couples</label>
+        <label class="chk"><input type="checkbox" data-grp="freestyle" checked> Freestyle</label>
+        <label class="chk"><input type="checkbox" data-grp="procession" checked> Procession</label>
+        <label class="chk"><input type="checkbox" data-grp="animals" checked> Animals</label>
+      </div>
+    </fieldset>
+
+    <fieldset>
+      <legend>Overlays</legend>
+      <div class="checks">
+        <label class="chk"><input type="checkbox" id="ovNames"> Animal names</label>
+        <label class="chk"><input type="checkbox" id="ovTrails" checked> Motion trails</label>
+        <label class="chk"><input type="checkbox" id="ovRoutes"> Route diagrams</label>
+        <label class="chk"><input type="checkbox" id="ovReduced"> Reduced motion</label>
+      </div>
+    </fieldset>
+
+    <fieldset>
+      <legend>Transport</legend>
+      <div class="transport" role="group" aria-label="Playback">
+        <button type="button" class="seg" id="btnPause" aria-pressed="false">⏸ Pause</button>
+        <button type="button" class="seg" id="btnRestart">↺ Restart</button>
+      </div>
+      <p class="hint">
+        <kbd>Space</kbd> pause · <kbd>1</kbd><kbd>2</kbd><kbd>3</kbd> cameras ·
+        <kbd>R</kbd> reduced motion · <kbd>N</kbd> names · <kbd>T</kbd> trails · <kbd>D</kbd> routes.
+      </p>
+    </fieldset>
+
+  </div>
+</form>
+
+<p class="note">
+  <b>An honest note.</b> This piece keeps its own solstice time. Its visual rhythm is
+  <b>authored in code</b> — every sway, spiral and bloom is choreographed by hand and does
+  <b>not</b> listen to any audio. Imagery is wholly original, evoking the generous,
+  improvisational spirit of open-air gatherings.
+</p>
+
+<script>
+(() => {
+"use strict";
+
+/* ---------- setup ---------- */
+const canvas = document.getElementById('scene');
+const ctx = canvas.getContext('2d');
+const STAGE_W = 1600, STAGE_H = 900;
+let W = 0, H = 0, DPR = 1, viewScale = 1;
+
+function resize(){
+  DPR = Math.min(window.devicePixelRatio||1, 2);
+  W = window.innerWidth; H = window.innerHeight;
+  canvas.width = Math.floor(W*DPR); canvas.height = Math.floor(H*DPR);
+  canvas.style.width = W+'px'; canvas.style.height = H+'px';
+  ctx.setTransform(DPR,0,0,DPR,0,0);
+  viewScale = Math.max(W/STAGE_W, H/STAGE_H) * 1.02;
+  if(state.reduced) render();
+}
+window.addEventListener('resize', resize);
+
+/* ---------- state ---------- */
+const state = {
+  tempo:1, density:0.7, paused:false, reduced:false,
+  camera:'rooftop',
+  sceneMode:'auto', scenePinned:0, scenePhase:0,
+  show:{circle:true,couples:true,freestyle:true,procession:true,animals:true},
+  names:false, trails:true, routes:false
+};
+
+/* ---------- seeded rng ---------- */
+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;};}
+const rand = mulberry32(20260621);
+
+/* ---------- palettes / scenes ---------- */
+const SCENES = [
+  { name:'Arrival',        top:'#f7b25a', mid:'#c9527d', low:'#3a2350', glow:'#ffd98a', star:0.0,  cx:0.30 },
+  { name:'Long Light',     top:'#ff8f5e', mid:'#b13a80', low:'#2c1e5a', glow:'#ff9ec4', star:0.15, cx:0.70 },
+  { name:'Midnight Bloom', top:'#3a2f74', mid:'#6a2c82', low:'#120a2e', glow:'#c39bff', star:1.0,  cx:0.95 }
+];
+const lerp=(a,b,t)=>a+(b-a)*t;
+function hx(c){return [parseInt(c.slice(1,3),16),parseInt(c.slice(3,5),16),parseInt(c.slice(5,7),16)];}
+function mix(c1,c2,t){const a=hx(c1),b=hx(c2);return `rgb(${Math.round(lerp(a[0],b[0],t))},${Math.round(lerp(a[1],b[1],t))},${Math.round(lerp(a[2],b[2],t))})`;}
+function scenePal(){
+  const p = state.scenePhase;
+  const i = Math.floor(p)%3, f = p-Math.floor(p), j=(i+1)%3;
+  const A=SCENES[i], B=SCENES[j];
+  return {
+    top:mix(A.top,B.top,f), mid:mix(A.mid,B.mid,f), low:mix(A.low,B.low,f),
+    glow:mix(A.glow,B.glow,f), star:lerp(A.star,B.star,f),
+    complexity:lerp(A.cx,B.cx,f), idx:i, frac:f, name:SCENES[i].name
+  };
+}
+
+/* ---------- static world geometry ---------- */
+// skyline buildings (parallax)
+const buildings = [];
+(function(){
+  // far layer
+  for(let i=0;i<26;i++){
+    const w=40+rand()*70; const x=rand()*STAGE_W;
+    buildings.push({x, w, h:120+rand()*260, depth:0.25, y:STAGE_H*0.62, hue:rand()});
+  }
+  // mid layer
+  for(let i=0;i<16;i++){
+    const w=70+rand()*120; const x=rand()*STAGE_W;
+    buildings.push({x, w, h:200+rand()*320, depth:0.55, y:STAGE_H*0.72, hue:rand()});
+  }
+})();
+buildings.sort((a,b)=>a.depth-b.depth);
+
+const clouds = [];
+for(let i=0;i<8;i++) clouds.push({x:rand()*STAGE_W, y:80+rand()*260, s:0.6+rand()*1.4, sp:6+rand()*14, d:0.35+rand()*0.4});
+
+// laundry lines between buildings
+const laundry=[];
+for(let i=0;i<5;i++){
+  const x=200+rand()*1100, y=180+rand()*180, len=90+rand()*140;
+  const items=3+Math.floor(rand()*4), pcs=[];
+  for(let k=0;k<items;k++) pcs.push({t:(k+1)/(items+1), c:['#ff9ec4','#ffe7a8','#9be7e0','#d9c6ff','#ffd0b0'][Math.floor(rand()*5)], w:14+rand()*10, h:20+rand()*16});
+  laundry.push({x,y,len,pcs,sag:14+rand()*10});
+}
+
+// rooftop platforms
+const roofs=[
+  {x:150,y:610,w:520,h:300},          // left roof
+  {x:640,y:560,w:640,h:340},          // central roof (dance floor)
+  {x:1270,y:640,w:300,h:280}          // right roof
+];
+const ROSE={x:960,y:610};             // rose sculpture center (on central roof)
+const towers=[{x:520,y:470},{x:1180,y:430}]; // water towers
+const fireEscapeX=1470;               // right building fire escape
+
+/* zone spread by camera (bird's-eye separates zones) */
+function camParams(){
+  switch(state.camera){
+    case 'skyline':  return {zoom:0.66, squash:1.0,  panX:0, panY:-40, spread:0.92, floor:0, figScale:0.86};
+    case 'birdseye': return {zoom:0.94, squash:0.74, panX:0, panY:120, spread:1.24, floor:1, figScale:1.0};
+    default:         return {zoom:1.0,  squash:1.0,  panX:0, panY:38,  spread:1.0,  floor:0, figScale:1.06};
+  }
+}
+// map a stage point outward from rose center by spread (for bird's-eye separation)
+function spr(x,y,cp){ return {x: ROSE.x + (x-ROSE.x)*cp.spread, y: ROSE.y + (y-ROSE.y)*cp.spread}; }
+
+/* ---------- trails buffers ---------- */
+const freeTrails=[]; // per freestyle dancer
+const MAXTRAIL=26;
+
+/* ---------- clock ---------- */
+let clock=0, sceneClock=0, last=performance.now(), rafId=null;
+
+function loop(now){
+  const dt=Math.min(0.05,(now-last)/1000); last=now;
+  if(!state.paused && !state.reduced){
+    clock += dt*state.tempo;
+    if(state.sceneMode==='auto'){
+      sceneClock += dt*state.tempo;
+      state.scenePhase = (sceneClock/22)%3;   // ~22s per chapter
+    }
+  }
+  render();
+  rafId=requestAnimationFrame(loop);
+}
+function startLoop(){ if(!rafId){ last=performance.now(); rafId=requestAnimationFrame(loop);} }
+function stopLoop(){ if(rafId){ cancelAnimationFrame(rafId); rafId=null;} }
+
+/* ---------- drawing helpers ---------- */
+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 drawDancer(x,y,s,ph,col,energy){
+  const e=state.reduced?0.18:energy;
+  const armA=Math.sin(ph)*1.1*e, armB=Math.sin(ph+2.1)*1.1*e;
+  const legA=Math.sin(ph+0.6)*0.5*e, legB=Math.sin(ph+3.1)*0.5*e;
+  const bob=Math.sin(ph*2)*2.4*e*s;
+  const cx=x, cy=y+bob;
+  ctx.save();
+  ctx.strokeStyle=col; ctx.lineWidth=2.2*s; ctx.lineCap='round'; ctx.fillStyle=col;
+  const hip=cy, sh=cy-13*s, hd=sh-8*s;
+  // legs
+  ctx.beginPath(); ctx.moveTo(cx,hip); ctx.lineTo(cx-6*s+legA*7*s, hip+15*s); ctx.stroke();
+  ctx.beginPath(); ctx.moveTo(cx,hip); ctx.lineTo(cx+6*s+legB*7*s, hip+15*s); ctx.stroke();
+  // torso
+  ctx.beginPath(); ctx.moveTo(cx,hip); ctx.lineTo(cx,sh); ctx.stroke();
+  // arms
+  ctx.beginPath(); ctx.moveTo(cx,sh+2*s); ctx.lineTo(cx-9*s, sh-6*s-armA*10*s); ctx.stroke();
+  ctx.beginPath(); ctx.moveTo(cx,sh+2*s); ctx.lineTo(cx+9*s, sh-6*s-armB*10*s); ctx.stroke();
+  // head
+  ctx.beginPath(); ctx.arc(cx,hd,4.2*s,0,7); ctx.fill();
+  ctx.restore();
+}
+
+function label(x,y,text,col){
+  ctx.save();
+  ctx.font='600 13px ui-sans-serif,system-ui,sans-serif';
+  ctx.textAlign='center';
+  const w=ctx.measureText(text).width+12;
+  ctx.fillStyle='rgba(8,4,20,.6)';
+  roundRect(x-w/2,y-16,w,17,8); ctx.fill();
+  ctx.fillStyle=col||'#fff';
+  ctx.fillText(text,x,y-3);
+  ctx.restore();
+}
+
+/* ---------- background ---------- */
+function drawSky(pal){
+  const g=ctx.createLinearGradient(0,0,0,STAGE_H);
+  g.addColorStop(0,pal.top); g.addColorStop(0.42,pal.mid); g.addColorStop(1,pal.low);
+  ctx.fillStyle=g; ctx.fillRect(-400,-400,STAGE_W+800,STAGE_H+800);
+  // sun / solstice orb sinking with scene
+  const sunY = lerp(320,540,pal.idx===2?1:pal.frac*0.5+ (pal.idx*0.5));
+  const sy = 260 + state.scenePhase*90;
+  const gr=ctx.createRadialGradient(1080,sy,10,1080,sy,220);
+  gr.addColorStop(0,pal.glow); gr.addColorStop(0.4,'rgba(255,210,150,'+(0.5-pal.star*0.3)+')'); gr.addColorStop(1,'rgba(255,210,150,0)');
+  ctx.fillStyle=gr; ctx.beginPath(); ctx.arc(1080,sy,220,0,7); ctx.fill();
+  ctx.fillStyle=mix(pal.glow,'#ffffff',0.4); ctx.beginPath(); ctx.arc(1080,sy,34-state.scenePhase*4,0,7); ctx.fill();
+  // stars
+  if(pal.star>0.02){
+    ctx.save(); ctx.fillStyle='#fff';
+    const sr=mulberry32(7);
+    for(let i=0;i<140;i++){
+      const x=sr()*STAGE_W, y=sr()*STAGE_H*0.55;
+      const tw=state.reduced?0.6:(0.4+0.6*Math.abs(Math.sin(clock*1.5+i)));
+      ctx.globalAlpha=pal.star*tw*0.9; ctx.fillRect(x,y,1.6,1.6);
+    }
+    ctx.restore();
+  }
+}
+
+function drawClouds(pal){
+  ctx.save();
+  for(const c of clouds){
+    const dx = state.reduced ? c.x : (c.x + clock*c.sp*c.d)%(STAGE_W+400)-200;
+    ctx.globalAlpha=0.16+0.12*(1-pal.star);
+    ctx.fillStyle=mix(pal.glow,'#ffffff',0.3);
+    for(let k=0;k<4;k++){
+      ctx.beginPath();
+      ctx.ellipse(dx+k*36*c.s, c.y+Math.sin(k)*8*c.s, 46*c.s-k*4, 20*c.s, 0,0,7);
+      ctx.fill();
+    }
+  }
+  ctx.restore();
+}
+
+function drawBuildings(pal,cp){
+  const drift = state.reduced?0:Math.sin(clock*0.06)*10;
+  for(const b of buildings){
+    const x=b.x + drift*b.depth;
+    const base = mix(pal.low, pal.mid, b.depth*0.5);
+    ctx.fillStyle=mix('#0c0722', base, 0.45+b.depth*0.4);
+    ctx.fillRect(x, b.y-b.h, b.w, b.h);
+    // reflective windows
+    const cols=Math.max(2,Math.floor(b.w/16)), rows=Math.floor(b.h/22);
+    for(let r=0;r<rows;r++){
+      for(let cc=0;cc<cols;cc++){
+        const wx=x+6+cc*16, wy=b.y-b.h+10+r*22;
+        if(wx>x+b.w-8) continue;
+        const idn=(r*7+cc*13+Math.floor(b.x));
+        const lit=(Math.sin(idn)*0.5+0.5);
+        let a=0.10+lit*0.35*(0.4+pal.star*0.8);
+        if(!state.reduced) a*= (0.6+0.4*Math.sin(clock*0.8+idn));
+        ctx.fillStyle= mix(pal.glow, pal.top, (cc+r)%2? .2:.7);
+        ctx.globalAlpha=Math.max(0,a);
+        ctx.fillRect(wx,wy,7,10);
+      }
+    }
+    ctx.globalAlpha=1;
+  }
+}
+
+function drawLaundry(pal){
+  ctx.save();
+  ctx.strokeStyle='rgba(255,255,255,.22)'; ctx.lineWidth=1.4;
+  for(const L of laundry){
+    ctx.beginPath();
+    ctx.moveTo(L.x,L.y);
+    ctx.quadraticCurveTo(L.x+L.len/2, L.y+L.sag, L.x+L.len, L.y);
+    ctx.stroke();
+    for(const p of L.pcs){
+      const px=L.x+L.len*p.t;
+      const py=L.y+L.sag*(1-Math.pow(2*p.t-1,2))*0.9;
+      const sway=state.reduced?0:Math.sin(clock*1.4+px*0.05)*3;
+      ctx.save(); ctx.translate(px,py); ctx.rotate(sway*0.03);
+      ctx.fillStyle=p.c; ctx.globalAlpha=0.85;
+      roundRect(-p.w/2,0,p.w,p.h,4); ctx.fill();
+      ctx.restore();
+    }
+  }
+  ctx.restore();
+}
+
+function drawRoofs(pal,cp){
+  for(const r of roofs){
+    const top=mix(pal.mid,pal.low,0.45);
+    ctx.fillStyle=mix('#150c30',top,0.5);
+    roundRect(r.x,r.y,r.w,r.h,10); ctx.fill();
+    // rooftop deck highlight
+    ctx.fillStyle=mix(pal.glow,pal.mid,0.7); ctx.globalAlpha=0.10+cp.floor*0.12;
+    roundRect(r.x+8,r.y+6,r.w-16,26,8); ctx.fill(); ctx.globalAlpha=1;
+    // parapet
+    ctx.strokeStyle='rgba(255,220,180,.18)'; ctx.lineWidth=2;
+    roundRect(r.x,r.y,r.w,r.h,10); ctx.stroke();
+  }
+  // bird's-eye floor grid on central roof
+  if(cp.floor){
+    ctx.save(); ctx.strokeStyle='rgba(255,210,170,.10)'; ctx.lineWidth=1;
+    const r=roofs[1];
+    for(let gx=r.x;gx<=r.x+r.w;gx+=40){ctx.beginPath();ctx.moveTo(gx,r.y);ctx.lineTo(gx,r.y+r.h);ctx.stroke();}
+    for(let gy=r.y;gy<=r.y+r.h;gy+=40){ctx.beginPath();ctx.moveTo(r.x,gy);ctx.lineTo(r.x+r.w,gy);ctx.stroke();}
+    ctx.restore();
+  }
+}
+
+/* lanterns + moths */
+const lanterns=[{x:720,y:560},{x:1180,y:560},{x:420,y:620},{x:1350,y:640},{x:960,y:470}];
+function drawLanterns(pal){
+  for(const L of lanterns){
+    const p=spr(L.x,L.y,camParams());
+    const pulse=state.reduced?1:(0.75+0.25*Math.sin(clock*2+L.x));
+    const gr=ctx.createRadialGradient(p.x,p.y,2,p.x,p.y,46);
+    gr.addColorStop(0,'rgba(255,224,150,'+(0.9*pulse)+')');
+    gr.addColorStop(1,'rgba(255,200,120,0)');
+    ctx.fillStyle=gr; ctx.beginPath(); ctx.arc(p.x,p.y,46,0,7); ctx.fill();
+    ctx.fillStyle='#ffe6a0'; ctx.beginPath(); ctx.arc(p.x,p.y,5,0,7); ctx.fill();
+    ctx.strokeStyle='rgba(255,255,255,.3)';ctx.lineWidth=1;
+    ctx.beginPath();ctx.moveTo(p.x,p.y-12);ctx.lineTo(p.x,p.y-30);ctx.stroke();
+  }
+}
+function drawMoths(pal,cp){
+  ctx.save();
+  for(let li=0;li<lanterns.length;li++){
+    const L=lanterns[li]; const c=spr(L.x,L.y,cp);
+    const n=3;
+    for(let m=0;m<n;m++){
+      const t=clock*1.6 + m*2.1 + li;
+      const rad=14+8*Math.sin(t*1.3+m);
+      const mx=c.x+Math.cos(t+m*2)*rad;
+      const my=c.y-8+Math.sin(t*1.4+m*2)*rad*0.7;
+      ctx.fillStyle='rgba(255,240,200,.9)';
+      const flut=state.reduced?3:(2.5+1.6*Math.abs(Math.sin(t*9)));
+      ctx.beginPath();ctx.ellipse(mx-2,my,flut,2.2,0.5,0,7);ctx.fill();
+      ctx.beginPath();ctx.ellipse(mx+2,my,flut,2.2,-0.5,0,7);ctx.fill();
+      if(li===0&&m===0&&state.names) label(mx,my-8,'moths','#ffe6a0');
+    }
+  }
+  ctx.restore();
+}
+
+/* ---------- the rose light sculpture ---------- */
+function drawRose(pal,cp){
+  const c=spr(ROSE.x,ROSE.y,cp);
+  const bloom = pal.idx===2 ? (0.6+0.4*pal.frac) : 0.35+pal.frac*0.35;
+  const rot = state.reduced?0:clock*0.35;
+  ctx.save(); ctx.translate(c.x,c.y-6); 
+  // glow base
+  const gr=ctx.createRadialGradient(0,0,4,0,0,120);
+  gr.addColorStop(0,'rgba(255,150,190,'+(0.5*bloom)+')');
+  gr.addColorStop(0.5,'rgba(200,120,255,'+(0.28*bloom)+')');
+  gr.addColorStop(1,'rgba(120,80,200,0)');
+  ctx.fillStyle=gr; ctx.beginPath(); ctx.arc(0,0,120,0,7); ctx.fill();
+  // layered petals (rose curves)
+  const layers=5;
+  for(let L=0;L<layers;L++){
+    const k = 3 + L;                       // petal count varies per layer
+    const R = (18+L*13)*(0.85+0.25*bloom);
+    const pulse = state.reduced?1:(0.92+0.08*Math.sin(clock*2 - L));
+    ctx.save(); ctx.rotate(rot*(L%2?-1:1)*(0.5+L*0.12));
+    ctx.beginPath();
+    for(let a=0;a<=Math.PI*2+0.05;a+=0.05){
+      const r=R*Math.abs(Math.cos(k*a))*pulse + R*0.28;
+      const x=Math.cos(a)*r, y=Math.sin(a)*r;
+      a===0?ctx.moveTo(x,y):ctx.lineTo(x,y);
+    }
+    ctx.closePath();
+    ctx.fillStyle = 'rgba('+[255,120+L*20,180-L*10]+','+(0.14+0.06*bloom)+')';
+    ctx.strokeStyle='rgba(255,190,225,'+(0.35+0.12*bloom)+')'; ctx.lineWidth=1.4;
+    ctx.fill(); ctx.stroke();
+    ctx.restore();
+  }
+  // core
+  ctx.fillStyle='#fff3fb'; ctx.beginPath(); ctx.arc(0,0,6*(0.9+bloom*0.4),0,7); ctx.fill();
+  ctx.restore();
+}
+
+/* ---------- choreography groups ---------- */
+function drawCircle(pal,cp){
+  const c=spr(ROSE.x,ROSE.y,cp);
+  const count=Math.round((7+pal.complexity*7)* (0.4+state.density*0.9));
+  const spin=state.reduced?0.6:clock*0.5;
+  const R=92*cp.spread;
+  for(let i=0;i<count;i++){
+    const a=spin + i/count*Math.PI*2;
+    const rr=R*(1+0.05*Math.sin(clock*1.5+i));
+    const x=c.x+Math.cos(a)*rr, y=c.y-6+Math.sin(a)*rr*0.55*cp.squash;
+    const s=(0.9+0.2*Math.sin(a))*cp.figScale;
+    const col=mix(pal.glow,'#ffd1e6',(i%2)?0.2:0.7);
+    drawDancer(x,y,s,clock*3+i, col, 0.9);
+  }
+}
+
+function drawCouples(pal,cp){
+  // couples crossing narrow bridges in mirrored steps
+  const bridges=[
+    {a:spr(roofs[0].x+roofs[0].w,660,cp), b:spr(roofs[1].x,650,cp)},
+    {a:spr(roofs[1].x+roofs[1].w,660,cp), b:spr(roofs[2].x,690,cp)}
+  ];
+  for(let bi=0;bi<bridges.length;bi++){
+    const B=bridges[bi];
+    // bridge plank
+    ctx.strokeStyle='rgba(255,220,180,.35)'; ctx.lineWidth=6*cp.figScale;
+    ctx.beginPath(); ctx.moveTo(B.a.x,B.a.y); ctx.lineTo(B.b.x,B.b.y); ctx.stroke();
+    ctx.strokeStyle='rgba(255,220,180,.15)'; ctx.lineWidth=1;
+    for(let g=0;g<=6;g++){const t=g/6;ctx.beginPath();ctx.moveTo(lerp(B.a.x,B.b.x,t),lerp(B.a.y,B.b.y,t)-14);ctx.lineTo(lerp(B.a.x,B.b.x,t),lerp(B.a.y,B.b.y,t));ctx.stroke();}
+    const pairs=Math.max(1,Math.round((1+pal.complexity*1.5)*(0.5+state.density)));
+    for(let p=0;p<pairs;p++){
+      const base=(clock*0.12 + p/pairs + bi*0.3);
+      const t=(Math.sin(base*Math.PI*2)*0.5+0.5); // shuttle back & forth mirrored
+      const x=lerp(B.a.x,B.b.x,t), y=lerp(B.a.y,B.b.y,t)-8;
+      const ph=clock*2.4+p;
+      // mirrored partners
+      const dx=Math.cos(Math.atan2(B.b.y-B.a.y,B.b.x-B.a.x)+Math.PI/2)*10*cp.figScale;
+      const dy=Math.sin(Math.atan2(B.b.y-B.a.y,B.b.x-B.a.x)+Math.PI/2)*6*cp.figScale;
+      drawDancer(x+dx,y+dy,0.95*cp.figScale,ph,'#9be7e0',0.7);
+      drawDancer(x-dx,y-dy,0.95*cp.figScale,-ph,'#fff0d0',0.7);
+    }
+  }
+}
+
+function drawFreestyle(pal,cp){
+  const count=Math.round((3+pal.complexity*5)*(0.4+state.density));
+  for(let i=0;i<count;i++){
+    while(freeTrails.length<=i) freeTrails.push([]);
+    const base=towers[i%towers.length];
+    const bp=spr(base.x,base.y+70,cp);
+    const t=clock*(0.8+0.2*(i%3));
+    const x=bp.x+Math.cos(t*1.3+i)*70*cp.spread + Math.sin(t*0.7+i)*24;
+    const y=bp.y+Math.sin(t*1.9+i)*34*cp.squash + Math.cos(t*0.5)*10;
+    // trail
+    const tr=freeTrails[i];
+    if(!state.reduced){ tr.push({x,y}); if(tr.length>MAXTRAIL) tr.shift(); }
+    if(state.trails && tr.length>1){
+      ctx.save(); ctx.lineCap='round';
+      for(let k=1;k<tr.length;k++){
+        ctx.globalAlpha=(k/tr.length)*0.5;
+        ctx.strokeStyle=mix('#b48bff','#7ff0e6',(i%2)?0.2:0.8);
+        ctx.lineWidth=3*(k/tr.length)*cp.figScale;
+        ctx.beginPath();ctx.moveTo(tr[k-1].x,tr[k-1].y);ctx.lineTo(tr[k].x,tr[k].y);ctx.stroke();
+      }
+      ctx.restore();
+    }
+    drawDancer(x,y,0.95*cp.figScale,t*3+i,mix('#d9c6ff','#9be7e0',(i%2)?0.3:0.7),1);
+    // water tower
+    const wt=spr(base.x,base.y,cp);
+    ctx.save();ctx.fillStyle=mix(pal.low,'#2a1c48',0.4);ctx.strokeStyle='rgba(255,220,180,.25)';ctx.lineWidth=2;
+    roundRect(wt.x-24,wt.y-10,48,52,6);ctx.fill();ctx.stroke();
+    ctx.beginPath();ctx.moveTo(wt.x-26,wt.y-10);ctx.lineTo(wt.x,wt.y-34);ctx.lineTo(wt.x+26,wt.y-10);ctx.closePath();
+    ctx.fillStyle=mix(pal.mid,pal.low,0.4);ctx.fill();ctx.stroke();
+    ctx.strokeStyle='rgba(255,220,180,.2)';ctx.beginPath();ctx.moveTo(wt.x-18,wt.y+42);ctx.lineTo(wt.x-14,wt.y+64);ctx.moveTo(wt.x+18,wt.y+42);ctx.lineTo(wt.x+14,wt.y+64);ctx.stroke();
+    ctx.restore();
+  }
+}
+
+function drawProcession(pal,cp){
+  // quiet procession climbing fire escapes into the celebration
+  const bx=fireEscapeX;
+  // fire escape structure
+  const top=spr(bx,560,cp), bot=spr(bx,860,cp);
+  ctx.save();
+  ctx.strokeStyle='rgba(255,220,180,.3)';ctx.lineWidth=2;
+  for(let f=0;f<5;f++){
+    const y=lerp(bot.y,top.y,f/5);
+    ctx.beginPath();ctx.moveTo(bx-34,y);ctx.lineTo(bx+18,y);ctx.stroke();      // platform
+    ctx.beginPath();ctx.moveTo(bx-34,y);ctx.lineTo(bx-34,y-((bot.y-top.y)/5));ctx.stroke();
+    // zigzag stair
+    ctx.beginPath();ctx.moveTo(bx+18,y);ctx.lineTo(bx-34,y-((bot.y-top.y)/5));ctx.stroke();
+  }
+  ctx.restore();
+  const count=Math.round((4+pal.complexity*4)*(0.4+state.density*0.8));
+  for(let i=0;i<count;i++){
+    const prog=((clock*0.05 + i/count)%1);
+    const y=lerp(bot.y,top.y,prog);
+    const zig=(Math.floor(prog*5)%2)? 12 : -20;
+    const x=bx+zig+Math.sin(prog*30)*6;
+    const s=0.8*cp.figScale;
+    ctx.globalAlpha=0.55+prog*0.45;
+    drawDancer(x,y,s,clock*1.2+i,mix('#ffd98a',pal.glow,0.5),0.35);
+    ctx.globalAlpha=1;
+  }
+  if(state.routes){ label(top.x, top.y-30,'ascending procession','#ffd98a'); }
+}
+
+/* ---------- animals ---------- */
+function drawBear(x,y,s,ph){
+  const sway=state.reduced?0:Math.sin(ph)*0.14;
+  const squash=1+Math.abs(Math.sin(ph))*0.05;
+  ctx.save();ctx.translate(x,y);ctx.rotate(sway);ctx.scale(1, squash);
+  ctx.fillStyle='#5a3b2e';
+  roundRect(-16*s,-30*s,32*s,32*s,12*s);ctx.fill();      // body
+  ctx.beginPath();ctx.arc(0,-34*s,11*s,0,7);ctx.fill();  // head
+  ctx.fillStyle='#4a2f24';
+  ctx.beginPath();ctx.arc(-8*s,-42*s,4*s,0,7);ctx.arc(8*s,-42*s,4*s,0,7);ctx.fill(); //ears
+  ctx.fillStyle='#c9a06b';ctx.beginPath();ctx.arc(0,-31*s,4*s,0,7);ctx.fill();       //muzzle
+  ctx.restore();
+}
+function drawCat(x,y,s,leap){
+  ctx.save();ctx.translate(x,y);
+  ctx.fillStyle='#3a3350';
+  ctx.beginPath();ctx.ellipse(0,0,12*s,6*s,0,0,7);ctx.fill();  // body
+  ctx.beginPath();ctx.arc(9*s,-4*s,4*s,0,7);ctx.fill();        // head
+  ctx.beginPath();ctx.moveTo(11*s,-7*s);ctx.lineTo(13*s,-12*s);ctx.lineTo(8*s,-8*s);ctx.fill(); //ear
+  ctx.strokeStyle='#3a3350';ctx.lineWidth=2*s;ctx.lineCap='round';
+  ctx.beginPath();ctx.moveTo(-11*s,0);ctx.quadraticCurveTo(-20*s,-6*s-leap*4,-16*s,-12*s);ctx.stroke();//tail
+  ctx.restore();
+}
+function drawDog(x,y,s){
+  ctx.save();ctx.translate(x,y);
+  ctx.fillStyle='#b5793f';
+  ctx.beginPath();ctx.ellipse(0,0,13*s,7*s,0,0,7);ctx.fill();
+  ctx.beginPath();ctx.arc(11*s,-3*s,5*s,0,7);ctx.fill();
+  ctx.beginPath();ctx.moveTo(9*s,-6*s);ctx.lineTo(8*s,-13*s);ctx.lineTo(13*s,-7*s);ctx.fill();//ear
+  ctx.strokeStyle='#b5793f';ctx.lineWidth=2*s;ctx.lineCap='round';
+  const wag=state.reduced?0:Math.sin(clock*8)*0.4;
+  ctx.beginPath();ctx.moveTo(-12*s,-1*s);ctx.lineTo(-19*s,-6*s+wag*8);ctx.stroke();
+  ctx.restore();
+}
+
+function drawAnimals(pal,cp){
+  // bears sway at roof edges
+  const bears=[spr(roofs[0].x+70,700,cp), spr(roofs[2].x+220,760,cp)];
+  bears.forEach((b,i)=>{ drawBear(b.x,b.y,0.9*cp.figScale, clock*1.1+i); if(state.names) label(b.x,b.y-58,'bear','#c9a06b'); });
+
+  // cats leap between ledges
+  const ledges=[spr(roofs[1].x+40,570,cp), spr(roofs[1].x+300,555,cp), spr(roofs[2].x+40,660,cp)];
+  for(let i=0;i<2;i++){
+    const seg=(clock*0.25+i*0.5)% ledges.length;
+    const a=ledges[Math.floor(seg)], b=ledges[(Math.floor(seg)+1)%ledges.length];
+    const t=state.reduced?0.5:(seg-Math.floor(seg));
+    const x=lerp(a.x,b.x,t), arc=Math.sin(t*Math.PI)*40;
+    const y=lerp(a.y,b.y,t)-arc;
+    drawCat(x,y,1.0*cp.figScale, state.reduced?0:Math.sin(t*Math.PI));
+    if(state.names && i===0) label(x,y-16,'cat','#cfc7e6');
+  }
+
+  // dogs weave between circle dancers
+  const c=spr(ROSE.x,ROSE.y,cp);
+  for(let i=0;i<2;i++){
+    const t=clock*0.9+i*3;
+    const x=c.x+Math.cos(t*0.6)*150*cp.spread;
+    const y=c.y+70 + Math.sin(t*3)*16*cp.squash;
+    drawDog(x,y,1.0*cp.figScale);
+    if(state.names && i===0) label(x,y-16,'dog','#e6b98a');
+  }
+
+  // pigeons rise in coordinated spiral
+  const pc=Math.round(10+pal.complexity*8);
+  const origin=spr(880,700,cp);
+  for(let i=0;i<pc;i++){
+    const t=(clock*0.6 + i*0.12);
+    const rise=(t%3)/3;                       // 0..1 loop
+    const ang=i*0.5 + t*2;
+    const rad=(1-rise)*70 + 8;
+    const x=origin.x+Math.cos(ang)*rad*cp.spread;
+    const y=origin.y-rise*260*cp.squash + Math.sin(ang)*rad*0.4;
+    const flap=state.reduced?0.5:Math.abs(Math.sin(clock*10+i));
+    ctx.save();ctx.translate(x,y);
+    ctx.strokeStyle=mix('#d9d2e8',pal.glow,0.3);ctx.lineWidth=2;ctx.lineCap='round';
+    ctx.beginPath();
+    ctx.moveTo(-7,0);ctx.quadraticCurveTo(0,-6*flap-2,0,0);ctx.quadraticCurveTo(0,-6*flap-2,7,0);
+    ctx.stroke();ctx.restore();
+  }
+  if(state.names) label(origin.x, origin.y-250*cp.squash-8,'pigeons','#e8e2f2');
+}
+
+/* ---------- route diagrams overlay ---------- */
+function drawRoutes(pal,cp){
+  ctx.save();
+  ctx.setLineDash([7,7]); ctx.lineWidth=2;
+  ctx.strokeStyle='rgba(127,240,230,.85)';
+  // circle route
+  const c=spr(ROSE.x,ROSE.y,cp);
+  ctx.beginPath();ctx.ellipse(c.x,c.y-6,92*cp.spread,92*cp.spread*0.55*cp.squash,0,0,7);ctx.stroke();
+  label(c.x,c.y-92*cp.spread*0.55-6,'rose circle','#7ff0e6');
+  // bridges
+  ctx.strokeStyle='rgba(155,231,224,.85)';
+  const b1a=spr(roofs[0].x+roofs[0].w,660,cp),b1b=spr(roofs[1].x,650,cp);
+  const b2a=spr(roofs[1].x+roofs[1].w,660,cp),b2b=spr(roofs[2].x,690,cp);
+  ctx.beginPath();ctx.moveTo(b1a.x,b1a.y);ctx.lineTo(b1b.x,b1b.y);ctx.moveTo(b2a.x,b2a.y);ctx.lineTo(b2b.x,b2b.y);ctx.stroke();
+  label((b1a.x+b1b.x)/2,(b1a.y+b1b.y)/2-12,'mirrored crossing','#9be7e0');
+  // freestyle loops
+  ctx.strokeStyle='rgba(180,139,255,.85)';
+  towers.forEach(tw=>{const p=spr(tw.x,tw.y+70,cp);ctx.beginPath();ctx.ellipse(p.x,p.y,70*cp.spread,40*cp.squash,0.3,0,7);ctx.stroke();});
+  label(spr(towers[1].x,towers[1].y+70,cp).x, spr(towers[1].x,towers[1].y+70,cp).y-44,'freestyle curves','#b48bff');
+  ctx.restore();
+}
+
+/* ---------- main render ---------- */
+function render(){
+  const pal=scenePal();
+  const cp=camParams();
+  document.getElementById('sceneFlag').textContent = pal.name;
+
+  ctx.setTransform(DPR,0,0,DPR,0,0);
+  ctx.clearRect(0,0,W,H);
+
+  // camera transform
+  ctx.save();
+  ctx.translate(W/2, H/2);
+  const idle=state.reduced?0:Math.sin(clock*0.08)*8;
+  ctx.scale(viewScale*cp.zoom, viewScale*cp.zoom*cp.squash);
+  ctx.translate(-STAGE_W/2 + cp.panX + idle, -STAGE_H/2 + cp.panY);
+
+  drawSky(pal);
+  drawClouds(pal);
+  drawBuildings(pal,cp);
+  drawLaundry(pal);
+  drawRoofs(pal,cp);
+
+  drawLanterns(pal);
+  drawRose(pal,cp);
+
+  if(state.routes) drawRoutes(pal,cp);
+
+  // choreography (respect isolation toggles)
+  if(state.show.procession) drawProcession(pal,cp);
+  if(state.show.couples)    drawCouples(pal,cp);
+  if(state.show.circle)     drawCircle(pal,cp);
+  if(state.show.freestyle)  drawFreestyle(pal,cp);
+  if(state.show.animals){   drawAnimals(pal,cp); drawMoths(pal,cp); }
+
+  ctx.restore();
+}
+
+/* ---------- controls wiring ---------- */
+function press(sel,val,attr='data-cam'){
+  document.querySelectorAll('['+attr+']').forEach(b=>{
+    const on=b.getAttribute(attr)===val;
+    b.setAttribute('aria-pressed', on?'true':'false');
+    b.classList.toggle('active',on);
+  });
+}
+
+document.querySelectorAll('[data-cam]').forEach(b=>{
+  b.addEventListener('click',()=>{ state.camera=b.dataset.cam; press('','','data-cam'); document.querySelectorAll('[data-cam]').forEach(x=>{const on=x.dataset.cam===state.camera;x.setAttribute('aria-pressed',on);x.classList.toggle('active',on);}); if(state.reduced)render(); });
+});
+function setCam(c){state.camera=c;document.querySelectorAll('[data-cam]').forEach(x=>{const on=x.dataset.cam===c;x.setAttribute('aria-pressed',on);x.classList.toggle('active',on);});if(state.reduced)render();}
+
+document.querySelectorAll('[data-scene]').forEach(b=>{
+  b.addEventListener('click',()=>{
+    const v=b.dataset.scene;
+    document.querySelectorAll('[data-scene]').forEach(x=>{const on=x===b;x.setAttribute('aria-pressed',on);x.classList.toggle('active',on);});
+    if(v==='auto'){ state.sceneMode='auto'; }
+    else { state.sceneMode='pinned'; state.scenePhase=parseInt(v,10); }
+    if(state.reduced)render();
+  });
+});
+
+const tempo=document.getElementById('tempo'), tempoOut=document.getElementById('tempoOut');
+tempo.addEventListener('input',()=>{state.tempo=parseFloat(tempo.value);tempoOut.textContent=state.tempo.toFixed(1)+'×';});
+const density=document.getElementById('density'), densityOut=document.getElementById('densityOut');
+density.addEventListener('input',()=>{state.density=parseFloat(density.value);densityOut.textContent=Math.round(state.density*100)+'%';if(state.reduced)render();});
+
+document.querySelectorAll('[data-grp]').forEach(c=>{
+  c.addEventListener('change',()=>{state.show[c.dataset.grp]=c.checked;if(state.reduced)render();});
+});
+
+const ovNames=document.getElementById('ovNames'),ovTrails=document.getElementById('ovTrails'),
+      ovRoutes=document.getElementById('ovRoutes'),ovReduced=document.getElementById('ovReduced');
+ovNames.addEventListener('change',()=>{state.names=ovNames.checked;if(state.reduced)render();});
+ovTrails.addEventListener('change',()=>{state.trails=ovTrails.checked;if(state.reduced)render();});
+ovRoutes.addEventListener('change',()=>{state.routes=ovRoutes.checked;if(state.reduced)render();});
+ovReduced.addEventListener('change',()=>setReduced(ovReduced.checked));
+
+const btnPause=document.getElementById('btnPause');
+btnPause.addEventListener('click',togglePause);
+function togglePause(){
+  state.paused=!state.paused;
+  btnPause.setAttribute('aria-pressed',state.paused);
+  btnPause.textContent=state.paused?'▶ Play':'⏸ Pause';
+}
+document.getElementById('btnRestart').addEventListener('click',()=>{
+  clock=0; sceneClock=0; state.scenePhase= state.sceneMode==='pinned'?state.scenePhase:0;
+  freeTrails.length=0;
+  if(state.reduced)render();
+});
+
+function setReduced(on){
+  state.reduced=on; ovReduced.checked=on;
+  if(on){ stopLoop(); render(); }
+  else { last=performance.now(); startLoop(); }
+}
+
+// panel collapse
+const panelToggle=document.getElementById('panelToggle'), ctlBody=document.getElementById('ctlBody'), panel=document.getElementById('controls');
+panelToggle.addEventListener('click',()=>{
+  const collapsed=panel.classList.toggle('collapsed');
+  panelToggle.textContent=collapsed?'Show':'Hide';
+  panelToggle.setAttribute('aria-expanded',(!collapsed).toString());
+});
+
+// keyboard
+window.addEventListener('keydown',e=>{
+  if(e.target.matches('input,textarea')) return;
+  switch(e.key){
+    case ' ': e.preventDefault(); togglePause(); break;
+    case '1': setCam('skyline'); break;
+    case '2': setCam('rooftop'); break;
+    case '3': setCam('birdseye'); break;
+    case 'r': case 'R': setReduced(!state.reduced); break;
+    case 'n': case 'N': ovNames.checked=!ovNames.checked; state.names=ovNames.checked; if(state.reduced)render(); break;
+    case 't': case 'T': ovTrails.checked=!ovTrails.checked; state.trails=ovTrails.checked; if(state.reduced)render(); break;
+    case 'd': case 'D': ovRoutes.checked=!ovRoutes.checked; state.routes=ovRoutes.checked; if(state.reduced)render(); break;
+  }
+});
+
+/* ---------- boot ---------- */
+resize();
+// honor OS reduced-motion preference on first load
+if(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches){
+  setReduced(true);
+} else {
+  startLoop();
+}
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/127317180daf/qwen25-7b.html b/data/artifacts/127317180daf/qwen25-7b.html
new file mode 100644
index 0000000..c94fd6a
--- /dev/null
+++ b/data/artifacts/127317180daf/qwen25-7b.html
@@ -0,0 +1,176 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Rose City Rooftop Solstice</title>
+    <style>
+        body { margin: 0; overflow: hidden; }
+        canvas { width: 100%; height: 100%; display: block; }
+        .controls { position: absolute; bottom: 10px; left: 10px; z-index: 2; }
+        button, input { margin: 5px; padding: 5px; font-size: 14px; }
+    </style>
+</head>
+<body>
+    <canvas id="scene"></canvas>
+    <div class="controls">
+        <label><input type="radio" name="camera" checked> Skyline</label>
+        <label><input type="radio" name="camera"> Rooftop</label>
+        <label><input type="radio" name="camera"> Bird's Eye</label>
+        <button id="toggleAnimals">Toggle Animals</button>
+        <button id="toggleRoutes">Toggle Routes</button>
+        <button id="toggleBuildings">Toggle Buildings</button>
+        <button id="pause">Pause</button>
+        <button id="restart">Restart</button>
+        <div id="settings"></div>
+    </div>
+
+    <script>
+        const canvas = document.getElementById('scene');
+        const ctx = canvas.getContext('2d');
+
+        class Animal {
+            constructor(x, y, type) {
+                this.x = x;
+                this.y = y;
+                this.type = type;
+                this.trail = [];
+                this.size = 15;
+            }
+            update() {
+                // Simple movement logic
+            }
+            draw() {
+                ctx.beginPath();
+                if (this.type === 'bear') {
+                    ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
+                } else if (this.type === 'cat') {
+                    ctx.rect(this.x - this.size / 3, this.y - this.size / 2, this.size, this.size / 2);
+                }
+                ctx.fillStyle = '#FF69B4';
+                ctx.fill();
+            }
+        }
+
+        class AnimalManager {
+            constructor() {
+                this.animals = [];
+            }
+            addAnimal(x, y, type) {
+                this.animals.push(new Animal(x, y, type));
+            }
+            update() {
+                for (let animal of this.animals) {
+                    animal.update();
+                }
+            }
+            draw() {
+                for (let animal of this.animals) {
+                    animal.draw();
+                }
+            }
+        }
+
+        const animals = new AnimalManager();
+
+        function createAnimals() {
+            // Randomly add some animals
+            for (let i = 0; i < 20; i++) {
+                let x = Math.random() * canvas.width;
+                let y = Math.random() * canvas.height;
+                let type = ['bear', 'cat'][Math.floor(Math.random() * 2)];
+                animals.addAnimal(x, y, type);
+            }
+        }
+
+        function drawBackground() {
+            ctx.fillStyle = '#FFD700';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+        }
+
+        function drawBuildings() {
+            // Simple parallax buildings
+            for (let i = -5; i < 2; i++) {
+                let x = i * canvas.width / 3;
+                let y = Math.random() * canvas.height;
+                ctx.fillStyle = `hsl(${(i + 1) * 60}, 75%, 45%)`;
+                ctx.fillRect(x, y - 150, canvas.width / 2, 150);
+            }
+        }
+
+        function drawRooftops() {
+            // Draw the rooftops and people
+            for (let i = 0; i < 10; i++) {
+                let x = Math.random() * canvas.width;
+                let y = canvas.height - 200 + (Math.sin(Date.now() / 500) * 40);
+                ctx.fillStyle = '#C71585';
+                ctx.fillRect(x, y, 30, 30);
+            }
+        }
+
+        function drawTrails() {
+            for (let animal of animals.animals) {
+                if (animal.trail.length > 0) {
+                    ctx.strokeStyle = 'rgba(255, 165, 0, 0.7)';
+                    ctx.beginPath();
+                    ctx.moveTo(animal.trail[0].x, animal.trail[0].y);
+                    for (let i = 1; i < animal.trail.length; i++) {
+                        ctx.lineTo(animal.trail[i].x, animal.trail[i].y);
+                    }
+                    ctx.stroke();
+                }
+            }
+        }
+
+        function update() {
+            drawBackground();
+            drawRooftops();
+            animals.update();
+            drawTrails();
+            requestAnimationFrame(update);
+        }
+
+        function toggleAnimals() {
+            if (document.querySelector('input[name="animals"]:checked').value === 'true') {
+                document.querySelectorAll('input[type=radio][name=camera]').forEach(r => r.checked = false);
+                update();
+            } else {
+                update();
+            }
+        }
+
+        function toggleRoutes() {
+            document.querySelectorAll('input[type=radio][name=camera]').forEach(r => r.checked = false);
+            if (document.querySelector('input[name="camera"]:checked').value === 'Rooftop') {
+                update();
+            }
+        }
+
+        function toggleBuildings() {
+            drawBuildings();
+            requestAnimationFrame(update);
+        }
+
+        document.getElementById('toggleAnimals').addEventListener('click', toggleAnimals);
+        document.getElementById('toggleRoutes').addEventListener('click', toggleRoutes);
+        document.getElementById('toggleBuildings').addEventListener('click', toggleBuildings);
+
+        createAnimals();
+        update();
+
+        document.getElementById('pause').addEventListener('click', () => {
+            let pause = !document.getElementById('pause').innerText.includes('Play');
+            if (pause) {
+                document.getElementById('pause').innerText = 'Play';
+            } else {
+                document.getElementById('pause').innerText = 'Pause';
+            }
+        });
+
+        document.getElementById('restart').addEventListener('click', () => {
+            createAnimals();
+            update();
+        });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/127317180daf/qwen3-14b.html b/data/artifacts/127317180daf/qwen3-14b.html
new file mode 100644
index 0000000..268013d
--- /dev/null
+++ b/data/artifacts/127317180daf/qwen3-14b.html
@@ -0,0 +1,524 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Rose City Rooftop Solstice</title>
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      background: #000;
+      font-family: 'Arial', sans-serif;
+    }
+    canvas {
+      display: block;
+    }
+    #controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      z-index: 10;
+      background: rgba(255,255,255,0.1);
+      padding: 10px;
+      border-radius: 10px;
+      color: #fff;
+      font-size: 14px;
+    }
+    #controls button, #controls select, #controls input {
+      margin: 5px 0;
+      padding: 6px 10px;
+      background: rgba(255,255,255,0.2);
+      border: 1px solid rgba(255,255,255,0.4);
+      color: #fff;
+      border-radius: 5px;
+      cursor: pointer;
+      transition: background 0.2s ease;
+    }
+    #controls button:hover, #controls select:hover, #controls input:hover {
+      background: rgba(255,255,255,0.3);
+    }
+    #controls label {
+      display: block;
+      margin: 5px 0 2px;
+    }
+    #info {
+      position: absolute;
+      bottom: 10px;
+      left: 10px;
+      z-index: 10;
+      color: #fff;
+      font-size: 12px;
+      text-align: left;
+    }
+  </style>
+</head>
+<body>
+  <canvas id="canvas"></canvas>
+  <div id="controls">
+    <label>Camera:</label>
+    <select id="camera">
+      <option value="skyline">Skyline</option>
+      <option value="rooftop">Rooftop</option>
+      <option value="birds-eye">Bird's-Eye</option>
+    </select>
+    <label>Scene:</label>
+    <select id="scene">
+      <option value="arrival">Arrival</option>
+      <option value="long-light">Long Light</option>
+      <option value="midnight-bloom">Midnight Bloom</option>
+    </select>
+    <label>Tempo:</label>
+    <input type="range" id="tempo" min="0.5" max="2" step="0.1" value="1">
+    <label>Density:</label>
+    <input type="range" id="density" min="0.3" max="1" step="0.1" value="0.7">
+    <label>Groups:</label>
+    <input type="checkbox" id="group1"> Central Circle
+    <input type="checkbox" id="group2"> Bridges
+    <input type="checkbox" id="group3"> Water Towers
+    <input type="checkbox" id="group4"> Fire Escapes
+    <label>Animals:</label>
+    <input type="checkbox" id="animalNames"> Show Names
+    <input type="checkbox" id="animalTrails"> Show Trails
+    <input type="checkbox" id="routeDiagrams"> Show Routes
+    <br>
+    <button id="pause">Pause</button>
+    <button id="restart">Restart</button>
+    <button id="reduceMotion">Reduce Motion</button>
+  </div>
+  <div id="info">
+    Visual rhythm is authored rather than listening to audio.
+    <br>
+    Keyboard controls: 
+    <br>
+    Arrow keys: Move camera
+    <br>
+    W/S: Zoom
+    <br>
+    R: Reset
+  </div>
+  <script>
+    const canvas = document.getElementById('canvas');
+    const ctx = canvas.getContext('2d');
+    const width = canvas.width = window.innerWidth;
+    const height = canvas.height = window.innerHeight;
+
+    const controlElements = {
+      camera: document.getElementById('camera'),
+      scene: document.getElementById('scene'),
+      tempo: document.getElementById('tempo'),
+      density: document.getElementById('density'),
+      group1: document.getElementById('group1'),
+      group2: document.getElementById('group2'),
+      group3: document.getElementById('group3'),
+      group4: document.getElementById('group4'),
+      animalNames: document.getElementById('animalNames'),
+      animalTrails: document.getElementById('animalTrails'),
+      routeDiagrams: document.getElementById('routeDiagrams'),
+      pause: document.getElementById('pause'),
+      restart: document.getElementById('restart'),
+      reduceMotion: document.getElementById('reduceMotion'),
+    };
+
+    let state = {
+      scene: 'arrival',
+      camera: 'skyline',
+      tempo: 1,
+      density: 0.7,
+      groups: {1: true, 2: true, 3: true, 4: true},
+      showNames: false,
+      showTrails: false,
+      showRoutes: false,
+      isPaused: false,
+      reduceMotion: false,
+      time: 0,
+      frame: 0,
+      lastTime: 0,
+    };
+
+    const sceneColors = {
+      arrival: {bg: '#111', rose: '#ff6b6b', light: '#f0c14b', cloud: '#60a5fa'},
+      'long-light': {bg: '#0a0a0a', rose: '#ff4081', light: '#ffd700', cloud: '#3b82f6'},
+      'midnight-bloom': {bg: '#000', rose: '#e63946', light: '#e9c46a', cloud: '#2a2240'},
+    };
+
+    const entities = {
+      dancers: [],
+      animals: [],
+      buildings: [],
+      waterTowers: [],
+      fireEscapes: [],
+    };
+
+    const initEntities = () => {
+      const numDancers = Math.floor(100 * state.density);
+      const numAnimals = Math.floor(20 * state.density);
+      const numBuildings = Math.floor(50 * state.density);
+
+      for (let i = 0; i < numDancers; i++) {
+        entities.dancers.push({
+          type: 'dancer',
+          x: Math.random() * width,
+          y: Math.random() * height,
+          angle: Math.random() * Math.PI * 2,
+          speed: (Math.random() + 0.5) * 0.01 * state.tempo,
+          group: Math.floor(Math.random() * 4) + 1,
+        });
+      }
+
+      for (let i = 0; i < numAnimals; i++) {
+        const types = ['bear', 'cat', 'dog', 'moth', 'pigeon'];
+        const type = types[Math.floor(Math.random() * types.length)];
+        entities.animals.push({
+          type,
+          x: Math.random() * width,
+          y: Math.random() * height,
+          angle: Math.random() * Math.PI * 2,
+          speed: (Math.random() + 0.5) * 0.01 * state.tempo,
+        });
+      }
+
+      for (let i = 0; i < numBuildings; i++) {
+        entities.buildings.push({
+          x: Math.random() * width,
+          y: Math.random() * height,
+          scale: 0.2 + Math.random() * 0.3,
+        });
+      }
+
+      for (let i = 0; i < 10; i++) {
+        entities.waterTowers.push({
+          x: width * 0.2 + Math.random() * width * 0.6,
+          y: height * 0.2 + Math.random() * height * 0.6,
+        });
+      }
+
+      for (let i = 0; i < 5; i++) {
+        entities.fireEscapes.push({
+          x: width * 0.2 + Math.random() * width * 0.6,
+          y: height * 0.2 + Math.random() * height * 0.6,
+        });
+      }
+    };
+
+    const draw = (time) => {
+      if (state.isPaused) return;
+      state.time = time;
+      state.frame++;
+      ctx.clearRect(0, 0, width, height);
+
+      const color = sceneColors[state.scene];
+      ctx.fillStyle = color.bg;
+      ctx.fillRect(0, 0, width, height);
+
+      // Draw buildings with parallax
+      ctx.save();
+      ctx.translate(width / 2, height / 2);
+      ctx.scale(1, 1);
+      ctx.fillStyle = color.bg;
+      ctx.fillRect(-width / 2, -height / 2, width, height);
+      ctx.restore();
+
+      // Draw clouds
+      ctx.fillStyle = color.cloud;
+      for (let i = 0; i < 10; i++) {
+        const cx = Math.random() * width;
+        const cy = Math.random() * height * 0.3;
+        const size = 10 + Math.random() * 20;
+        ctx.beginPath();
+        ctx.arc(cx, cy, size, 0, Math.PI * 2);
+        ctx.fill();
+      }
+
+      // Draw water towers
+      ctx.fillStyle = color.light;
+      for (let tower of entities.waterTowers) {
+        const size = 15 + Math.random() * 10;
+        const x = tower.x;
+        const y = tower.y;
+        ctx.beginPath();
+        ctx.arc(x, y, size, 0, Math.PI * 2);
+        ctx.fill();
+      }
+
+      // Draw fire escapes
+      ctx.strokeStyle = color.light;
+      ctx.lineWidth = 2;
+      for (let escape of entities.fireEscapes) {
+        const size = 10 + Math.random() * 10;
+        const x = escape.x;
+        const y = escape.y;
+        ctx.beginPath();
+        ctx.moveTo(x - size, y);
+        ctx.lineTo(x + size, y);
+        ctx.stroke();
+      }
+
+      // Draw dancers
+      for (let d of entities.dancers) {
+        if (!state.groups[d.group]) continue;
+
+        let x = d.x;
+        let y = d.y;
+        let angle = d.angle;
+        if (d.group === 1) {
+          // Central Circle
+          const centerX = width / 2;
+          const centerY = height / 2;
+          const radius = 100 + Math.sin(state.time * d.speed) * 50;
+          x = centerX + Math.cos(angle) * radius;
+          y = centerY + Math.sin(angle) * radius;
+          angle += state.time * d.speed;
+          d.angle = angle;
+        } else if (d.group === 2) {
+          // Bridges
+          const start = {x: 0, y: height / 2};
+          const end = {x: width, y: height / 2};
+          const t = (Math.sin(state.time * d.speed) + 1) / 2;
+          x = start.x + t * (end.x - start.x);
+          y = start.y + t * (end.y - start.y);
+          angle = Math.atan2(end.y - start.y, end.x - start.x);
+        } else if (d.group === 3) {
+          // Water Towers
+          const size = 50 + Math.sin(state.time * d.speed) * 20;
+          x = d.x + Math.cos(angle) * size;
+          y = d.y + Math.sin(angle) * size;
+          angle += state.time * d.speed;
+        } else if (d.group === 4) {
+          // Fire Escapes
+          const speed = 0.005 * state.tempo;
+          d.y -= speed;
+          if (d.y < 0) {
+            d.y = height;
+            d.x = Math.random() * width;
+          }
+        }
+
+        d.x = x;
+        d.y = y;
+        d.angle = angle;
+
+        // Draw dancer
+        ctx.save();
+        ctx.translate(x, y);
+        ctx.rotate(angle);
+        ctx.fillStyle = color.rose;
+        ctx.beginPath();
+        ctx.arc(0, 0, 4, 0, Math.PI * 2);
+        ctx.fill();
+        ctx.restore();
+      }
+
+      // Draw animals
+      for (let a of entities.animals) {
+        let x = a.x;
+        let y = a.y;
+        let angle = a.angle;
+        if (a.type === 'bear') {
+          x += Math.cos(angle) * 5;
+          y += Math.sin(angle) * 5;
+        } else if (a.type === 'cat') {
+          x += Math.cos(angle) * 3;
+          y += Math.sin(angle) * 3;
+        } else if (a.type === 'dog') {
+          x += Math.cos(angle) * 4;
+          y += Math.sin(angle) * 4;
+        } else if (a.type === 'moth') {
+          x += Math.cos(angle) * 2;
+          y += Math.sin(angle) * 2;
+          angle += state.time * 0.01;
+        } else if (a.type === 'pigeon') {
+          x += Math.cos(angle) * 5;
+          y += Math.sin(angle) * 5;
+          angle += state.time * 0.005;
+        }
+
+        a.x = x;
+        a.y = y;
+        a.angle = angle;
+
+        if (state.showTrails) {
+          ctx.strokeStyle = color.light;
+          ctx.lineWidth = 1;
+          ctx.beginPath();
+          ctx.moveTo(a.x, a.y);
+          ctx.lineTo(a.x + Math.cos(angle) * 5, a.y + Math.sin(angle) * 5);
+          ctx.stroke();
+        }
+
+        ctx.save();
+        ctx.translate(x, y);
+        ctx.rotate(angle);
+        ctx.fillStyle = color.light;
+        ctx.beginPath();
+        if (a.type === 'bear') {
+          ctx.fillRect(-4, -4, 8, 8);
+        } else if (a.type === 'cat') {
+          ctx.beginPath();
+          ctx.arc(0, 0, 4, 0, Math.PI * 2);
+          ctx.fill();
+        } else if (a.type === 'dog') {
+          ctx.beginPath();
+          ctx.arc(0, 0, 4, 0, Math.PI * 2);
+          ctx.fill();
+          ctx.fillRect(-2, -2, 4, 4);
+        } else if (a.type === 'moth') {
+          ctx.beginPath();
+          ctx.arc(0, 0, 3, 0, Math.PI * 2);
+          ctx.fill();
+          ctx.beginPath();
+          ctx.moveTo(-3, 0);
+          ctx.lineTo(0, 4);
+          ctx.lineTo(3, 0);
+          ctx.fill();
+        } else if (a.type === 'pigeon') {
+          ctx.beginPath();
+          ctx.arc(0, 0, 5, 0, Math.PI * 2);
+          ctx.fill();
+          ctx.beginPath();
+          ctx.moveTo(0, 0);
+          ctx.lineTo(0, -5);
+          ctx.stroke();
+        }
+        ctx.restore();
+
+        if (state.showNames) {
+          ctx.fillStyle = color.light;
+          ctx.font = '10px Arial';
+          ctx.fillText(a.type, x + 6, y - 6);
+        }
+      }
+
+      // Draw central rose sculpture
+      ctx.fillStyle = color.rose;
+      ctx.beginPath();
+      ctx.moveTo(width / 2, height / 2);
+      for (let i = 0; i < 12; i++) {
+        const angle = (Math.PI * 2 / 12) * i;
+        const r = 40 + 20 * Math.sin(state.time * 0.01 * i);
+        const x = width / 2 + Math.cos(angle) * r;
+        const y = height / 2 + Math.sin(angle) * r;
+        ctx.lineTo(x, y);
+      }
+      ctx.closePath();
+      ctx.fill();
+
+      // Draw light trails
+      if (state.showTrails) {
+        ctx.strokeStyle = color.light;
+        ctx.lineWidth = 1;
+        for (let d of entities.dancers) {
+          ctx.beginPath();
+          ctx.moveTo(d.x, d.y);
+          ctx.lineTo(d.x + Math.cos(d.angle) * 5, d.y + Math.sin(d.angle) * 5);
+          ctx.stroke();
+        }
+      }
+
+      // Draw route diagrams
+      if (state.showRoutes) {
+        ctx.strokeStyle = color.light;
+        ctx.lineWidth = 1;
+        ctx.globalAlpha = 0.3;
+        for (let i = 0; i < 10; i++) {
+          ctx.beginPath();
+          ctx.moveTo(0, height / 2);
+          ctx.lineTo(width, height / 2);
+          ctx.stroke();
+        }
+        ctx.globalAlpha = 1;
+      }
+
+      requestAnimationFrame(draw);
+    };
+
+    const updateState = () => {
+      state.scene = controlElements.scene.value;
+      state.camera = controlElements.camera.value;
+      state.tempo = parseFloat(controlElements.tempo.value);
+      state.density = parseFloat(controlElements.density.value);
+      state.groups = {
+        1: controlElements.group1.checked,
+        2: controlElements.group2.checked,
+        3: controlElements.group3.checked,
+        4: controlElements.group4.checked,
+      };
+      state.showNames = controlElements.animalNames.checked;
+      state.showTrails = controlElements.animalTrails.checked;
+      state.showRoutes = controlElements.routeDiagrams.checked;
+      state.reduceMotion = controlElements.reduceMotion.checked;
+    };
+
+    const bindControls = () => {
+      controlElements.camera.addEventListener('change', updateState);
+      controlElements.scene.addEventListener('change', updateState);
+      controlElements.tempo.addEventListener('input', updateState);
+      controlElements.density.addEventListener('input', updateState);
+      controlElements.group1.addEventListener('change', updateState);
+      controlElements.group2.addEventListener('change', updateState);
+      controlElements.group3.addEventListener('change', updateState);
+      controlElements.group4.addEventListener('change', updateState);
+      controlElements.animalNames.addEventListener('change', updateState);
+      controlElements.animalTrails.addEventListener('change', updateState);
+      controlElements.routeDiagrams.addEventListener('change', updateState);
+      controlElements.pause.addEventListener('click', () => {
+        state.isPaused = !state.isPaused;
+        controlElements.pause.textContent = state.isPaused ? 'Resume' : 'Pause';
+      });
+      controlElements.restart.addEventListener('click', () => {
+        state.time = 0;
+        state.frame = 0;
+        initEntities();
+      });
+      controlElements.reduceMotion.addEventListener('click', () => {
+        state.reduceMotion = !state.reduceMotion;
+        controlElements.reduceMotion.textContent = state.reduceMotion ? 'Normal Motion' : 'Reduce Motion';
+      });
+
+      document.addEventListener('keydown', (e) => {
+        if (e.key === 'ArrowUp') {
+          e.preventDefault();
+          state.camera = 'skyline';
+          controlElements.camera.value = 'skyline';
+        } else if (e.key === 'ArrowDown') {
+          e.preventDefault();
+          state.camera = 'rooftop';
+          controlElements.camera.value = 'rooftop';
+        } else if (e.key === 'ArrowLeft') {
+          e.preventDefault();
+          state.camera = 'birds-eye';
+          controlElements.camera.value = 'birds-eye';
+        } else if (e.key === 'ArrowRight') {
+          e.preventDefault();
+          state.scene = 'arrival';
+          controlElements.scene.value = 'arrival';
+        } else if (e.key === 'w') {
+          e.preventDefault();
+          state.density = Math.min(state.density + 0.1, 1);
+          controlElements.density.value = state.density;
+        } else if (e.key === 's') {
+          e.preventDefault();
+          state.density = Math.max(state.density - 0.1, 0.3);
+          controlElements.density.value = state.density;
+        } else if (e.key === 'r') {
+          e.preventDefault();
+          state.time = 0;
+          state.frame = 0;
+          initEntities();
+        }
+      });
+    };
+
+    window.addEventListener('resize', () => {
+      canvas.width = window.innerWidth;
+      canvas.height = window.innerHeight;
+      width = canvas.width;
+      height = canvas.height;
+    });
+
+    initEntities();
+    bindControls();
+    draw(0);
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/77fc58739877/claude-code.html b/data/artifacts/77fc58739877/claude-code.html
new file mode 100644
index 0000000..216de33
--- /dev/null
+++ b/data/artifacts/77fc58739877/claude-code.html
@@ -0,0 +1,973 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Indigo Train Through Dream Country — An Animated Narrative Installation</title>
+<style>
+  :root{
+    --bg:#070512;
+    --ink:#eae4ff;
+    --ink-dim:#a99fce;
+    --glass:rgba(20,14,42,0.72);
+    --glass-line:rgba(150,120,220,0.28);
+    --accent:#c9a6ff;
+    --focus:#ffd27a;
+  }
+  *{box-sizing:border-box}
+  html,body{margin:0;height:100%;background:var(--bg);color:var(--ink);
+    font-family:"Georgia","Iowan Old Style",serif;overflow:hidden}
+  #stage{position:fixed;inset:0}
+  canvas{display:block;width:100%;height:100%}
+
+  /* Title */
+  #titlebar{position:fixed;top:14px;left:18px;right:18px;pointer-events:none;
+    display:flex;justify-content:space-between;gap:12px;align-items:flex-start;z-index:5}
+  #titlebar h1{font-size:clamp(16px,2.4vw,26px);margin:0;letter-spacing:.06em;
+    text-shadow:0 2px 14px rgba(0,0,0,.7);font-weight:600}
+  #titlebar h1 span{display:block;font-size:.52em;letter-spacing:.28em;
+    color:var(--ink-dim);font-style:italic;text-transform:uppercase;margin-top:3px}
+  #disclaimer{pointer-events:auto;max-width:340px;font-family:system-ui,sans-serif;
+    font-size:11px;line-height:1.45;color:var(--ink-dim);
+    background:var(--glass);border:1px solid var(--glass-line);border-radius:10px;
+    padding:8px 11px;backdrop-filter:blur(8px)}
+  #disclaimer strong{color:var(--accent)}
+
+  /* Caption */
+  #caption{position:fixed;left:18px;bottom:18px;z-index:5;max-width:min(46ch,60vw);
+    background:var(--glass);border:1px solid var(--glass-line);border-radius:14px;
+    padding:14px 18px;backdrop-filter:blur(10px);box-shadow:0 10px 40px rgba(0,0,0,.5);
+    transition:opacity .5s}
+  #caption h2{margin:0 0 4px;font-size:19px;letter-spacing:.02em}
+  #caption .meta{font-family:system-ui,sans-serif;font-size:10.5px;text-transform:uppercase;
+    letter-spacing:.16em;color:var(--accent);margin-bottom:7px}
+  #caption p{margin:0;font-size:13.5px;line-height:1.55;color:var(--ink-dim);font-style:italic}
+
+  /* Control panel */
+  #panel{position:fixed;top:0;right:0;height:100%;width:326px;z-index:6;
+    background:linear-gradient(180deg,rgba(14,9,32,.94),rgba(10,7,24,.97));
+    border-left:1px solid var(--glass-line);backdrop-filter:blur(14px);
+    padding:16px 16px 26px;overflow-y:auto;font-family:system-ui,sans-serif;
+    transition:transform .35s ease}
+  #panel.hidden{transform:translateX(100%)}
+  #panel h3{font-family:"Georgia",serif;font-size:14px;letter-spacing:.14em;
+    text-transform:uppercase;color:var(--accent);margin:20px 0 10px;
+    border-bottom:1px solid var(--glass-line);padding-bottom:5px}
+  #panel h3:first-of-type{margin-top:6px}
+  .field{margin:11px 0}
+  .field label{display:block;font-size:11.5px;letter-spacing:.05em;color:var(--ink-dim);
+    margin-bottom:5px;text-transform:uppercase}
+  .field .val{color:var(--ink);font-variant-numeric:tabular-nums;float:right;font-weight:600}
+  input[type=range]{width:100%;accent-color:var(--accent);cursor:pointer;height:22px}
+  select{width:100%;padding:7px 8px;background:#160f30;color:var(--ink);
+    border:1px solid var(--glass-line);border-radius:8px;font-size:13px}
+  .cams{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px}
+  .cams button{padding:9px 4px;font-size:11px}
+  button{background:#20163f;color:var(--ink);border:1px solid var(--glass-line);
+    border-radius:8px;padding:8px 10px;font-size:12.5px;cursor:pointer;
+    transition:background .2s,border-color .2s}
+  button:hover{background:#2c1f57}
+  button[aria-pressed=true]{background:var(--accent);color:#160f30;border-color:var(--accent);font-weight:700}
+  .row{display:flex;gap:7px;flex-wrap:wrap}
+  .row button{flex:1}
+  .toggle{display:flex;align-items:center;justify-content:space-between;
+    gap:8px;padding:7px 0;font-size:12.5px;color:var(--ink-dim)}
+  .switch{position:relative;width:42px;height:23px;flex:0 0 auto}
+  .switch input{position:absolute;opacity:0;width:100%;height:100%;margin:0;cursor:pointer}
+  .switch .track{position:absolute;inset:0;background:#2a2050;border-radius:20px;
+    border:1px solid var(--glass-line);transition:background .2s}
+  .switch .knob{position:absolute;top:2px;left:2px;width:17px;height:17px;border-radius:50%;
+    background:var(--ink-dim);transition:transform .2s,background .2s}
+  .switch input:checked + .track{background:var(--accent)}
+  .switch input:checked + .track + .knob{transform:translateX(19px);background:#160f30}
+  #panelToggle{position:fixed;top:14px;right:14px;z-index:7;font-size:12px;
+    font-family:system-ui,sans-serif;padding:8px 12px}
+  .hint{font-size:10.5px;color:#7d7099;line-height:1.5;margin-top:8px}
+  kbd{background:#20163f;border:1px solid var(--glass-line);border-radius:4px;
+    padding:1px 5px;font-size:10.5px;font-family:ui-monospace,monospace}
+
+  /* Focus visibility (accessibility) */
+  a:focus-visible, button:focus-visible, select:focus-visible,
+  input:focus-visible{outline:3px solid var(--focus);outline-offset:2px;border-radius:6px}
+  .switch input:focus-visible + .track{outline:3px solid var(--focus);outline-offset:2px}
+
+  .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:640px){
+    #panel{width:290px}
+    #disclaimer{display:none}
+    #caption{max-width:70vw}
+  }
+</style>
+</head>
+<body>
+<div id="stage">
+  <canvas id="scene" role="img"
+    aria-label="Animated night train crossing five dream landscapes; travellers dance in a cutaway carriage while animals keep rhythm on the roof and alongside."></canvas>
+</div>
+
+<header id="titlebar">
+  <h1>Indigo Train Through Dream Country
+    <span>A gallery installation in motion</span></h1>
+  <div id="disclaimer">
+    <strong>Please note:</strong> every rhythm here is <strong>choreographed animation</strong> —
+    hand-timed dancer and animal motion. It is <em>not</em> generated from, synced to, or an
+    analysis of any music. All graphics are original psychedelic travel-poster artwork.
+  </div>
+</header>
+
+<figure id="caption" aria-live="polite">
+  <div class="meta" id="capMeta">Landscape 1 of 5</div>
+  <h2 id="capTitle">Desert Mesas</h2>
+  <p id="capText"></p>
+</figure>
+
+<button id="panelToggle" aria-controls="panel" aria-expanded="true">☰ Controls</button>
+
+<aside id="panel" aria-label="Installation controls">
+  <h3>Camera</h3>
+  <div class="cams" role="group" aria-label="Camera view">
+    <button data-cam="exterior" aria-pressed="true">Exterior<br>tracking</button>
+    <button data-cam="cutaway" aria-pressed="false">Carriage<br>cutaway</button>
+    <button data-cam="conductor" aria-pressed="false">Conductor</button>
+  </div>
+
+  <h3>Journey</h3>
+  <div class="field">
+    <label for="scrub">Scrub landscape <span class="val" id="scrubVal">1 · Desert</span></label>
+    <input type="range" id="scrub" min="0" max="1000" value="50" step="1"
+      aria-describedby="scrubHint">
+    <div class="hint" id="scrubHint">The train never stops — scrubbing re-tunes the world it moves through.</div>
+  </div>
+  <div class="toggle">
+    <label for="autoJourney" style="margin:0">Auto-drift through landscapes</label>
+    <span class="switch"><input type="checkbox" id="autoJourney" checked>
+      <span class="track"></span><span class="knob"></span></span>
+  </div>
+
+  <h3>Motion</h3>
+  <div class="field">
+    <label for="tempo">Visual tempo <span class="val" id="tempoVal">1.0×</span></label>
+    <input type="range" id="tempo" min="20" max="220" value="100">
+  </div>
+  <div class="field">
+    <label for="speed">Train speed <span class="val" id="speedVal">1.0×</span></label>
+    <input type="range" id="speed" min="0" max="220" value="100">
+  </div>
+  <div class="field">
+    <label for="trail">Trail intensity <span class="val" id="trailVal">Low</span></label>
+    <input type="range" id="trail" min="0" max="100" value="18">
+  </div>
+
+  <h3>Cast</h3>
+  <div class="field">
+    <label for="density">Crowd density <span class="val" id="densityVal">Medium</span></label>
+    <input type="range" id="density" min="2" max="10" value="6">
+  </div>
+  <div class="field">
+    <label for="animals">Animal visibility <span class="val" id="animalsVal">All</span></label>
+    <input type="range" id="animals" min="0" max="100" value="100">
+  </div>
+
+  <h3>Reveal &amp; Access</h3>
+  <div class="toggle">
+    <label for="paths" style="margin:0">Reveal movement paths</label>
+    <span class="switch"><input type="checkbox" id="paths">
+      <span class="track"></span><span class="knob"></span></span>
+  </div>
+  <div class="toggle">
+    <label for="reduced" style="margin:0">Reduced motion</label>
+    <span class="switch"><input type="checkbox" id="reduced">
+      <span class="track"></span><span class="knob"></span></span>
+  </div>
+
+  <h3>Transport</h3>
+  <div class="row">
+    <button id="pauseBtn" aria-pressed="false">⏸ Pause</button>
+    <button id="resetBtn">↺ Reset</button>
+  </div>
+
+  <p class="hint">Keys: <kbd>Space</kbd> pause · <kbd>R</kbd> reset ·
+    <kbd>1</kbd>–<kbd>5</kbd> landscape · <kbd>C</kbd> camera ·
+    <kbd>P</kbd> paths · <kbd>M</kbd> reduced motion · <kbd>←</kbd>/<kbd>→</kbd> scrub.</p>
+</aside>
+
+<div class="sr-only" role="status" aria-live="polite" id="live"></div>
+
+<script>
+(() => {
+"use strict";
+const cv = document.getElementById('scene');
+const ctx = cv.getContext('2d');
+let W=0,H=0,DPR=1;
+
+function resize(){
+  DPR = Math.min(2, window.devicePixelRatio||1);
+  W = window.innerWidth; H = window.innerHeight;
+  cv.width = Math.floor(W*DPR); cv.height = Math.floor(H*DPR);
+  ctx.setTransform(DPR,0,0,DPR,0,0);
+}
+window.addEventListener('resize', resize); resize();
+
+/* ---------- Landscapes: palette, lighting, movement vocabulary, caption ---------- */
+const LAND = [
+  { name:"Desert Mesas", meta:"Landscape 1 of 5 · dusk copper",
+    sky:['#160d3e','#3a2668','#5b3d7a'], sun:'#ffcf7a', sunGlow:'rgba(255,180,90,.5)',
+    ground:['#6a3b2b','#4a2620','#2c1830'], accent:'#f0a24e', star:'#ffe6b0',
+    move:{speed:0.72, sway:1.0, style:'grounded'},
+    cap:"The train leaves at dusk over copper mesas. Warm light pools low; the dancers keep their weight in their heels, swaying like heat rising off the stone. A slow, grounded vocabulary." },
+  { name:"Rose Garden", meta:"Landscape 2 of 5 · twilight bloom",
+    sky:['#2a0f42','#5a1f5e','#7a2f6a'], sun:'#ffb0d0', sunGlow:'rgba(255,120,170,.45)',
+    ground:['#2e5a3a','#1f3f2c','#152a30'], accent:'#ff6c98', star:'#ffd0e6',
+    move:{speed:0.9, sway:1.35, style:'romantic'},
+    cap:"A garden of impossible roses drifts past. Couples find shared centres and turn around them; the movement softens and curves, romantic and unhurried, petals answering the sway." },
+  { name:"Flooded Moonlit Town", meta:"Landscape 3 of 5 · silver water",
+    sky:['#07122a','#123157','#1c4a72'], sun:'#dff0ff', sunGlow:'rgba(150,200,255,.4)',
+    ground:['#0b2742','#08243b','#06131f'], accent:'#8fc0e6', star:'#eaf6ff',
+    move:{speed:0.55, sway:0.8, style:'drifting'},
+    cap:"Rooftops stand in still silver water; the whole world doubles in reflection. Motion slows and floats — dancers drift as if buoyed, and the train glides across its own mirror image." },
+  { name:"Crystalline Forest", meta:"Landscape 4 of 5 · prism light",
+    sky:['#06132c','#0f3a49','#12586a'], sun:'#c9fff0', sunGlow:'rgba(90,255,215,.42)',
+    ground:['#0c3a44','#0a2c38','#07202b'], accent:'#5ff0d0', star:'#c9fff4',
+    move:{speed:1.25, sway:0.7, style:'crystalline'},
+    cap:"Trees of faceted glass throw prism light through the windows. The vocabulary turns sharp and quick — solo spinners brace crisply through their turns, refracting the beat into angles." },
+  { name:"Sunrise Coast", meta:"Landscape 5 of 5 · dawn gold",
+    sky:['#301a4a','#8a4a6a','#e0966a'], sun:'#ffe6a8', sunGlow:'rgba(255,200,120,.55)',
+    ground:['#2a3a6a','#3a4a7a','#1a2440'], accent:'#ffb066', star:'#fff0cc',
+    move:{speed:1.0, sway:1.15, style:'rising'},
+    cap:"The journey opens onto a dawn coast. Light lifts, gold and rose; the dancers rise onto their toes and the whole carriage seems to breathe upward as the sea brightens toward morning." }
+];
+
+/* ---------- State ---------- */
+const S = {
+  t:0, worldX:0, paused:false, cam:'exterior',
+  journey:0.05,        // 0..1 across the five landscapes
+  auto:true,
+  tempo:1, speed:1, trail:0.18, density:6, animals:1,
+  paths:false, reduced:false, curLand:0
+};
+
+/* ---------- Helpers ---------- */
+const lerp=(a,b,t)=>a+(b-a)*t;
+const clamp=(v,a,b)=>v<a?a:v>b?b:v;
+const smooth=t=>t*t*(3-2*t);
+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)];}
+function mix(c1,c2,t){const a=hex(c1),b=hex(c2);return`rgb(${Math.round(lerp(a[0],b[0],t))},${Math.round(lerp(a[1],b[1],t))},${Math.round(lerp(a[2],b[2],t))})`;}
+function mixA(c1,c2,t,al){const a=hex(c1),b=hex(c2);return`rgba(${Math.round(lerp(a[0],b[0],t))},${Math.round(lerp(a[1],b[1],t))},${Math.round(lerp(a[2],b[2],t))},${al})`;}
+
+// weight of each landscape given journey position
+function landWeights(j){
+  const w=[0,0,0,0,0];
+  const p=j*5; // 0..5
+  for(let i=0;i<5;i++){
+    const c=i+0.5;
+    const d=Math.abs(p-c);
+    w[i]=Math.max(0,1-d); // triangular, overlapping neighbours
+  }
+  // normalize
+  let s=w.reduce((a,b)=>a+b,0)||1;
+  return w.map(x=>x/s);
+}
+// blended palette getters
+function blend(field, idxA){ // field: 'sun','accent','star'; blend two dominant
+  const w=landWeights(S.journey);
+  let r=0,g=0,b=0;
+  for(let i=0;i<5;i++){const c=hex(LAND[i][field]);r+=c[0]*w[i];g+=c[1]*w[i];b+=c[2]*w[i];}
+  return `rgb(${Math.round(r)},${Math.round(g)},${Math.round(b)})`;
+}
+function blendArr(field,k){
+  const w=landWeights(S.journey);let r=0,g=0,b=0;
+  for(let i=0;i<5;i++){const c=hex(LAND[i][field][k]);r+=c[0]*w[i];g+=c[1]*w[i];b+=c[2]*w[i];}
+  return `rgb(${Math.round(r)},${Math.round(g)},${Math.round(b)})`;
+}
+function blendMove(){
+  const w=landWeights(S.journey);let sp=0,sw=0;
+  for(let i=0;i<5;i++){sp+=LAND[i].move.speed*w[i];sw+=LAND[i].move.sway*w[i];}
+  return {speed:sp,sway:sw};
+}
+function dominant(){const w=landWeights(S.journey);let m=0,idx=0;for(let i=0;i<5;i++)if(w[i]>m){m=w[i];idx=i;}return idx;}
+
+/* ---------- Cast ---------- */
+let dancers=[], animals=[], trailHist=new Map();
+function buildDancers(){
+  dancers=[];
+  const clusters = S.density; // 2..10
+  const types=['circle','couple','solo','seated','seated','circle'];
+  let id=0;
+  for(let c=0;c<clusters;c++){
+    const u = (c+0.5)/clusters;             // horizontal slot in carriage
+    const type = types[c%types.length];
+    const seatRow = (c%2===0)?0.34:0.66;
+    dancers.push({id:id++, type, u, v: type==='seated'? (c%2?0.72:0.3):0.5,
+      phase: c*0.9, dir:1, size: type==='couple'?1:1});
+  }
+}
+function buildAnimals(){
+  // roofline + alongside; distinct rhythms
+  animals=[
+    {kind:'bear',    u:0.18, roof:true,  rate:0.7,  amp:5,  phase:0.0},
+    {kind:'raven',   u:0.5,  roof:true,  rate:2.4,  amp:9,  phase:1.1},
+    {kind:'raven',   u:0.74, roof:true,  rate:2.1,  amp:8,  phase:2.3},
+    {kind:'fox',     u:0.88, roof:true,  rate:1.5,  amp:6,  phase:0.6},
+    {kind:'jackrabbit', u:0.28, roof:false, rate:1.0, amp:26, phase:0.0}, // hop bursts
+    {kind:'jackrabbit', u:0.62, roof:false, rate:1.0, amp:24, phase:0.5},
+    {kind:'turtle',  u:0.82, roof:false, rate:0.28, amp:2, phase:0.0}
+  ];
+}
+buildDancers(); buildAnimals();
+
+/* ---------- Geometry: carriage rect per camera ---------- */
+function carriageRect(){
+  if(S.cam==='cutaway'){
+    return {x:W*0.06, y:H*0.24, w:W*0.68, h:H*0.44, scale:1.9};
+  }
+  if(S.cam==='conductor'){
+    return {x:W*0.30, y:H*0.60, w:W*0.40, h:H*0.16, scale:0.7};
+  }
+  // exterior
+  return {x:W*0.16, y:H*0.44, w:W*0.62, h:H*0.20, scale:1.0};
+}
+
+/* ---------- Drawing: sky + poster sun ---------- */
+function drawSky(){
+  const g=ctx.createLinearGradient(0,0,0,H);
+  g.addColorStop(0, blendArr('sky',0));
+  g.addColorStop(0.5, blendArr('sky',1));
+  g.addColorStop(1, blendArr('sky',2));
+  ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
+
+  // stars
+  const star=blend('star');
+  ctx.save();
+  for(let i=0;i<70;i++){
+    const sx=((i*137.5)%W);
+    const sy=(( (i*i*53.3)%1)*H*0.55);
+    const tw = S.reduced?0.6:(0.4+0.6*Math.abs(Math.sin(S.t*0.7+i)));
+    ctx.globalAlpha=0.5*tw;
+    ctx.fillStyle=star;
+    ctx.fillRect(sx, sy, 1.6,1.6);
+  }
+  ctx.restore();
+
+  // psychedelic travel-poster sun with radiating rays behind the horizon
+  const horizon=H*0.62;
+  const sunX=W*0.5, sunY=horizon;
+  const sunCol=blend('sun');
+  ctx.save();
+  ctx.translate(sunX,sunY);
+  const rot = S.reduced?0:S.t*0.05;
+  ctx.rotate(rot);
+  ctx.globalAlpha=0.16;
+  for(let r=0;r<24;r++){
+    ctx.rotate(Math.PI/12);
+    ctx.fillStyle= r%2? sunCol : blend('accent');
+    ctx.beginPath();ctx.moveTo(0,0);
+    ctx.lineTo(-W*0.9, -18); ctx.lineTo(-W*0.9, 18); ctx.closePath();ctx.fill();
+  }
+  ctx.restore();
+  // sun disc glow
+  const rg=ctx.createRadialGradient(sunX,sunY,0,sunX,sunY,W*0.22);
+  rg.addColorStop(0, sunCol);
+  rg.addColorStop(0.28, sunCol);
+  rg.addColorStop(1,'rgba(0,0,0,0)');
+  ctx.save();ctx.globalAlpha=0.55;ctx.fillStyle=rg;
+  ctx.beginPath();ctx.arc(sunX,sunY,W*0.22,0,7);ctx.fill();ctx.restore();
+  ctx.fillStyle=sunCol;ctx.globalAlpha=0.9;
+  ctx.beginPath();ctx.arc(sunX,sunY,W*0.055,0,7);ctx.fill();ctx.globalAlpha=1;
+  return horizon;
+}
+
+/* ---------- Landscape decor (crossfaded per weight) ---------- */
+function motifX(i,spacing,parallax,pad){
+  const span=W+pad*2;
+  let x=((i*spacing - S.worldX*parallax)%span);
+  if(x<0)x+=span;
+  return x-pad;
+}
+function drawLand(horizon){
+  const w=landWeights(S.journey);
+  // base rolling hills (poster bands) using blended ground colours
+  for(let band=0;band<3;band++){
+    const yy=horizon + band*H*0.08 + H*0.02;
+    ctx.fillStyle=blendArr('ground',band);
+    ctx.beginPath();ctx.moveTo(0,yy);
+    for(let x=0;x<=W;x+=24){
+      const amp=(3-band)*10;
+      const y=yy + Math.sin(x*0.006 + band + S.worldX*0.002*(3-band))*amp;
+      ctx.lineTo(x,y);
+    }
+    ctx.lineTo(W,H);ctx.lineTo(0,H);ctx.closePath();ctx.fill();
+  }
+  const acc=blend('accent');
+  // 0 Desert mesas
+  if(w[0]>0.02){ctx.save();ctx.globalAlpha=w[0];ctx.fillStyle=LAND[0].ground[0];
+    for(let i=0;i<7;i++){const x=motifX(i,240,0.5,140);const bh=70+((i*53)%60);const bw=90;
+      ctx.fillStyle=mix(LAND[0].ground[0],LAND[0].accent,0.25+0.1*(i%3));
+      ctx.fillRect(x, horizon-bh, bw, bh);
+      ctx.fillRect(x+bw*0.2, horizon-bh-24, bw*0.55, 24);}
+    ctx.restore();}
+  // 1 Rose garden
+  if(w[1]>0.02){ctx.save();ctx.globalAlpha=w[1];
+    for(let i=0;i<14;i++){const x=motifX(i,120,0.75,80);const y=horizon-8-((i*37)%26);
+      const s=10+((i*17)%8);
+      ctx.fillStyle='#1f6a33';ctx.fillRect(x-1,y,2,26);
+      for(let p=0;p<6;p++){ctx.fillStyle=p%2?LAND[1].accent:'#ff9dc0';
+        ctx.beginPath();ctx.ellipse(x+Math.cos(p)*s*0.4,y+Math.sin(p)*s*0.4,s*0.5,s*0.32,p,0,7);ctx.fill();}
+      ctx.fillStyle='#ffd7ea';ctx.beginPath();ctx.arc(x,y,s*0.32,0,7);ctx.fill();}
+    ctx.restore();}
+  // 2 Flooded town (rooftops + reflection handled by water layer)
+  if(w[2]>0.02){ctx.save();ctx.globalAlpha=w[2];
+    for(let i=0;i<9;i++){const x=motifX(i,170,0.6,120);const bh=44+((i*41)%50);const bw=64;
+      ctx.fillStyle=mix('#0b2742','#8fc0e6',0.15+0.08*(i%3));
+      ctx.fillRect(x,horizon-bh,bw,bh);
+      ctx.fillStyle=acc;ctx.beginPath();ctx.moveTo(x-6,horizon-bh);ctx.lineTo(x+bw/2,horizon-bh-26);
+      ctx.lineTo(x+bw+6,horizon-bh);ctx.closePath();ctx.fill();
+      // lit windows
+      ctx.fillStyle='rgba(255,220,150,'+(0.5+0.5*Math.sin(S.t+i))+')';
+      ctx.fillRect(x+12,horizon-bh+14,8,10);ctx.fillRect(x+bw-22,horizon-bh+14,8,10);}
+    ctx.restore();}
+  // 3 Crystalline forest
+  if(w[3]>0.02){ctx.save();ctx.globalAlpha=w[3];
+    for(let i=0;i<11;i++){const x=motifX(i,150,0.7,120);const ch=90+((i*61)%80);
+      const grad=ctx.createLinearGradient(x,horizon-ch,x+30,horizon);
+      grad.addColorStop(0, LAND[3].accent);grad.addColorStop(1,'#123a49');
+      ctx.fillStyle=grad;
+      ctx.beginPath();ctx.moveTo(x,horizon);ctx.lineTo(x+8,horizon-ch);
+      ctx.lineTo(x+18,horizon-ch*0.7);ctx.lineTo(x+28,horizon);ctx.closePath();
+      ctx.globalAlpha=w[3]*(0.6+0.4*Math.abs(Math.sin(S.t*0.9+i)));ctx.fill();}
+    ctx.restore();}
+  // 4 Sunrise coast (waves)
+  if(w[4]>0.02){ctx.save();ctx.globalAlpha=w[4]*0.9;
+    for(let r=0;r<4;r++){const yy=horizon+6+r*10;
+      ctx.strokeStyle=mixA(LAND[4].accent,'#ffffff',0.2+0.15*r,0.5);ctx.lineWidth=2;
+      ctx.beginPath();
+      for(let x=0;x<=W;x+=14){const y=yy+Math.sin(x*0.03 - S.t*1.4 + r)*4;
+        x===0?ctx.moveTo(x,y):ctx.lineTo(x,y);}
+      ctx.stroke();}
+    ctx.restore();}
+}
+
+/* ---------- Water / reflection layer (strong on flooded town) ---------- */
+function drawWater(horizon){
+  const w=landWeights(S.journey);
+  const wet = w[2]*1.0 + w[4]*0.35; // flooded strongest, coast some
+  if(wet<0.03) return;
+  ctx.save();
+  ctx.globalAlpha=clamp(wet,0,1)*0.5;
+  const g=ctx.createLinearGradient(0,horizon,0,H);
+  g.addColorStop(0, blendArr('ground',2));
+  g.addColorStop(1, blendArr('sky',2));
+  ctx.fillStyle=g;ctx.fillRect(0,horizon,W,H-horizon);
+  // shimmer lines
+  ctx.globalAlpha=clamp(wet,0,1)*0.28;
+  ctx.strokeStyle=blend('star');ctx.lineWidth=1;
+  for(let i=0;i<26;i++){const y=horizon+ (i/26)*(H-horizon);
+    const off=Math.sin(S.t*(S.reduced?0.3:1.2)+i)*8*(S.reduced?0.4:1);
+    ctx.beginPath();ctx.moveTo(0,y+off);ctx.lineTo(W,y-off);ctx.stroke();}
+  ctx.restore();
+}
+
+/* ---------- The Train ---------- */
+function drawTrain(horizon){
+  const acc=blend('accent'); const glow=blend('sun');
+  if(S.cam==='conductor'){ drawConductor(horizon,acc,glow); return; }
+
+  const rc=carriageRect();
+  // reflection of train (on water landscapes)
+  const w=landWeights(S.journey); const wet=clamp(w[2]+w[4]*0.4,0,1);
+  const bodyY=rc.y, bodyH=rc.h, bodyX=rc.x, bodyW=rc.w;
+
+  function bodyShape(yBase, h, flip){
+    ctx.beginPath();
+    const r=14;
+    ctx.moveTo(bodyX+r,yBase);
+    ctx.lineTo(bodyX+bodyW-r,yBase);
+    ctx.quadraticCurveTo(bodyX+bodyW,yBase,bodyX+bodyW,yBase+(flip?-r:r));
+    ctx.lineTo(bodyX+bodyW,yBase+(flip?-h+r:h-r));
+    ctx.quadraticCurveTo(bodyX+bodyW,yBase+(flip?-h:h),bodyX+bodyW-r,yBase+(flip?-h:h));
+    ctx.lineTo(bodyX+r,yBase+(flip?-h:h));
+    ctx.quadraticCurveTo(bodyX,yBase+(flip?-h:h),bodyX,yBase+(flip?-h+r:h-r));
+    ctx.lineTo(bodyX,yBase+(flip?-r:r));
+    ctx.quadraticCurveTo(bodyX,yBase,bodyX+r,yBase);
+    ctx.closePath();
+  }
+
+  // reflected ghost
+  if(wet>0.05){
+    ctx.save();ctx.globalAlpha=wet*0.32;
+    ctx.translate(0, 2*(bodyY+bodyH));ctx.scale(1,-1);
+    bodyShape(bodyY,bodyH,false);
+    ctx.fillStyle=mix('#0b1030',acc,0.3);ctx.fill();
+    ctx.restore();
+  }
+
+  // locomotive nose (left)
+  const noseW=Math.min(120,bodyW*0.22);
+  ctx.save();
+  const ng=ctx.createLinearGradient(bodyX-noseW,0,bodyX,0);
+  ng.addColorStop(0,mix('#0c1236',acc,0.15));ng.addColorStop(1,mix('#161d4a',acc,0.3));
+  ctx.fillStyle=ng;
+  ctx.beginPath();
+  ctx.moveTo(bodyX+8,bodyY);
+  ctx.lineTo(bodyX-noseW+18,bodyY+bodyH*0.16);
+  ctx.quadraticCurveTo(bodyX-noseW,bodyY+bodyH*0.55,bodyX-noseW+18,bodyY+bodyH*0.9);
+  ctx.lineTo(bodyX+8,bodyY+bodyH);
+  ctx.closePath();ctx.fill();
+  // headlamp
+  ctx.fillStyle=glow;ctx.globalAlpha=0.9;
+  ctx.beginPath();ctx.arc(bodyX-noseW+24,bodyY+bodyH*0.52,5,0,7);ctx.fill();
+  if(!S.reduced){ctx.globalAlpha=0.18;ctx.beginPath();
+    ctx.moveTo(bodyX-noseW+24,bodyY+bodyH*0.52);
+    ctx.lineTo(bodyX-noseW-160,bodyY+bodyH*0.1);
+    ctx.lineTo(bodyX-noseW-160,bodyY+bodyH*0.95);ctx.closePath();ctx.fill();}
+  ctx.restore();
+
+  // steam puffs from nose
+  drawSteam(bodyX-noseW+30, bodyY, glow);
+
+  // carriage body
+  ctx.save();
+  const bg=ctx.createLinearGradient(0,bodyY,0,bodyY+bodyH);
+  bg.addColorStop(0,mix('#1a2050',acc,0.10));
+  bg.addColorStop(1,mix('#0a0d28',acc,0.05));
+  bodyShape(bodyY,bodyH,false);
+  ctx.fillStyle=bg;ctx.fill();
+  // luminous outline
+  ctx.shadowColor=glow;ctx.shadowBlur=S.reduced?6:22;
+  ctx.lineWidth=2.2;ctx.strokeStyle=mix(glow,'#ffffff',0.2);ctx.stroke();
+  ctx.shadowBlur=0;
+  // accent stripe
+  ctx.strokeStyle=acc;ctx.lineWidth=3;ctx.globalAlpha=0.7;
+  ctx.beginPath();ctx.moveTo(bodyX,bodyY+bodyH*0.72);ctx.lineTo(bodyX+bodyW,bodyY+bodyH*0.72);ctx.stroke();
+  ctx.restore();
+
+  // ---- interior floor & the cutaway (windows) ----
+  // dark interior
+  ctx.save();
+  bodyShape(bodyY,bodyH,false);ctx.clip();
+  ctx.fillStyle='rgba(8,6,22,0.55)';ctx.fillRect(bodyX,bodyY,bodyW,bodyH);
+  // curtains at window tops responding to motion
+  drawCurtains(bodyX,bodyY,bodyW,bodyH,acc);
+  ctx.restore();
+
+  // window frame divisions
+  const winCount=Math.max(4,Math.floor(bodyW/90));
+  ctx.save();ctx.strokeStyle=mixA(glow,'#ffffff',0.3,0.5);ctx.lineWidth=1.5;
+  for(let i=1;i<winCount;i++){const x=bodyX+bodyW*i/winCount;
+    ctx.beginPath();ctx.moveTo(x,bodyY+6);ctx.lineTo(x,bodyY+bodyH-6);ctx.stroke();}
+  ctx.restore();
+
+  // DANCERS in the cutaway
+  drawDancers(rc);
+
+  // wheels + bogies
+  ctx.save();ctx.fillStyle='#05060f';
+  const wy=bodyY+bodyH; const wheelN=Math.max(3,Math.floor(bodyW/120));
+  for(let i=0;i<wheelN;i++){const wx=bodyX+bodyW*(i+0.5)/wheelN;
+    ctx.beginPath();ctx.arc(wx,wy+10,10,0,7);ctx.fill();
+    ctx.strokeStyle=acc;ctx.lineWidth=2;
+    const spin=S.worldX*0.05;
+    ctx.beginPath();ctx.moveTo(wx+Math.cos(spin)*7,wy+10+Math.sin(spin)*7);
+    ctx.lineTo(wx-Math.cos(spin)*7,wy+10-Math.sin(spin)*7);ctx.stroke();}
+  ctx.restore();
+
+  // ANIMALS (roof + alongside)
+  drawAnimals(rc, wy);
+
+  // luggage on roof responding to motion
+  drawLuggage(bodyX,bodyY,bodyW);
+}
+
+function drawConductor(horizon,acc,glow){
+  // forward view: converging rails into the landscape
+  const cx=W*0.5;
+  ctx.save();
+  ctx.strokeStyle=mixA(glow,'#ffffff',0.3,0.7);ctx.lineWidth=3;
+  const gaugeSway=Math.sin(S.t*1.2)*(S.reduced?2:8);
+  for(const s of [-1,1]){
+    ctx.beginPath();
+    ctx.moveTo(cx+s*30, horizon);
+    ctx.lineTo(cx+s*(W*0.42)+gaugeSway, H);
+    ctx.stroke();
+  }
+  // ties
+  ctx.strokeStyle=mixA(acc,'#000',0.2,0.5);ctx.lineWidth=4;
+  for(let i=0;i<16;i++){
+    const tt=(i/16 + (S.worldX*0.004)%(1/16));
+    const p=tt%1;const y=lerp(horizon,H,p*p);const wdt=lerp(30, W*0.42, p*p);
+    ctx.globalAlpha=0.6*p;
+    ctx.beginPath();ctx.moveTo(cx-wdt+gaugeSway*p,y);ctx.lineTo(cx+wdt+gaugeSway*p,y);ctx.stroke();
+  }
+  ctx.globalAlpha=1;
+  ctx.restore();
+  // steam ahead
+  drawSteam(cx, horizon-10, glow, true);
+  // locomotive nose framing bottom
+  ctx.save();
+  const g=ctx.createLinearGradient(0,H*0.82,0,H);
+  g.addColorStop(0,mix('#12183e',acc,0.2));g.addColorStop(1,'#04050e');
+  ctx.fillStyle=g;
+  ctx.beginPath();ctx.moveTo(0,H);ctx.lineTo(0,H*0.9);
+  ctx.quadraticCurveTo(cx,H*0.78,W,H*0.9);ctx.lineTo(W,H);ctx.closePath();ctx.fill();
+  // headlamp glow
+  ctx.fillStyle=glow;ctx.shadowColor=glow;ctx.shadowBlur=30;
+  ctx.beginPath();ctx.arc(cx,H*0.86,10,0,7);ctx.fill();ctx.shadowBlur=0;
+  // instrument dials
+  ctx.strokeStyle=mixA(glow,'#fff',0.3,0.6);ctx.lineWidth=2;
+  for(const dx of [-90,90]){ctx.beginPath();ctx.arc(cx+dx,H*0.93,16,0,7);ctx.stroke();
+    const a=-2.4+Math.sin(S.t*1.5+dx)*(S.reduced?0.1:0.5);
+    ctx.beginPath();ctx.moveTo(cx+dx,H*0.93);ctx.lineTo(cx+dx+Math.cos(a)*13,H*0.93+Math.sin(a)*13);ctx.stroke();}
+  ctx.restore();
+  // animals running alongside visible at edges
+  drawConductorAnimals(horizon);
+}
+
+/* ---------- Steam ---------- */
+let steamPuffs=[];
+function drawSteam(x,y,glow,upward){
+  if(S.reduced){ // gentle static-ish
+  }
+  // spawn
+  if(!S.paused && Math.random()<0.4*S.speed){
+    steamPuffs.push({x,y,r:6,life:1,vx:(upward?0:-1.2)*S.speed - (upward?0:0.5), vy:-0.6-Math.random()*0.7});
+  }
+  ctx.save();
+  for(const p of steamPuffs){
+    ctx.globalAlpha=p.life*0.35;
+    ctx.fillStyle=mix(glow,'#ffffff',0.5);
+    ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,7);ctx.fill();
+  }
+  ctx.restore();
+  if(!S.paused){
+    const dt=(S.reduced?0.4:1);
+    for(const p of steamPuffs){p.x+=p.vx*dt;p.y+=p.vy*dt;p.r+=0.5*dt;p.life-=0.012*dt;}
+    steamPuffs=steamPuffs.filter(p=>p.life>0).slice(-60);
+  }
+}
+
+/* ---------- Curtains & Luggage ---------- */
+function drawCurtains(x,y,w,h,acc){
+  const n=Math.max(4,Math.floor(w/90));
+  ctx.save();
+  for(let i=0;i<n;i++){
+    const wx=x+w*i/n, ww=w/n;
+    const sway=Math.sin(S.t*1.4 + i)* (S.reduced?2:6) * S.speed;
+    ctx.fillStyle=mixA(acc,'#000',0.35,0.5);
+    ctx.beginPath();ctx.moveTo(wx+4,y+4);
+    ctx.quadraticCurveTo(wx+ww*0.5+sway,y+h*0.28,wx+ww-4,y+4);
+    ctx.lineTo(wx+ww-4,y+4);ctx.closePath();ctx.fill();
+  }
+  ctx.restore();
+}
+function drawLuggage(x,y,w){
+  const cases=[[0.3,acc0(),18,12],[0.55,'#8a5a2a',22,14],[0.72,'#5a6a8a',16,11]];
+  function acc0(){return blend('accent');}
+  const bump = Math.sin(S.worldX*0.02)* (S.reduced?0.5:2.5)*S.speed;
+  ctx.save();
+  for(const c of cases){
+    const cx=x+w*c[0]; const cy=y-c[3]-2+bump*(0.6+c[0]);
+    ctx.fillStyle=c[1];ctx.fillRect(cx,cy,c[2],c[3]);
+    ctx.strokeStyle='rgba(0,0,0,.4)';ctx.strokeRect(cx,cy,c[2],c[3]);
+    ctx.fillStyle='rgba(255,255,255,.3)';ctx.fillRect(cx+2,cy+c[3]*0.4,c[2]-4,1.5);
+  }
+  ctx.restore();
+}
+
+/* ---------- Dancers ---------- */
+function pushTrail(id,x,y){
+  let arr=trailHist.get(id);if(!arr){arr=[];trailHist.set(id,arr);}
+  arr.push([x,y]);
+  const maxLen=Math.round(2 + S.trail*60);
+  while(arr.length>maxLen)arr.shift();
+  return arr;
+}
+function drawTrail(arr,col){
+  if(S.trail<0.02||arr.length<2)return;
+  ctx.save();
+  for(let i=1;i<arr.length;i++){
+    ctx.globalAlpha=(i/arr.length)*0.5*S.trail*3;
+    ctx.strokeStyle=col;ctx.lineWidth=2;
+    ctx.beginPath();ctx.moveTo(arr[i-1][0],arr[i-1][1]);ctx.lineTo(arr[i][0],arr[i][1]);ctx.stroke();
+  }
+  ctx.restore();
+}
+function figure(x,y,r,col,lean){
+  ctx.save();ctx.translate(x,y);ctx.rotate(lean||0);
+  ctx.fillStyle=col;
+  ctx.beginPath();ctx.arc(0,-r*1.4,r*0.55,0,7);ctx.fill();      // head
+  ctx.beginPath();ctx.moveTo(0,-r*0.9);ctx.lineTo(-r*0.5,r*0.6);
+  ctx.lineTo(r*0.5,r*0.6);ctx.closePath();ctx.fill();           // body
+  ctx.restore();
+}
+function drawDancers(rc){
+  const mv=blendMove();
+  const spd=mv.speed*S.tempo*(S.reduced?0.35:1);
+  const swayF=mv.sway*(S.reduced?0.4:1);
+  const acc=blend('accent'); const sun=blend('sun');
+  const base=Math.min(rc.w,rc.h)*0.05*rc.scale;
+  const t=S.t;
+  for(const d of dancers){
+    const cx=rc.x + rc.w*d.u;
+    const cy=rc.y + rc.h*d.v;
+    const R=base*(rc.h/ (H*0.2));
+    const col = mix('#f4ecff', d.id%2?acc:sun, 0.4);
+    if(d.type==='circle'){
+      // small circle that reverses direction every ~5s
+      const period=5;
+      const dir = Math.floor((t*spd)/period)%2===0?1:-1;
+      const ang = t*spd*1.4*dir + d.phase;
+      const rr=R*1.3;
+      const x=cx+Math.cos(ang)*rr, y=cy+Math.sin(ang)*rr*0.6;
+      if(S.paths){ctx.save();ctx.globalAlpha=0.4;ctx.strokeStyle=acc;ctx.setLineDash([3,4]);
+        ctx.beginPath();ctx.ellipse(cx,cy,rr,rr*0.6,0,0,7);ctx.stroke();ctx.restore();}
+      const arr=pushTrail(d.id,x,y);drawTrail(arr,col);
+      figure(x,y,R,col, Math.sin(t*spd*2+d.phase)*0.15*swayF);
+    } else if(d.type==='couple'){
+      // two figures rotating a shared centre
+      const ang=t*spd*1.1 + d.phase; const rr=R*1.6;
+      const x1=cx+Math.cos(ang)*rr,y1=cy+Math.sin(ang)*rr*0.6;
+      const x2=cx-Math.cos(ang)*rr,y2=cy-Math.sin(ang)*rr*0.6;
+      if(S.paths){ctx.save();ctx.globalAlpha=0.4;ctx.strokeStyle=sun;ctx.setLineDash([3,4]);
+        ctx.beginPath();ctx.ellipse(cx,cy,rr,rr*0.6,0,0,7);ctx.stroke();
+        ctx.fillStyle=sun;ctx.beginPath();ctx.arc(cx,cy,2,0,7);ctx.fill();ctx.restore();}
+      const a1=pushTrail(d.id*100+1,x1,y1);drawTrail(a1,acc);
+      const a2=pushTrail(d.id*100+2,x2,y2);drawTrail(a2,sun);
+      // joined hands line
+      ctx.save();ctx.globalAlpha=0.35;ctx.strokeStyle=col;ctx.lineWidth=1.5;
+      ctx.beginPath();ctx.moveTo(x1,y1);ctx.lineTo(x2,y2);ctx.stroke();ctx.restore();
+      figure(x1,y1,R,mix(col,acc,0.4),(x1-cx)*0.01);
+      figure(x2,y2,R,mix(col,sun,0.4),(x2-cx)*0.01);
+    } else if(d.type==='solo'){
+      // spinner bracing through turns (lean opposes spin)
+      const ang=t*spd*3 + d.phase;
+      const lean=Math.sin(ang)*0.35*swayF;
+      const x=cx+Math.sin(ang*0.5)*R*0.4, y=cy;
+      if(S.paths){ctx.save();ctx.globalAlpha=0.35;ctx.strokeStyle=acc;ctx.setLineDash([2,4]);
+        ctx.beginPath();ctx.arc(cx,cy,R*0.9,0,7);ctx.stroke();ctx.restore();}
+      const arr=pushTrail(d.id,x,y-R);drawTrail(arr,col);
+      ctx.save();ctx.translate(x,y);ctx.scale(Math.cos(ang)*0.4+0.7,1);ctx.translate(-x,-y);
+      figure(x,y,R,col,lean);ctx.restore();
+    } else { // seated clapping in offset waves
+      const wave = Math.sin(t*spd*2 - d.u*4)*0.5+0.5; // offset by position => wave
+      const spread=R*wave*swayF;
+      // body seated
+      figure(cx,cy+R*0.2,R*0.9,mix(col,'#b9a8e0',0.3),0);
+      // clapping hands
+      ctx.save();ctx.fillStyle=sun;
+      ctx.beginPath();ctx.arc(cx-spread,cy-R*0.6,R*0.22,0,7);ctx.fill();
+      ctx.beginPath();ctx.arc(cx+spread,cy-R*0.6,R*0.22,0,7);ctx.fill();
+      if(wave>0.9){ctx.globalAlpha=0.5;ctx.strokeStyle=sun;
+        ctx.beginPath();ctx.arc(cx,cy-R*0.6,R*0.4,0,7);ctx.stroke();}
+      ctx.restore();
+      if(S.paths){ctx.save();ctx.globalAlpha=0.3;ctx.strokeStyle=sun;ctx.setLineDash([2,3]);
+        ctx.beginPath();ctx.moveTo(cx-R,cy-R*0.6);ctx.lineTo(cx+R,cy-R*0.6);ctx.stroke();ctx.restore();}
+    }
+  }
+}
+
+/* ---------- Animals ---------- */
+function animalGlyph(kind,x,y,s,col,t,a){
+  ctx.save();ctx.translate(x,y);ctx.fillStyle=col;
+  if(kind==='bear'){
+    ctx.beginPath();ctx.ellipse(0,0,s*1.1,s*0.7,0,0,7);ctx.fill();
+    ctx.beginPath();ctx.arc(s*0.9,-s*0.4,s*0.5,0,7);ctx.fill();
+    ctx.beginPath();ctx.arc(s*0.7,-s*0.9,s*0.18,0,7);ctx.fill();
+    ctx.beginPath();ctx.arc(s*1.1,-s*0.9,s*0.18,0,7);ctx.fill();
+  } else if(kind==='raven'){
+    const flap=Math.sin(t*a.rate+a.phase);
+    ctx.beginPath();ctx.ellipse(0,0,s*0.7,s*0.35,0,0,7);ctx.fill();
+    ctx.beginPath();ctx.moveTo(0,0);ctx.lineTo(-s*1.2,-flap*s);ctx.lineTo(-s*0.4,s*0.2);ctx.closePath();ctx.fill();
+    ctx.beginPath();ctx.moveTo(0,0);ctx.lineTo(s*1.2,-flap*s);ctx.lineTo(s*0.4,s*0.2);ctx.closePath();ctx.fill();
+    ctx.beginPath();ctx.arc(s*0.6,-s*0.3,s*0.22,0,7);ctx.fill();
+  } else if(kind==='fox'){
+    ctx.beginPath();ctx.ellipse(0,0,s,s*0.42,0,0,7);ctx.fill();
+    ctx.beginPath();ctx.arc(s*0.8,-s*0.3,s*0.35,0,7);ctx.fill();
+    ctx.beginPath();ctx.moveTo(s*0.7,-s*0.6);ctx.lineTo(s*0.95,-s*1.05);ctx.lineTo(s*1.05,-s*0.55);ctx.closePath();ctx.fill();
+    ctx.beginPath();ctx.moveTo(-s,0);ctx.lineTo(-s*1.7,-s*0.3);ctx.lineTo(-s*0.9,s*0.25);ctx.closePath();ctx.fill();
+  } else if(kind==='jackrabbit'){
+    ctx.beginPath();ctx.ellipse(0,0,s*0.7,s*0.5,0,0,7);ctx.fill();
+    ctx.beginPath();ctx.arc(s*0.6,-s*0.4,s*0.3,0,7);ctx.fill();
+    ctx.beginPath();ctx.ellipse(s*0.6,-s*1.1,s*0.12,s*0.5,0.2,0,7);ctx.fill();
+    ctx.beginPath();ctx.ellipse(s*0.85,-s*1.1,s*0.12,s*0.5,0.4,0,7);ctx.fill();
+  } else if(kind==='turtle'){
+    ctx.beginPath();ctx.ellipse(0,0,s*1.1,s*0.6,0,Math.PI,0);ctx.fill();
+    ctx.fillRect(-s*1.1,0,s*2.2,s*0.2);
+    ctx.beginPath();ctx.arc(s*1.2,-s*0.05,s*0.28,0,7);ctx.fill();
+    ctx.strokeStyle='rgba(0,0,0,.3)';ctx.lineWidth=1;
+    ctx.beginPath();ctx.moveTo(-s*0.4,-s*0.5);ctx.lineTo(-s*0.4,0);
+    ctx.moveTo(s*0.4,-s*0.5);ctx.lineTo(s*0.4,0);ctx.stroke();
+  }
+  ctx.restore();
+}
+function drawAnimals(rc, wheelY){
+  if(S.animals<0.02)return;
+  const acc=blend('accent'); const sun=blend('sun');
+  const t=S.t*S.tempo*(S.reduced?0.4:1);
+  const roofY=rc.y-6; const groundY=wheelY+18;
+  const cols={bear:'#5a3a24',raven:'#1a1428',fox:'#d06a2a',jackrabbit:'#c9b48a',turtle:'#3a6a4a'};
+  let idx=0;
+  for(const a of animals){
+    idx++;
+    if((idx/animals.length) > S.animals+0.001) continue; // visibility fades cast out gradually
+    const s=Math.min(rc.w,rc.h)*0.045*(a.roof?1:1.1);
+    let x,y;
+    if(a.roof){
+      x=rc.x+rc.w*a.u;
+      y=roofY - Math.abs(Math.sin(t*a.rate+a.phase))*a.amp - s*0.5;
+    } else {
+      // alongside on the ground; jackrabbits hop in bursts
+      x=rc.x+rc.w*a.u + (a.kind==='turtle'? Math.sin(t*a.rate)*4 : 0);
+      let hop=0;
+      if(a.kind==='jackrabbit'){
+        const cyc=(t*a.rate+a.phase)%2.2;
+        hop = cyc<0.6? Math.sin(cyc/0.6*Math.PI)*a.amp : 0;
+      } else hop=Math.abs(Math.sin(t*a.rate+a.phase))*a.amp;
+      y=groundY - hop;
+    }
+    ctx.save();ctx.globalAlpha=clamp((S.animals+0.05)*1.2,0,1);
+    // faint rhythm path
+    if(S.paths){ctx.globalAlpha*=0.5;ctx.strokeStyle=acc;ctx.setLineDash([2,4]);
+      ctx.beginPath();ctx.moveTo(x-a.amp*0.2,y+a.amp);ctx.lineTo(x-a.amp*0.2,y);ctx.stroke();ctx.setLineDash([]);}
+    animalGlyph(a.kind,x,y,s,cols[a.kind]||acc,t,a);
+    ctx.restore();
+  }
+}
+function drawConductorAnimals(horizon){
+  if(S.animals<0.02)return;
+  const t=S.t*S.tempo*(S.reduced?0.4:1);
+  const cols={fox:'#d06a2a',jackrabbit:'#c9b48a',raven:'#1a1428'};
+  const runners=[{kind:'fox',side:-1,u:0.7,rate:1.5,amp:14},
+                 {kind:'jackrabbit',side:1,u:0.62,rate:1.0,amp:26},
+                 {kind:'raven',side:-1,u:0.4,rate:2.4,amp:12}];
+  for(const r of runners){
+    const p=r.u;const y=lerp(horizon,H*0.95,p*p);
+    const x=W*0.5 + r.side*lerp(40,W*0.4,p*p);
+    const s=lerp(6,26,p);
+    let hop=r.kind==='jackrabbit'? (((t*r.rate)%2<0.6)?Math.sin(((t*r.rate)%2)/0.6*Math.PI)*r.amp:0)
+            :Math.abs(Math.sin(t*r.rate))*r.amp;
+    ctx.save();ctx.globalAlpha=clamp(S.animals,0,1)*p;
+    animalGlyph(r.kind,x,y-hop,s,cols[r.kind],t,{rate:r.rate,phase:0});
+    ctx.restore();
+  }
+}
+
+/* ---------- Main loop ---------- */
+let last=performance.now();
+function frame(now){
+  const dt=Math.min(0.05,(now-last)/1000); last=now;
+  if(!S.paused){
+    const eff=S.reduced?0.4:1;
+    S.t += dt*eff;
+    S.worldX += dt*60*S.speed*eff*(1+blendMove().speed*0.3);
+    if(S.auto){
+      S.journey += dt*0.02*(S.reduced?0.4:1);
+      if(S.journey>1) S.journey-=1;
+      syncScrub();
+    }
+  }
+  // background: fully repaint (trails handled per-object)
+  const horizon=drawSky();
+  drawLand(horizon);
+  drawWater(horizon);
+  drawTrain(horizon);
+  // vignette
+  const vg=ctx.createRadialGradient(W/2,H/2,H*0.3,W/2,H/2,H*0.85);
+  vg.addColorStop(0,'rgba(0,0,0,0)');vg.addColorStop(1,'rgba(0,0,0,0.55)');
+  ctx.fillStyle=vg;ctx.fillRect(0,0,W,H);
+
+  updateCaption();
+  requestAnimationFrame(frame);
+}
+requestAnimationFrame(frame);
+
+/* ---------- Caption / landscape sync ---------- */
+const capMeta=document.getElementById('capMeta'),capTitle=document.getElementById('capTitle'),
+      capText=document.getElementById('capText'),live=document.getElementById('live');
+function updateCaption(){
+  const idx=dominant();
+  if(idx!==S.curLand){
+    S.curLand=idx;
+    const L=LAND[idx];
+    capMeta.textContent=L.meta;capTitle.textContent=L.name;capText.textContent=L.cap;
+    live.textContent="Now crossing "+L.name+". "+L.cap;
+  }
+}
+updateCaption();
+
+/* ---------- Controls wiring ---------- */
+const $=id=>document.getElementById(id);
+const scrub=$('scrub'), scrubVal=$('scrubVal');
+function syncScrub(){ scrub.value=Math.round(S.journey*1000);
+  const i=dominant(); scrubVal.textContent=(i+1)+" · "+LAND[i].name.split(' ')[0];}
+scrub.addEventListener('input',()=>{S.journey=clamp(scrub.value/1000,0,1);syncScrub();});
+$('autoJourney').addEventListener('change',e=>S.auto=e.target.checked);
+
+const camBtns=document.querySelectorAll('.cams button');
+function setCam(cam){
+  S.cam=cam; trailHist.clear();
+  camBtns.forEach(b=>b.setAttribute('aria-pressed', b.dataset.cam===cam));
+  live.textContent="Camera: "+cam;
+}
+camBtns.forEach(b=>b.addEventListener('click',()=>setCam(b.dataset.cam)));
+
+$('tempo').addEventListener('input',e=>{S.tempo=e.target.value/100;$('tempoVal').textContent=S.tempo.toFixed(1)+'×';});
+$('speed').addEventListener('input',e=>{S.speed=e.target.value/100;$('speedVal').textContent=S.speed.toFixed(1)+'×';});
+$('trail').addEventListener('input',e=>{S.trail=e.target.value/100;
+  $('trailVal').textContent=S.trail<0.05?'Off':S.trail<0.35?'Low':S.trail<0.7?'Medium':'High';});
+$('density').addEventListener('input',e=>{S.density=+e.target.value;buildDancers();trailHist.clear();
+  $('densityVal').textContent=S.density<4?'Sparse':S.density<7?'Medium':'Crowded';});
+$('animals').addEventListener('input',e=>{S.animals=e.target.value/100;
+  $('animalsVal').textContent=S.animals<0.05?'Hidden':S.animals<0.5?'Few':S.animals<0.99?'Most':'All';});
+$('paths').addEventListener('change',e=>{S.paths=e.target.checked;live.textContent="Movement paths "+(S.paths?'revealed':'hidden');});
+$('reduced').addEventListener('change',e=>{S.reduced=e.target.checked;live.textContent="Reduced motion "+(S.reduced?'on':'off');});
+
+const pauseBtn=$('pauseBtn');
+function togglePause(){S.paused=!S.paused;pauseBtn.setAttribute('aria-pressed',S.paused);
+  pauseBtn.textContent=S.paused?'▶ Play':'⏸ Pause';live.textContent=S.paused?'Paused':'Playing';}
+pauseBtn.addEventListener('click',togglePause);
+
+$('resetBtn').addEventListener('click',reset);
+function reset(){
+  S.t=0;S.worldX=0;S.journey=0.05;S.tempo=1;S.speed=1;S.trail=0.18;S.density=6;
+  S.animals=1;S.paths=false;S.reduced=false;S.auto=true;S.paused=false;S.cam='exterior';
+  steamPuffs=[];trailHist.clear();buildDancers();buildAnimals();
+  $('tempo').value=100;$('speed').value=100;$('trail').value=18;$('density').value=6;
+  $('animals').value=100;$('paths').checked=false;$('reduced').checked=false;$('autoJourney').checked=true;
+  $('tempoVal').textContent='1.0×';$('speedVal').textContent='1.0×';$('trailVal').textContent='Low';
+  $('densityVal').textContent='Medium';$('animalsVal').textContent='All';
+  pauseBtn.setAttribute('aria-pressed',false);pauseBtn.textContent='⏸ Pause';
+  setCam('exterior');syncScrub();live.textContent='Reset to the start of the journey.';
+}
+
+// panel toggle
+const panel=$('panel'), pt=$('panelToggle');
+pt.addEventListener('click',()=>{const h=panel.classList.toggle('hidden');
+  pt.setAttribute('aria-expanded',!h);});
+
+// Respect OS reduced-motion preference on load
+if(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches){
+  S.reduced=true; $('reduced').checked=true;
+}
+
+// Keyboard shortcuts
+window.addEventListener('keydown',e=>{
+  if(['INPUT','SELECT','TEXTAREA'].includes(document.activeElement.tagName) && e.key!=='Escape') {
+    // still allow global if not typing in range? ranges use arrows; let them.
+  }
+  const k=e.key.toLowerCase();
+  if(k===' '){e.preventDefault();togglePause();}
+  else if(k==='r'){reset();}
+  else if(k==='c'){const order=['exterior','cutaway','conductor'];setCam(order[(order.indexOf(S.cam)+1)%3]);}
+  else if(k==='p'){S.paths=!S.paths;$('paths').checked=S.paths;}
+  else if(k==='m'){S.reduced=!S.reduced;$('reduced').checked=S.reduced;}
+  else if(k>='1'&&k<='5'){S.auto=false;$('autoJourney').checked=false;
+    S.journey=(+k-1)/5+0.1;syncScrub();}
+  else if(k==='arrowleft'){S.auto=false;$('autoJourney').checked=false;S.journey=clamp(S.journey-0.02,0,1);syncScrub();}
+  else if(k==='arrowright'){S.auto=false;$('autoJourney').checked=false;S.journey=clamp(S.journey+0.02,0,1);syncScrub();}
+});
+
+syncScrub();
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/77fc58739877/qwen25-7b.html b/data/artifacts/77fc58739877/qwen25-7b.html
new file mode 100644
index 0000000..7496c8a
--- /dev/null
+++ b/data/artifacts/77fc58739877/qwen25-7b.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Indigo Train Through Dream Country</title>
+    <style>
+        body, html {
+            margin: 0;
+            padding: 0;
+            overflow: hidden;
+            height: 100%;
+            background-color: #283747;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
+        canvas {
+            width: 100vw;
+            height: 100vh;
+            z-index: -1;
+        }
+    </style>
+</head>
+<body>
+    <canvas id="dreamTrainCanvas"></canvas>
+    <script>
+        const canvas = document.getElementById('dreamTrainCanvas');
+        const ctx = canvas.getContext('2d');
+        canvas.width = window.innerWidth;
+        canvas.height = window.innerHeight;
+
+        function setupScene(palette) {
+            ctx.fillStyle = palette.background;
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+        }
+
+        function drawDesertMesa() {
+            setupScene({background: '#964B00', text: '#fff'});
+            // Draw desert mesas
+            ctx.beginPath();
+            ctx.arc(canvas.width / 2, canvas.height / 2, 350, 0, Math.PI * 2);
+            ctx.fillStyle = '#F0D8B3';
+            ctx.fill();
+        }
+
+        function drawRoseGarden() {
+            setupScene({background: '#ff69b4', text: '#fff'});
+            // Draw rose garden
+            for (let i = -canvas.width; i < canvas.width; i += 15) {
+                ctx.beginPath();
+                ctx.arc(i, canvas.height / 2, 30 + Math.random() * 50, 0, Math.PI * 2);
+                ctx.fillStyle = '#ff69b4';
+                ctx.fill();
+            }
+        }
+
+        function drawFloodedTown() {
+            setupScene({background: '#1c1c1c', text: '#fff'});
+            // Draw flooded town
+            for (let i = -canvas.width; i < canvas.width; i += 20) {
+                ctx.beginPath();
+                ctx.arc(i, Math.random() * canvas.height + 50, 30 + Math.random() * 10, 0, Math.PI * 2);
+                ctx.fillStyle = '#ff69b4';
+                ctx.fill();
+            }
+        }
+
+        function drawCrystallineForest() {
+            setupScene({background: '#87CEEB', text: '#fff'});
+            // Draw crystalline forest
+            for (let i = -canvas.width; i < canvas.width; i += 10) {
+                ctx.beginPath();
+                ctx.arc(i, Math.random() * canvas.height + 50, 30 + Math.random() * 10, 0, Math.PI * 2);
+                ctx.fillStyle = '#87CEEB';
+                ctx.fill();
+            }
+        }
+
+        function drawSunriseCoast() {
+            setupScene({background: '#FADBD8', text: '#000'});
+            // Draw sunrise coast
+            for (let i = -canvas.width; i < canvas.width; i += 25) {
+                ctx.beginPath();
+                ctx.arc(i, Math.random() * canvas.height + 50, 30 + Math.random() * 10, 0, Math.PI * 2);
+                ctx.fillStyle = '#FADBD8';
+                ctx.fill();
+            }
+        }
+
+        function animateTrain(path) {
+            let x = 0;
+            const speed = 0.5;
+
+            function drawFrame(timestamp) {
+                ctx.clearRect(0, 0, canvas.width, canvas.height);
+                path(x);
+                x += speed * Math.sin(timestamp / 1000);
+                if (x > canvas.width) x -= canvas.width;
+                requestAnimationFrame(drawFrame);
+            }
+
+            requestAnimationFrame(drawFrame);
+        }
+
+        function drawTrainAnimation() {
+            animateTrain(drawDesertMesa);
+        }
+
+        function startAnimation(path) {
+            const animation = requestAnimationFrame(() => {
+                ctx.clearRect(0, 0, canvas.width, canvas.height);
+                path();
+                animation;
+            });
+        }
+
+        window.addEventListener('load', () => {
+            drawTrainAnimation();
+        });
+
+        // Demo: Change to another scene
+        function changeScene() {
+            startAnimation(drawRoseGarden);
+        }
+    </script>
+</body>
+</html>
diff --git a/data/artifacts/77fc58739877/qwen25-7b.png b/data/artifacts/77fc58739877/qwen25-7b.png
index e52e66a..60dfcf3 100644
Binary files a/data/artifacts/77fc58739877/qwen25-7b.png and b/data/artifacts/77fc58739877/qwen25-7b.png differ
diff --git a/data/artifacts/77fc58739877/qwen3-14b.html b/data/artifacts/77fc58739877/qwen3-14b.html
new file mode 100644
index 0000000..9faee00
--- /dev/null
+++ b/data/artifacts/77fc58739877/qwen3-14b.html
@@ -0,0 +1,423 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>Indigo Train Through Dream Country</title>
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      height: 100%;
+      overflow: hidden;
+      background: #000;
+      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+      color: #fff;
+    }
+    canvas {
+      display: block;
+    }
+    #controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      background: rgba(0, 0, 0, 0.5);
+      padding: 10px;
+      border-radius: 8px;
+      z-index: 10;
+    }
+    #controls label {
+      display: block;
+      margin: 5px 0;
+    }
+    #controls input, #controls select, #controls button {
+      margin: 5px 0;
+      padding: 6px;
+      font-size: 14px;
+      width: 150px;
+      border: 1px solid #888;
+      background: #222;
+      color: #fff;
+      border-radius: 4px;
+    }
+    #controls button {
+      cursor: pointer;
+    }
+    #caption {
+      position: absolute;
+      bottom: 10px;
+      left: 10px;
+      background: rgba(0, 0, 0, 0.5);
+      padding: 10px;
+      border-radius: 8px;
+      z-index: 10;
+      max-width: 600px;
+      white-space: pre-wrap;
+    }
+  </style>
+</head>
+<body>
+  <div id="controls">
+    <label>View:</label>
+    <select id="view" aria-label="View">
+      <option value="exterior">Exterior Tracking</option>
+      <option value="carriage">Carriage Cutaway</option>
+      <option value="conductor">Conductor Camera</option>
+    </select>
+    <label>Current Landscape:</label>
+    <select id="landscape" aria-label="Landscape">
+      <option value="desert">Desert Mesas</option>
+      <option value="garden">Rose Garden</option>
+      <option value="flooded">Flooded Moonlit Town</option>
+      <option value="crystal">Crystalline Forest</option>
+      <option value="sunrise">Sunrise Coast</option>
+    </select>
+    <label>Tempo:</label>
+    <input type="range" id="tempo" min="0.5" max="2" step="0.1" value="1" aria-label="Tempo" />
+    <label>Train Speed:</label>
+    <input type="range" id="speed" min="1" max="5" step="0.1" value="2" aria-label="Train Speed" />
+    <label>Crowd Density:</label>
+    <input type="range" id="density" min="0" max="1" step="0.1" value="0.5" aria-label="Crowd Density" />
+    <label>Animal Visibility:</label>
+    <input type="range" id="animal" min="0" max="1" step="0.1" value="1" aria-label="Animal Visibility" />
+    <label>Trail Intensity:</label>
+    <input type="range" id="trail" min="0" max="1" step="0.1" value="0.3" aria-label="Trail Intensity" />
+    <button id="reset">Reset</button>
+    <button id="pause">Pause</button>
+    <button id="paths">Show Movement Paths</button>
+  </div>
+  <div id="caption"></div>
+  <canvas id="canvas"></canvas>
+
+  <script>
+    const canvas = document.getElementById('canvas');
+    const ctx = canvas.getContext('2d');
+    const width = window.innerWidth;
+    const height = window.innerHeight;
+    canvas.width = width;
+    canvas.height = height;
+    let isPaused = false;
+    let showPaths = false;
+
+    const landscapes = [
+      {
+        name: 'Desert Mesas',
+        color: '#331155',
+        light: '#8800cc',
+        caption: 'Amidst golden dunes and jagged rock spires, travelers move in small, reverberating circles, their steps echoing against the sky.'
+      },
+      {
+        name: 'Rose Garden',
+        color: '#553377',
+        light: '#cc00ff',
+        caption: 'Petals drift in the air as dancers rotate in couples, their movements slow and deliberate, like petals unfolding in the breeze.'
+      },
+      {
+        name: 'Flooded Moonlit Town',
+        color: '#224466',
+        light: '#0088cc',
+        caption: 'Reflecting waters mirror the travelers, whose choreography is fluid and mirrored, like ripples in a pond.'
+      },
+      {
+        name: 'Crystalline Forest',
+        color: '#116688',
+        light: '#00ddff',
+        caption: 'Shimmering crystals refract the light as dancers spin in intricate spirals, their motion precise and resonant.'
+      },
+      {
+        name: 'Sunrise Coast',
+        color: '#443322',
+        light: '#ffaa00',
+        caption: 'Golden light rises from the sea as dancers move in synchronized waves, their steps echoing the rhythm of the tide.'
+      }
+    ];
+
+    const animals = [
+      { type: 'bear', color: '#884422', speed: 0.5, offset: 0 },
+      { type: 'jackrabbit', color: '#cc8844', speed: 1.2, offset: 1 },
+      { type: 'raven', color: '#444444', speed: 0.8, offset: 2 },
+      { type: 'fox', color: '#aa5500', speed: 1.5, offset: 3 },
+      { type: 'turtle', color: '#335533', speed: 0.3, offset: 4 }
+    ];
+
+    const travelers = [];
+    const animalPositions = [];
+
+    function initTravelers(count) {
+      travelers.length = 0;
+      for (let i = 0; i < count; i++) {
+        const type = Math.floor(Math.random() * 4);
+        travelers.push({
+          type,
+          x: Math.random() * canvas.width,
+          y: Math.random() * canvas.height,
+          angle: Math.random() * Math.PI * 2,
+          speed: 0.5 + Math.random(),
+          path: []
+        });
+      }
+    }
+
+    function initAnimals(count) {
+      animalPositions.length = 0;
+      for (let i = 0; i < count; i++) {
+        const animal = animals[Math.floor(Math.random() * animals.length)];
+        animalPositions.push({
+          ...animal,
+          x: Math.random() * canvas.width,
+          y: Math.random() * canvas.height,
+          angle: Math.random() * Math.PI * 2,
+          path: []
+        });
+      }
+    }
+
+    function drawCircle(x, y, radius, color, alpha) {
+      ctx.beginPath();
+      ctx.arc(x, y, radius, 0, Math.PI * 2);
+      ctx.fillStyle = `rgba(${color}, ${alpha})`;
+      ctx.fill();
+    }
+
+    function drawLine(x1, y1, x2, y2, color, alpha) {
+      ctx.beginPath();
+      ctx.moveTo(x1, y1);
+      ctx.lineTo(x2, y2);
+      ctx.strokeStyle = `rgba(${color}, ${alpha})`;
+      ctx.stroke();
+    }
+
+    function drawTriangle(x, y, size, color, alpha) {
+      ctx.beginPath();
+      ctx.moveTo(x, y);
+      ctx.lineTo(x + size, y + size);
+      ctx.lineTo(x - size, y + size);
+      ctx.closePath();
+      ctx.fillStyle = `rgba(${color}, ${alpha})`;
+      ctx.fill();
+    }
+
+    function drawRectangle(x, y, width, height, color, alpha) {
+      ctx.fillStyle = `rgba(${color}, ${alpha})`;
+      ctx.fillRect(x, y, width, height);
+    }
+
+    function drawText(x, y, text, color, size, alpha) {
+      ctx.fillStyle = `rgba(${color}, ${alpha})`;
+      ctx.font = `${size}px sans-serif`;
+      ctx.fillText(text, x, y);
+    }
+
+    function updateTravelers(delta) {
+      const density = parseFloat(document.getElementById('density').value);
+      const count = Math.floor(20 * density);
+      if (travelers.length !== count) {
+        initTravelers(count);
+      }
+      for (let t of travelers) {
+        t.path.push({ x: t.x, y: t.y });
+        if (t.path.length > 50) t.path.shift();
+        t.angle += t.speed * delta * 0.1;
+        t.x += Math.cos(t.angle) * t.speed * delta;
+        t.y += Math.sin(t.angle) * t.speed * delta;
+        if (t.x < 0) t.x = canvas.width;
+        if (t.x > canvas.width) t.x = 0;
+        if (t.y < 0) t.y = canvas.height;
+        if (t.y > canvas.height) t.y = 0;
+      }
+    }
+
+    function updateAnimals(delta) {
+      const visibility = parseFloat(document.getElementById('animal').value);
+      const count = Math.floor(10 * visibility);
+      if (animalPositions.length !== count) {
+        initAnimals(count);
+      }
+      for (let a of animalPositions) {
+        a.path.push({ x: a.x, y: a.y });
+        if (a.path.length > 50) a.path.shift();
+        a.angle += a.speed * delta * 0.1;
+        a.x += Math.cos(a.angle) * a.speed * delta;
+        a.y += Math.sin(a.angle) * a.speed * delta;
+        if (a.x < 0) a.x = canvas.width;
+        if (a.x > canvas.width) a.x = 0;
+        if (a.y < 0) a.y = canvas.height;
+        if (a.y > canvas.height) a.y = 0;
+      }
+    }
+
+    function drawTravelers() {
+      for (let t of travelers) {
+        if (t.type === 0) {
+          drawCircle(t.x, t.y, 10, 0xff00ff, 0.6);
+        } else if (t.type === 1) {
+          drawTriangle(t.x, t.y, 10, 0x00ffff, 0.6);
+        } else if (t.type === 2) {
+          drawRectangle(t.x, t.y, 15, 10, 0xffff00, 0.6);
+        } else if (t.type === 3) {
+          drawCircle(t.x, t.y, 8, 0x00cc00, 0.6);
+        }
+      }
+    }
+
+    function drawAnimals() {
+      for (let a of animalPositions) {
+        if (a.type === 'bear') {
+          drawCircle(a.x, a.y, 15, a.color, 0.5);
+        } else if (a.type === 'jackrabbit') {
+          drawTriangle(a.x, a.y, 10, a.color, 0.5);
+        } else if (a.type === 'raven') {
+          drawLine(a.x, a.y, a.x + 20, a.y + 20, a.color, 0.5);
+          drawLine(a.x, a.y, a.x - 20, a.y + 20, a.color, 0.5);
+        } else if (a.type === 'fox') {
+          drawLine(a.x, a.y, a.x + 20, a.y, a.color, 0.5);
+          drawLine(a.x, a.y, a.x - 20, a.y, a.color, 0.5);
+        } else if (a.type === 'turtle') {
+          drawLine(a.x, a.y, a.x + 15, a.y + 10, a.color, 0.5);
+          drawLine(a.x, a.y, a.x - 15, a.y + 10, a.color, 0.5);
+        }
+      }
+    }
+
+    function drawTrails() {
+      const trail = parseFloat(document.getElementById('trail').value);
+      for (let t of travelers) {
+        for (let i = 0; i < t.path.length - 1; i++) {
+          const p = t.path[i];
+          const n = t.path[i + 1];
+          const alpha = trail * (1 - i / t.path.length);
+          drawLine(p.x, p.y, n.x, n.y, 0xffffff, alpha);
+        }
+      }
+      for (let a of animalPositions) {
+        for (let i = 0; i < a.path.length - 1; i++) {
+          const p = a.path[i];
+          const n = a.path[i + 1];
+          const alpha = trail * (1 - i / a.path.length);
+          drawLine(p.x, p.y, n.x, n.y, a.color, alpha);
+        }
+      }
+    }
+
+    function drawLandscape(landscape) {
+      const color = landscape.color;
+      const light = landscape.light;
+      const palette = [color, light, 0x333333, 0x666666, 0x999999, 0x000000];
+      ctx.fillStyle = `rgba(0, 0, 0, 1)`;
+      ctx.fillRect(0, 0, canvas.width, canvas.height);
+      for (let i = 0; i < 100; i++) {
+        const x = Math.random() * canvas.width;
+        const y = Math.random() * canvas.height;
+        const size = Math.random() * 30;
+        const alpha = 0.1;
+        ctx.fillStyle = `rgba(${palette[Math.floor(Math.random() * 6)]}, ${alpha})`;
+        ctx.beginPath();
+        ctx.arc(x, y, size, 0, Math.PI * 2);
+        ctx.fill();
+      }
+    }
+
+    function drawTrain(view) {
+      const w = 100;
+      const h = 50;
+      const x = canvas.width / 2;
+      const y = canvas.height / 2;
+      ctx.fillStyle = '#0000aa';
+      ctx.fillRect(x - w / 2, y - h / 2, w, h);
+      ctx.fillStyle = '#0088ff';
+      ctx.fillRect(x - w / 4, y - h / 4, w / 2, h / 2);
+      ctx.fillStyle = '#0000cc';
+      ctx.fillRect(x - w / 4, y - h / 4, w / 2, h / 2);
+    }
+
+    function drawCutaway() {
+      const x = canvas.width / 2;
+      const y = canvas.height / 2;
+      const w = 100;
+      const h = 50;
+      ctx.fillStyle = '#333333';
+      ctx.fillRect(x - w / 2, y - h / 2, w, h);
+      ctx.fillStyle = '#555555';
+      ctx.fillRect(x - w / 2 + 10, y - h / 2 + 10, w - 20, h - 20);
+      ctx.fillStyle = '#ffffff';
+      ctx.font = '12px sans-serif';
+      ctx.fillText('Cutaway', x - 20, y - 10);
+    }
+
+    function drawConductor() {
+      const x = canvas.width / 2;
+      const y = canvas.height / 2;
+      ctx.fillStyle = '#000000';
+      ctx.fillRect(x - 50, y - 25, 100, 50);
+      ctx.fillStyle = '#ffffff';
+      ctx.font = '12px sans-serif';
+      ctx.fillText('Conductor\'s View', x - 30, y + 10);
+    }
+
+    function drawCaption(landscape) {
+      document.getElementById('caption').textContent = landscape.caption;
+    }
+
+    function animate() {
+      if (isPaused) return;
+      const now = performance.now();
+      const delta = now - (lastTime || now);
+      lastTime = now;
+      const view = document.getElementById('view').value;
+      const landscapeControl = document.getElementById('landscape');
+      const landscape = landscapes[landscapeControl.selectedIndex] || landscapes[0];
+      const tempo = parseFloat(document.getElementById('tempo').value);
+      const speed = parseFloat(document.getElementById('speed').value);
+
+      ctx.clearRect(0, 0, canvas.width, canvas.height);
+      drawLandscape(landscape);
+      drawTrain(view);
+      if (view === 'carriage') drawCutaway();
+      if (view === 'conductor') drawConductor();
+      updateTravelers(delta * tempo * speed);
+      updateAnimals(delta * tempo * speed);
+      drawTravelers();
+      drawAnimals();
+      if (showPaths) drawTrails();
+    }
+
+    let lastTime = 0;
+    let animationId = 0;
+    function loop() {
+      if (!isPaused) {
+        animationId = requestAnimationFrame(loop);
+        animate();
+      }
+    }
+
+    function reset() {
+      isPaused = false;
+      showPaths = false;
+      lastTime = performance.now();
+      initTravelers(20);
+      initAnimals(10);
+      loop();
+    }
+
+    window.addEventListener('resize', () => {
+      canvas.width = window.innerWidth;
+      canvas.height = window.innerHeight;
+    });
+
+    document.getElementById('reset').addEventListener('click', reset);
+    document.getElementById('pause').addEventListener('click', () => {
+      isPaused = !isPaused;
+    });
+    document.getElementById('paths').addEventListener('click', () => {
+      showPaths = !showPaths;
+    });
+
+    document.getElementById('landscape').addEventListener('change', (e) => {
+      const landscape = landscapes[e.target.selectedIndex] || landscapes[0];
+      drawCaption(landscape);
+    });
+
+    reset();
+  </script>
+</body>
+</html>
diff --git a/data/artifacts/77fc58739877/qwen3-14b.png b/data/artifacts/77fc58739877/qwen3-14b.png
index 02c6333..5c3cb5c 100644
Binary files a/data/artifacts/77fc58739877/qwen3-14b.png and b/data/artifacts/77fc58739877/qwen3-14b.png differ
diff --git a/data/artifacts/97621618f31c/claude-code.html b/data/artifacts/97621618f31c/claude-code.html
new file mode 100644
index 0000000..53e6fdf
--- /dev/null
+++ b/data/artifacts/97621618f31c/claude-code.html
@@ -0,0 +1,865 @@
+<!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>Terrapin Lantern Observatory — An Animated Surrealist Exhibition</title>
+<style>
+  :root{
+    --ink:#e9e4f5;
+    --dim:#9a93b8;
+    --panel:rgba(14,12,28,0.78);
+    --panelbrd:rgba(150,140,210,0.22);
+    --accent:#e7b96b;
+    --accent2:#7fb7d8;
+    --focus:#ffd98a;
+    --radius:13px;
+  }
+  *{box-sizing:border-box}
+  html,body{margin:0;height:100%;background:#05040d;color:var(--ink);
+    font-family:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
+    -webkit-font-smoothing:antialiased;overflow:hidden;}
+  #stage{position:fixed;inset:0}
+  canvas{display:block;width:100%;height:100%}
+
+  /* ---------- Header / title ---------- */
+  .titlebar{
+    position:fixed;left:0;right:0;top:0;padding:14px 18px 30px;
+    background:linear-gradient(180deg,rgba(4,3,12,0.72),rgba(4,3,12,0));
+    pointer-events:none;z-index:5;display:flex;flex-direction:column;gap:2px;
+  }
+  .titlebar h1{
+    margin:0;font-size:clamp(15px,2.4vw,25px);font-weight:600;letter-spacing:.14em;
+    text-transform:uppercase;color:var(--ink);text-shadow:0 2px 18px rgba(0,0,0,.8);
+  }
+  .titlebar .sub{font-size:clamp(10px,1.4vw,13px);color:var(--dim);letter-spacing:.06em}
+  .scenechip{
+    display:inline-block;margin-top:6px;font-size:11px;letter-spacing:.18em;text-transform:uppercase;
+    color:var(--accent);opacity:.9;
+  }
+
+  /* ---------- Control dock ---------- */
+  .dock{
+    position:fixed;left:14px;bottom:14px;z-index:10;width:min(340px,calc(100vw - 28px));
+    background:var(--panel);border:1px solid var(--panelbrd);border-radius:var(--radius);
+    backdrop-filter:blur(9px);-webkit-backdrop-filter:blur(9px);
+    padding:12px 13px 13px;box-shadow:0 18px 46px rgba(0,0,0,.55);
+    max-height:calc(100vh - 28px);overflow:auto;transition:transform .35s ease,opacity .35s;
+  }
+  .dock.collapsed{transform:translateY(calc(100% - 46px))}
+  .dock h2{margin:0 0 2px;font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--dim)}
+  .dockhead{display:flex;align-items:center;justify-content:space-between;gap:8px;cursor:pointer}
+  .dockhead .hint{font-size:10px;color:var(--dim);letter-spacing:.05em}
+  .group{margin-top:11px}
+  .glabel{font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--dim);margin-bottom:5px}
+  .row{display:flex;flex-wrap:wrap;gap:6px}
+  button.ctl,.seg button{
+    font-family:inherit;color:var(--ink);background:rgba(40,36,66,0.55);
+    border:1px solid rgba(150,140,210,0.25);border-radius:9px;padding:7px 10px;
+    font-size:12px;letter-spacing:.02em;cursor:pointer;transition:.16s;flex:1 1 auto;
+    min-height:34px;
+  }
+  button.ctl:hover,.seg button:hover{background:rgba(70,62,110,0.7);border-color:rgba(200,190,255,.4)}
+  .seg button.active,button.ctl.active{
+    background:linear-gradient(180deg,rgba(231,185,107,.32),rgba(231,185,107,.14));
+    border-color:var(--accent);color:#fff2d8;
+  }
+  .seg{display:flex;gap:5px}
+  button:focus-visible,input:focus-visible,.dockhead:focus-visible{
+    outline:2px solid var(--focus);outline-offset:2px;border-radius:9px;
+  }
+  .slider{display:flex;align-items:center;gap:9px}
+  .slider input[type=range]{flex:1;accent-color:var(--accent);height:22px}
+  .slider .val{font-size:11px;color:var(--accent2);min-width:64px;text-align:right;letter-spacing:.04em}
+  .toggle-row{display:grid;grid-template-columns:1fr 1fr;gap:6px}
+
+  /* ---------- Info / labels ---------- */
+  .honest{
+    position:fixed;right:14px;bottom:14px;z-index:9;max-width:min(310px,calc(100vw - 28px));
+    background:var(--panel);border:1px solid var(--panelbrd);border-radius:var(--radius);
+    backdrop-filter:blur(9px);-webkit-backdrop-filter:blur(9px);padding:11px 13px;
+    font-size:11.5px;line-height:1.5;color:var(--dim);box-shadow:0 14px 40px rgba(0,0,0,.5);
+    transition:opacity .3s;
+  }
+  .honest b{color:var(--ink);font-weight:600}
+  .honest .close{float:right;cursor:pointer;color:var(--dim);border:none;background:none;font-size:15px;margin:-2px -3px 0 6px}
+  @media (max-width:720px){ .honest{display:none} }
+
+  .toast{
+    position:fixed;left:50%;top:16px;transform:translateX(-50%);z-index:20;
+    background:rgba(10,8,22,.9);border:1px solid var(--panelbrd);border-radius:20px;
+    padding:7px 16px;font-size:12px;letter-spacing:.05em;color:var(--ink);
+    opacity:0;transition:opacity .3s;pointer-events:none;white-space:nowrap;
+  }
+  .toast.show{opacity:1}
+  .kbd{font-size:10px;color:var(--dim);letter-spacing:.05em;margin-top:9px;line-height:1.7}
+  .kbd span{display:inline-block;border:1px solid var(--panelbrd);border-radius:5px;padding:1px 5px;margin-right:2px;color:var(--ink)}
+  a.creditlink{color:var(--accent2);text-decoration:none}
+</style>
+</head>
+<body>
+<div id="stage">
+  <canvas id="c" role="img" aria-label="An open-air observatory riding on the shell of an immense walking cosmic tortoise crossing a midnight desert, with dancers around brass telescopes and animated animal constellations overhead."></canvas>
+</div>
+
+<div class="titlebar">
+  <h1>Terrapin Lantern Observatory</h1>
+  <div class="sub">An animated surrealist exhibition — projection installation, choreographed light</div>
+  <span class="scenechip" id="sceneChip" aria-live="polite">Blue Hour</span>
+</div>
+
+<div class="toast" id="toast" role="status" aria-live="polite"></div>
+
+<div class="honest" id="honest">
+  <button class="close" id="honestClose" aria-label="Dismiss note">×</button>
+  <b>A note on rhythm.</b> Every pulse, sway and orbit here is <b>designed</b>, not derived from live music — no microphone, no audio input, no beat detection. The tortoise's gait is a hand-authored cycle; the dancers keep time to a silent, composed clock.
+</div>
+
+<div class="dock" id="dock" aria-label="Exhibition controls">
+  <div class="dockhead" id="dockHead" role="button" tabindex="0" aria-expanded="true" aria-controls="dockBody">
+    <h2>Controls</h2>
+    <span class="hint" id="dockHint">tap to fold · press H for help</span>
+  </div>
+  <div id="dockBody">
+
+    <div class="group">
+      <div class="glabel">Scene</div>
+      <div class="seg" role="group" aria-label="Scene">
+        <button data-scene="0" class="active">Blue Hour</button>
+        <button data-scene="1">Zenith</button>
+        <button data-scene="2">Rose Dawn</button>
+      </div>
+    </div>
+
+    <div class="group">
+      <div class="glabel">Camera</div>
+      <div class="seg" role="group" aria-label="Camera">
+        <button data-cam="ground" class="active">Ground</button>
+        <button data-cam="shell">Shell</button>
+        <button data-cam="sky">Sky</button>
+      </div>
+    </div>
+
+    <div class="group">
+      <div class="glabel">Visual tempo</div>
+      <div class="slider">
+        <input id="tempo" type="range" min="0" max="200" value="100" aria-label="Visual tempo percent">
+        <span class="val" id="tempoVal">100% · designed</span>
+      </div>
+    </div>
+
+    <div class="group">
+      <div class="glabel">Reveal</div>
+      <div class="toggle-row">
+        <button class="ctl" data-tog="paths">Choreography paths</button>
+        <button class="ctl" data-tog="labels">Constellation labels</button>
+        <button class="ctl" data-tog="people">Isolate people</button>
+        <button class="ctl" data-tog="animals">Isolate animals</button>
+      </div>
+    </div>
+
+    <div class="group">
+      <div class="row">
+        <button class="ctl" id="btnPause">❚❚ Pause</button>
+        <button class="ctl" id="btnReset">↺ Reset</button>
+        <button class="ctl" id="btnStill" aria-pressed="false">Still mode</button>
+      </div>
+    </div>
+
+    <div class="kbd">
+      <span>1</span><span>2</span><span>3</span> scene &nbsp;·&nbsp;
+      <span>G</span><span>S</span><span>K</span> camera &nbsp;·&nbsp;
+      <span>Space</span> pause &nbsp;·&nbsp; <span>P</span> paths &nbsp;·&nbsp;
+      <span>L</span> labels &nbsp;·&nbsp; <span>R</span> reset &nbsp;·&nbsp; <span>M</span> still
+    </div>
+  </div>
+</div>
+
+<script>
+"use strict";
+(function(){
+const cv = document.getElementById('c');
+const ctx = cv.getContext('2d', {alpha:false});
+let W=0,H=0,DPR=1;
+
+function resize(){
+  DPR = Math.min(2, window.devicePixelRatio||1);
+  W = window.innerWidth; H = window.innerHeight;
+  cv.width = Math.floor(W*DPR); cv.height = Math.floor(H*DPR);
+  ctx.setTransform(DPR,0,0,DPR,0,0);
+}
+window.addEventListener('resize', resize); resize();
+
+/* ---------------- State ---------------- */
+const reduceMQ = window.matchMedia('(prefers-reduced-motion: reduce)');
+const state = {
+  scene:0, cam:'ground', tempo:1.0, paused:false, still:false,
+  paths:false, labels:false, isoPeople:false, isoAnimals:false,
+  t:0,           // designed clock (seconds, tempo-scaled)
+  trans:1,       // scene transition 0..1
+  camTrans:1,
+};
+
+/* Palettes per scene — Blue Hour, Zenith, Rose Dawn */
+const SCENES = [
+  { name:"Blue Hour",
+    sky:['#0a1030','#12224a','#243a63'], horizon:'#3a5a86', dune:'#0c1226', dune2:'#141d3a',
+    lamp:'#ffd27a', star:'#cfe0ff', tortoise:'#0e1430', shellA:'#1d2a55', shellB:'#33528a',
+    accent:'#7fb7d8', density:0.72, tint:'rgba(60,90,150,0.10)' },
+  { name:"Zenith",
+    sky:['#03040f','#070b22','#0d1436'], horizon:'#1a2450', dune:'#070a18', dune2:'#0d1228',
+    lamp:'#ffe6a3', star:'#ffffff', tortoise:'#080c22', shellA:'#141f47', shellB:'#2b459a',
+    accent:'#b39bff', density:1.0, tint:'rgba(30,20,70,0.14)' },
+  { name:"Rose Dawn",
+    sky:['#2a1836','#5a2c46','#a8586a'], horizon:'#e08a72', dune:'#241428', dune2:'#3a2036',
+    lamp:'#fff0c8', star:'#ffe4d0', tortoise:'#1b1026', shellA:'#3a2148', shellB:'#8a4f74',
+    accent:'#f0a27f', density:0.52, tint:'rgba(180,90,110,0.10)' },
+];
+function pal(){ return SCENES[state.scene]; }
+
+/* ---------------- Utilities ---------------- */
+const TAU=Math.PI*2;
+function lerp(a,b,t){return a+(b-a)*t;}
+function mix(c1,c2,t){ // hex mix
+  const a=hx(c1),b=hx(c2);
+  return `rgb(${Math.round(lerp(a[0],b[0],t))},${Math.round(lerp(a[1],b[1],t))},${Math.round(lerp(a[2],b[2],t))})`;
+}
+function hx(h){h=h.replace('#','');return [parseInt(h.substr(0,2),16),parseInt(h.substr(2,2),16),parseInt(h.substr(4,2),16)];}
+function ease(t){return t<.5?2*t*t:1-Math.pow(-2*t+2,2)/2;}
+// deterministic pseudo-random
+function rnd(seed){let x=Math.sin(seed*127.1+31.7)*43758.5453;return x-Math.floor(x);}
+
+/* ---------------- Gait model ----------------
+   The tortoise walk is a hand-authored cycle. It produces a gentle world SWAY
+   (rock + heave) that moves lamps, cloth, shadows and figures — but the CAMERA
+   framing is held perfectly steady so the interface never shakes. */
+function gait(t){
+  const p = t*0.42;                       // slow footfall cadence
+  const rock = Math.sin(p)*0.9 + Math.sin(p*2+0.6)*0.28;  // side-to-side roll (deg-ish)
+  const heave = Math.sin(p*2+1.1)*0.5 + Math.sin(p+0.3)*0.9; // vertical rise/fall
+  const surge = Math.sin(p+0.9)*0.4;      // slight forward lurch
+  const step  = (Math.sin(p*2)+1)*0.5;    // 0..1 footfall phase
+  return {rock, heave, surge, step, p};
+}
+
+/* ---------------- STARFIELD / SKY ---------------- */
+const stars=[];
+function buildStars(){
+  stars.length=0;
+  for(let i=0;i<220;i++){
+    stars.push({x:rnd(i*1.3), y:rnd(i*2.7)*0.72, s:0.4+rnd(i*3.1)*1.7, tw:rnd(i*4.9)*TAU, sp:0.6+rnd(i*5.5)});
+  }
+}
+buildStars();
+
+function drawSky(g){
+  const P=pal();
+  const grad=ctx.createLinearGradient(0,0,0,H);
+  grad.addColorStop(0,P.sky[0]); grad.addColorStop(0.45,P.sky[1]); grad.addColorStop(0.8,P.sky[2]);
+  grad.addColorStop(1,P.horizon);
+  ctx.fillStyle=grad; ctx.fillRect(0,0,W,H);
+
+  // subtle nebula floral-geometry wash
+  const cx=W*0.5, cy=H*0.32;
+  for(let k=0;k<3;k++){
+    const rad=Math.min(W,H)*(0.35+k*0.18);
+    const gg=ctx.createRadialGradient(cx+Math.sin(g.p+k)*30,cy,0,cx,cy,rad);
+    gg.addColorStop(0,P.tint); gg.addColorStop(1,'rgba(0,0,0,0)');
+    ctx.fillStyle=gg; ctx.fillRect(0,0,W,H);
+  }
+  // stars
+  const dimFactor = state.isoPeople?0.18:1;
+  for(const s of stars){
+    const x=s.x*W, y=s.y*H;
+    const tw = state.still?0.8:(0.55+0.45*Math.sin(g.p*s.sp+s.tw));
+    ctx.globalAlpha=tw*0.9*dimFactor;
+    ctx.fillStyle=P.star;
+    ctx.beginPath(); ctx.arc(x,y,s.s*0.9,0,TAU); ctx.fill();
+  }
+  ctx.globalAlpha=1;
+}
+
+/* ---------------- CONSTELLATION ANIMALS ----------------
+   Original abstract animal constellations: hares leap, owls orbit the dome,
+   coyotes bow & turn, tiny bears waltz along the rim. Points are stylised. */
+const HARE=[[0,0],[.6,-.5],[1.1,-.4],[1.5,-.9],[1.9,-.5],[1.4,.1],[.9,.35],[.4,.4]];
+const OWL=[[0,0],[.5,-.7],[1,-.7],[1.5,0],[1.2,.6],[.6,.85],[0,.6],[-.3,-.05],[.75,-.25]];
+const COYOTE=[[0,0],[.5,-.35],[1.1,-.2],[1.7,-.55],[2.1,-.1],[1.6,.35],[1.0,.45],[.45,.55],[0,.4]];
+const BEAR=[[0,0],[.45,-.5],[.95,-.55],[1.35,-.15],[1.1,.4],[.55,.55],[.05,.4]];
+
+function makeConstellations(){
+  const arr=[];
+  // hares — leaping across upper sky
+  for(let i=0;i<3;i++){
+    arr.push({type:'hare',pts:HARE,name:'The Leaping Hare',x:0.16+i*0.30,y:0.16+ (i%2)*0.05,
+      scale:0.055,phase:i*2.1,motion:'leap',hue:pal().accent});
+  }
+  // owls orbit the dome
+  for(let i=0;i<2;i++){
+    arr.push({type:'owl',pts:OWL,name:'The Orbiting Owl',x:0.5,y:0.3,scale:0.05,phase:i*Math.PI,motion:'orbit',
+      orbR:0.16+i*0.06,hue:pal().accent});
+  }
+  // coyotes bow and turn near horizon
+  for(let i=0;i<2;i++){
+    arr.push({type:'coyote',pts:COYOTE,name:'The Bowing Coyote',x:0.30+i*0.42,y:0.42,scale:0.05,
+      phase:i*1.7,motion:'bow',hue:pal().accent});
+  }
+  // tiny bears waltz along shell rim (positioned relative later)
+  for(let i=0;i<4;i++){
+    arr.push({type:'bear',pts:BEAR,name:'The Waltzing Bear',rim:i/4,scale:0.028,phase:i*1.3,motion:'waltz',hue:pal().accent});
+  }
+  return arr;
+}
+let CONST=makeConstellations();
+
+function drawConstellation(c,g){
+  const P=pal();
+  let ox,oy,rot=0,sc=Math.min(W,H)*c.scale;
+  const beat=g.p;
+  if(c.motion==='orbit'){
+    const a=beat*0.25+c.phase;
+    ox=W*(0.5+Math.cos(a)*c.orbR); oy=H*(0.30+Math.sin(a)*c.orbR*0.55);
+    rot=Math.sin(a)*0.3;
+  } else if(c.motion==='leap'){
+    const cyc=(beat*0.5+c.phase)%TAU;
+    const hop=Math.abs(Math.sin(cyc))* -0.05;
+    ox=W*(c.x + ((beat*0.02+c.phase)%1)*0.0); oy=H*(c.y+hop);
+    // drift slowly rightward and wrap
+    ox=W*((c.x + (beat*0.012)) %1.1 -0.05);
+    rot=Math.sin(cyc)*0.12;
+    sc*= 1+Math.max(0,Math.sin(cyc))*0.06;
+  } else if(c.motion==='bow'){
+    ox=W*c.x; oy=H*c.y + Math.sin(beat*0.5+c.phase)*6;
+    rot=Math.sin(beat*0.6+c.phase)*0.22 - 0.05;
+  } else if(c.motion==='waltz'){
+    // placed along the shell rim in world; handled in shell draw — skip here
+    return;
+  }
+  drawStarFigure(c,ox,oy,sc,rot,g);
+}
+
+function drawStarFigure(c,ox,oy,sc,rot,g){
+  const P=pal();
+  const tw=state.still?1:(0.6+0.4*Math.sin(g.p*1.4+c.phase));
+  ctx.save();
+  ctx.translate(ox,oy); ctx.rotate(rot);
+  // connecting lines
+  ctx.strokeStyle = `rgba(${hx(P.accent).join(',')},${0.32*tw})`;
+  ctx.lineWidth=1; ctx.beginPath();
+  for(let i=0;i<c.pts.length;i++){
+    const px=c.pts[i][0]*sc, py=c.pts[i][1]*sc;
+    if(i===0)ctx.moveTo(px,py); else ctx.lineTo(px,py);
+  }
+  ctx.stroke();
+  // stars
+  for(let i=0;i<c.pts.length;i++){
+    const px=c.pts[i][0]*sc, py=c.pts[i][1]*sc;
+    const r=(i===0?2.6:1.7)*(0.8+0.3*tw);
+    ctx.fillStyle=P.star;
+    ctx.shadowColor=P.accent; ctx.shadowBlur=8*tw;
+    ctx.beginPath(); ctx.arc(px,py,r,0,TAU); ctx.fill();
+  }
+  ctx.shadowBlur=0;
+  if(state.labels){
+    ctx.rotate(-rot);
+    ctx.fillStyle=`rgba(${hx(P.accent).join(',')},0.9)`;
+    ctx.font='11px Georgia'; ctx.textAlign='center';
+    ctx.fillText(c.name, 0, sc*0.9+16);
+  }
+  ctx.restore();
+}
+
+/* ---------------- DUNES / DESERT ---------------- */
+function drawDunes(g){
+  const P=pal();
+  const baseY=H*0.72;
+  ctx.fillStyle=P.dune2;
+  ctx.beginPath(); ctx.moveTo(0,H);
+  for(let x=0;x<=W;x+=24){
+    const y=baseY+Math.sin(x*0.006 + 1.2)*18 + Math.sin(x*0.0016)*40 + 30;
+    ctx.lineTo(x,y);
+  }
+  ctx.lineTo(W,H); ctx.closePath(); ctx.fill();
+
+  ctx.fillStyle=P.dune;
+  ctx.beginPath(); ctx.moveTo(0,H);
+  const drift = g.p*6; // sand slowly passing as tortoise walks
+  for(let x=0;x<=W;x+=20){
+    const y=baseY+70+Math.sin((x+drift)*0.008)*24 + Math.sin((x-drift)*0.003)*46;
+    ctx.lineTo(x,y);
+  }
+  ctx.lineTo(W,H); ctx.closePath(); ctx.fill();
+}
+
+/* ---------------- HUMAN FIGURES ----------------
+   Four choreographies: nested spinning circles, pairs exchanging along curves,
+   children tracing star shapes with lanterns, robed mirrored gestures. */
+function stickFigure(x,y,scale,pose,color,lantern,g,phase){
+  ctx.save(); ctx.translate(x,y); ctx.scale(scale,scale);
+  const sway=Math.sin(g.p*1.5+phase)*0.08;
+  ctx.rotate(sway*0.4);
+  ctx.strokeStyle=color; ctx.lineWidth=2.4; ctx.lineCap='round';
+  const armSwing=Math.sin(g.p*2+phase)*(pose==='mirror'?0.9:0.6);
+  const legSwing=Math.sin(g.p*2+phase+Math.PI)*0.5;
+  // body
+  ctx.beginPath();
+  ctx.moveTo(0,-14); ctx.lineTo(0,4); // torso
+  ctx.stroke();
+  // head
+  ctx.beginPath(); ctx.arc(0,-19,4.2,0,TAU); ctx.fillStyle=color; ctx.fill();
+  // robe (for mirror figures) — cloth reacts to gait heave
+  if(pose==='robe'||pose==='mirror'){
+    const flare=6+g.heave*1.2;
+    ctx.beginPath(); ctx.moveTo(0,-12);
+    ctx.quadraticCurveTo(-flare-legSwing*6,10,-flare*1.4,20+g.heave);
+    ctx.lineTo(flare*1.4,20-g.heave);
+    ctx.quadraticCurveTo(flare+legSwing*6,10,0,-12);
+    ctx.closePath();
+    ctx.globalAlpha=0.28; ctx.fillStyle=color; ctx.fill(); ctx.globalAlpha=1;
+  } else {
+    // legs
+    ctx.beginPath();
+    ctx.moveTo(0,4); ctx.lineTo(-4-legSwing*6,20);
+    ctx.moveTo(0,4); ctx.lineTo(4+legSwing*6,20);
+    ctx.stroke();
+  }
+  // arms
+  ctx.beginPath();
+  ctx.moveTo(0,-9); ctx.lineTo(-9-armSwing*7,-4+armSwing*4);
+  ctx.moveTo(0,-9); ctx.lineTo(9+armSwing*7,-4-armSwing*4);
+  ctx.stroke();
+  // lantern in hand
+  if(lantern){
+    const lx=9+armSwing*7, ly=-4-armSwing*4;
+    ctx.fillStyle=pal().lamp; ctx.shadowColor=pal().lamp; ctx.shadowBlur=14;
+    ctx.beginPath(); ctx.arc(lx,ly,3.1,0,TAU); ctx.fill(); ctx.shadowBlur=0;
+  }
+  ctx.restore();
+}
+
+function drawDancers(deck, g){
+  const P=pal();
+  const dens = pal().density * (reduceMQ.matches?0.7:1);
+  const cx=deck.cx, topY=deck.topY, dscale=deck.scale;
+
+  // ---- helper to draw a choreography path (revealed) ----
+  function path(fn){
+    if(!state.paths)return;
+    ctx.save();
+    ctx.strokeStyle=`rgba(${hx(P.accent).join(',')},0.5)`;
+    ctx.setLineDash([4,5]); ctx.lineWidth=1.2;
+    ctx.beginPath(); fn(); ctx.stroke(); ctx.setLineDash([]); ctx.restore();
+  }
+
+  // GROUP 1 — nested spinning circles (three rings)
+  const g1x=cx-140*dscale, g1y=topY-4;
+  for(let ring=0;ring<3;ring++){
+    const R=(18+ring*15)*dscale;
+    const n=4+ring*2;
+    path(()=>{ctx.ellipse(g1x,g1y,R,R*0.42,0,0,TAU);});
+    for(let i=0;i<n;i++){
+      const a=g.p*(ring%2?-0.5:0.6)+ (i/n)*TAU;
+      const px=g1x+Math.cos(a)*R, py=g1y+Math.sin(a)*R*0.42;
+      stickFigure(px,py-6,0.7*dscale,'stand',mix(P.star,P.accent,0.4),false,g,i+ring*3);
+    }
+  }
+
+  // GROUP 2 — pairs exchange positions along curved paths
+  const g2x=cx+120*dscale, g2y=topY-2;
+  for(let pr=0;pr<3;pr++){
+    const off=pr*26*dscale;
+    const swap=(Math.sin(g.p*0.5+pr)+1)*0.5; // 0..1 exchange
+    path(()=>{
+      ctx.moveTo(g2x-40*dscale, g2y-off);
+      ctx.quadraticCurveTo(g2x, g2y-off-30*dscale, g2x+40*dscale, g2y-off);
+    });
+    const ax=lerp(g2x-40*dscale, g2x+40*dscale, swap);
+    const ay=g2y-off - Math.sin(swap*Math.PI)*26*dscale;
+    const bx=lerp(g2x+40*dscale, g2x-40*dscale, swap);
+    const by=g2y-off - Math.sin((1-swap)*Math.PI)*26*dscale;
+    stickFigure(ax,ay,0.72*dscale,'stand',mix(P.star,P.accent2,0.5),false,g,pr*2);
+    stickFigure(bx,by,0.72*dscale,'stand',mix(P.star,P.accent2,0.5),false,g,pr*2+1);
+  }
+
+  // GROUP 3 — children tracing star shapes with lanterns
+  const g3x=cx, g3y=topY+30*dscale;
+  const starPath=[];
+  for(let i=0;i<=5;i++){const a=-Math.PI/2 + i*(TAU*2/5); starPath.push([Math.cos(a),Math.sin(a)]);}
+  const R3=30*dscale;
+  path(()=>{ for(let i=0;i<starPath.length;i++){const p=starPath[i]; const x=g3x+p[0]*R3,y=g3y+p[1]*R3*0.5; i?ctx.lineTo(x,y):ctx.moveTo(x,y);} });
+  for(let ch=0;ch<3;ch++){
+    const prog=((g.p*0.28+ch/3)%1)*5;   // travels the 5-point star
+    const seg=Math.floor(prog), fr=prog-seg;
+    const a=starPath[seg], b=starPath[(seg+1)%5];
+    const px=g3x+lerp(a[0],b[0],fr)*R3;
+    const py=g3y+lerp(a[1],b[1],fr)*R3*0.5;
+    stickFigure(px,py,0.5*dscale,'stand',P.star,true,g,ch*3+1);
+  }
+
+  // GROUP 4 — robed figures, slow mirrored gestures
+  const g4x=cx, g4y=topY-40*dscale;
+  for(let m=0;m<2;m++){
+    const dir=m===0?-1:1;
+    const px=g4x+dir*46*dscale;
+    stickFigure(px,g4y,0.9*dscale,'mirror',mix(P.accent,P.star,0.2),false,
+      {p:g.p, heave:g.heave*dir}, m*Math.PI); // mirrored phase
+  }
+}
+
+/* ---------------- BRASS TELESCOPES ---------------- */
+function drawTelescope(x,y,s,g,phase){
+  const P=pal();
+  ctx.save(); ctx.translate(x,y);
+  // tripod
+  ctx.strokeStyle=mix('#8a6a34','#d8a94e',0.4); ctx.lineWidth=2.2*s; ctx.lineCap='round';
+  ctx.beginPath();
+  ctx.moveTo(0,0); ctx.lineTo(-7*s,16*s);
+  ctx.moveTo(0,0); ctx.lineTo(7*s,16*s);
+  ctx.moveTo(0,0); ctx.lineTo(0,17*s);
+  ctx.stroke();
+  // barrel — tilts gently to track the sky
+  const tilt=-0.9 + Math.sin(g.p*0.4+phase)*0.14;
+  ctx.rotate(tilt);
+  const grad=ctx.createLinearGradient(0,-2*s,26*s,-2*s);
+  grad.addColorStop(0,'#6a4e22'); grad.addColorStop(0.5,'#e9c濁'.replace('濁','b76')); grad.addColorStop(1,'#8a6a34');
+  ctx.fillStyle=grad;
+  ctx.beginPath(); ctx.roundRect? ctx.roundRect(-2*s,-3*s,28*s,6*s,3*s):ctx.rect(-2*s,-3*s,28*s,6*s);
+  ctx.fill();
+  ctx.fillStyle=mix('#3a2a12','#d8a94e',0.5);
+  ctx.beginPath(); ctx.arc(28*s,0,4.2*s,0,TAU); ctx.fill();
+  ctx.restore();
+}
+
+/* ---------------- THE TORTOISE + SHELL OBSERVATORY ---------------- */
+function drawTortoise(g){
+  const P=pal();
+  // camera-independent world sway from gait (interface stays steady)
+  const baseX=W*0.5, baseY=H*0.66;
+  const sway = state.still?0:g.rock;
+  const heave= state.still?0:g.heave;
+
+  // --- camera framing (steady): choose focus & zoom, no shake ---
+  let camY=0, zoom=1;
+  if(state.cam==='ground'){ camY=0; zoom=1.0; }
+  else if(state.cam==='shell'){ camY=-H*0.10; zoom=1.32; }
+  else if(state.cam==='sky'){ camY=H*0.12; zoom=0.86; }
+
+  ctx.save();
+  ctx.translate(W*0.5, H*0.5);
+  ctx.scale(zoom,zoom);
+  ctx.translate(-W*0.5, -H*0.5 + camY);
+
+  const originX=baseX, originY=baseY+heave*1.6;
+
+  // ---- shadow on sand (reacts to heave) ----
+  ctx.save();
+  ctx.translate(originX, baseY+120);
+  ctx.scale(1, 0.3);
+  const shg=ctx.createRadialGradient(0,0,10,0,0,300+heave*8);
+  shg.addColorStop(0,'rgba(0,0,0,0.45)'); shg.addColorStop(1,'rgba(0,0,0,0)');
+  ctx.fillStyle=shg; ctx.beginPath(); ctx.arc(0,0,300,0,TAU); ctx.fill();
+  ctx.restore();
+
+  ctx.save();
+  ctx.translate(originX, originY);
+  ctx.rotate(sway*0.012);      // whole creature rolls subtly with the gait
+
+  // ---- LEGS (four columns, walk cycle) ----
+  const legColor=mix(P.tortoise,'#000',0.2);
+  for(let i=0;i<4;i++){
+    const lx=(i<2?-1:1)*(150 - (i%2)*60);
+    const lift = Math.max(0, Math.sin(g.p*2 + (i%2?Math.PI:0) + (i<2?0:Math.PI)))*10;
+    ctx.fillStyle=legColor;
+    ctx.save(); ctx.translate(lx, 60-lift*0.5);
+    ctx.beginPath();
+    (ctx.roundRect? ctx.roundRect(-26,0,52,80-lift,16): ctx.rect(-26,0,52,80-lift));
+    ctx.fill();
+    // toe glimmer
+    ctx.fillStyle=mix(P.shellA,P.star,0.15);
+    ctx.beginPath(); ctx.ellipse(0,80-lift,26,10,0,0,Math.PI); ctx.fill();
+    ctx.restore();
+  }
+
+  // ---- HEAD & NECK (front-left), slow bob ----
+  ctx.save();
+  const neckB=Math.sin(g.p*0.6)*8;
+  ctx.translate(-230, -10+neckB*0.3);
+  ctx.fillStyle=mix(P.tortoise,P.shellA,0.3);
+  ctx.beginPath();
+  ctx.moveTo(0,20); ctx.quadraticCurveTo(-70,0,-120,-30-neckB);
+  ctx.quadraticCurveTo(-140,-46-neckB,-150,-40-neckB);
+  ctx.quadraticCurveTo(-120,10,-40,44); ctx.closePath(); ctx.fill();
+  // eye — a small star
+  ctx.fillStyle=P.star; ctx.shadowColor=P.accent; ctx.shadowBlur=10;
+  ctx.beginPath(); ctx.arc(-132,-30-neckB,3.2,0,TAU); ctx.fill(); ctx.shadowBlur=0;
+  ctx.restore();
+
+  // ---- SHELL BODY (dome carrying the observatory) ----
+  const shellGrad=ctx.createLinearGradient(0,-160,0,80);
+  shellGrad.addColorStop(0,P.shellB); shellGrad.addColorStop(0.6,P.shellA); shellGrad.addColorStop(1,P.tortoise);
+  ctx.fillStyle=shellGrad;
+  ctx.beginPath();
+  ctx.moveTo(-260,60);
+  ctx.bezierCurveTo(-260,-150,260,-150,260,60);
+  ctx.bezierCurveTo(160,90,-160,90,-260,60);
+  ctx.closePath(); ctx.fill();
+
+  // shell rim highlight
+  ctx.strokeStyle=`rgba(${hx(P.star).join(',')},0.25)`; ctx.lineWidth=2;
+  ctx.beginPath(); ctx.moveTo(-256,58);
+  ctx.bezierCurveTo(-256,-146,256,-146,256,58); ctx.stroke();
+
+  // ---- Shell plates: abstract floral geometry (original motif) ----
+  ctx.save();
+  ctx.beginPath();
+  ctx.moveTo(-260,60);
+  ctx.bezierCurveTo(-260,-150,260,-150,260,60);
+  ctx.bezierCurveTo(160,90,-160,90,-260,60); ctx.closePath();
+  ctx.clip();
+  ctx.strokeStyle=`rgba(${hx(P.star).join(',')},0.16)`; ctx.lineWidth=1.4;
+  // radiating petal cells
+  const cxs=0, cys=40;
+  for(let ring=1;ring<=4;ring++){
+    const rr=ring*46;
+    const petals=6+ring*2;
+    for(let i=0;i<petals;i++){
+      const a=(i/petals)*TAU + ring*0.2 + (state.still?0:g.p*0.02);
+      const x=cxs+Math.cos(a)*rr, y=cys - Math.abs(Math.sin(a))*rr*0.9;
+      ctx.beginPath();
+      ctx.ellipse(x,y,20,11,a,0,TAU); ctx.stroke();
+    }
+  }
+  // faint constellation studs glimmering in the plates
+  for(let i=0;i<14;i++){
+    const a=rnd(i)*TAU, r=40+rnd(i*2)*180;
+    const x=Math.cos(a)*r, y=40-Math.abs(Math.sin(a))*r*0.8;
+    ctx.fillStyle=`rgba(${hx(P.lamp).join(',')},${state.still?0.5:0.3+0.3*Math.sin(g.p*1.3+i)})`;
+    ctx.beginPath(); ctx.arc(x,y,1.6,0,TAU); ctx.fill();
+  }
+  ctx.restore();
+
+  /* ======== OBSERVATORY DECK on the shell ======== */
+  const deckTopY = -120;
+  // deck platform
+  ctx.fillStyle=mix(P.tortoise,P.shellA,0.6);
+  ctx.beginPath(); ctx.ellipse(0,deckTopY+8,215,34,0,0,TAU); ctx.fill();
+  ctx.fillStyle=`rgba(${hx(P.star).join(',')},0.08)`;
+  ctx.beginPath(); ctx.ellipse(0,deckTopY+4,205,28,0,0,Math.PI,true); ctx.fill();
+
+  // ---- Hanging lamps (pendulums that swing with the gait) ----
+  if(!state.isoAnimals){
+    const lampCount=5;
+    for(let i=0;i<lampCount;i++){
+      const lx=-150+i*75;
+      // pendulum: gait rock drives the swing; each lamp slightly phase-shifted
+      const swing = state.still?0.02: (Math.sin(g.p*1.1 - i*0.5)*0.16 + g.rock*0.02);
+      const armLen=46;
+      const ax=lx, ay=deckTopY-70;
+      const bx=ax+Math.sin(swing)*armLen, by=ay+Math.cos(swing)*armLen;
+      ctx.strokeStyle=`rgba(${hx(P.star).join(',')},0.4)`; ctx.lineWidth=1.2;
+      ctx.beginPath(); ctx.moveTo(ax,ay); ctx.lineTo(bx,by); ctx.stroke();
+      // lamp glow
+      const fl=state.still?1:(0.75+0.25*Math.sin(g.p*3+i));
+      const lg=ctx.createRadialGradient(bx,by,0,bx,by,30);
+      lg.addColorStop(0,`rgba(${hx(P.lamp).join(',')},${0.9*fl})`);
+      lg.addColorStop(1,'rgba(0,0,0,0)');
+      ctx.fillStyle=lg; ctx.beginPath(); ctx.arc(bx,by,30,0,TAU); ctx.fill();
+      ctx.fillStyle=P.lamp; ctx.beginPath(); ctx.arc(bx,by,4.5,0,TAU); ctx.fill();
+    }
+  }
+
+  // ---- Cloth banners strung over the deck (react to gait heave) ----
+  if(!state.isoAnimals){
+    ctx.fillStyle=`rgba(${hx(P.accent).join(',')},0.20)`;
+    for(let b=0;b<2;b++){
+      const y0=deckTopY-30-b*8;
+      ctx.beginPath();
+      ctx.moveTo(-200+b*10,y0);
+      for(let x=-200+b*10;x<=200-b*10;x+=16){
+        const droop=Math.sin((x)*0.02 + g.p*(state.still?0:2)+b)* (6+heave*0.6) + 16;
+        ctx.lineTo(x,y0+droop);
+      }
+      ctx.lineTo(200-b*10,y0);
+      ctx.closePath(); ctx.fill();
+    }
+  }
+
+  // ---- Telescopes on deck ----
+  if(!state.isoAnimals){
+    drawTelescope(-120,deckTopY+6,1.0,g,0);
+    drawTelescope(0,deckTopY-2,1.15,g,1.4);
+    drawTelescope(120,deckTopY+6,1.0,g,2.6);
+  }
+
+  // ---- Human dancers ----
+  if(!state.isoAnimals){
+    drawDancers({cx:0, topY:deckTopY-6, scale:1.0}, g);
+  }
+
+  // ---- tiny bears waltzing along the shell rim ----
+  if(!state.isoPeople){
+    const rimN=CONST.filter(c=>c.type==='bear');
+    let bi=0;
+    for(const c of CONST){
+      if(c.type!=='bear')continue;
+      const a = -Math.PI + ( (c.rim + (state.still?0:g.p*0.05)) %1 )*Math.PI; // travel around dome top
+      const rr=250;
+      const bx=Math.cos(a)*rr, by=-140 + Math.sin(a)*-90 + Math.sin(g.p*3+c.phase)*4;
+      drawStarFigure(c,bx,by,Math.min(W,H)*c.scale,Math.sin(g.p*2+c.phase)*0.3,g);
+      bi++;
+    }
+  }
+
+  ctx.restore(); // creature
+  ctx.restore(); // camera
+}
+
+/* ---------------- COMPOSITE FRAME ---------------- */
+let last=performance.now();
+function frame(now){
+  const dtReal=Math.min(0.05,(now-last)/1000); last=now;
+  if(!state.paused && !state.still){
+    state.t += dtReal*state.tempo;
+  }
+  if(state.trans<1) state.trans=Math.min(1,state.trans+dtReal*2.2);
+
+  const g = gait(state.t);
+
+  drawSky(g);
+
+  // animal constellations behind/around dome (unless isolating people)
+  if(!state.isoPeople){
+    for(const c of CONST){ if(c.type!=='bear') drawConstellation(c,g); }
+  }
+
+  drawDunes(g);
+  drawTortoise(g);
+
+  // scene tint film for projection feel
+  const P=pal();
+  ctx.fillStyle=P.tint; ctx.fillRect(0,0,W,H);
+  // vignette
+  const vg=ctx.createRadialGradient(W/2,H/2,Math.min(W,H)*0.3,W/2,H/2,Math.max(W,H)*0.75);
+  vg.addColorStop(0,'rgba(0,0,0,0)'); vg.addColorStop(1,'rgba(0,0,0,0.5)');
+  ctx.fillStyle=vg; ctx.fillRect(0,0,W,H);
+
+  if(state.still){
+    ctx.fillStyle='rgba(0,0,0,0.35)';
+    ctx.fillRect(W-0,0,0,0);
+  }
+
+  requestAnimationFrame(frame);
+}
+requestAnimationFrame(frame);
+
+/* ---------------- CONTROLS WIRING ---------------- */
+const toastEl=document.getElementById('toast');
+let toastTimer;
+function toast(msg){
+  toastEl.textContent=msg; toastEl.classList.add('show');
+  clearTimeout(toastTimer); toastTimer=setTimeout(()=>toastEl.classList.remove('show'),1500);
+}
+
+function setScene(i){
+  state.scene=i; state.trans=0;
+  CONST=makeConstellations();
+  document.querySelectorAll('[data-scene]').forEach(b=>b.classList.toggle('active',+b.dataset.scene===i));
+  const chip=document.getElementById('sceneChip'); chip.textContent=SCENES[i].name;
+  // adjust dancer density feel via density already in palette
+  toast(SCENES[i].name + (i===0?' — soft & sparse':i===1?' — dense & radiant':' — warm & unhurried'));
+}
+function setCam(c){
+  state.cam=c;
+  document.querySelectorAll('[data-cam]').forEach(b=>b.classList.toggle('active',b.dataset.cam===c));
+  toast(c.charAt(0).toUpperCase()+c.slice(1)+' camera');
+}
+function setTog(key){
+  const map={paths:'paths',labels:'labels',people:'isoPeople',animals:'isoAnimals'};
+  const s=map[key]; state[s]=!state[s];
+  // people/animals isolate are mutually informative but allowed together
+  document.querySelector(`[data-tog="${key}"]`).classList.toggle('active',state[s]);
+  const names={paths:'Choreography paths',labels:'Constellation labels',people:'Isolate people',animals:'Isolate animals'};
+  toast(names[key]+': '+(state[s]?'on':'off'));
+}
+
+document.querySelectorAll('[data-scene]').forEach(b=>b.addEventListener('click',()=>setScene(+b.dataset.scene)));
+document.querySelectorAll('[data-cam]').forEach(b=>b.addEventListener('click',()=>setCam(b.dataset.cam)));
+document.querySelectorAll('[data-tog]').forEach(b=>b.addEventListener('click',()=>setTog(b.dataset.tog)));
+
+const tempo=document.getElementById('tempo'), tempoVal=document.getElementById('tempoVal');
+tempo.addEventListener('input',()=>{
+  state.tempo=+tempo.value/100;
+  tempoVal.textContent=`${tempo.value}% · designed`;
+});
+
+const btnPause=document.getElementById('btnPause');
+function togglePause(){
+  state.paused=!state.paused;
+  btnPause.textContent=state.paused?'▶ Resume':'❚❚ Pause';
+  btnPause.classList.toggle('active',state.paused);
+  toast(state.paused?'Paused':'Playing');
+}
+btnPause.addEventListener('click',togglePause);
+
+const btnStill=document.getElementById('btnStill');
+function toggleStill(force){
+  state.still = (force!==undefined)?force:!state.still;
+  btnStill.classList.toggle('active',state.still);
+  btnStill.setAttribute('aria-pressed',state.still);
+  btnStill.textContent=state.still?'Still mode ✓':'Still mode';
+  toast(state.still?'Composed still mode — motion held':'Motion resumed');
+}
+btnStill.addEventListener('click',()=>toggleStill());
+
+document.getElementById('btnReset').addEventListener('click',()=>{
+  state.t=0; state.tempo=1; tempo.value=100; tempoVal.textContent='100% · designed';
+  state.paused=false; btnPause.textContent='❚❚ Pause'; btnPause.classList.remove('active');
+  state.paths=state.labels=state.isoPeople=state.isoAnimals=false;
+  document.querySelectorAll('[data-tog]').forEach(b=>b.classList.remove('active'));
+  setScene(0); setCam('ground'); toggleStill(reduceMQ.matches);
+  toast('Reset');
+});
+
+/* dock collapse */
+const dock=document.getElementById('dock'), dockHead=document.getElementById('dockHead');
+function toggleDock(){
+  const c=dock.classList.toggle('collapsed');
+  dockHead.setAttribute('aria-expanded',!c);
+}
+dockHead.addEventListener('click',toggleDock);
+dockHead.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();toggleDock();}});
+
+document.getElementById('honestClose').addEventListener('click',()=>{
+  document.getElementById('honest').style.display='none';
+});
+
+/* keyboard */
+window.addEventListener('keydown',e=>{
+  if(e.target.tagName==='INPUT')return;
+  switch(e.key.toLowerCase()){
+    case '1':setScene(0);break;
+    case '2':setScene(1);break;
+    case '3':setScene(2);break;
+    case 'g':setCam('ground');break;
+    case 's':setCam('shell');break;
+    case 'k':setCam('sky');break;
+    case 'p':setTog('paths');break;
+    case 'l':setTog('labels');break;
+    case 'o':setTog('people');break;
+    case 'a':setTog('animals');break;
+    case 'r':document.getElementById('btnReset').click();break;
+    case 'm':toggleStill();break;
+    case 'h':toggleDock();toast('Controls — fold/unfold');break;
+    case ' ':e.preventDefault();togglePause();break;
+  }
+});
+
+/* prefers-reduced-motion → boot into composed still mode */
+if(reduceMQ.matches){ toggleStill(true); }
+reduceMQ.addEventListener?.('change',e=>{ if(e.matches) toggleStill(true); });
+
+setScene(0);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/97621618f31c/qwen25-7b.html b/data/artifacts/97621618f31c/qwen25-7b.html
new file mode 100644
index 0000000..25ef066
--- /dev/null
+++ b/data/artifacts/97621618f31c/qwen25-7b.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Terrapin Lantern Observatory</title>
+    <style>
+        body, html {
+            margin: 0;
+            padding: 0;
+            width: 100%;
+            height: 100%;
+            overflow: hidden;
+            background-color: #000;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            font-family: Arial, sans-serif;
+        }
+        canvas {
+            position: absolute;
+            width: 100%;
+            height: 100%;
+            pointer-events: none;
+        }
+        .controls {
+            position: fixed;
+            bottom: 20px;
+            left: 50%;
+            transform: translateX(-50%);
+            display: flex;
+            gap: 16px;
+            z-index: 1000;
+        }
+        button, label {
+            cursor: pointer;
+            padding: 8px 12px;
+            font-size: 14px;
+            border-radius: 4px;
+            background-color: rgba(255, 255, 255, 0.3);
+            color: white;
+        }
+        button:hover {
+            background-color: rgba(255, 255, 255, 0.5);
+        }
+    </style>
+</head>
+<body>
+    <canvas id="canvas"></canvas>
+    <div class="controls">
+        <button onclick="changeCamera('ground')">Ground Cam</button>
+        <button onclick="changeCamera('shell')">Shell Cam</button>
+        <button onclick="changeCamera('sky')">Sky Cam</button>
+        <label><input type="checkbox" onchange="toggleConstellationLabels()">Toggle Constellations</label>
+        <button onclick="setTempo(1)">Normal</button>
+        <button onclick="setTempo(2)">Faster</button>
+        <button onclick="setTempo(0.5)">Slower</button>
+        <button onclick="pause()">Pause</button>
+        <button onclick="reset()">Reset</button>
+        <label><input type="checkbox" onchange="reduceMotion()">Reduce Motion</label>
+    </div>
+
+    <script>
+        const canvas = document.getElementById('canvas');
+        let ctx = null;
+        let framesPerSecond = 60;
+        let animationFrameId = null;
+
+        function setupCanvas() {
+            if (ctx) return;
+            canvas.width = window.innerWidth;
+            canvas.height = window.innerHeight;
+            ctx = canvas.getContext('2d');
+            init();
+        }
+
+        function init() {
+            // Initialize your scene here
+        }
+
+        function animate() {
+            requestAnimationFrame(() => {
+                drawFrame();
+                animationFrameId = requestAnimationFrame(animate);
+            });
+        }
+
+        function drawFrame() {
+            clearCanvas();
+            setupScene();
+            updateScene();
+            drawScene();
+        }
+
+        function clearCanvas() {
+            ctx.fillStyle = 'black';
+            ctx.fillRect(0, 0, canvas.width, canvas.height);
+        }
+
+        function setupScene() {
+            // Set up your scene here
+        }
+
+        function updateScene() {
+            // Update your scene elements here based on time and motion
+        }
+
+        function drawScene() {
+            // Draw your scene elements here
+        }
+
+        function changeCamera(cameraType) {
+            console.log(`Switching to ${cameraType} camera`);
+        }
+
+        function setTempo(tempo) {
+            framesPerSecond = tempo * 60;
+            if (animationFrameId) cancelAnimationFrame(animationFrameId);
+            animationFrameId = requestAnimationFrame(animate);
+        }
+
+        function pause() {
+            if (animationFrameId) cancelAnimationFrame(animationFrameId);
+            console.log('Paused');
+        }
+
+        function reset() {
+            // Reset your scene here
+            init();
+        }
+
+        function toggleConstellationLabels() {
+            // Toggle constellation labels here
+        }
+
+        function reduceMotion() {
+            // Handle reduced motion preferences here
+        }
+
+        setupCanvas();
+        animate();
+
+        window.addEventListener('resize', setupCanvas);
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/97621618f31c/qwen3-14b.html b/data/artifacts/97621618f31c/qwen3-14b.html
new file mode 100644
index 0000000..8be0c4c
--- /dev/null
+++ b/data/artifacts/97621618f31c/qwen3-14b.html
@@ -0,0 +1,511 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>Terrapin Lantern Observatory</title>
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      background: black;
+      color: white;
+      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+      user-select: none;
+    }
+    #controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      background: rgba(255, 255, 255, 0.1);
+      padding: 10px;
+      border-radius: 8px;
+      z-index: 1000;
+    }
+    #controls button {
+      margin: 0 5px;
+      padding: 5px 10px;
+      background: rgba(255, 255, 255, 0.2);
+      border: none;
+      color: white;
+      cursor: pointer;
+      border-radius: 4px;
+    }
+    #controls button:hover {
+      background: rgba(255, 255, 255, 0.3);
+    }
+    canvas {
+      display: block;
+      width: 100vw;
+      height: 100vh;
+    }
+    .label {
+      position: absolute;
+      font-size: 12px;
+      color: #aaa;
+      pointer-events: none;
+    }
+  </style>
+</head>
+<body>
+  <div id="controls">
+    <button onclick="setScene('bluehour')">Blue Hour</button>
+    <button onclick="setScene('zenith')">Zenith</button>
+    <button onclick="setScene('rosedawn')">Rose Dawn</button>
+    <button onclick="toggleCamera('ground')">Ground</button>
+    <button onclick="toggleCamera('shell')">Shell</button>
+    <button onclick="toggleCamera('sky')">Sky</button>
+    <button onclick="toggleTempo()">Toggle Tempo</button>
+    <button onclick="togglePaths()">Toggle Paths</button>
+    <button onclick="togglePeople()">Toggle People</button>
+    <button onclick="toggleAnimals()">Toggle Animals</button>
+    <button onclick="toggleLabels()">Toggle Labels</button>
+    <button onclick="pause()">Pause</button>
+    <button onclick="reset()">Reset</button>
+    <button onclick="setStill()">Still Mode</button>
+  </div>
+  <canvas id="canvas"></canvas>
+  <div id="credits" style="position: absolute; bottom: 10px; left: 10px; font-size: 12px; color: #aaa;">
+    All rhythm is designed. No live music used.
+  </div>
+  <script>
+    const canvas = document.getElementById('canvas');
+    const ctx = canvas.getContext('2d');
+    let width = canvas.width = window.innerWidth;
+    let height = canvas.height = window.innerHeight;
+    let scene = 'bluehour';
+    let activeCamera = 'ground';
+    let tempo = 1;
+    let showPaths = false;
+    let showPeople = true;
+    let showAnimals = true;
+    let showLabels = true;
+    let paused = false;
+    let still = false;
+
+    const people = [];
+    const animals = [];
+    const lanterns = [];
+    const constellations = [];
+
+    const tortoise = {
+      x: width / 2,
+      y: height / 2,
+      size: 200,
+      angle: 0,
+      speed: 0.005,
+      step: 0,
+      stepSize: 1.5,
+      sway: 0,
+      swaySpeed: 0.01,
+      swayMax: 0.3
+    };
+
+    const starfield = {
+      stars: [],
+      speed: 0.01
+    };
+
+    function init() {
+      for (let i = 0; i < 100; i++) {
+        starfield.stars.push({
+          x: Math.random() * width,
+          y: Math.random() * height,
+          size: Math.random() * 2 + 1,
+          speed: Math.random() * 0.02 + 0.005
+        });
+      }
+      for (let i = 0; i < 10; i++) {
+        people.push({
+          x: Math.random() * width,
+          y: Math.random() * height,
+          angle: Math.random() * Math.PI * 2,
+          radius: 30,
+          speed: 0.01 + Math.random() * 0.01,
+          path: Math.random() < 0.5 ? 'circle' : 'line'
+        });
+      }
+      for (let i = 0; i < 10; i++) {
+        animals.push({
+          x: Math.random() * width,
+          y: Math.random() * height,
+          angle: Math.random() * Math.PI * 2,
+          radius: 20,
+          speed: 0.01 + Math.random() * 0.01,
+          type: ['hare', 'owl', 'coyote', 'bear'][Math.floor(Math.random() * 4)]
+        });
+      }
+      for (let i = 0; i < 15; i++) {
+        lanterns.push({
+          x: Math.random() * width,
+          y: Math.random() * height,
+          radius: 15,
+          angle: Math.random() * Math.PI * 2,
+          speed: 0.01 + Math.random() * 0.01
+        });
+      }
+      for (let i = 0; i < 10; i++) {
+        constellations.push({
+          x: Math.random() * width,
+          y: Math.random() * height,
+          size: 40,
+          type: ['hare', 'owl', 'coyote', 'bear'][Math.floor(Math.random() * 4)]
+        });
+      }
+    }
+
+    function draw() {
+      if (still) {
+        return;
+      }
+      if (!paused) {
+        update();
+      }
+      ctx.clearRect(0, 0, width, height);
+
+      drawBackground();
+
+      drawTortoise();
+      drawPeople();
+      drawAnimals();
+      drawLanterns();
+      drawConstellations();
+      drawStarfield();
+
+      if (showLabels) {
+        drawLabels();
+      }
+    }
+
+    function update() {
+      tortoise.step += tortoise.stepSize;
+      tortoise.angle += tortoise.speed;
+      tortoise.sway += tortoise.swaySpeed;
+      if (Math.abs(tortoise.sway) > tortoise.swayMax) {
+        tortoise.swaySpeed *= -1;
+      }
+
+      for (let p of people) {
+        if (p.path === 'circle') {
+          p.x = tortoise.x + Math.cos(p.angle) * 100 + Math.sin(tortoise.step) * 20;
+          p.y = tortoise.y + Math.sin(p.angle) * 100 + Math.cos(tortoise.step) * 20;
+          p.angle += p.speed * tempo;
+        } else {
+          p.x += Math.cos(p.angle) * p.speed * tempo;
+          p.y += Math.sin(p.angle) * p.speed * tempo;
+          p.angle += 0.01 * tempo;
+        }
+      }
+
+      for (let a of animals) {
+        a.x += Math.cos(a.angle) * a.speed * tempo;
+        a.y += Math.sin(a.angle) * a.speed * tempo;
+        a.angle += 0.01 * tempo;
+      }
+
+      for (let l of lanterns) {
+        l.x += Math.cos(l.angle) * l.speed * tempo;
+        l.y += Math.sin(l.angle) * l.speed * tempo;
+        l.angle += 0.01 * tempo;
+      }
+
+      for (let s of starfield.stars) {
+        s.y += s.speed * tempo;
+        if (s.y > height) {
+          s.y = -20;
+        }
+      }
+    }
+
+    function drawBackground() {
+      ctx.fillStyle = 'black';
+      ctx.fillRect(0, 0, width, height);
+    }
+
+    function drawTortoise() {
+      ctx.save();
+      ctx.translate(tortoise.x, tortoise.y);
+      ctx.rotate(tortoise.angle);
+      ctx.translate(-tortoise.size / 2, -tortoise.size / 2);
+
+      ctx.fillStyle = '#654321';
+      ctx.beginPath();
+      ctx.moveTo(0, 0);
+      ctx.lineTo(tortoise.size, 0);
+      ctx.lineTo(tortoise.size, tortoise.size);
+      ctx.lineTo(0, tortoise.size);
+      ctx.closePath();
+      ctx.fill();
+
+      ctx.fillStyle = '#4b2e1a';
+      ctx.beginPath();
+      ctx.arc(tortoise.size / 2, tortoise.size / 2, tortoise.size / 4, 0, Math.PI * 2);
+      ctx.fill();
+
+      ctx.fillStyle = '#8b7550';
+      ctx.beginPath();
+      ctx.arc(tortoise.size / 2, tortoise.size / 2, tortoise.size / 6, 0, Math.PI * 2);
+      ctx.fill();
+
+      ctx.restore();
+    }
+
+    function drawPeople() {
+      for (let p of people) {
+        if (!showPeople) continue;
+        if (activeCamera === 'ground') {
+          drawPerson(p.x, p.y, p.radius, p.angle);
+        } else if (activeCamera === 'shell') {
+          drawPerson(tortoise.x + Math.cos(p.angle) * 100, tortoise.y + Math.sin(p.angle) * 100, p.radius, p.angle);
+        } else if (activeCamera === 'sky') {
+          drawPerson(p.x, p.y - tortoise.size * 0.5, p.radius, p.angle);
+        }
+      }
+    }
+
+    function drawPerson(x, y, radius, angle) {
+      ctx.save();
+      ctx.translate(x, y);
+      ctx.rotate(angle);
+      ctx.fillStyle = '#fff';
+      ctx.beginPath();
+      ctx.moveTo(0, -radius);
+      ctx.lineTo(-radius / 2, radius);
+      ctx.lineTo(radius / 2, radius);
+      ctx.closePath();
+      ctx.fill();
+      ctx.restore();
+    }
+
+    function drawAnimals() {
+      for (let a of animals) {
+        if (!showAnimals) continue;
+        if (activeCamera === 'ground') {
+          drawAnimal(a.x, a.y, a.radius, a.angle, a.type);
+        } else if (activeCamera === 'shell') {
+          drawAnimal(tortoise.x + Math.cos(a.angle) * 100, tortoise.y + Math.sin(a.angle) * 100, a.radius, a.angle, a.type);
+        } else if (activeCamera === 'sky') {
+          drawAnimal(a.x, a.y - tortoise.size * 0.5, a.radius, a.angle, a.type);
+        }
+      }
+    }
+
+    function drawAnimal(x, y, radius, angle, type) {
+      ctx.save();
+      ctx.translate(x, y);
+      ctx.rotate(angle);
+      ctx.fillStyle = type === 'hare' ? '#f0d9b5' : type === 'owl' ? '#a9a9a9' : type === 'coyote' ? '#b87333' : '#8b4513';
+      if (type === 'hare') {
+        ctx.beginPath();
+        ctx.moveTo(0, -radius);
+        ctx.lineTo(-radius / 2, radius);
+        ctx.lineTo(radius / 2, radius);
+        ctx.closePath();
+        ctx.fill();
+      } else if (type === 'owl') {
+        ctx.beginPath();
+        ctx.arc(0, 0, radius, 0, Math.PI * 2);
+        ctx.fill();
+        ctx.beginPath();
+        ctx.moveTo(0, -radius / 2);
+        ctx.lineTo(-radius / 4, -radius / 2 - radius / 3);
+        ctx.lineTo(0, -radius / 2 - radius / 2);
+        ctx.lineTo(radius / 4, -radius / 2 - radius / 3);
+        ctx.closePath();
+        ctx.fill();
+      } else if (type === 'coyote') {
+        ctx.beginPath();
+        ctx.moveTo(-radius / 2, -radius / 2);
+        ctx.lineTo(0, -radius);
+        ctx.lineTo(radius / 2, -radius / 2);
+        ctx.lineTo(0, -radius / 2);
+        ctx.closePath();
+        ctx.fill();
+      } else if (type === 'bear') {
+        ctx.beginPath();
+        ctx.moveTo(-radius / 2, -radius);
+        ctx.lineTo(0, -radius / 2);
+        ctx.lineTo(radius / 2, -radius);
+        ctx.lineTo(0, -radius / 2);
+        ctx.closePath();
+        ctx.fill();
+      }
+      ctx.restore();
+    }
+
+    function drawLanterns() {
+      for (let l of lanterns) {
+        if (activeCamera === 'ground') {
+          drawLantern(l.x, l.y, l.radius, l.angle);
+        } else if (activeCamera === 'shell') {
+          drawLantern(tortoise.x + Math.cos(l.angle) * 100, tortoise.y + Math.sin(l.angle) * 100, l.radius, l.angle);
+        } else if (activeCamera === 'sky') {
+          drawLantern(l.x, l.y - tortoise.size * 0.5, l.radius, l.angle);
+        }
+      }
+    }
+
+    function drawLantern(x, y, radius, angle) {
+      ctx.save();
+      ctx.translate(x, y);
+      ctx.rotate(angle);
+      ctx.fillStyle = '#ffcc00';
+      ctx.beginPath();
+      ctx.arc(0, 0, radius, 0, Math.PI * 2);
+      ctx.fill();
+      ctx.fillStyle = '#000';
+      ctx.beginPath();
+      ctx.moveTo(0, -radius);
+      ctx.lineTo(-radius / 2, -radius / 2);
+      ctx.lineTo(0, 0);
+      ctx.lineTo(radius / 2, -radius / 2);
+      ctx.closePath();
+      ctx.fill();
+      ctx.restore();
+    }
+
+    function drawConstellations() {
+      for (let c of constellations) {
+        if (activeCamera === 'ground') {
+          drawConstellation(c.x, c.y, c.size, c.type);
+        } else if (activeCamera === 'shell') {
+          drawConstellation(tortoise.x + Math.cos(c.angle) * 100, tortoise.y + Math.sin(c.angle) * 100, c.size, c.type);
+        } else if (activeCamera === 'sky') {
+          drawConstellation(c.x, c.y - tortoise.size * 0.5, c.size, c.type);
+        }
+      }
+    }
+
+    function drawConstellation(x, y, size, type) {
+      ctx.save();
+      ctx.translate(x, y);
+      if (type === 'hare') {
+        drawFloralGeometry(size, 4, 0.5);
+      } else if (type === 'owl') {
+        drawFloralGeometry(size, 6, 0.4);
+      } else if (type === 'coyote') {
+        drawFloralGeometry(size, 5, 0.6);
+      } else if (type === 'bear') {
+        drawFloralGeometry(size, 8, 0.3);
+      }
+      ctx.restore();
+    }
+
+    function drawFloralGeometry(size, segments, density) {
+      ctx.fillStyle = '#ccc';
+      for (let i = 0; i < segments; i++) {
+        let angle = (Math.PI * 2 / segments) * i;
+        let radius = size * (1 + Math.sin(angle * density));
+        ctx.beginPath();
+        ctx.arc(0, 0, radius, 0, Math.PI * 2);
+        ctx.fill();
+      }
+    }
+
+    function drawStarfield() {
+      for (let s of starfield.stars) {
+        ctx.fillStyle = '#fff';
+        ctx.beginPath();
+        ctx.arc(s.x, s.y, s.size, 0, Math.PI * 2);
+        ctx.fill();
+      }
+    }
+
+    function drawLabels() {
+      for (let p of people) {
+        ctx.fillStyle = '#fff';
+        ctx.font = '12px sans-serif';
+        ctx.fillText('Dancer', p.x, p.y - 10);
+      }
+      for (let a of animals) {
+        ctx.fillStyle = '#fff';
+        ctx.font = '12px sans-serif';
+        ctx.fillText(a.type.charAt(0).toUpperCase() + a.type.slice(1), a.x, a.y - 10);
+      }
+      for (let c of constellations) {
+        ctx.fillStyle = '#fff';
+        ctx.font = '12px sans-serif';
+        ctx.fillText(c.type.charAt(0).toUpperCase() + c.type.slice(1), c.x, c.y - 10);
+      }
+    }
+
+    function setScene(s) {
+      scene = s;
+      if (s === 'bluehour') {
+        document.body.style.background = '#0e0e1e';
+      } else if (s === 'zenith') {
+        document.body.style.background = '#1e1e2e';
+      } else if (s === 'rosedawn') {
+        document.body.style.background = '#2e1e1e';
+      }
+    }
+
+    function toggleCamera(cam) {
+      activeCamera = cam;
+    }
+
+    function toggleTempo() {
+      tempo = tempo === 1 ? 2 : 1;
+    }
+
+    function togglePaths() {
+      showPaths = !showPaths;
+    }
+
+    function togglePeople() {
+      showPeople = !showPeople;
+    }
+
+    function toggleAnimals() {
+      showAnimals = !showAnimals;
+    }
+
+    function toggleLabels() {
+      showLabels = !showLabels;
+    }
+
+    function pause() {
+      paused = !paused;
+    }
+
+    function reset() {
+      paused = false;
+      init();
+    }
+
+    function setStill() {
+      still = true;
+    }
+
+    function resize() {
+      width = canvas.width = window.innerWidth;
+      height = canvas.height = window.innerHeight;
+      init();
+    }
+
+    window.addEventListener('resize', resize);
+    document.addEventListener('keydown', e => {
+      if (e.key === 'ArrowLeft') {
+        toggleCamera('ground');
+      } else if (e.key === 'ArrowRight') {
+        toggleCamera('shell');
+      } else if (e.key === 'ArrowUp') {
+        toggleCamera('sky');
+      } else if (e.key === ' ') {
+        toggleTempo();
+      } else if (e.key === 'p') {
+        pause();
+      } else if (e.key === 'r') {
+        reset();
+      } else if (e.key === 's') {
+        setStill();
+      }
+    });
+
+    init();
+    draw();
+    setInterval(draw, 1000 / 60);
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/cd83de847d24/claude-code.html b/data/artifacts/cd83de847d24/claude-code.html
new file mode 100644
index 0000000..6aec660
--- /dev/null
+++ b/data/artifacts/cd83de847d24/claude-code.html
@@ -0,0 +1,887 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Scarlet Riverboat Masquerade — An Animated Gallery</title>
+<style>
+  :root{
+    --ink:#f4e8dd;
+    --ink-dim:#c9a89a;
+    --gold:#e8b968;
+    --scarlet:#c9354a;
+    --panel:rgba(24,8,14,0.82);
+    --panel-edge:rgba(232,185,104,0.28);
+    --focus:#ffd98a;
+    --serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
+    --sans:"Avenir Next","Segoe UI",system-ui,-apple-system,sans-serif;
+  }
+  *{box-sizing:border-box}
+  html,body{margin:0;height:100%;background:#0a0407;color:var(--ink);font-family:var(--sans);overflow:hidden}
+  #stage{position:fixed;inset:0}
+  canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
+
+  /* ---- Title / chapter overlay ---- */
+  .masthead{
+    position:fixed;top:0;left:0;right:0;padding:18px 22px 40px;
+    background:linear-gradient(180deg,rgba(10,4,7,0.72),rgba(10,4,7,0));
+    pointer-events:none;z-index:5;
+  }
+  .masthead h1{
+    font-family:var(--serif);font-weight:600;letter-spacing:.045em;
+    margin:0;font-size:clamp(1.35rem,3.4vw,2.6rem);
+    color:var(--ink);text-shadow:0 2px 18px rgba(0,0,0,.6);
+  }
+  .masthead h1 em{color:var(--scarlet);font-style:italic}
+  .masthead .kicker{
+    font-family:var(--sans);text-transform:uppercase;letter-spacing:.42em;
+    font-size:.62rem;color:var(--gold);margin:0 0 6px;opacity:.9;
+  }
+  .chapline{font-family:var(--serif);font-style:italic;color:var(--ink-dim);
+    font-size:clamp(.8rem,1.6vw,1rem);margin-top:6px;transition:opacity .6s}
+
+  /* ---- Chapter rail ---- */
+  .chapters{
+    position:fixed;left:50%;transform:translateX(-50%);bottom:18px;z-index:6;
+    display:flex;gap:10px;background:var(--panel);border:1px solid var(--panel-edge);
+    padding:8px 10px;border-radius:40px;backdrop-filter:blur(8px);
+  }
+  .chapters button{
+    font-family:var(--sans);font-size:.7rem;letter-spacing:.12em;text-transform:uppercase;
+    color:var(--ink-dim);background:transparent;border:none;padding:8px 16px;border-radius:30px;
+    cursor:pointer;transition:color .3s,background .3s;position:relative;
+  }
+  .chapters button[aria-current="true"]{color:#1a0a0e;background:var(--gold);font-weight:600}
+  .chapters button:hover{color:var(--ink)}
+  .chapters button[aria-current="true"]:hover{color:#1a0a0e}
+
+  /* ---- Control panel ---- */
+  .panel{
+    position:fixed;top:0;right:0;height:100%;width:320px;max-width:86vw;z-index:8;
+    background:var(--panel);border-left:1px solid var(--panel-edge);
+    backdrop-filter:blur(12px);padding:20px 20px 30px;overflow-y:auto;
+    transform:translateX(0);transition:transform .45s cubic-bezier(.6,0,.2,1);
+  }
+  .panel.hidden{transform:translateX(100%)}
+  .panel h2{font-family:var(--serif);font-size:1rem;letter-spacing:.08em;
+    margin:4px 0 4px;color:var(--gold);font-weight:600}
+  .panel .sub{font-size:.7rem;color:var(--ink-dim);margin:0 0 18px;line-height:1.5}
+  .ctl{margin:0 0 16px}
+  .ctl label{display:flex;justify-content:space-between;font-size:.72rem;
+    letter-spacing:.08em;text-transform:uppercase;color:var(--ink-dim);margin-bottom:6px}
+  .ctl label .val{color:var(--gold);font-variant-numeric:tabular-nums}
+  input[type=range]{width:100%;accent-color:var(--scarlet);height:22px;cursor:pointer}
+  .btnrow{display:flex;flex-wrap:wrap;gap:8px;margin:6px 0 20px}
+  .btnrow button, .toggle{
+    font-family:var(--sans);font-size:.72rem;letter-spacing:.06em;
+    color:var(--ink);background:rgba(201,53,74,.16);border:1px solid var(--panel-edge);
+    padding:9px 13px;border-radius:8px;cursor:pointer;transition:background .25s,border-color .25s;
+    flex:1 1 auto;min-width:70px;
+  }
+  .btnrow button:hover,.toggle:hover{background:rgba(232,185,104,.2)}
+  .toggle[aria-pressed="true"]{background:var(--gold);color:#1a0a0e;font-weight:600;border-color:var(--gold)}
+  .cams{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:18px}
+  .cams button{
+    font-size:.68rem;letter-spacing:.05em;color:var(--ink);
+    background:rgba(0,0,0,.25);border:1px solid var(--panel-edge);
+    padding:12px 6px;border-radius:8px;cursor:pointer;transition:background .25s;
+  }
+  .cams button[aria-current="true"]{background:var(--scarlet);color:#fff;font-weight:600;border-color:var(--scarlet)}
+  .cams button:hover{background:rgba(232,185,104,.18)}
+
+  .toggles{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:20px}
+  .toggles .toggle{flex:1 1 44%}
+
+  .label-note{
+    font-family:var(--serif);font-size:.74rem;line-height:1.6;color:var(--ink-dim);
+    border-top:1px solid var(--panel-edge);padding-top:14px;margin-top:8px;
+  }
+  .label-note strong{color:var(--ink);font-weight:600}
+
+  /* Panel toggle + play FAB */
+  .fab{
+    position:fixed;z-index:9;background:var(--panel);color:var(--ink);
+    border:1px solid var(--panel-edge);border-radius:50%;width:48px;height:48px;
+    display:flex;align-items:center;justify-content:center;cursor:pointer;
+    font-size:1.1rem;backdrop-filter:blur(8px);transition:background .25s;
+  }
+  .fab:hover{background:rgba(232,185,104,.22)}
+  #panelToggle{top:16px;right:16px}
+  #playToggle{top:16px;right:76px;font-variant-numeric:tabular-nums}
+
+  /* Visible focus everywhere */
+  a:focus-visible,button:focus-visible,input:focus-visible,[tabindex]:focus-visible{
+    outline:3px solid var(--focus);outline-offset:3px;border-radius:6px;
+  }
+  .hint{position:fixed;left:16px;bottom:76px;z-index:6;font-size:.62rem;
+    letter-spacing:.1em;color:var(--ink-dim);opacity:.6;pointer-events:none;text-transform:uppercase}
+  @media(max-width:640px){
+    #playToggle{right:72px}
+    .chapters{bottom:78px}
+    .hint{display:none}
+  }
+  .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
+</style>
+</head>
+<body>
+<div id="stage">
+  <canvas id="scene" aria-label="Animated scene: a fantastical paddlewheel riverboat gliding through a crimson canyon at dusk while masked dancers fill three decks and animals dance along the shore." role="img"></canvas>
+</div>
+
+<header class="masthead" aria-hidden="false">
+  <p class="kicker">A Gallery in Authored Visual Rhythm</p>
+  <h1>Scarlet <em>Riverboat</em> Masquerade</h1>
+  <p class="chapline" id="chapline">Chapter I — Departure · dusk over the crimson canyon</p>
+</header>
+
+<button class="fab" id="playToggle" aria-label="Pause the scene" title="Pause / Play (Space)">❚❚</button>
+<button class="fab" id="panelToggle" aria-label="Hide controls" aria-expanded="true" title="Toggle controls (C)">≡</button>
+
+<nav class="chapters" aria-label="Chapter markers">
+  <button data-chap="0" aria-current="true">Departure</button>
+  <button data-chap="1">Moon Bend</button>
+  <button data-chap="2">Dawn Landing</button>
+</nav>
+
+<p class="hint">Space · pause &nbsp;|&nbsp; 1·2·3 · cameras &nbsp;|&nbsp; ←→ · chapters &nbsp;|&nbsp; C · controls</p>
+
+<aside class="panel" id="panel" aria-label="Scene controls">
+  <h2>The Conductor's Desk</h2>
+  <p class="sub">Every motion here is <em>authored visual rhythm</em> — hand-choreographed loops, not sound analysis. Shape the tableau to taste.</p>
+
+  <h2 style="font-size:.78rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-dim)">Camera</h2>
+  <div class="cams" role="group" aria-label="Camera view">
+    <button data-cam="0" aria-current="true">Wide River</button>
+    <button data-cam="1">On Deck</button>
+    <button data-cam="2">Overhead</button>
+  </div>
+
+  <div class="ctl">
+    <label for="tempo">Visual Tempo <span class="val" id="tempoV">1.0×</span></label>
+    <input type="range" id="tempo" min="0" max="240" value="100" aria-valuetext="1.0 times speed">
+  </div>
+  <div class="ctl">
+    <label for="lantern">Lantern Intensity <span class="val" id="lanternV">70%</span></label>
+    <input type="range" id="lantern" min="0" max="100" value="70">
+  </div>
+  <div class="ctl">
+    <label for="crowd">Crowd Density <span class="val" id="crowdV">Full</span></label>
+    <input type="range" id="crowd" min="10" max="100" value="100">
+  </div>
+  <div class="ctl">
+    <label for="trails">Motion Trails <span class="val" id="trailsV">40%</span></label>
+    <input type="range" id="trails" min="0" max="100" value="40">
+  </div>
+
+  <div class="toggles" role="group" aria-label="Visibility">
+    <button class="toggle" id="animalsT" aria-pressed="true">Shoreline Animals</button>
+    <button class="toggle" id="motionT" aria-pressed="false">Reduced Motion</button>
+  </div>
+
+  <div class="btnrow">
+    <button id="restart">↺ Restart Voyage</button>
+  </div>
+
+  <p class="label-note">
+    <strong>Museum Label.</strong> <em>Scarlet Riverboat Masquerade</em> (digital, generative loop).
+    The work invents an original visual language for communal celebration: no logos, no emblems, no
+    borrowed art — only silhouette, lantern-glow and repeating gesture. Dancers read as pure shape —
+    couples <strong>turning</strong> beneath swinging lanterns, a linked circle <strong>revolving</strong>
+    around the smokestack, solo figures <strong>spinning</strong> ribbons of scarf, and musicians reduced
+    to abstract standing forms so the eye rests on rhythm, not identity. Along the shore, each animal
+    keeps its own weighted cadence — the fox's springing trot, the heron's slow neck-fold, the rabbit's
+    double-hop, and the bear who conducts fireflies with the gravity of a tired maestro. The three
+    chapters — <strong>Departure</strong>, <strong>Moon Bend</strong>, <strong>Dawn Landing</strong> —
+    re-tune the palette and step. All movement is time-authored choreography; the piece listens to no
+    audio and analyzes no sound.
+  </p>
+</aside>
+
+<script>
+(() => {
+  "use strict";
+  const cv = document.getElementById('scene');
+  const ctx = cv.getContext('2d');
+  let W=0,H=0,DPR=1;
+  function resize(){
+    DPR = Math.min(window.devicePixelRatio||1, 2);
+    W = cv.clientWidth; H = cv.clientHeight;
+    cv.width = Math.round(W*DPR); cv.height = Math.round(H*DPR);
+    ctx.setTransform(DPR,0,0,DPR,0,0);
+  }
+  window.addEventListener('resize', resize);
+
+  // ---- State ----
+  const S = {
+    t: 0,               // authored clock (seconds of rhythm)
+    playing: true,
+    tempo: 1.0,
+    lantern: 0.7,
+    crowd: 1.0,
+    trails: 0.4,
+    animals: true,
+    reduced: false,
+    cam: 0,
+    chapter: 0,
+    camBlend: 0,        // eased camera position
+    chapBlend: 0,       // eased chapter position (float across chapters)
+  };
+
+  // Deterministic pseudo-random for stable placement
+  function rng(seed){ let s = seed%2147483647; if(s<=0)s+=2147483646;
+    return ()=> (s = s*16807 % 2147483647)/2147483647; }
+
+  // ---- Palettes per chapter ----
+  const PAL = [
+    { // Departure — dusk crimson
+      skyTop:'#2a0a16', skyBot:'#7a1526', canyonBack:'#4a0f1e', canyonMid:'#63142a',
+      canyonFront:'#3a0a16', water:'#5a1020', waterHi:'#b8324f', boat:'#1c0810',
+      lantern:'#ffce7a', accent:'#e8b968', dancer:'#120508', moon:'#f6d9a0', star:'#e8b968'
+    },
+    { // Moon Bend — deep indigo night, silver moon
+      skyTop:'#0a0a24', skyBot:'#2a1440', canyonBack:'#241338', canyonMid:'#31184a',
+      canyonFront:'#170d26', water:'#141033', waterHi:'#6a5cc0', boat:'#0c0818',
+      lantern:'#bfe0ff', accent:'#9fb6ff', dancer:'#080614', moon:'#e8f0ff', star:'#cfe0ff'
+    },
+    { // Dawn Landing — rose gold sunrise
+      skyTop:'#3a1a2a', skyBot:'#e9905a', canyonBack:'#8a3a3e', canyonMid:'#b25848',
+      canyonFront:'#5a2028', water:'#c96a58', waterHi:'#ffd9a0', boat:'#2a1418',
+      lantern:'#fff0c8', accent:'#ffcf8a', dancer:'#241014', moon:'#fff4dc', star:'#ffe6b0'
+    }
+  ];
+  const CHAPNAMES=[
+    'Chapter I — Departure · dusk over the crimson canyon',
+    'Chapter II — Moon Bend · the river turns beneath a silver moon',
+    'Chapter III — Dawn Landing · rose-gold light on the water'
+  ];
+
+  function lerp(a,b,t){return a+(b-a)*t;}
+  function hx(h){return [parseInt(h.slice(1,3),16),parseInt(h.slice(3,5),16),parseInt(h.slice(5,7),16)];}
+  function mix(c1,c2,t){const a=hx(c1),b=hx(c2);
+    return `rgb(${Math.round(lerp(a[0],b[0],t))},${Math.round(lerp(a[1],b[1],t))},${Math.round(lerp(a[2],b[2],t))})`;}
+  // Blended palette value across chapters
+  function P(key){
+    const f=S.chapBlend; const i=Math.floor(f); const j=Math.min(i+1,2); const t=f-i;
+    return mix(PAL[i][key],PAL[j][key],t);
+  }
+
+  // ---- Deterministic populations ----
+  const rand = rng(20260901);
+  const DANCERS_DECK = []; // {deck, x, phase, kind}
+  // three decks; kinds: couple, circle, solo, musician
+  function buildDancers(){
+    DANCERS_DECK.length=0;
+    const decks=[0,1,2];
+    decks.forEach(d=>{
+      // couples turning under lanterns
+      for(let i=0;i<6;i++) DANCERS_DECK.push({deck:d,slot:i/6,phase:rand()*6.28,kind:'couple',r:rand()});
+      // solo scarf spinners
+      for(let i=0;i<3;i++) DANCERS_DECK.push({deck:d,slot:rand(),phase:rand()*6.28,kind:'solo',r:rand()});
+      // musicians (abstract) at the ends
+      for(let i=0;i<2;i++) DANCERS_DECK.push({deck:d,slot:i?0.9:0.08,phase:rand()*6.28,kind:'musician',r:rand()});
+    });
+    // linked circle rotates around smokestack — top deck only, generated in draw
+  }
+  buildDancers();
+
+  const FIREFLIES = Array.from({length:60},()=>({x:rand(),y:rand(),ph:rand()*6.28,sp:0.4+rand()*0.8}));
+
+  // ---- Camera definitions: return {ox,oy,scale} in scene space ----
+  // Scene is authored in a virtual 1000x600 space, mapped to canvas.
+  function camParams(){
+    // three cameras: wide (full), deck (zoom into boat mid), overhead (top-down tilt)
+    const cams=[
+      {scale:1.0, ox:0, oy:0, tilt:0},
+      {scale:2.15, ox:-0.06, oy:0.18, tilt:0},
+      {scale:1.35, ox:0, oy:-0.10, tilt:0.55}
+    ];
+    // blend between current integer cam target
+    const f=S.camBlend; const i=Math.floor(f); const j=Math.min(i+1,2); const t=f-i;
+    const a=cams[i],b=cams[j];
+    return {
+      scale:lerp(a.scale,b.scale,t),
+      ox:lerp(a.ox,b.ox,t),
+      oy:lerp(a.oy,b.oy,t),
+      tilt:lerp(a.tilt,b.tilt,t)
+    };
+  }
+
+  // Virtual->screen helpers set per frame
+  let VX=0,VY=0,VS=1,TILT=0;
+  function setupProjection(){
+    const c=camParams();
+    const base=Math.min(W/1000,H/600);
+    VS = base*c.scale;
+    const cw=1000*VS, ch=600*VS;
+    VX = (W-cw)/2 + c.ox*cw;
+    VY = (H-ch)/2 + c.oy*ch;
+    TILT=c.tilt;
+  }
+  function px(x){return VX + x*VS;}
+  function py(y){return VY + y*VS;}
+  function pu(u){return u*VS;} // scale a virtual length
+
+  // ---- Drawing ----
+  function drawSky(){
+    const g=ctx.createLinearGradient(0,py(0),0,py(360));
+    g.addColorStop(0,P('skyTop')); g.addColorStop(1,P('skyBot'));
+    ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
+    // stars / soft glow — twinkle authored by sine
+    const starN=70;
+    ctx.save();
+    for(let i=0;i<starN;i++){
+      const sx=px((i*137.5%1000)); const sy=py((i*53.7%300));
+      const tw=0.4+0.6*Math.abs(Math.sin(S.t*0.6 + i));
+      ctx.globalAlpha=tw*0.7*(1-S.chapBlend*0.15);
+      ctx.fillStyle=P('star');
+      ctx.beginPath();ctx.arc(sx,sy,pu(0.9+ (i%3)*0.5),0,6.283);ctx.fill();
+    }
+    ctx.restore();
+    // Moon — drifts subtly, phase depends on chapter
+    const moonX=200+Math.sin(S.t*0.05)*20 + S.chapBlend*260;
+    const moonY=90 - S.chapBlend*20;
+    const mg=ctx.createRadialGradient(px(moonX),py(moonY),0,px(moonX),py(moonY),pu(120));
+    mg.addColorStop(0,P('moon')); mg.addColorStop(0.18,P('moon'));
+    mg.addColorStop(0.19,'rgba(0,0,0,0)');
+    ctx.save();ctx.globalAlpha=0.9;ctx.fillStyle=mg;
+    ctx.beginPath();ctx.arc(px(moonX),py(moonY),pu(120),0,6.283);ctx.fill();
+    ctx.globalAlpha=1;ctx.fillStyle=P('moon');
+    ctx.beginPath();ctx.arc(px(moonX),py(moonY),pu(26),0,6.283);ctx.fill();
+    ctx.restore();
+  }
+
+  // Layered parallax canyon — each layer sways slightly & drifts opposite to boat travel
+  function canyonLayer(color, baseY, amp, seed, speed, jag){
+    const r=rng(seed);
+    ctx.fillStyle=color;
+    ctx.beginPath();
+    ctx.moveTo(px(-40),py(600));
+    const drift = (S.t*speed)%200;
+    for(let x=-40;x<=1040;x+=jag){
+      const n=Math.sin((x*0.012)+seed)+0.5*Math.sin((x*0.03)+seed*1.7);
+      const y=baseY + n*amp - drift*0.02*amp + (r()-0.5)*amp*0.15;
+      ctx.lineTo(px(x),py(y));
+    }
+    ctx.lineTo(px(1040),py(600));ctx.closePath();ctx.fill();
+  }
+  function drawCanyon(){
+    canyonLayer(P('canyonBack'),180,60,11,3,26);
+    canyonLayer(P('canyonMid'),240,80,29,6,20);
+    canyonLayer(P('canyonFront'),300,70,41,10,16);
+  }
+
+  function drawWater(){
+    ctx.fillStyle=P('water');
+    ctx.fillRect(px(-40),py(360),pu(1080),pu(280));
+    // moving highlight bands (authored ripple)
+    ctx.save();
+    for(let i=0;i<9;i++){
+      const yy=380+i*26;
+      const phase=S.t*(0.6+i*0.05)+i;
+      const off=Math.sin(phase)*10;
+      ctx.globalAlpha=0.12+0.05*Math.sin(phase*1.3);
+      ctx.strokeStyle=P('waterHi');
+      ctx.lineWidth=pu(2+ (i%2));
+      ctx.beginPath();
+      for(let x=-40;x<=1040;x+=40){
+        const y=yy + Math.sin(x*0.03 + phase)*6 + off;
+        if(x===-40)ctx.moveTo(px(x),py(y));else ctx.lineTo(px(x),py(y));
+      }
+      ctx.stroke();
+    }
+    ctx.restore();
+    // boat & lantern reflections
+  }
+
+  // ---- The riverboat ----
+  // Positioned mid-scene, bobs gently. Returns geometry used by dancers.
+  function boatGeom(){
+    const bob=Math.sin(S.t*0.7)*4;
+    const glideX = 470 + Math.sin(S.t*0.06)*14;
+    return {cx:glideX, cy:352+bob, w:360, hullH:60, bob};
+  }
+
+  function drawReflection(g){
+    // wobbling mirrored glow of the hull
+    ctx.save();
+    ctx.globalAlpha=0.22;
+    ctx.fillStyle=P('waterHi');
+    const wob=Math.sin(S.t*1.1)*6;
+    ctx.beginPath();
+    ctx.ellipse(px(g.cx),py(g.cy+70),pu(g.w*0.42),pu(20+Math.abs(wob)),0,0,6.283);
+    ctx.fill();
+    ctx.restore();
+  }
+
+  function drawPaddlewheel(g){
+    // right-rear wheel, turns with tempo; weight = eased ease of spokes
+    const wx=g.cx - g.w*0.5 + 6, wy=g.cy - 6, R=44;
+    ctx.save();
+    ctx.translate(px(wx),py(wy));
+    ctx.rotate(-S.t*1.1); // authored rotation
+    // housing
+    ctx.strokeStyle=P('accent');ctx.lineWidth=pu(3);
+    ctx.fillStyle=P('boat');
+    ctx.beginPath();ctx.arc(0,0,pu(R),0,6.283);ctx.fill();
+    for(let i=0;i<8;i++){
+      ctx.save();ctx.rotate(i/8*6.283);
+      ctx.fillStyle= i%2? P('accent'):P('lantern');
+      ctx.globalAlpha=0.9;
+      ctx.fillRect(-pu(4),-pu(R),pu(8),pu(R*0.9));
+      ctx.restore();
+    }
+    ctx.beginPath();ctx.arc(0,0,pu(R),0,6.283);ctx.stroke();
+    ctx.fillStyle=P('accent');ctx.beginPath();ctx.arc(0,0,pu(6),0,6.283);ctx.fill();
+    ctx.restore();
+    // churned water
+    ctx.save();ctx.globalAlpha=0.5;ctx.fillStyle=P('waterHi');
+    for(let i=0;i<5;i++){const a=0.3+0.2*Math.sin(S.t*3+i);
+      ctx.globalAlpha=a*0.5;
+      ctx.beginPath();ctx.arc(px(wx-14-i*8),py(wy+30+Math.sin(S.t*4+i)*3),pu(4+i),0,6.283);ctx.fill();}
+    ctx.restore();
+  }
+
+  function drawHull(g){
+    ctx.save();
+    ctx.fillStyle=P('boat');
+    // hull
+    ctx.beginPath();
+    ctx.moveTo(px(g.cx-g.w/2),py(g.cy));
+    ctx.quadraticCurveTo(px(g.cx-g.w/2-30),py(g.cy+40),px(g.cx-g.w/2+30),py(g.cy+g.hullH));
+    ctx.lineTo(px(g.cx+g.w/2-30),py(g.cy+g.hullH));
+    ctx.quadraticCurveTo(px(g.cx+g.w/2+40),py(g.cy+30),px(g.cx+g.w/2),py(g.cy));
+    ctx.closePath();ctx.fill();
+    // gold trim waterline
+    ctx.strokeStyle=P('accent');ctx.lineWidth=pu(2);ctx.globalAlpha=0.8;
+    ctx.beginPath();ctx.moveTo(px(g.cx-g.w/2+4),py(g.cy+4));ctx.lineTo(px(g.cx+g.w/2-4),py(g.cy+4));ctx.stroke();
+    ctx.restore();
+  }
+
+  // deck y positions (top of each deck floor) relative to boat cy
+  function deckY(g,d){ return g.cy - 22 - d*46; }
+  function deckSpan(g,d){ const shrink=d*26; return {x0:g.cx-g.w*0.44+shrink, x1:g.cx+g.w*0.44-shrink}; }
+
+  function drawDeckStructure(g){
+    ctx.save();
+    for(let d=0;d<3;d++){
+      const y=deckY(g,d); const s=deckSpan(g,d);
+      // deck floor
+      ctx.fillStyle=P('boat');
+      ctx.globalAlpha=0.96;
+      ctx.fillRect(px(s.x0),py(y),pu(s.x1-s.x0),pu(46));
+      // rail
+      ctx.strokeStyle=P('accent');ctx.lineWidth=pu(1.5);ctx.globalAlpha=0.7;
+      ctx.beginPath();ctx.moveTo(px(s.x0),py(y));ctx.lineTo(px(s.x1),py(y));ctx.stroke();
+      // rail posts
+      for(let x=s.x0;x<=s.x1;x+=24){ctx.beginPath();ctx.moveTo(px(x),py(y));ctx.lineTo(px(x),py(y+10));ctx.stroke();}
+      // roof canopy scalloped
+      ctx.globalAlpha=0.9;ctx.fillStyle=mix(P('boat'),P('accent'),0.12);
+      ctx.beginPath();ctx.moveTo(px(s.x0-6),py(y));
+      for(let x=s.x0-6;x<=s.x1+6;x+=16){ctx.lineTo(px(x),py(y-6+Math.sin(x*0.4)*2));}
+      ctx.lineTo(px(s.x1+6),py(y));ctx.closePath();ctx.fill();
+    }
+    // smokestacks
+    const stackY=deckY(g,2)-4;
+    [g.cx-30,g.cx+30].forEach(sx=>{
+      ctx.fillStyle=P('boat');
+      ctx.fillRect(px(sx-6),py(stackY-70),pu(12),pu(70));
+      ctx.fillStyle=P('accent');ctx.globalAlpha=0.7;
+      ctx.fillRect(px(sx-8),py(stackY-74),pu(16),pu(5));
+      // gentle smoke
+      ctx.globalAlpha=0.12;ctx.fillStyle=P('star');
+      for(let i=0;i<3;i++){const yy=stackY-70-i*24-((S.t*14)%24);
+        ctx.beginPath();ctx.arc(px(sx+Math.sin(S.t+i)*6),py(yy),pu(6+i*2),0,6.283);ctx.fill();}
+    });
+    ctx.restore();
+  }
+
+  // lantern strings along decks — swing authored, intensity from control
+  function drawLanterns(g){
+    const inten=S.lantern;
+    ctx.save();
+    for(let d=0;d<3;d++){
+      const y=deckY(g,d)-4; const s=deckSpan(g,d);
+      for(let x=s.x0+14;x<s.x1;x+=40){
+        const sw=Math.sin(S.t*1.4 + x*0.05)*6*(S.reduced?0.2:1);
+        const lx=x+sw, ly=y-14+Math.abs(Math.cos(S.t*1.4+x*0.05))*2;
+        // glow
+        const gg=ctx.createRadialGradient(px(lx),py(ly),0,px(lx),py(ly),pu(30));
+        gg.addColorStop(0,P('lantern'));gg.addColorStop(1,'rgba(0,0,0,0)');
+        ctx.globalAlpha=0.5*inten;ctx.fillStyle=gg;
+        ctx.beginPath();ctx.arc(px(lx),py(ly),pu(30),0,6.283);ctx.fill();
+        // body
+        ctx.globalAlpha=0.85*inten+0.15;ctx.fillStyle=P('lantern');
+        ctx.beginPath();ctx.ellipse(px(lx),py(ly),pu(4),pu(6),0,0,6.283);ctx.fill();
+        // string
+        ctx.globalAlpha=0.4;ctx.strokeStyle=P('accent');ctx.lineWidth=pu(0.8);
+        ctx.beginPath();ctx.moveTo(px(x),py(y));ctx.lineTo(px(lx),py(ly-6));ctx.stroke();
+      }
+    }
+    ctx.restore();
+  }
+
+  // ---- Dancer figures (silhouettes) ----
+  // A dancer is drawn as a small vertical body; motion depends on kind.
+  function figure(x,y,scale,rot,leanX,armSpread,color,alpha){
+    ctx.save();
+    ctx.globalAlpha=alpha;
+    ctx.translate(px(x),py(y));
+    ctx.rotate(rot);
+    const u=pu(scale);
+    ctx.fillStyle=color;
+    // head
+    ctx.beginPath();ctx.arc(leanX*u,-9*u,3.2*u,0,6.283);ctx.fill();
+    // torso (trapezoid)
+    ctx.beginPath();
+    ctx.moveTo(-2.4*u,-6*u);ctx.lineTo(2.4*u,-6*u);
+    ctx.lineTo(3*u+leanX*u,1*u);ctx.lineTo(-3*u+leanX*u,1*u);ctx.closePath();ctx.fill();
+    // skirt / legs
+    ctx.beginPath();
+    ctx.moveTo(-3*u+leanX*u,1*u);ctx.lineTo(3*u+leanX*u,1*u);
+    ctx.lineTo(4.5*u+leanX*u,9*u);ctx.lineTo(-4.5*u+leanX*u,9*u);ctx.closePath();ctx.fill();
+    // arms
+    ctx.strokeStyle=color;ctx.lineWidth=1.6*u;ctx.lineCap='round';
+    ctx.beginPath();
+    ctx.moveTo(0,-4*u);ctx.lineTo(-armSpread*u,-6*u-Math.abs(armSpread)*0.4*u);
+    ctx.moveTo(0,-4*u);ctx.lineTo(armSpread*u,-6*u-Math.abs(armSpread)*0.4*u);
+    ctx.stroke();
+    ctx.restore();
+  }
+
+  function drawDancers(g){
+    const dcolor=P('dancer');
+    const beat = S.t*(1.0 + S.chapBlend*0.15); // step quickens slightly by chapter
+    const density=S.crowd;
+    // per-deck authored choreography
+    DANCERS_DECK.forEach((p,idx)=>{
+      if((idx%10)/10 > density) return; // thin out by density
+      const y=deckY(g,p.deck)+30; const s=deckSpan(g,p.deck);
+      const x=lerp(s.x0+16,s.x1-16,p.slot);
+      if(p.kind==='couple'){
+        // two figures turning around a shared center (waltz)
+        const spin=beat*1.1+p.phase;
+        const rad=6+p.r*3;
+        const lean=Math.sin(spin)*0.5;
+        figure(x+Math.cos(spin)*rad, y+Math.sin(spin)*rad*0.35, 1.0, Math.sin(spin)*0.15, lean, 3.5+Math.sin(spin)*1.5, dcolor, 0.95);
+        figure(x-Math.cos(spin)*rad, y-Math.sin(spin)*rad*0.35, 1.0, -Math.sin(spin)*0.15, -lean, 3.5-Math.sin(spin)*1.5, dcolor, 0.95);
+      } else if(p.kind==='solo'){
+        // spins with a trailing scarf
+        const spin=beat*1.6+p.phase;
+        const lean=Math.sin(spin*1.3)*0.7;
+        figure(x, y, 1.05, Math.sin(spin)*0.25, lean, 4+Math.sin(spin*2)*2, dcolor, 0.95);
+        // scarf ribbon
+        ctx.save();ctx.globalAlpha=0.8;
+        ctx.strokeStyle=P('scarlet')||'#c9354a';ctx.strokeStyle='#c9354a';
+        ctx.lineWidth=pu(2);ctx.lineCap='round';
+        ctx.beginPath();
+        for(let k=0;k<10;k++){
+          const a=spin - k*0.4; const rr=(4+k*1.6);
+          const sxp=x+Math.cos(a)*rr, syp=y-10 - Math.sin(a)*rr*0.5;
+          if(k===0)ctx.moveTo(px(sxp),py(syp));else ctx.lineTo(px(sxp),py(syp));
+        }
+        ctx.stroke();ctx.restore();
+      } else if(p.kind==='musician'){
+        // abstract standing silhouette, minimal sway — suggests players
+        const sway=Math.sin(beat*0.7+p.phase)*0.12;
+        ctx.save();ctx.globalAlpha=0.9;ctx.fillStyle=dcolor;
+        ctx.translate(px(x),py(y));ctx.rotate(sway);
+        const u=pu(1.1);
+        ctx.beginPath();ctx.arc(0,-11*u,3.6*u,0,6.283);ctx.fill();
+        ctx.beginPath();ctx.moveTo(-4*u,-8*u);ctx.lineTo(4*u,-8*u);ctx.lineTo(5*u,10*u);ctx.lineTo(-5*u,10*u);ctx.closePath();ctx.fill();
+        // suggestion of an instrument shape (abstract arc)
+        ctx.strokeStyle=P('accent');ctx.lineWidth=1.6*u;ctx.globalAlpha=0.55;
+        ctx.beginPath();ctx.arc(6*u,-2*u,5*u,-1,1.4);ctx.stroke();
+        ctx.restore();
+      }
+    });
+    // ---- Linked circle rotating around the smokestack (top deck) ----
+    const topY=deckY(g,2)+26;
+    const ring=Math.round(8*density);
+    const spin=beat*0.5;
+    ctx.save();
+    for(let i=0;i<ring;i++){
+      const a=spin + i/ring*6.283;
+      const rx=g.cx+Math.cos(a)*54;
+      const ry=topY+Math.sin(a)*16;
+      const facing=Math.sin(a);
+      figure(rx,ry,0.9,0,facing*0.4,2.5,dcolor,0.9);
+    }
+    // joined-hands hint (faint arc)
+    ctx.globalAlpha=0.3;ctx.strokeStyle=P('accent');ctx.lineWidth=pu(1.2);
+    ctx.beginPath();ctx.ellipse(px(g.cx),py(topY),pu(54),pu(16),0,0,6.283);ctx.stroke();
+    ctx.restore();
+  }
+
+  // ---- Shoreline animals — each a distinct weighted loop ----
+  function drawAnimals(){
+    if(!S.animals) return;
+    const baseY=422;
+    const c=P('dancer');
+    const beat=S.t;
+    ctx.save();
+    ctx.fillStyle=c;
+
+    // FOX — springing trot near right shore
+    (()=>{
+      const cyc=(beat*1.3)%6.283;
+      const hop=Math.max(0,Math.sin(cyc))*10; // springy up
+      const fx=760+Math.sin(beat*0.3)*10, fy=baseY-hop;
+      ctx.save();ctx.translate(px(fx),py(fy));
+      const u=pu(1);
+      ctx.globalAlpha=0.95;
+      // body
+      ctx.beginPath();ctx.ellipse(0,0,10*u,5*u,Math.sin(cyc)*0.15,0,6.283);ctx.fill();
+      // head
+      ctx.beginPath();ctx.moveTo(9*u,-2*u);ctx.lineTo(16*u,-3*u);ctx.lineTo(9*u,3*u);ctx.closePath();ctx.fill();
+      // ears
+      ctx.beginPath();ctx.moveTo(11*u,-4*u);ctx.lineTo(12*u,-9*u);ctx.lineTo(14*u,-4*u);ctx.closePath();ctx.fill();
+      // bushy tail sweeps opposite the spring (weight)
+      ctx.save();ctx.translate(-9*u,0);ctx.rotate(-0.6+Math.sin(cyc)*0.5);
+      ctx.beginPath();ctx.ellipse(-7*u,0,9*u,3.5*u,0,0,6.283);ctx.fill();ctx.restore();
+      // legs — trotting
+      ctx.strokeStyle=c;ctx.lineWidth=1.6*u;ctx.lineCap='round';
+      for(let l=0;l<4;l++){const lx=(-6+l*4)*u;const sw=Math.sin(cyc+l*1.6)*3*u;
+        ctx.beginPath();ctx.moveTo(lx,4*u);ctx.lineTo(lx+sw,10*u);ctx.stroke();}
+      ctx.restore();
+    })();
+
+    // HERON — slow neck fold, standing in shallows left
+    (()=>{
+      const cyc=(beat*0.5);
+      const fold=(Math.sin(cyc)+1)/2; // 0..1 slow
+      const hx=210, hy=baseY-2;
+      ctx.save();ctx.translate(px(hx),py(hy));const u=pu(1);
+      ctx.globalAlpha=0.95;
+      // legs
+      ctx.strokeStyle=c;ctx.lineWidth=1.4*u;ctx.lineCap='round';
+      ctx.beginPath();ctx.moveTo(-2*u,0);ctx.lineTo(-2*u,14*u);
+      ctx.moveTo(3*u,0);ctx.lineTo(3*u+Math.sin(cyc*1.2)*u,14*u);ctx.stroke();
+      // body
+      ctx.beginPath();ctx.ellipse(0,-2*u,8*u,5*u,0,0,6.283);ctx.fill();
+      // neck — folds down and up slowly (S-curve)
+      ctx.strokeStyle=c;ctx.lineWidth=2.2*u;
+      const neckTop=-2- (1-fold)*10;
+      ctx.beginPath();
+      ctx.moveTo(4*u,-4*u);
+      ctx.quadraticCurveTo((8+fold*4)*u,(neckTop-4)*u,(6)*u,(neckTop-8)*u);
+      ctx.stroke();
+      // head + beak
+      ctx.beginPath();ctx.arc(6*u,(neckTop-9)*u,2.4*u,0,6.283);ctx.fill();
+      ctx.beginPath();ctx.moveTo(8*u,(neckTop-9)*u);ctx.lineTo(14*u,(neckTop-8)*u);ctx.lineTo(8*u,(neckTop-7.5)*u);ctx.closePath();ctx.fill();
+      ctx.restore();
+    })();
+
+    // RABBIT — double-hop mid shore
+    (()=>{
+      const cyc=(beat*1.6)%6.283;
+      // two quick hops then pause: shape via piecewise
+      const ph=cyc/6.283;
+      let hop=0, fwd=0;
+      if(ph<0.3){hop=Math.sin(ph/0.3*3.14)*8; fwd=ph/0.3*6;}
+      else if(ph<0.55){hop=Math.sin((ph-0.3)/0.25*3.14)*8; fwd=6+ (ph-0.3)/0.25*6;}
+      else {fwd=12;}
+      const rx=540+ (fwd)-6 +Math.sin(beat*0.2)*4, ry=baseY-hop;
+      ctx.save();ctx.translate(px(rx),py(ry));const u=pu(1);ctx.globalAlpha=0.95;
+      // body
+      ctx.beginPath();ctx.ellipse(0,0,7*u,5*u,0,0,6.283);ctx.fill();
+      // head
+      ctx.beginPath();ctx.arc(6*u,-2*u,3.2*u,0,6.283);ctx.fill();
+      // ears — lean back with hop weight
+      const el=-0.3-hop*0.03;
+      ctx.save();ctx.translate(6*u,-4*u);ctx.rotate(el);
+      ctx.beginPath();ctx.ellipse(-1*u,-4*u,1.4*u,4.5*u,0,0,6.283);ctx.fill();
+      ctx.beginPath();ctx.ellipse(2*u,-4*u,1.4*u,4.5*u,0,0,6.283);ctx.fill();ctx.restore();
+      // hind legs tuck
+      ctx.beginPath();ctx.ellipse(-4*u,3*u,3*u,2*u,0,0,6.283);ctx.fill();
+      // tail
+      ctx.beginPath();ctx.arc(-7*u,0,1.8*u,0,6.283);ctx.fill();
+      ctx.restore();
+    })();
+
+    // BEAR — slow, heavy, conducting fireflies (far left, larger)
+    (()=>{
+      const cyc=beat*0.8;
+      const bx=90, by=baseY+6;
+      ctx.save();ctx.translate(px(bx),py(by));const u=pu(1.5);ctx.globalAlpha=0.96;
+      // heavy body sways with weight
+      const sway=Math.sin(cyc*0.5)*0.06;
+      ctx.rotate(sway);
+      ctx.beginPath();ctx.ellipse(0,-2*u,11*u,9*u,0,0,6.283);ctx.fill();
+      // haunch
+      ctx.beginPath();ctx.ellipse(-8*u,2*u,6*u,6*u,0,0,6.283);ctx.fill();
+      // head
+      ctx.beginPath();ctx.arc(8*u,-8*u,5*u,0,6.283);ctx.fill();
+      ctx.beginPath();ctx.arc(4*u,-13*u,2*u,0,6.283);ctx.fill();
+      ctx.beginPath();ctx.arc(11*u,-13*u,2*u,0,6.283);ctx.fill();
+      // conducting arm — slow figure-eight (weighted, ease in/out)
+      const ax=Math.sin(cyc)*8, ay=Math.sin(cyc*2)*4;
+      ctx.strokeStyle=c;ctx.lineWidth=3.5*u;ctx.lineCap='round';
+      ctx.beginPath();ctx.moveTo(8*u,-4*u);ctx.lineTo((10+ax)*u,(-10+ay)*u);ctx.stroke();
+      // baton glimmer
+      ctx.strokeStyle=P('lantern');ctx.lineWidth=1.4*u;ctx.globalAlpha=0.9;
+      ctx.beginPath();ctx.moveTo((10+ax)*u,(-10+ay)*u);ctx.lineTo((14+ax*1.4)*u,(-14+ay*1.4)*u);ctx.stroke();
+      // legs
+      ctx.strokeStyle=c;ctx.lineWidth=4*u;ctx.globalAlpha=1;
+      const step=Math.sin(cyc*0.5)*2*u;
+      ctx.beginPath();ctx.moveTo(-4*u,7*u);ctx.lineTo(-4*u+step,13*u);
+      ctx.moveTo(5*u,7*u);ctx.lineTo(5*u-step,13*u);ctx.stroke();
+      ctx.restore();
+    })();
+
+    ctx.restore();
+  }
+
+  function drawFireflies(){
+    if(!S.animals) return;
+    ctx.save();
+    for(const f of FIREFLIES){
+      // gather toward the bear's baton region, orbiting (conducted)
+      const t=S.t*f.sp + f.ph;
+      const cx=110 + Math.cos(t)*40*(0.5+f.x); // near bear left
+      const cy=390 + Math.sin(t*1.3)*30*(0.4+f.y);
+      const glow=0.4+0.6*Math.abs(Math.sin(t*2));
+      ctx.globalAlpha=glow*0.9;
+      ctx.fillStyle=P('lantern');
+      ctx.beginPath();ctx.arc(px(cx),py(cy),pu(1.5+glow),0,6.283);ctx.fill();
+    }
+    ctx.restore();
+  }
+
+  // ---- Main compose ----
+  function frame(){
+    setupProjection();
+    // trails: instead of clearing fully, overlay a translucent wash
+    const wash = 1 - S.trails*0.75; // more trails => less clearing
+    if(S.reduced || S.trails<=0.01){
+      ctx.globalAlpha=1; ctx.globalCompositeOperation='source-over';
+      ctx.fillStyle='#0a0407'; ctx.fillRect(0,0,W,H);
+    } else {
+      ctx.globalAlpha=1; ctx.globalCompositeOperation='source-over';
+      ctx.fillStyle=`rgba(10,4,7,${wash})`;
+      ctx.fillRect(0,0,W,H);
+    }
+
+    // slight overhead tilt: skew the whole scene vertically when TILT>0
+    ctx.save();
+    if(TILT>0.001){
+      ctx.translate(W/2,H*0.5);
+      ctx.transform(1,0,0,1-TILT*0.35, 0, TILT*40);
+      ctx.translate(-W/2,-H*0.5);
+    }
+
+    drawSky();
+    drawCanyon();
+    drawWater();
+
+    const g=boatGeom();
+    drawReflection(g);
+    drawPaddlewheel(g);
+    drawHull(g);
+    drawDeckStructure(g);
+    drawLanterns(g);
+    drawDancers(g);
+
+    // shoreline layer sits in front of water at bottom
+    drawAnimals();
+    drawFireflies();
+
+    // vignette
+    ctx.restore();
+    const vg=ctx.createRadialGradient(W/2,H*0.5,H*0.2,W/2,H*0.5,H*0.85);
+    vg.addColorStop(0,'rgba(0,0,0,0)');vg.addColorStop(1,'rgba(0,0,0,0.55)');
+    ctx.globalCompositeOperation='source-over';ctx.globalAlpha=1;
+    ctx.fillStyle=vg;ctx.fillRect(0,0,W,H);
+  }
+
+  // ---- Loop with real-time clock but authored advance ----
+  let last=performance.now();
+  function loop(now){
+    const dt=Math.min(0.05,(now-last)/1000); last=now;
+    // ease camera & chapter blends
+    S.camBlend += (S.cam - S.camBlend)*Math.min(1,dt*3.5);
+    S.chapBlend += (S.chapter - S.chapBlend)*Math.min(1,dt*2.2);
+    if(S.playing){
+      const speed = S.tempo * (S.reduced?0.35:1);
+      S.t += dt*speed;
+    }
+    frame();
+    requestAnimationFrame(loop);
+  }
+
+  // ---- Controls wiring ----
+  const $=id=>document.getElementById(id);
+  const tempo=$('tempo'),lantern=$('lantern'),crowd=$('crowd'),trails=$('trails');
+  const tempoV=$('tempoV'),lanternV=$('lanternV'),crowdV=$('crowdV'),trailsV=$('trailsV');
+  function crowdLabel(v){return v>85?'Full':v>55?'Lively':v>30?'Sparse':'Quiet';}
+  tempo.addEventListener('input',()=>{S.tempo=tempo.value/100;tempoV.textContent=S.tempo.toFixed(1)+'×';tempo.setAttribute('aria-valuetext',S.tempo.toFixed(1)+' times speed');});
+  lantern.addEventListener('input',()=>{S.lantern=lantern.value/100;lanternV.textContent=lantern.value+'%';});
+  crowd.addEventListener('input',()=>{S.crowd=crowd.value/100;crowdV.textContent=crowdLabel(+crowd.value);});
+  trails.addEventListener('input',()=>{S.trails=trails.value/100;trailsV.textContent=trails.value+'%';});
+
+  const animalsT=$('animalsT'),motionT=$('motionT');
+  animalsT.addEventListener('click',()=>{S.animals=!S.animals;animalsT.setAttribute('aria-pressed',S.animals);});
+  motionT.addEventListener('click',()=>{
+    S.reduced=!S.reduced;motionT.setAttribute('aria-pressed',S.reduced);
+  });
+
+  // Cameras
+  const camBtns=[...document.querySelectorAll('.cams button')];
+  function setCam(i){S.cam=i;camBtns.forEach((b,k)=>b.setAttribute('aria-current',k===i));}
+  camBtns.forEach((b,i)=>b.addEventListener('click',()=>setCam(i)));
+
+  // Chapters
+  const chapBtns=[...document.querySelectorAll('.chapters button')];
+  const chapline=$('chapline');
+  function setChapter(i){
+    S.chapter=i;
+    chapBtns.forEach((b,k)=>b.setAttribute('aria-current',k===i?'true':'false'));
+    chapline.style.opacity=0;
+    setTimeout(()=>{chapline.textContent=CHAPNAMES[i];chapline.style.opacity=1;},300);
+  }
+  chapBtns.forEach((b,i)=>b.addEventListener('click',()=>setChapter(i)));
+
+  // Play / pause
+  const playToggle=$('playToggle');
+  function setPlay(p){S.playing=p;playToggle.textContent=p?'❚❚':'►';
+    playToggle.setAttribute('aria-label',p?'Pause the scene':'Play the scene');}
+  playToggle.addEventListener('click',()=>setPlay(!S.playing));
+
+  // Restart
+  $('restart').addEventListener('click',()=>{S.t=0;setChapter(0);setPlay(true);});
+
+  // Panel toggle
+  const panel=$('panel'),panelToggle=$('panelToggle');
+  function setPanel(open){
+    panel.classList.toggle('hidden',!open);
+    panelToggle.setAttribute('aria-expanded',open);
+    panelToggle.setAttribute('aria-label',open?'Hide controls':'Show controls');
+  }
+  panelToggle.addEventListener('click',()=>setPanel(panel.classList.contains('hidden')));
+
+  // Keyboard shortcuts (respect typing in range via not interfering)
+  window.addEventListener('keydown',e=>{
+    if(e.target.tagName==='INPUT' && e.key!=='Escape') { /* allow slider arrows */ }
+    switch(e.key){
+      case ' ': case 'Spacebar': e.preventDefault(); setPlay(!S.playing); break;
+      case '1': setCam(0); break;
+      case '2': setCam(1); break;
+      case '3': setCam(2); break;
+      case 'ArrowRight': if(!/INPUT/.test(e.target.tagName)){setChapter(Math.min(2,S.chapter+1));} break;
+      case 'ArrowLeft': if(!/INPUT/.test(e.target.tagName)){setChapter(Math.max(0,S.chapter-1));} break;
+      case 'c': case 'C': setPanel(panel.classList.contains('hidden')); break;
+      case 'r': case 'R': S.t=0; setChapter(0); setPlay(true); break;
+    }
+  });
+
+  // Honor OS reduced-motion preference at load
+  if(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches){
+    S.reduced=true; motionT.setAttribute('aria-pressed','true');
+    S.trails=0; trails.value=0; trailsV.textContent='0%';
+  }
+
+  resize();
+  requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/cd83de847d24/claude-code.png b/data/artifacts/cd83de847d24/claude-code.png
index 5089113..fa6a231 100644
Binary files a/data/artifacts/cd83de847d24/claude-code.png and b/data/artifacts/cd83de847d24/claude-code.png differ
diff --git a/data/artifacts/cd83de847d24/qwen25-7b.html b/data/artifacts/cd83de847d24/qwen25-7b.html
new file mode 100644
index 0000000..be781dc
--- /dev/null
+++ b/data/artifacts/cd83de847d24/qwen25-7b.html
@@ -0,0 +1,182 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Scarlet Riverboat Masquerade</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+
+        body {
+            overflow: hidden;
+            font-family: 'Courier New', Courier, monospace;
+        }
+
+        .gallery-container {
+            width: 100vw;
+            height: 100vh;
+            perspective: 500px;
+        }
+
+        .layer {
+            position: absolute;
+            will-change: transform;
+        }
+
+        #river, #deck, #overhead {
+            z-index: -1;
+            transition: opacity 2s ease-in-out, transform 3s;
+        }
+
+        #river {
+            width: 100%;
+            height: 50vh;
+            background-image: linear-gradient(to bottom, crimson, darkred);
+            filter: blur(5px);
+            animation: riverFlow 30s infinite linear;
+        }
+
+        #deck {
+            width: 80vw;
+            height: 20vh;
+            background-color: black;
+            transform-origin: center top;
+            animation: deckSpin 60s infinite linear;
+        }
+
+        #overhead {
+            width: 100%;
+            height: 50vh;
+            background-image: radial-gradient(circle, crimson, transparent);
+            filter: blur(2px);
+            opacity: 0.7;
+            animation: overheadLight 30s infinite ease-in-out;
+        }
+
+        .animal-loop {
+            position: absolute;
+            width: 10%;
+            height: 10%;
+            z-index: -5;
+            animation: walk 10s linear infinite;
+        }
+
+        .animal-rabbit, .animal-fox, .animal-heron, .animal-bear {
+            transform-origin: center bottom;
+            animation-play-state: running;
+        }
+
+        @keyframes riverFlow {
+            0% { transform: translateX(0); }
+            100% { transform: translateX(-100vw); }
+        }
+
+        @keyframes deckSpin {
+            0% { transform: rotateY(0deg); }
+            100% { transform: rotateY(360deg); }
+        }
+
+        @keyframes overheadLight {
+            0%, 100% { opacity: 0.7; }
+            50% { opacity: 0.2; }
+        }
+
+        @keyframes walk {
+            0% { transform: translateY(0px) scale(1); }
+            50% { transform: translateY(-30px) scale(0.9); }
+            100% { transform: translateY(0px) scale(1); }
+        }
+
+        .label-note {
+            position: absolute;
+            bottom: 20px;
+            left: 20px;
+            font-size: 14px;
+            color: white;
+        }
+    </style>
+</head>
+<body>
+
+<div class="gallery-container" tabindex="0">
+    <div id="river" class="layer"></div>
+    <div id="deck" class="layer">
+        <div class="animal-loop animal-rabbit" style="left: 20%; top: 15%; transform: rotate(45deg) scale(0.8); background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60"%3E%3Cpath fill="%23fff" d="M15 47c9.58 0 16-7.42 16-16S24.58 13 34 13s16 7.42 16 16-7.42 16-16 16zm19-29c0-2.21-1.79-4-4-4s-4 1.79-4 4 1.79 4 4 4zm0 34c-5.53 0-10-4.47-10-10s4.47-10 10-10 10 4.47 10 10-4.47 10-10 10z" /%3E%3C/svg%3E')"></div>
+        <div class="animal-loop animal-fox" style="left: 45%; top: 20%; transform: rotate(-60deg) scale(0.8); background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60"%3E%3Cpath fill="%23fff" d="M45.9 18c-.7-1.4-1.8-2.6-3.1-3.3a11.9 11.9 0 0 0-3.9-1.5l-1.2-1A5.1 5.1 0 0 0 37.5 14c-.8-.6-2-.9-3.1-.8s-2.2.5-3.1.8a5.1 5.1 0 0 0-1.2 1.5l-1.2 1A12 12 0 0 1 24 19c-7 0-12.6 5.3-12.6 12s5.6 12 12.6 12c8.7 0 14.3-7.3 14.3-15.9a20.2 20.2 0 0 0-.6-5zm-1.4 5H34v4h-4V25z" /%3E%3C/svg%3E')"></div>
+        <div class="animal-loop animal-heron" style="left: 70%; top: 10%; transform: rotate(90deg) scale(0.8); background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60"%3E%3Cpath fill="%23fff" d="M47.8 19h-25c-.6 0-1 .4-1 1v22c0 .6.4 1 1 1h25c.6 0 1-.4 1-1V20c0-.6-.4-1-1-1zm-3 7a8.3 8.3 0 1 1 0-16 8.3 8.3 0 0 1 0 16zm-3 5h-12v2h12v-2z" /%3E%3C/svg%3E')"></div>
+        <div class="animal-loop animal-bear" style="left: 5%; top: 80%; transform: rotate(-90deg) scale(0.6); background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 60"%3E%3Cpath fill="%23fff" d="M41.5 39c-3 .7-5.8-.5-6.5-2.7a3 3 0 0 0-4.3-1.8L26.5 33H15.5l16.8-12.5a1 1 0 0 0-.9-1.1c-2.8-1.7-5.7-.8-7.5 1.4a3 3 0 0 0-2.5 3.9l-6 3v23h40v-23l-6-3a3 3 0 0 0-2.5-3.9c-1.8-2.7-.7-5.7 1.4-7.5z" /%3E%3C/svg%3E')"></div>
+    </div>
+    <div id="overhead" class="layer"></div>
+
+    <div onclick="toggleLayer('river', this)" tabindex="0">RIVER</div>
+    <div onclick="toggleLayer('deck', this)" tabindex="0">DECK</div>
+    <div onclick="toggleLayer('overhead', this)" tabindex="0">OVERHEAD</div>
+
+    <button onclick="changeChapter('Departure')">Departure</button>
+    <button onclick="changeChapter('Moon Bend')">Moon Bend</button>
+    <button onclick="changeChapter('Dawn Landing')">Dawn Landing</button>
+
+    <label class="label-note" for="gallery">Inspired by the communal spirit of Grateful Dead concerts, this gallery features a fantastical paddlewheel riverboat gliding through a crimson canyon at dusk. Masked dancers perform on three decks while animals celebrate along the shore.</label>
+</div>
+
+<script>
+    let layers = ['river', 'deck', 'overhead'];
+    const chapterMarkers = {
+        Departure: { palette: ['#ff004d', '#4c1f23'], deckSpinSpeed: 5, overheadLightSpeed: 6 },
+        MoonBend: { palette: ['#e01870', '#5b0d1a'], deckSpinSpeed: 3, overheadLightSpeed: 4 },
+        DawnLanding: { palette: ['#ba0f27', '#28090c'], deckSpinSpeed: 6, overheadLightSpeed: 2 }
+    };
+
+    function toggleLayer(layerId, event) {
+        const layer = document.getElementById(layerId);
+        if (layer.style.opacity === '0') {
+            layer.style.opacity = '1';
+            layer.style.transform = 'translateZ(-50px)';
+            event.setAttribute('aria-pressed', 'true');
+        } else {
+            layer.style.opacity = '0';
+            layer.style.transform = 'translateZ(-200px)';
+            event.setAttribute('aria-pressed', 'false');
+        }
+    }
+
+    function changeChapter(chapter) {
+        const currentLayer = document.getElementById(layers[1]);
+        if (currentLayer.style.opacity === '0') return;
+
+        layers.forEach(layer => document.getElementById(layer).style.opacity = '0');
+        setTimeout(() => {
+            for (let i = 0; i < layers.length; i++) {
+                const layer = document.getElementById(layers[i]);
+                layer.style.transform = `translateZ(-50px)`;
+                layer.style.opacity = '1';
+                if (layers[i] === chapter) break;
+            }
+        }, 200);
+
+        updateChapters(chapter);
+    }
+
+    function updateChapters(currentChapter) {
+        layers.forEach(layer => document.getElementById(layer).style.transform = `translateZ(-50px)`);
+
+        let palette = ['#ff004d', '#4c1f23'];
+        if (currentChapter in chapterMarkers) palette = chapterMarkers[currentChapter].palette;
+
+        const deck = document.getElementById('deck');
+        deck.style.animationDuration = `${chapterMarkers[currentChapter].deckSpinSpeed}s`;
+        
+        const overhead = document.getElementById('overhead');
+        overhead.style.animationDuration = `${chapterMarkers[currentChapter].overheadLightSpeed}s`;
+
+        palette.forEach(color => {
+            document.documentElement.style.setProperty('--' + color, color);
+        });
+    }
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/cd83de847d24/qwen25-7b.png b/data/artifacts/cd83de847d24/qwen25-7b.png
index a64603f..2cda1a7 100644
Binary files a/data/artifacts/cd83de847d24/qwen25-7b.png and b/data/artifacts/cd83de847d24/qwen25-7b.png differ
diff --git a/data/artifacts/cd83de847d24/qwen3-14b.html b/data/artifacts/cd83de847d24/qwen3-14b.html
new file mode 100644
index 0000000..c5edaca
--- /dev/null
+++ b/data/artifacts/cd83de847d24/qwen3-14b.html
@@ -0,0 +1,384 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Scarlet Riverboat Masquerade</title>
+  <style>
+    * {
+      box-sizing: border-box;
+      margin: 0;
+      padding: 0;
+    }
+    html, body {
+      height: 100%;
+      overflow: hidden;
+      font-family: 'Georgia', serif;
+      background-color: #1a1a1a;
+      color: #fff;
+      position: relative;
+    }
+    #gallery {
+      width: 100%;
+      height: 100%;
+      position: relative;
+      overflow: hidden;
+      background: linear-gradient(to bottom, #4a0033, #220011);
+    }
+    .layer {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      pointer-events: none;
+      will-change: transform;
+    }
+    .river {
+      background: radial-gradient(circle at 50% 50%, #a30029 0%, #6a0016 100%);
+      z-index: 1;
+    }
+    .boat {
+      position: absolute;
+      bottom: 50%;
+      left: 50%;
+      width: 20%;
+      height: 30%;
+      transform: translate(-50%, -50%) rotate(0deg);
+      z-index: 2;
+    }
+    .deck {
+      position: absolute;
+      bottom: 10%;
+      left: 20%;
+      width: 60%;
+      height: 30%;
+      z-index: 3;
+    }
+    .overhead {
+      position: absolute;
+      top: 20%;
+      left: 50%;
+      width: 100%;
+      height: 60%;
+      z-index: 4;
+    }
+    .mask {
+      position: absolute;
+      width: 100px;
+      height: 100px;
+      background: #ffffff22;
+      border-radius: 50%;
+      box-shadow: 0 0 10px #fff;
+      animation: rotate 4s linear infinite;
+      z-index: 5;
+    }
+    .circle {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      width: 200px;
+      height: 200px;
+      border: 2px solid #fff;
+      border-radius: 50%;
+      animation: spin 6s linear infinite;
+      z-index: 5;
+    }
+    .dancer {
+      position: absolute;
+      width: 50px;
+      height: 50px;
+      background: #ff3366;
+      border-radius: 50%;
+      animation: spin 3s linear infinite;
+      z-index: 6;
+    }
+    .animal {
+      position: absolute;
+      width: 80px;
+      height: 80px;
+      background: #fff;
+      border-radius: 50%;
+      box-shadow: 0 0 10px #fff;
+      animation: bounce 5s ease-in-out infinite;
+      z-index: 7;
+    }
+    .musician {
+      position: absolute;
+      width: 60px;
+      height: 60px;
+      background: #00000088;
+      border: 2px solid #fff;
+      border-radius: 50%;
+      animation: sway 8s ease-in-out infinite;
+      z-index: 8;
+    }
+    .lantern {
+      position: absolute;
+      width: 30px;
+      height: 30px;
+      background: #ffaa33;
+      border-radius: 50%;
+      box-shadow: 0 0 15px #ffaa33;
+      animation: pulse 3s ease-in-out infinite;
+      z-index: 9;
+    }
+    .note {
+      position: absolute;
+      bottom: 20px;
+      left: 20px;
+      background: #00000088;
+      padding: 10px;
+      border-radius: 8px;
+      font-size: 14px;
+      line-height: 1.4;
+      max-width: 70%;
+      z-index: 10;
+    }
+    .controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      background: #00000088;
+      padding: 10px;
+      border-radius: 8px;
+      z-index: 100;
+    }
+    .controls label {
+      display: block;
+      margin: 5px 0;
+    }
+    .controls input[type="range"] {
+      width: 100%;
+    }
+    .controls button {
+      margin-top: 10px;
+      padding: 6px 12px;
+      background: #ff3366;
+      color: #fff;
+      border: none;
+      border-radius: 5px;
+      cursor: pointer;
+      font-size: 14px;
+    }
+    .controls button:focus {
+      outline: 2px solid #ffaa33;
+    }
+    @keyframes rotate {
+      0% { transform: rotate(0deg); }
+      100% { transform: rotate(360deg); }
+    }
+    @keyframes spin {
+      0% { transform: rotate(0deg); }
+      100% { transform: rotate(360deg); }
+    }
+    @keyframes bounce {
+      0%, 100% { transform: translateY(0); }
+      50% { transform: translateY(-20px); }
+    }
+    @keyframes sway {
+      0% { transform: rotate(0deg); }
+      50% { transform: rotate(10deg); }
+      100% { transform: rotate(0deg); }
+    }
+    @keyframes pulse {
+      0%, 100% { opacity: 0.4; }
+      50% { opacity: 1; }
+    }
+  </style>
+</head>
+<body>
+  <div id="gallery">
+    <div class="layer river" id="river"></div>
+    <div class="layer boat" id="boat">
+      <div class="mask" style="left: 20%; top: 20%;"></div>
+      <div class="mask" style="left: 40%; top: 40%;"></div>
+      <div class="mask" style="left: 60%; top: 60%;"></div>
+      <div class="mask" style="left: 80%; top: 80%;"></div>
+    </div>
+    <div class="layer deck" id="deck">
+      <div class="circle" style="left: 50%; top: 50%;"></div>
+      <div class="dancer" style="left: 20%; top: 20%;"></div>
+      <div class="dancer" style="left: 40%; top: 40%;"></div>
+      <div class="dancer" style="left: 60%; top: 60%;"></div>
+      <div class="dancer" style="left: 80%; top: 80%;"></div>
+    </div>
+    <div class="layer overhead" id="overhead">
+      <div class="lantern" style="left: 10%; top: 10%;"></div>
+      <div class="lantern" style="left: 30%; top: 30%;"></div>
+      <div class="lantern" style="left: 50%; top: 50%;"></div>
+      <div class="lantern" style="left: 70%; top: 70%;"></div>
+      <div class="lantern" style="left: 90%; top: 90%;"></div>
+    </div>
+    <div class="layer" id="shore">
+      <div class="animal fox" style="left: 10%; top: 10%;"></div>
+      <div class="animal heron" style="left: 30%; top: 30%;"></div>
+      <div class="animal rabbit" style="left: 50%; top: 50%;"></div>
+      <div class="animal bear" style="left: 70%; top: 70%;"></div>
+    </div>
+    <div class="layer" id="music">
+      <div class="musician" style="left: 20%; top: 20%;"></div>
+      <div class="musician" style="left: 40%; top: 40%;"></div>
+      <div class="musician" style="left: 60%; top: 60%;"></div>
+      <div class="musician" style="left: 80%; top: 80%;"></div>
+    </div>
+    <div class="note">
+      <strong>Visual Language Note:</strong><br>
+      This gallery is inspired by the exploratory, communal spirit of Grateful Dead concerts, with a focus on visual rhythm, layered choreography, and surreal animation. No band logos or album art are used; instead, the experience is built through abstract silhouettes, masked figures, and natural elements in motion. Animations are carefully synchronized to create a sense of flow, tempo, and spatial harmony.
+    </div>
+    <div class="controls">
+      <label>Camera View:
+        <select id="camera" aria-label="Camera View">
+          <option value="river">Wide River</option>
+          <option value="deck">Deck</option>
+          <option value="overhead">Overhead</option>
+        </select>
+      </label>
+      <label>Visual Tempo:
+        <input type="range" id="tempo" min="1" max="4" step="0.1" value="2" aria-label="Visual Tempo">
+      </label>
+      <label>Lantern Intensity:
+        <input type="range" id="lantern" min="0" max="1" step="0.1" value="0.6" aria-label="Lantern Intensity">
+      </label>
+      <label>Crowd Density:
+        <input type="range" id="crowd" min="1" max="5" step="1" value="3" aria-label="Crowd Density">
+      </label>
+      <label>Animal Visibility:
+        <input type="range" id="animal" min="0" max="1" step="0.1" value="0.8" aria-label="Animal Visibility">
+      </label>
+      <label>Motion Trails:
+        <input type="range" id="motion" min="0" max="1" step="0.1" value="0.5" aria-label="Motion Trails">
+      </label>
+      <button id="pause" aria-label="Pause Animation">Pause</button>
+      <button id="restart" aria-label="Restart Animation">Restart</button>
+      <button id="reduce" aria-label="Enable Reduced Motion">Reduce Motion</button>
+    </div>
+  </div>
+
+  <script>
+    const gallery = document.getElementById('gallery');
+    const river = document.getElementById('river');
+    const boat = document.getElementById('boat');
+    const deck = document.getElementById('deck');
+    const overhead = document.getElementById('overhead');
+    const shore = document.getElementById('shore');
+    const music = document.getElementById('music');
+
+    const controls = {
+      camera: document.getElementById('camera'),
+      tempo: document.getElementById('tempo'),
+      lantern: document.getElementById('lantern'),
+      crowd: document.getElementById('crowd'),
+      animal: document.getElementById('animal'),
+      motion: document.getElementById('motion'),
+      pause: document.getElementById('pause'),
+      restart: document.getElementById('restart'),
+      reduce: document.getElementById('reduce')
+    };
+
+    const elements = {
+      dancers: deck.querySelectorAll('.dancer'),
+      masks: boat.querySelectorAll('.mask'),
+      circle: deck.querySelector('.circle'),
+      lanterns: overhead.querySelectorAll('.lantern'),
+      fox: shore.querySelector('.fox'),
+      heron: shore.querySelector('.heron'),
+      rabbit: shore.querySelector('.rabbit'),
+      bear: shore.querySelector('.bear'),
+      musicians: music.querySelectorAll('.musician')
+    };
+
+    let isPaused = false;
+    let reducedMotion = false;
+
+    function animate() {
+      if (isPaused) return;
+
+      // Update lanterns
+      elements.lanterns.forEach(lantern => {
+        lantern.style.opacity = controls.lantern.value;
+      });
+
+      // Update dancers
+      const tempo = parseFloat(controls.tempo.value);
+      elements.dancers.forEach(dancer => {
+        dancer.style.animationDuration = `${tempo}s`;
+      });
+
+      // Update circle rotation
+      elements.circle.style.animationDuration = `${tempo * 1.5}s`;
+
+      // Update animal visibility
+      elements.fox.style.opacity = controls.animal.value;
+      elements.heron.style.opacity = controls.animal.value;
+      elements.rabbit.style.opacity = controls.animal.value;
+      elements.bear.style.opacity = controls.animal.value;
+
+      // Update motion trails
+      elements.dancers.forEach(dancer => {
+        dancer.style.animationTimingFunction = controls.motion.value > 0.5 ? 'cubic-bezier(0.4, 0, 0.2, 1)' : 'linear';
+      });
+
+      // Update musicians (sway)
+      elements.musicians.forEach(musician => {
+        musician.style.animationDuration = `${tempo * 2}s`;
+      });
+    }
+
+    function updateCamera() {
+      const view = controls.camera.value;
+      if (view === 'river') {
+        river.style.transform = 'translateY(0)';
+        deck.style.transform = 'translateY(100%)';
+        overhead.style.transform = 'translateY(100%)';
+      } else if (view === 'deck') {
+        river.style.transform = 'translateY(-100%)';
+        deck.style.transform = 'translateY(0)';
+        overhead.style.transform = 'translateY(100%)';
+      } else if (view === 'overhead') {
+        river.style.transform = 'translateY(-100%)';
+        deck.style.transform = 'translateY(-100%)';
+        overhead.style.transform = 'translateY(0)';
+      }
+    }
+
+    function togglePause() {
+      isPaused = !isPaused;
+      controls.pause.textContent = isPaused ? 'Resume' : 'Pause';
+    }
+
+    function restart() {
+      isPaused = false;
+      controls.pause.textContent = 'Pause';
+      animate();
+    }
+
+    function reduceMotionToggle() {
+      reducedMotion = !reducedMotion;
+      controls.reduce.textContent = reducedMotion ? 'Enable Motion' : 'Reduce Motion';
+      elements.dancers.forEach(dancer => {
+        dancer.style.animationTimingFunction = reducedMotion ? 'linear' : 'cubic-bezier(0.4, 0, 0.2, 1)';
+      });
+    }
+
+    controls.camera.addEventListener('change', updateCamera);
+    controls.tempo.addEventListener('input', animate);
+    controls.lantern.addEventListener('input', animate);
+    controls.crowd.addEventListener('input', () => {
+      const count = parseInt(controls.crowd.value);
+      const max = 10;
+      elements.dancers.forEach((d, i) => {
+        d.style.display = i < count ? 'block' : 'none';
+      });
+    });
+    controls.animal.addEventListener('input', animate);
+    controls.motion.addEventListener('input', animate);
+    controls.pause.addEventListener('click', togglePause);
+    controls.restart.addEventListener('click', restart);
+    controls.reduce.addEventListener('click', reduceMotionToggle);
+
+    // Initial setup
+    updateCamera();
+    animate();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ec9e68a64f43/claude-code.html b/data/artifacts/ec9e68a64f43/claude-code.html
new file mode 100644
index 0000000..9b1817f
--- /dev/null
+++ b/data/artifacts/ec9e68a64f43/claude-code.html
@@ -0,0 +1,794 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Golden Road Carnival of Mirrors — A Kinetic Poster-World</title>
+<style>
+  :root{
+    --ink:#f7e9c8; --paper:#0b0614; --gold:#f4b83a; --mag:#e0459b;
+    --teal:#38d6c0; --violet:#8a5cff; --edge:#26183a;
+    --focus:#7cf5ff;
+    --panel:#150a26cc;
+  }
+  *{box-sizing:border-box}
+  html,body{margin:0;padding:0;background:
+      radial-gradient(120% 90% at 50% -10%, #241041 0%, #120826 45%, #070311 100%);
+      color:var(--ink);font-family:"Trebuchet MS","Segoe UI",system-ui,sans-serif;}
+  a{color:var(--teal)}
+  .wrap{max-width:1400px;margin:0 auto;padding:18px clamp(10px,2.5vw,28px) 60px;}
+
+  /* ---------- Hand-lettered psychedelic title (original lettering) ---------- */
+  header.marquee{text-align:center;position:relative;padding:6px 0 10px;}
+  .kicker{letter-spacing:.42em;text-transform:uppercase;font-size:clamp(9px,1.6vw,13px);
+    color:var(--teal);opacity:.85;margin:0 0 4px;}
+  h1.title{margin:0;line-height:.92;font-weight:900;
+    font-size:clamp(30px,7.4vw,84px);
+    display:flex;flex-wrap:wrap;justify-content:center;gap:.02em;
+    filter:drop-shadow(0 4px 0 #5b1741) drop-shadow(0 0 26px #e0459b55);}
+  h1.title span{display:inline-block;
+    background:linear-gradient(180deg,#fff1c4 0%,var(--gold) 38%,var(--mag) 78%,var(--violet) 100%);
+    -webkit-background-clip:text;background-clip:text;color:transparent;
+    text-shadow:0 0 1px #0000;transform-origin:50% 80%;}
+  .sub{margin:8px auto 0;max-width:760px;font-style:italic;color:#e9d7ff;opacity:.86;
+    font-size:clamp(12px,2vw,16px)}
+  .rays{position:absolute;inset:-30% -10% auto -10%;height:180%;z-index:-1;pointer-events:none;
+    background:repeating-conic-gradient(from 0deg at 50% 30%,
+      #f4b83a10 0deg 7deg,#0000 7deg 14deg);opacity:.5;mix-blend-mode:screen}
+
+  /* ---------- Stage ---------- */
+  .stagewrap{position:relative;margin-top:14px;border-radius:16px;overflow:hidden;
+    border:2px solid #3a2258;box-shadow:0 20px 60px #000a, inset 0 0 90px #1a0d33;}
+  canvas#stage{display:block;width:100%;height:auto;aspect-ratio:16/9;background:#0a0518;touch-action:none}
+  .phaseband{position:absolute;top:10px;left:12px;display:flex;gap:8px;align-items:center;
+    font-size:12px;letter-spacing:.18em;text-transform:uppercase;
+    background:#0b0620aa;border:1px solid #4a2f70;padding:6px 12px;border-radius:999px;
+    backdrop-filter:blur(4px)}
+  .phasedot{width:9px;height:9px;border-radius:50%;background:var(--gold);
+    box-shadow:0 0 12px var(--gold);animation:pulse 2.4s ease-in-out infinite}
+  @keyframes pulse{50%{transform:scale(1.5);opacity:.6}}
+  .badge{position:absolute;bottom:10px;right:12px;font-size:11px;letter-spacing:.12em;
+    color:#cbb9ff;background:#0b0620aa;border:1px solid #4a2f70;padding:5px 10px;border-radius:8px}
+
+  /* ---------- Controls ---------- */
+  .controls{margin-top:16px;display:grid;gap:14px;
+    grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
+    background:var(--panel);border:1px solid #3a2258;border-radius:14px;padding:16px;
+    backdrop-filter:blur(6px)}
+  fieldset{border:1px solid #40295f;border-radius:10px;margin:0;padding:10px 12px 12px}
+  legend{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold);padding:0 6px}
+  .ctl{display:flex;flex-direction:column;gap:5px;margin-top:6px}
+  .ctl label{font-size:12px;color:#e6dbff;display:flex;justify-content:space-between;gap:8px}
+  .ctl output{color:var(--teal);font-variant-numeric:tabular-nums}
+  input[type=range]{width:100%;accent-color:var(--mag);height:22px}
+  select,button{font-family:inherit;font-size:13px;color:var(--ink);
+    background:#1c1030;border:1px solid #5a3a86;border-radius:8px;padding:8px 10px;cursor:pointer}
+  .btnrow{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
+  button{transition:transform .08s ease, background .15s, box-shadow .15s;font-weight:700;letter-spacing:.04em}
+  button:hover{background:#2a1848;box-shadow:0 0 0 1px #7a52c0 inset}
+  button:active{transform:translateY(2px) scale(.98)}
+  button[aria-pressed=true]{background:linear-gradient(180deg,var(--mag),#7a1f5a);
+    border-color:var(--gold);box-shadow:0 0 16px #e0459b66}
+  .toggle{display:flex;gap:8px;align-items:center}
+  input[type=checkbox]{width:20px;height:20px;accent-color:var(--teal)}
+  /* strong focus states */
+  a:focus-visible,button:focus-visible,select:focus-visible,input:focus-visible,summary:focus-visible{
+    outline:3px solid var(--focus);outline-offset:2px;border-radius:6px}
+  input[type=range]:focus-visible{outline:3px solid var(--focus);outline-offset:4px}
+
+  .cameraseg{display:flex;gap:6px;flex-wrap:wrap}
+  .cameraseg button{flex:1 1 auto;min-width:88px}
+
+  /* curator note */
+  .note{margin-top:18px;display:grid;grid-template-columns:auto 1fr;gap:14px;align-items:start;
+    background:#100822cc;border:1px solid #3a2258;border-left:4px solid var(--gold);
+    border-radius:12px;padding:16px 18px}
+  .note h2{margin:0 0 6px;font-size:14px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold)}
+  .note p{margin:0;font-size:13.5px;line-height:1.6;color:#e7dcff;max-width:70ch}
+  .seal{width:54px;height:54px;border-radius:50%;background:
+     conic-gradient(from 0deg,var(--gold),var(--mag),var(--teal),var(--violet),var(--gold));
+     display:grid;place-items:center;color:#12061f;font-weight:900;font-size:11px;text-align:center;
+     box-shadow:0 0 18px #f4b83a55}
+  .legend-key{margin-top:10px;font-size:11.5px;color:#c9bbe6;display:flex;flex-wrap:wrap;gap:12px}
+  .legend-key span{display:inline-flex;align-items:center;gap:6px}
+  .swatch{width:12px;height:12px;border-radius:3px;display:inline-block}
+  .skip{position:absolute;left:-999px;top:0}
+  .skip:focus{left:8px;top:8px;z-index:9;background:#000;padding:8px 12px;border-radius:8px}
+  footer{margin-top:26px;text-align:center;font-size:11px;color:#8f7fb5;letter-spacing:.1em}
+  .sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
+</style>
+</head>
+<body>
+<a class="skip" href="#controls">Skip to controls</a>
+<div class="wrap">
+
+  <header class="marquee">
+    <div class="rays" aria-hidden="true"></div>
+    <p class="kicker">A Kinetic Paper-Cut Midway · Authored Visual Score</p>
+    <h1 class="title" aria-label="Golden Road Carnival of Mirrors" id="titleLettering"></h1>
+    <p class="sub">Striped tents, mirrored arches &amp; hand-lettered signs unfold in layers while an ensemble spins, trades reflections, and dances the long golden road home.</p>
+  </header>
+
+  <div class="stagewrap">
+    <canvas id="stage" role="img"
+      aria-label="Animated poster of a dreamlike carnival midway. A ring of dancers expands and contracts around a lit carousel, stilt walkers cross in counterpoint, people spin through mirror portals whose reflections lag slightly behind, and dancing animals — a shuffling bear, weaving dogs, fanning peacocks, swinging monkeys, and a patient tortoise — move through striped tents and mirrored arches. The scene cycles through three moods: Invitation, Mirror Jam, and Last Lantern."></canvas>
+    <div class="phaseband"><span class="phasedot" aria-hidden="true"></span><span id="phaseName">Invitation</span></div>
+    <div class="badge" id="fpsBadge">score · running</div>
+  </div>
+
+  <p class="sr-only" role="status" aria-live="polite" id="live"></p>
+
+  <section class="controls" id="controls" aria-label="Choreography controls">
+    <fieldset>
+      <legend>Camera Route</legend>
+      <div class="cameraseg" role="group" aria-label="Choose a camera route">
+        <button type="button" data-cam="0" aria-pressed="true">Promenade</button>
+        <button type="button" data-cam="1" aria-pressed="false">Carousel Orbit</button>
+        <button type="button" data-cam="2" aria-pressed="false">Lantern Rise</button>
+      </div>
+      <div class="ctl" style="margin-top:12px">
+        <label for="animal">Animal focus <output id="animalOut">Whole troupe</output></label>
+        <select id="animal">
+          <option value="all">Whole troupe</option>
+          <option value="bear">Bear · heel-toe shuffle</option>
+          <option value="dogs">Dogs · weaving the legs</option>
+          <option value="peacocks">Peacocks · fan patterns</option>
+          <option value="monkeys">Monkeys · sign-to-sign</option>
+          <option value="tortoise">Tortoise · patient crossing</option>
+        </select>
+      </div>
+    </fieldset>
+
+    <fieldset>
+      <legend>Motion</legend>
+      <div class="ctl">
+        <label for="tempo">Tempo <output id="tempoOut">1.00×</output></label>
+        <input id="tempo" type="range" min="0" max="220" value="100">
+      </div>
+      <div class="ctl">
+        <label for="delay">Reflection delay <output id="delayOut">0.42 s</output></label>
+        <input id="delay" type="range" min="0" max="120" value="42">
+      </div>
+      <div class="ctl">
+        <label for="trail">Trail length <output id="trailOut">18</output></label>
+        <input id="trail" type="range" min="0" max="60" value="18">
+      </div>
+    </fieldset>
+
+    <fieldset>
+      <legend>Crowd &amp; Overlay</legend>
+      <div class="ctl">
+        <label for="density">Crowd density <output id="densityOut">medium</output></label>
+        <input id="density" type="range" min="0" max="100" value="55">
+      </div>
+      <div class="toggle" style="margin-top:12px">
+        <input type="checkbox" id="diagram">
+        <label for="diagram">Diagram overlay — expose each choreography loop</label>
+      </div>
+      <div class="toggle" style="margin-top:10px">
+        <input type="checkbox" id="reduced">
+        <label for="reduced">Reduced motion</label>
+      </div>
+    </fieldset>
+
+    <fieldset>
+      <legend>Transport</legend>
+      <div class="btnrow">
+        <button type="button" id="playBtn" aria-pressed="true">⏸ Pause</button>
+        <button type="button" id="restartBtn">↺ Restart</button>
+      </div>
+      <div class="legend-key" aria-hidden="true">
+        <span><i class="swatch" style="background:var(--gold)"></i>ring dance</span>
+        <span><i class="swatch" style="background:var(--teal)"></i>stilts</span>
+        <span><i class="swatch" style="background:var(--mag)"></i>portals</span>
+        <span><i class="swatch" style="background:var(--violet)"></i>animals</span>
+      </div>
+    </fieldset>
+  </section>
+
+  <section class="note" aria-label="Curator note">
+    <div class="seal" aria-hidden="true">GRC<br>M</div>
+    <div>
+      <h2>Curator Note</h2>
+      <p><strong>This is an authored visual score, not audio-reactive analysis.</strong> Every motion — the breathing ring around the carousel, the counterpoint of the stilt walkers, the slightly-late echoes in the mirror portals, the bear's weighted heel-toe, the tortoise's collision-free line — is composed by hand as a repeating choreography. Nothing here listens to sound; the rhythm is written into the geometry. The three movements <em>Invitation → Mirror Jam → Last Lantern</em> shift color, scale and energy, then return seamlessly to the beginning, so the poster can loop forever like a lantern turning on the golden road. Lettering and ornament are drawn fresh in a psychedelic concert-poster spirit — no existing marks are reproduced.</p>
+    </div>
+  </section>
+
+  <footer>Golden Road Carnival of Mirrors · a single-file kinetic artwork · open the diagram overlay to read the loops</footer>
+</div>
+
+<script>
+(() => {
+"use strict";
+
+/* ---------- hand-letter the title with playful per-letter warp ---------- */
+(() => {
+  const txt = "Golden Road Carnival of Mirrors";
+  const el = document.getElementById('titleLettering');
+  [...txt].forEach((ch,i) => {
+    const s = document.createElement('span');
+    s.textContent = ch === ' ' ? '\u00A0\u00A0' : ch;
+    const rot = Math.sin(i*0.9)*6;
+    const sk = Math.cos(i*0.7)*4;
+    s.style.transform = `rotate(${rot}deg) skewX(${sk}deg)`;
+    el.appendChild(s);
+  });
+})();
+
+/* ---------- setup ---------- */
+const cv = document.getElementById('stage');
+const ctx = cv.getContext('2d');
+const WORLD_W = 1600, WORLD_H = 900;
+let dpr = Math.min(window.devicePixelRatio || 1, 2);
+let cssW = 0, cssH = 0;
+
+function resize(){
+  const r = cv.getBoundingClientRect();
+  cssW = r.width; cssH = r.height;
+  dpr = Math.min(window.devicePixelRatio || 1, 2);
+  cv.width = Math.round(cssW*dpr);
+  cv.height = Math.round(cssH*dpr);
+}
+new ResizeObserver(resize).observe(cv);
+resize();
+
+/* ---------- state ---------- */
+const state = {
+  cam: 0, tempo: 1, delay: 0.42, trail: 18, density: 0.55,
+  animal: 'all', diagram: false, reduced: false, playing: true,
+};
+let clock = 0;              // choreography time in "beats"
+let last = performance.now();
+const trails = new Map();   // id -> [{x,y}...]
+
+/* respect OS reduced motion at load */
+if(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches){
+  state.reduced = true; document.getElementById('reduced').checked = true;
+}
+
+/* ---------- palettes per movement ---------- */
+const PH = [
+  { name:'Invitation', bg1:'#1a0b30', bg2:'#0a0518', glow:'#f4b83a',
+    tentA:'#e0459b', tentB:'#f4b83a', ink:'#ffe9b8', accent:'#38d6c0', energy:0.72, warmth:1 },
+  { name:'Mirror Jam', bg1:'#2a0a44', bg2:'#0c0322', glow:'#8a5cff',
+    tentA:'#38d6c0', tentB:'#e0459b', ink:'#eafcff', accent:'#8a5cff', energy:1.15, warmth:0.35 },
+  { name:'Last Lantern', bg1:'#2b1338', bg2:'#0b0410', glow:'#ff7a3c',
+    tentA:'#ff7a3c', tentB:'#b7285f', ink:'#ffd9a8', accent:'#ffb04a', energy:0.6, warmth:1.25 },
+];
+const PHASE_LEN = 14;                    // beats per movement
+const CYCLE = PHASE_LEN*PH.length;
+
+function hexToRgb(h){const n=parseInt(h.slice(1),16);return[n>>16&255,n>>8&255,n&255];}
+function mix(a,b,t){const A=hexToRgb(a),B=hexToRgb(b);
+  return `rgb(${Math.round(A[0]+(B[0]-A[0])*t)},${Math.round(A[1]+(B[1]-A[1])*t)},${Math.round(A[2]+(B[2]-A[2])*t)})`;}
+function mixN(...args){/* unused */}
+
+function phaseAt(t){
+  const f = ((t % CYCLE)+CYCLE)%CYCLE / PHASE_LEN;  // 0..3
+  const i = Math.floor(f), frac = f - i;
+  const a = PH[i%3], b = PH[(i+1)%3];
+  // ease crossfade
+  const e = frac*frac*(3-2*frac);
+  return {
+    idx:i%3, frac,
+    name:a.name,
+    bg1:mix(a.bg1,b.bg1,e), bg2:mix(a.bg2,b.bg2,e),
+    glow:mix(a.glow,b.glow,e), tentA:mix(a.tentA,b.tentA,e), tentB:mix(a.tentB,b.tentB,e),
+    ink:mix(a.ink,b.ink,e), accent:mix(a.accent,b.accent,e),
+    energy:a.energy+(b.energy-a.energy)*e,
+    warmth:a.warmth+(b.warmth-a.warmth)*e,
+  };
+}
+
+/* ---------- camera routes ---------- */
+function camera(t){
+  const R = ((t)*0.5);
+  if(state.cam===0){ // Promenade: slow lateral sweep
+    return { cx: WORLD_W/2 + Math.sin(R*0.5)*260, cy: WORLD_H*0.52, zoom: 1.02 + Math.sin(R*0.33)*0.05 };
+  } else if(state.cam===1){ // Carousel orbit
+    return { cx: WORLD_W/2 + Math.cos(R*0.6)*220, cy: WORLD_H*0.48 + Math.sin(R*0.6)*90, zoom: 1.32 + Math.sin(R*0.4)*0.12 };
+  } else { // Lantern rise
+    const rise = (Math.sin(R*0.35)*0.5+0.5);
+    return { cx: WORLD_W/2 + Math.sin(R*0.2)*120, cy: WORLD_H*(0.62-0.30*rise), zoom: 1.1 + rise*0.5 };
+  }
+}
+
+/* transform helper */
+function applyCam(cam){
+  const scale = (cssW/WORLD_W)*cam.zoom;
+  ctx.setTransform(dpr*scale,0,0,dpr*scale,
+     dpr*(cssW/2 - cam.cx*scale), dpr*(cssH/2 - cam.cy*scale));
+  return scale;
+}
+
+/* ---------- figure drawing primitives (paper-cut) ---------- */
+function person(x,y,s,rot,col,limb,tall){
+  ctx.save(); ctx.translate(x,y); ctx.rotate(rot); ctx.scale(s,s);
+  const legL = tall? 46 : 22;
+  const sw = Math.sin(limb), sw2 = Math.sin(limb+Math.PI);
+  ctx.strokeStyle=col; ctx.lineWidth=tall?4.5:6; ctx.lineCap='round';
+  // legs
+  ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(sw*8, legL); ctx.moveTo(0,0); ctx.lineTo(sw2*8, legL); ctx.stroke();
+  // torso (paper triangle skirt)
+  ctx.fillStyle=col; ctx.beginPath();
+  ctx.moveTo(-9,-18); ctx.lineTo(9,-18); ctx.lineTo(6,2); ctx.lineTo(-6,2); ctx.closePath(); ctx.fill();
+  // arms
+  ctx.beginPath(); ctx.moveTo(0,-14); ctx.lineTo(sw2*15,-6); ctx.moveTo(0,-14); ctx.lineTo(sw*15,-6); ctx.stroke();
+  // head
+  ctx.beginPath(); ctx.arc(0,-24,6.5,0,7); ctx.fill();
+  ctx.restore();
+}
+
+function stiltWalker(x,y,s,rot,col,limb){
+  ctx.save(); ctx.translate(x,y); ctx.rotate(rot); ctx.scale(s,s);
+  const sw=Math.sin(limb);
+  ctx.strokeStyle=col; ctx.lineWidth=4; ctx.lineCap='round';
+  ctx.beginPath(); ctx.moveTo(-4,-30); ctx.lineTo(-4+sw*6,60); ctx.moveTo(4,-30); ctx.lineTo(4-sw*6,60); ctx.stroke();
+  ctx.fillStyle=col;
+  ctx.beginPath(); ctx.moveTo(-11,-46); ctx.lineTo(11,-46); ctx.lineTo(6,-28); ctx.lineTo(-6,-28); ctx.closePath(); ctx.fill();
+  ctx.beginPath(); ctx.moveTo(0,-40); ctx.lineTo(sw*20,-52); ctx.moveTo(0,-40); ctx.lineTo(-sw*20,-52); ctx.stroke();
+  ctx.beginPath(); ctx.arc(0,-54,7,0,7); ctx.fill();
+  // little flag topper
+  ctx.fillStyle='#fff2c0'; ctx.beginPath(); ctx.moveTo(0,-61); ctx.lineTo(10,-58); ctx.lineTo(0,-55); ctx.closePath(); ctx.fill();
+  ctx.restore();
+}
+
+function bear(x,y,s,limb,col,hi){
+  ctx.save(); ctx.translate(x,y); ctx.scale(s,s);
+  const rock=Math.sin(limb)*0.14; ctx.rotate(rock);
+  const foot=Math.sin(limb*2); // heel-toe lift
+  ctx.fillStyle=col;
+  ctx.beginPath(); ctx.ellipse(0,-6,26,30,0,0,7); ctx.fill();     // body
+  ctx.beginPath(); ctx.arc(0,-40,17,0,7); ctx.fill();             // head
+  ctx.beginPath(); ctx.arc(-13,-52,7,0,7); ctx.arc(13,-52,7,0,7); ctx.fill(); // ears
+  // muzzle
+  ctx.fillStyle=hi; ctx.beginPath(); ctx.ellipse(0,-36,8,6,0,0,7); ctx.fill();
+  ctx.fillStyle='#1a0d0a'; ctx.beginPath(); ctx.arc(0,-40,2.4,0,7); ctx.fill();
+  // heel-toe feet
+  ctx.fillStyle=col;
+  ctx.beginPath(); ctx.ellipse(-13,24-foot*7,11,7,0,0,7); ctx.fill();
+  ctx.beginPath(); ctx.ellipse(13,24+foot*7,11,7,0,0,7); ctx.fill();
+  // arms swinging
+  ctx.strokeStyle=col; ctx.lineWidth=10; ctx.lineCap='round';
+  ctx.beginPath(); ctx.moveTo(-18,-8); ctx.lineTo(-30,6+foot*5); ctx.moveTo(18,-8); ctx.lineTo(30,6-foot*5); ctx.stroke();
+  ctx.restore();
+}
+
+function dog(x,y,s,limb,col){
+  ctx.save(); ctx.translate(x,y); ctx.scale(s,s);
+  const g=Math.sin(limb*3);
+  ctx.fillStyle=col;
+  ctx.beginPath(); ctx.ellipse(0,0,18,9,0,0,7); ctx.fill();      // body
+  ctx.beginPath(); ctx.ellipse(16,-6,8,7,0,0,7); ctx.fill();     // head
+  ctx.beginPath(); ctx.moveTo(20,-12);ctx.lineTo(24,-4);ctx.lineTo(18,-4);ctx.closePath();ctx.fill(); // ear
+  ctx.strokeStyle=col; ctx.lineWidth=4; ctx.lineCap='round';
+  ctx.beginPath(); ctx.moveTo(-10,6);ctx.lineTo(-10+g*4,16); ctx.moveTo(8,6);ctx.lineTo(8-g*4,16); ctx.stroke();
+  ctx.beginPath(); ctx.moveTo(-16,-2);ctx.quadraticCurveTo(-26,-8,-22,2); ctx.stroke(); // tail
+  ctx.restore();
+}
+
+function peacock(x,y,s,open,col,accent){
+  ctx.save(); ctx.translate(x,y); ctx.scale(s,s);
+  // fan
+  const N=11, spread=(0.15+open*0.85)*1.5;
+  for(let i=0;i<N;i++){
+    const a=(i/(N-1)-0.5)*spread - Math.PI/2;
+    ctx.strokeStyle= i%2? accent: col; ctx.lineWidth=3;
+    ctx.beginPath(); ctx.moveTo(0,0);
+    const ex=Math.cos(a)*46*(0.7+open*0.5), ey=Math.sin(a)*46*(0.7+open*0.5);
+    ctx.lineTo(ex,ey); ctx.stroke();
+    ctx.fillStyle= i%2? col:accent; ctx.beginPath(); ctx.arc(ex,ey,4.5,0,7); ctx.fill();
+    ctx.fillStyle='#0a0518'; ctx.beginPath(); ctx.arc(ex,ey,1.8,0,7); ctx.fill();
+  }
+  // body
+  ctx.fillStyle=col; ctx.beginPath(); ctx.ellipse(0,6,8,13,0,0,7); ctx.fill();
+  ctx.beginPath(); ctx.arc(0,-8,6,0,7); ctx.fill();
+  ctx.strokeStyle=col;ctx.lineWidth=3; ctx.beginPath();ctx.moveTo(0,18);ctx.lineTo(-4,28);ctx.moveTo(0,18);ctx.lineTo(4,28);ctx.stroke();
+  ctx.restore();
+}
+
+function monkey(x,y,s,swing,col){
+  ctx.save(); ctx.translate(x,y); ctx.rotate(swing*0.5); ctx.scale(s,s);
+  ctx.fillStyle=col;
+  ctx.beginPath(); ctx.ellipse(0,0,10,14,0,0,7); ctx.fill();  // body
+  ctx.beginPath(); ctx.arc(0,-18,8,0,7); ctx.fill();          // head
+  ctx.strokeStyle=col; ctx.lineWidth=4; ctx.lineCap='round';
+  // arms reaching up (holding sign line)
+  ctx.beginPath(); ctx.moveTo(-6,-14);ctx.lineTo(-14,-30); ctx.moveTo(6,-14);ctx.lineTo(14,-30); ctx.stroke();
+  // legs
+  ctx.beginPath(); ctx.moveTo(-4,12);ctx.lineTo(-9,24); ctx.moveTo(4,12);ctx.lineTo(9,24); ctx.stroke();
+  // curly tail
+  ctx.beginPath(); ctx.moveTo(0,10); ctx.quadraticCurveTo(22,20,10,-4); ctx.stroke();
+  ctx.restore();
+}
+
+function tortoise(x,y,s,limb,col,accent){
+  ctx.save(); ctx.translate(x,y); ctx.scale(s,s);
+  const step=Math.sin(limb*2);
+  ctx.fillStyle=col;
+  ctx.beginPath(); ctx.ellipse(0,0,28,18,0,Math.PI,0); ctx.fill(); // shell
+  ctx.fillStyle=accent;
+  for(let i=-2;i<=2;i++){ ctx.beginPath(); ctx.ellipse(i*10,-4,4.5,6,0,0,7); ctx.fill(); }
+  ctx.fillStyle=col;
+  ctx.beginPath(); ctx.ellipse(30,2,9,6,0,0,7); ctx.fill();   // head
+  ctx.fillStyle='#0a0518'; ctx.beginPath(); ctx.arc(33,1,1.6,0,7); ctx.fill();
+  ctx.strokeStyle=col; ctx.lineWidth=5; ctx.lineCap='round';
+  ctx.beginPath(); ctx.moveTo(-16,6);ctx.lineTo(-18,12+step*3); ctx.moveTo(14,6);ctx.lineTo(16,12-step*3); ctx.stroke();
+  ctx.restore();
+}
+
+/* ---------- scenery: layered paper-cut midway ---------- */
+function stripedTent(x,y,w,h,cA,cB,n){
+  ctx.save();
+  for(let i=0;i<n;i++){
+    ctx.fillStyle= i%2? cA:cB;
+    const x0=x - w/2 + i*(w/n), x1=x - w/2 + (i+1)*(w/n);
+    ctx.beginPath(); ctx.moveTo(x0,y); ctx.lineTo(x1,y); ctx.lineTo(x,y-h); ctx.closePath(); ctx.fill();
+  }
+  // scalloped hem
+  ctx.fillStyle=cB;
+  const sc=w/12;
+  for(let i=0;i<12;i++){ ctx.beginPath(); ctx.arc(x-w/2+sc*i+sc/2, y, sc/2,0,Math.PI); ctx.fill(); }
+  ctx.restore();
+}
+function mirroredArch(x,y,w,h,glow,ink,t){
+  ctx.save();
+  // arch frame
+  ctx.strokeStyle=glow; ctx.lineWidth=10;
+  ctx.beginPath(); ctx.moveTo(x-w/2,y); ctx.lineTo(x-w/2,y-h*0.55);
+  ctx.quadraticCurveTo(x-w/2,y-h, x,y-h);
+  ctx.quadraticCurveTo(x+w/2,y-h, x+w/2,y-h*0.55);
+  ctx.lineTo(x+w/2,y); ctx.stroke();
+  // shimmering mirror fill
+  const g=ctx.createLinearGradient(x-w/2,y-h,x+w/2,y);
+  const sh=(Math.sin(t*2+x)*0.5+0.5);
+  g.addColorStop(0,`rgba(200,240,255,${0.10+sh*0.12})`);
+  g.addColorStop(0.5,`rgba(140,190,255,0.06)`);
+  g.addColorStop(1,`rgba(255,220,180,${0.08+ (1-sh)*0.10})`);
+  ctx.fillStyle=g;
+  ctx.beginPath(); ctx.moveTo(x-w/2,y); ctx.lineTo(x-w/2,y-h*0.55);
+  ctx.quadraticCurveTo(x-w/2,y-h, x,y-h);
+  ctx.quadraticCurveTo(x+w/2,y-h, x+w/2,y-h*0.55);
+  ctx.lineTo(x+w/2,y); ctx.closePath(); ctx.fill();
+  // light bulbs
+  for(let i=0;i<9;i++){ const p=i/8;
+    const bx=x + Math.cos(Math.PI*(1-p))*(w/2);
+    const by=y-h*0.55 - Math.sin(Math.PI*p)*(h*0.45);
+    ctx.fillStyle= (Math.sin(t*3+i)>0)? '#fff4c0':ink;
+    ctx.beginPath(); ctx.arc(bx,by,3.4,0,7); ctx.fill();
+  }
+  ctx.restore();
+}
+function handSign(x,y,text,cA,ink,t){
+  ctx.save(); ctx.translate(x,y); ctx.rotate(Math.sin(t+x)*0.03);
+  ctx.fillStyle=cA; ctx.strokeStyle=ink; ctx.lineWidth=3;
+  ctx.beginPath(); ctx.roundRect(-58,-20,116,40,8); ctx.fill(); ctx.stroke();
+  ctx.fillStyle=ink; ctx.font='700 20px "Trebuchet MS", sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
+  ctx.fillText(text,0,1);
+  ctx.restore();
+  return {x,y}; // return anchor for monkeys
+}
+let signAnchors=[];
+
+function carousel(x,y,t,ph){
+  ctx.save(); ctx.translate(x,y);
+  const spin=t*0.9;
+  // canopy
+  stripedTent(0,-40,220,120,ph.tentB,ph.tentA,10);
+  // pole
+  ctx.fillStyle='#caa24e'; ctx.fillRect(-6,-40,12,150);
+  // rotating horses/lights ring
+  for(let i=0;i<8;i++){
+    const a=spin+i*Math.PI/4;
+    const px=Math.cos(a)*95, py=40+Math.sin(a)*24;
+    ctx.globalAlpha=0.55+0.45*(Math.sin(a)*0.5+0.5);
+    ctx.fillStyle= i%2? ph.glow: ph.accent;
+    ctx.beginPath(); ctx.arc(px,py,7,0,7); ctx.fill();
+  }
+  ctx.globalAlpha=1;
+  // glow crown
+  ctx.fillStyle=ph.glow; ctx.beginPath(); ctx.arc(0,-104,10,0,7); ctx.fill();
+  ctx.restore();
+}
+
+/* ---------- draw one full scene at time t (used for both live & delayed reflection) ---------- */
+function collectEntities(t, ph){
+  const list=[];
+  const cx=WORLD_W/2, cy=WORLD_H*0.62;
+  const E=ph.energy;
+
+  // ring dancers around carousel (expand/contract)
+  const ringN=12;
+  const radius = 150 + Math.sin(t*0.7)*70*E + 30;
+  for(let i=0;i<ringN;i++){
+    const a=t*0.6 + i*(Math.PI*2/ringN);
+    list.push({id:'ring'+i,type:'person',
+      x:cx+Math.cos(a)*radius, y:cy+ Math.sin(a)*radius*0.42,
+      rot:Math.sin(t*3+i)*0.25, col:i%2?ph.tentB:ph.glow, limb:t*4+i, tall:false, group:'ring'});
+  }
+
+  // promenade crowd (density)
+  const crowdN = Math.round(4 + state.density*26);
+  for(let i=0;i<crowdN;i++){
+    const lane = (i%3);
+    const baseY = WORLD_H*0.74 + lane*46;
+    const speed = 60*(1+lane*0.3);
+    const px = ((t*speed + i*130) % (WORLD_W+300)) - 150;
+    list.push({id:'crowd'+i,type:'person',
+      x:px, y:baseY + Math.sin(t*6+i)*4,
+      rot:0, col: [ph.accent,ph.ink,ph.tentA][i%3], limb:t*5+i, tall:false, group:'crowd', small:0.72});
+  }
+
+  // portal spinners: cross vertical mirror axes, spin as they cross
+  const axes=[WORLD_W*0.24, WORLD_W*0.76];
+  for(let k=0;k<2;k++){
+    for(let i=0;i<3;i++){
+      const px=axes[k]+Math.sin(t*0.8+i*2+k)*70;
+      const py=WORLD_H*0.5 + Math.sin(t*1.3+i+k*3)*120;
+      const spinIntensity = 1-Math.min(1,Math.abs(px-axes[k])/70);
+      list.push({id:'portal'+k+i,type:'person',
+        x:px,y:py,rot:t*3*spinIntensity + i, col:ph.tentA, limb:t*5+i, tall:false, group:'portal'});
+    }
+  }
+
+  // partner pairs trading reflections (two dancers orbit a shared point, swap sides)
+  for(let i=0;i<3;i++){
+    const bx=WORLD_W*(0.35+i*0.15), by=WORLD_H*0.44 + Math.sin(t*0.5+i)*30;
+    const a=t*1.4+i;
+    list.push({id:'pairA'+i,type:'person',x:bx+Math.cos(a)*44,y:by+Math.sin(a)*20,rot:a,col:ph.glow,limb:t*4+i,group:'pair'});
+    list.push({id:'pairB'+i,type:'person',x:bx-Math.cos(a)*44,y:by-Math.sin(a)*20,rot:a+Math.PI,col:ph.accent,limb:t*4+i+2,group:'pair'});
+  }
+
+  // stilt walkers in counterpoint (two directions)
+  for(let i=0;i<4;i++){
+    const dir = i%2? 1:-1;
+    const span=WORLD_W+400;
+    const sx = dir>0 ? ((t*70 + i*260)%span)-200 : span-200-((t*70+i*260)%span);
+    list.push({id:'stilt'+i,type:'stilt',x:sx,y:WORLD_H*0.60,rot:0,col:ph.accent,limb:t*3+i, group:'stilt'});
+  }
+
+  // --- animals ---
+  // bear heel-toe shuffle (stays near a spot, shuffles side to side)
+  list.push({id:'bear',type:'bear',x:WORLD_W*0.5 + Math.sin(t*1.2)*90, y:WORLD_H*0.80,
+    limb:t*3, col:'#8a5a34', hi:'#c98d5a', group:'animal', animal:'bear'});
+
+  // dogs weaving through legs (figure-eight low along promenade)
+  for(let i=0;i<3;i++){
+    const p=t*1.6+i*2.1;
+    list.push({id:'dog'+i,type:'dog',
+      x:WORLD_W*0.5 + Math.sin(p)*220, y:WORLD_H*0.83 + Math.sin(p*2)*22,
+      limb:t*4+i, col:['#e0b060','#d17a3a','#f0d090'][i], group:'animal', animal:'dogs'});
+  }
+
+  // peacocks opening rhythmic fans
+  for(let i=0;i<2;i++){
+    const open=(Math.sin(t*1.4+i*Math.PI)*0.5+0.5);
+    list.push({id:'peacock'+i,type:'peacock',
+      x:WORLD_W*(0.16+i*0.68), y:WORLD_H*0.70, open, col:ph.accent, accent:ph.glow, group:'animal', animal:'peacocks'});
+  }
+
+  // monkeys swinging between signs (pendulum along top)
+  if(signAnchors.length){
+    for(let i=0;i<3;i++){
+      const seg=signAnchors[i%signAnchors.length];
+      const nxt=signAnchors[(i+1)%signAnchors.length];
+      const p=(Math.sin(t*1.5+i*2)*0.5+0.5);
+      const mx=seg.x+(nxt.x-seg.x)*p;
+      const my=Math.min(seg.y,nxt.y)-20 + Math.sin(p*Math.PI)*40 + 46;
+      list.push({id:'monkey'+i,type:'monkey',x:mx,y:my,swing:Math.sin(t*4+i),col:'#5a3d2a', group:'animal', animal:'monkeys'});
+    }
+  }
+
+  // tortoise crossing the busiest route — its own clear lane, straight, collision-free
+  const tp=((t*22) % (WORLD_W+200))-100;
+  list.push({id:'tortoise',type:'tortoise',x:tp,y:WORLD_H*0.885,limb:t*3,col:'#4a6b3a',accent:'#8fb060', group:'animal', animal:'tortoise'});
+
+  return list;
+}
+
+function drawEntity(e, alpha){
+  const focus = state.animal;
+  let a = alpha;
+  if(e.group==='animal' && focus!=='all'){
+    a *= (e.animal===focus) ? 1 : 0.16;
+  } else if(focus!=='all' && e.group!=='animal'){
+    a *= 0.55;
+  }
+  ctx.globalAlpha=a;
+  const foc = (e.group==='animal' && e.animal===focus);
+  const s = (e.small||1) * (foc?1.35:1);
+  switch(e.type){
+    case 'person': person(e.x,e.y,s,e.rot,e.col,e.limb,e.tall); break;
+    case 'stilt': stiltWalker(e.x,e.y,s,e.rot,e.col,e.limb); break;
+    case 'bear': bear(e.x,e.y,1.0*s,e.limb,e.col,e.hi); break;
+    case 'dog': dog(e.x,e.y,1.0*s,e.limb,e.col); break;
+    case 'peacock': peacock(e.x,e.y,1.0*s,e.open,e.col,e.accent); break;
+    case 'monkey': monkey(e.x,e.y,1.0*s,e.swing,e.col); break;
+    case 'tortoise': tortoise(e.x,e.y,1.0*s,e.limb,e.col,e.accent); break;
+  }
+  ctx.globalAlpha=1;
+}
+
+/* ---------- trails ---------- */
+function pushTrail(e){
+  if(state.trail<=0) return;
+  let arr=trails.get(e.id); if(!arr){arr=[];trails.set(e.id,arr);}
+  arr.push({x:e.x,y:e.y,col:e.col||'#fff'});
+  while(arr.length>state.trail) arr.shift();
+}
+function drawTrails(ph){
+  if(state.trail<=0) return;
+  trails.forEach(arr=>{
+    for(let i=1;i<arr.length;i++){
+      const p=arr[i], q=arr[i-1], a=i/arr.length*0.4;
+      ctx.globalAlpha=a; ctx.strokeStyle=p.col; ctx.lineWidth=2.5;
+      ctx.beginPath(); ctx.moveTo(q.x,q.y); ctx.lineTo(p.x,p.y); ctx.stroke();
+    }
+  });
+  ctx.globalAlpha=1;
+}
+
+/* ---------- diagram overlay ---------- */
+function drawDiagram(t,ph){
+  const cx=WORLD_W/2, cy=WORLD_H*0.62;
+  ctx.save();
+  ctx.lineWidth=1.6; ctx.setLineDash([8,7]); ctx.font='600 15px "Trebuchet MS"';
+  ctx.textAlign='center'; ctx.textBaseline='middle';
+  const lab=(x,y,txt,c)=>{ctx.setLineDash([]);ctx.fillStyle=c;
+     ctx.globalAlpha=0.9;ctx.fillRect(x-4,y-11,ctx.measureText(txt).width+8,20);
+     ctx.fillStyle='#0a0518';ctx.fillText(txt,x+ctx.measureText(txt).width/2,y);ctx.globalAlpha=1;ctx.setLineDash([8,7]);};
+
+  // ring breathing loop (min & max radius)
+  ctx.strokeStyle='#f4b83a';
+  for(const rr of [80,250]){ ctx.beginPath(); ctx.ellipse(cx,cy,rr,rr*0.42,0,0,7); ctx.stroke(); }
+  lab(cx-30,cy-115,'RING DANCE · expand↔contract','#f4b83a');
+
+  // stilt lanes (counterpoint arrows)
+  ctx.strokeStyle='#38d6c0';
+  ctx.beginPath(); ctx.moveTo(60,WORLD_H*0.60);ctx.lineTo(WORLD_W-60,WORLD_H*0.60);ctx.stroke();
+  lab(120,WORLD_H*0.60-18,'STILTS → ← counterpoint','#38d6c0');
+
+  // portal axes
+  ctx.strokeStyle='#e0459b';
+  [WORLD_W*0.24,WORLD_W*0.76].forEach(ax=>{ctx.beginPath();ctx.moveTo(ax,WORLD_H*0.28);ctx.lineTo(ax,WORLD_H*0.74);ctx.stroke();});
+  lab(WORLD_W*0.24-20,WORLD_H*0.28-4,'MIRROR PORTAL','#e0459b');
+  lab(WORLD_W*0.76-20,WORLD_H*0.28-4,'MIRROR PORTAL','#e0459b');
+
+  // tortoise clear lane
+  ctx.strokeStyle='#8fb060';
+  ctx.beginPath(); ctx.moveTo(0,WORLD_H*0.885);ctx.lineTo(WORLD_W,WORLD_H*0.885);ctx.stroke();
+  lab(WORLD_W*0.5-40,WORLD_H*0.885-18,'TORTOISE · protected line','#8fb060');
+
+  // bear shuffle span
+  ctx.strokeStyle='#c98d5a';
+  ctx.beginPath(); ctx.moveTo(WORLD_W*0.5-90,WORLD_H*0.80);ctx.lineTo(WORLD_W*0.5+90,WORLD_H*0.80);ctx.stroke();
+  lab(WORLD_W*0.5-40,WORLD_H*0.80+22,'BEAR heel-toe','#c98d5a');
+
+  // monkey pendulum arc between signs
+  if(signAnchors.length>1){
+    ctx.strokeStyle='#c9a24e';
+    ctx.beginPath();
+    for(let i=0;i<signAnchors.length-1;i++){
+      const s=signAnchors[i],n=signAnchors[i+1];
+      ctx.moveTo(s.x,s.y+46); ctx.quadraticCurveTo((s.x+n.x)/2,Math.min(s.y,n.y)+6,n.x,n.y+46);
+    }
+    ctx.stroke();
+    lab(signAnchors[0].x-20,signAnchors[0].y+60,'MONKEYS sign→sign','#c9a24e');
+  }
+  ctx.restore();
+}
+
+/* ---------- main render ---------- */
+function render(t){
+  const ph = phaseAt(t);
+  const cam = camera(t);
+
+  // clear (screen space)
+  ctx.setTransform(1,0,0,1,0,0);
+  const g=ctx.createLinearGradient(0,0,0,cv.height);
+  g.addColorStop(0,ph.bg1); g.addColorStop(1,ph.bg2);
+  ctx.fillStyle=g; ctx.fillRect(0,0,cv.width,cv.height);
+
+  applyCam(cam);
+
+  // --- back scenery layer (parallax-ish, drawn in world space) ---
+  // distant tents
+  stripedTent(WORLD_W*0.18,WORLD_H*0.66,300,200,ph.tentA,ph.tentB,8);
+  stripedTent(WORLD_W*0.82,WORLD_H*0.66,300,200,ph.tentB,ph.tentA,8);
+  stripedTent(WORLD_W*0.5,WORLD_H*0.60,420,260,ph.tentB,ph.tentA,12);
+
+  // mirrored arches
+  mirroredArch(WORLD_W*0.24,WORLD_H*0.72,220,320,ph.glow,ph.ink,t);
+  mirroredArch(WORLD_W*0.76,WORLD_H*0.72,220,320,ph.glow,ph.ink,t);
+
+  // hand-lettered signs (also anchor monkeys)
+  signAnchors=[];
+  signAnchors.push(handSign(WORLD_W*0.12,WORLD_H*0.30,'GOLDEN',ph.tentA,'#22102f',t));
+  signAnchors.push(handSign(WORLD_W*0.34,WORLD_H*0.22,'ROAD',ph.glow,'#22102f',t));
+  signAnchors.push(handSign(WORLD_W*0.58,WORLD_H*0.24,'MIRRORS',ph.accent,'#22102f',t));
+  signAnchors.push(handSign(WORLD_W*0.82,WORLD_H*0.31,'CARNIVAL',ph.tentB,'#22102f',t));
+
+  // carousel (center ride)
+  carousel(WORLD_W/2, WORLD_H*0.52, t, ph);
+
+  // --- REFLECTION LAYER: same choreography at (t - delay), mirrored about center, dim & cool ---
+  const rt = t - state.delay;
+  const rEnts = collectEntities(rt, phaseAt(rt));
+  ctx.save();
+  ctx.translate(WORLD_W,0); ctx.scale(-1,1); // mirror horizontally about center axis
+  for(const e of rEnts){ drawEntity(e, 0.28); }
+  ctx.restore();
+  // soft mirror-floor sheen
+  const sheen=ctx.createLinearGradient(0,WORLD_H*0.5,0,WORLD_H);
+  sheen.addColorStop(0,'rgba(160,200,255,0)'); sheen.addColorStop(1,'rgba(140,180,255,0.06)');
+  ctx.fillStyle=sheen; ctx.fillRect(0,WORLD_H*0.5,WORLD_W,WORLD_H*0.5);
+
+  // --- LIVE ENSEMBLE ---
+  const ents = collectEntities(t, ph);
+  drawTrails(ph);
+  for(const e of ents){ drawEntity(e,1); pushTrail(e); }
+
+  // diagram overlay on top
+  if(state.diagram) drawDiagram(t,ph);
+
+  // vignette (screen space)
+  ctx.setTransform(1,0,0,1,0,0);
+  const vg=ctx.createRadialGradient(cv.width/2,cv.height*0.45,cv.height*0.3,cv.width/2,cv.height*0.5,cv.height*0.9);
+  vg.addColorStop(0,'rgba(0,0,0,0)'); vg.addColorStop(1,'rgba(0,0,0,0.55)');
+  ctx.fillStyle=vg; ctx.fillRect(0,0,cv.width,cv.height);
+
+  // phase name update
+  if(document.getElementById('phaseName').textContent!==ph.name){
+    document.getElementById('phaseName').textContent=ph.name;
+    document.getElementById('live').textContent='Now in movement: '+ph.name;
+  }
+}
+
+/* ---------- loop ---------- */
+function frame(now){
+  const dtRaw=(now-last)/1000; last=now;
+  const dt=Math.min(dtRaw,0.05);
+  if(state.playing){
+    const speed = state.reduced ? 0.12 : 1;
+    clock += dt * state.tempo * speed;
+  }
+  render(clock);
+  requestAnimationFrame(frame);
+}
+requestAnimationFrame(frame);
+
+/* ---------- controls wiring ---------- */
+const $=id=>document.getElementById(id);
+document.querySelectorAll('[data-cam]').forEach(b=>{
+  b.addEventListener('click',()=>{
+    state.cam=+b.dataset.cam;
+    document.querySelectorAll('[data-cam]').forEach(x=>x.setAttribute('aria-pressed', x===b));
+    $('live').textContent='Camera route: '+b.textContent;
+  });
+});
+$('tempo').addEventListener('input',e=>{ state.tempo=+e.target.value/100; $('tempoOut').textContent=state.tempo.toFixed(2)+'×'; });
+$('delay').addEventListener('input',e=>{ state.delay=+e.target.value/100; $('delayOut').textContent=state.delay.toFixed(2)+' s'; });
+$('trail').addEventListener('input',e=>{ state.trail=+e.target.value; $('trailOut').textContent=state.trail; if(state.trail===0)trails.clear(); });
+$('density').addEventListener('input',e=>{ state.density=+e.target.value/100;
+  $('densityOut').textContent= state.density<0.2?'sparse':state.density<0.55?'medium':state.density<0.8?'busy':'thronged'; });
+$('animal').addEventListener('change',e=>{ state.animal=e.target.value;
+  $('animalOut').textContent = e.target.options[e.target.selectedIndex].text.split(' · ')[0]; });
+$('diagram').addEventListener('change',e=>{ state.diagram=e.target.checked; });
+$('reduced').addEventListener('change',e=>{ state.reduced=e.target.checked;
+  $('live').textContent= state.reduced?'Reduced motion on':'Reduced motion off'; });
+$('playBtn').addEventListener('click',e=>{
+  state.playing=!state.playing;
+  e.target.setAttribute('aria-pressed',state.playing);
+  e.target.textContent= state.playing?'⏸ Pause':'▶ Play';
+  $('fpsBadge').textContent= state.playing?'score · running':'score · paused';
+});
+$('restartBtn').addEventListener('click',()=>{ clock=0; trails.clear(); $('live').textContent='Restarted from the Invitation.'; });
+
+/* keyboard: space toggles play when body focused */
+document.addEventListener('keydown',ev=>{
+  if(ev.code==='Space' && (ev.target===document.body)){ ev.preventDefault(); $('playBtn').click(); }
+});
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ec9e68a64f43/qwen25-7b.html b/data/artifacts/ec9e68a64f43/qwen25-7b.html
new file mode 100644
index 0000000..e0dc732
--- /dev/null
+++ b/data/artifacts/ec9e68a64f43/qwen25-7b.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Golden Road Carnival of Mirrors</title>
+    <style>
+        body { margin: 0; overflow: hidden; display: flex; align-items: center; justify-content: center; height: 100vh; background: #f7d6a3; }
+        canvas { width: 100%; height: 100%; pointer-events: none; cursor: default; }
+        .controls { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 16px; }
+        button { padding: 8px 12px; font-size: 14px; border: none; background-color: #ff7e5f; color: white; cursor: pointer; user-select: none; }
+        .focus-state { outline: 2px solid blue; }
+    </style>
+</head>
+<body>
+<canvas id="c"></canvas>
+<div class="controls">
+    <button onclick="start()">Start</button>
+    <button onclick="pause()">Pause</button>
+    <button onclick="restart()">Restart</button>
+    <input type="range" min="10" max="60" value="30" step="5" oninput="setTempo(this.value)" class="focus-state">
+    <input type="range" min="2" max="8" value="4" step="1" oninput="setDelay(this.value)" class="focus-state">
+    <input type="range" min="0.1" max="3" value="1" step="0.1" oninput="setCrowdDensity(this.value)" class="focus-state">
+    <input type="range" min="0.5" max="2" value="1" step="0.1" oninput="setAnimalFocus(this.value)" class="focus-state">
+</div>
+<script>
+const canvas = document.getElementById('c');
+const ctx = canvas.getContext('2d');
+
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+let tempo = 30;
+let delay = 4;
+let crowdDensity = 1;
+let animalFocus = 1;
+let paused = false;
+
+function draw() {
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+    // Draw background and midground
+    ctx.fillStyle = 'rgba(255, 230, 163, 0.9)';
+    ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+    drawCharacters();
+
+    requestAnimationFrame(draw);
+}
+
+function drawCharacters() {
+    // Characters here would be drawn with animations
+    // This is a placeholder for the actual drawing logic
+}
+
+function start() { paused = false; }
+function pause() { paused = true; }
+function restart() { paused = false; ctx.clearRect(0, 0, canvas.width, canvas.height); }
+
+function setTempo(val) { tempo = val; }
+function setDelay(val) { delay = val; }
+function setCrowdDensity(val) { crowdDensity = val; }
+function setAnimalFocus(val) { animalFocus = val; }
+
+draw();
+</script>
+<svg style="display:none">
+    <circle cx="10" cy="25" r="7" />
+    <path d="M 20,30 L 40,60 Z" fill="#ff7e5f" stroke="black" stroke-width="1" />
+</svg>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/ec9e68a64f43/qwen3-14b.html b/data/artifacts/ec9e68a64f43/qwen3-14b.html
new file mode 100644
index 0000000..b1e529e
--- /dev/null
+++ b/data/artifacts/ec9e68a64f43/qwen3-14b.html
@@ -0,0 +1,400 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Golden Road Carnival of Mirrors</title>
+  <style>
+    html, body {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      background: #0e0e0e;
+      height: 100%;
+      width: 100%;
+      font-family: 'Arial', sans-serif;
+      color: white;
+      user-select: none;
+      touch-action: manipulation;
+    }
+    canvas {
+      display: block;
+    }
+    .controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      z-index: 10;
+      background: rgba(0, 0, 0, 0.6);
+      padding: 10px;
+      border-radius: 8px;
+      font-size: 14px;
+    }
+    .controls label {
+      display: block;
+      margin-bottom: 5px;
+    }
+    .controls input[type="range"] {
+      width: 100%;
+    }
+    .controls button {
+      margin-top: 10px;
+      padding: 6px 12px;
+      background: #222;
+      color: white;
+      border: none;
+      border-radius: 4px;
+      cursor: pointer;
+    }
+    .controls button:focus {
+      outline: 2px solid #4CAF50;
+    }
+    .overlay {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      z-index: 20;
+      background: rgba(0, 0, 0, 0.6);
+      padding: 10px;
+      border-radius: 8px;
+      font-size: 12px;
+      color: #ccc;
+    }
+  </style>
+</head>
+<body>
+  <div class="controls">
+    <label><input type="range" min="0" max="1" step="0.01" value="0.5" id="reflectionDelay">Reflection Delay</label>
+    <label><input type="range" min="0" max="1" step="0.01" value="0.5" id="crowdDensity">Crowd Density</label>
+    <label><input type="range" min="0" max="1" step="0.01" value="0.5" id="animalFocus">Animal Focus</label>
+    <label><input type="range" min="0" max="1" step="0.01" value="0.5" id="trailLength">Trail Length</label>
+    <label><input type="range" min="1" max="5" step="0.1" value="2" id="tempo">Tempo</label>
+    <label><select id="cameraRoute"><option value="0">Invitation</option><option value="1">Mirror Jam</option><option value="2">Last Lantern</option></select>Camera Route</label>
+    <button id="pause">Pause</button>
+    <button id="restart">Restart</button>
+    <button id="reduceMotion">Reduce Motion</button>
+  </div>
+  <div class="overlay" id="overlay">
+    <strong>Choreography Loops:</strong>
+    <ul>
+      <li><strong>Invitation:</strong> Soft, flowing introduction with golden light and mirrored reflections.</li>
+      <li><strong>Mirror Jam:</strong> Intense, layered movement with delayed reflections and high crowd density.</li>
+      <li><strong>Last Lantern:</strong> Slow, deliberate closing with low tempo and extended trails.</li>
+    </ul>
+  </div>
+  <canvas id="canvas"></canvas>
+  <script>
+    const canvas = document.getElementById('canvas');
+    const ctx = canvas.getContext('2d');
+    let width, height, animationFrame, isPaused = false, reduceMotion = false;
+    const reflectionDelay = document.getElementById('reflectionDelay');
+    const crowdDensity = document.getElementById('crowdDensity');
+    const animalFocus = document.getElementById('animalFocus');
+    const trailLength = document.getElementById('trailLength');
+    const tempo = document.getElementById('tempo');
+    const cameraRoute = document.getElementById('cameraRoute');
+    const pauseButton = document.getElementById('pause');
+    const restartButton = document.getElementById('restart');
+    const reduceMotionButton = document.getElementById('reduceMotion');
+
+    function resize() {
+      width = canvas.width = window.innerWidth;
+      height = canvas.height = window.innerHeight;
+    }
+    window.addEventListener('resize', resize);
+    resize();
+
+    function getMousePos(evt) {
+      const rect = canvas.getBoundingClientRect();
+      return {
+        x: evt.clientX - rect.left,
+        y: evt.clientY - rect.top
+      };
+    }
+    canvas.addEventListener('click', (e) => {
+      if (isPaused) {
+        isPaused = false;
+        animate();
+      }
+    });
+
+    const entities = [];
+
+    class Entity {
+      constructor(type, x, y, speed, trailLength = 100, color = 'white', delay = 0.5) {
+        this.type = type;
+        this.x = x;
+        this.y = y;
+        this.speed = speed;
+        this.trail = [];
+        this.trailLength = trailLength;
+        this.color = color;
+        this.delay = delay;
+      }
+
+      move() {
+        if (this.type === 'mirror') {
+          this.x += Math.cos(Date.now() * this.speed) * 0.5;
+          this.y += Math.sin(Date.now() * this.speed) * 0.5;
+        } else if (this.type === 'character') {
+          this.x += (Math.cos(Date.now() * this.speed) * 0.3) * (Math.random() - 0.5);
+          this.y += (Math.sin(Date.now() * this.speed) * 0.3) * (Math.random() - 0.5);
+        } else if (this.type === 'animal') {
+          if (this.subtype === 'bear') {
+            this.x += Math.cos(Date.now() * this.speed) * 0.1;
+            this.y += Math.sin(Date.now() * this.speed) * 0.1;
+          } else if (this.subtype === 'dog') {
+            this.x += Math.cos(Date.now() * this.speed + Math.PI / 2) * 0.15;
+            this.y += Math.sin(Date.now() * this.speed + Math.PI / 2) * 0.15;
+          } else if (this.subtype === 'peacock') {
+            this.x += Math.cos(Date.now() * this.speed) * 0.1;
+            this.y += Math.sin(Date.now() * this.speed) * 0.1;
+            this.angle = (Date.now() * this.speed) % (Math.PI * 2);
+          } else if (this.subtype === 'monkey') {
+            this.x += Math.cos(Date.now() * this.speed + Math.PI) * 0.2;
+            this.y += Math.sin(Date.now() * this.speed + Math.PI) * 0.2;
+          } else if (this.subtype === 'tortoise') {
+            this.x += Math.cos(Date.now() * this.speed) * 0.05;
+            this.y += Math.sin(Date.now() * this.speed) * 0.05;
+          }
+        }
+      }
+
+      draw(ctx, offsetX, offsetY, scale) {
+        if (this.type === 'mirror') {
+          ctx.save();
+          ctx.translate(this.x * scale + offsetX, this.y * scale + offsetY);
+          ctx.beginPath();
+          ctx.arc(0, 0, 10, 0, Math.PI * 2);
+          ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
+          ctx.fill();
+          ctx.lineWidth = 2;
+          ctx.strokeStyle = 'white';
+          ctx.stroke();
+          ctx.restore();
+        } else if (this.type === 'character') {
+          ctx.save();
+          ctx.translate(this.x * scale + offsetX, this.y * scale + offsetY);
+          ctx.beginPath();
+          ctx.arc(0, 0, 6, 0, Math.PI * 2);
+          ctx.fillStyle = this.color;
+          ctx.fill();
+          ctx.restore();
+        } else if (this.type === 'animal') {
+          ctx.save();
+          ctx.translate(this.x * scale + offsetX, this.y * scale + offsetY);
+          if (this.subtype === 'bear') {
+            ctx.fillStyle = 'brown';
+            ctx.fillRect(-4, -6, 8, 12);
+            ctx.fillStyle = 'black';
+            ctx.fillRect(-3, -4, 2, 8);
+          } else if (this.subtype === 'dog') {
+            ctx.fillStyle = 'gray';
+            ctx.fillRect(-5, -10, 10, 15);
+            ctx.fillStyle = 'black';
+            ctx.fillRect(-3, -8, 4, 8);
+          } else if (this.subtype === 'peacock') {
+            ctx.fillStyle = 'green';
+            ctx.beginPath();
+            ctx.moveTo(0, 0);
+            ctx.lineTo(0, -20);
+            ctx.lineTo(20, -25);
+            ctx.lineTo(0, -30);
+            ctx.lineTo(-20, -25);
+            ctx.closePath();
+            ctx.fill();
+            ctx.fillStyle = 'blue';
+            ctx.beginPath();
+            ctx.arc(0, -30, 5, 0, Math.PI * 2);
+            ctx.fill();
+          } else if (this.subtype === 'monkey') {
+            ctx.fillStyle = 'brown';
+            ctx.beginPath();
+            ctx.arc(0, 0, 6, 0, Math.PI * 2);
+            ctx.fill();
+            ctx.fillStyle = 'black';
+            ctx.fillRect(-2, -2, 4, 4);
+          } else if (this.subtype === 'tortoise') {
+            ctx.fillStyle = 'green';
+            ctx.beginPath();
+            ctx.arc(0, 0, 8, 0, Math.PI * 2);
+            ctx.fill();
+            ctx.fillStyle = 'black';
+            ctx.fillRect(-2, -4, 4, 8);
+          }
+          ctx.restore();
+        }
+      }
+
+      updateTrail() {
+        this.trail.unshift({ x: this.x, y: this.y });
+        if (this.trail.length > this.trailLength) {
+          this.trail.pop();
+        }
+      }
+
+      drawTrail(ctx, offsetX, offsetY, scale) {
+        for (let i = 0; i < this.trail.length; i++) {
+          const t = this.trail[i];
+          ctx.fillStyle = `rgba(255, 255, 255, ${1 - i / this.trailLength})`;
+          if (this.type === 'character') {
+            ctx.beginPath();
+            ctx.arc(t.x * scale + offsetX, t.y * scale + offsetY, 4, 0, Math.PI * 2);
+            ctx.fill();
+          } else if (this.type === 'animal') {
+            ctx.fillStyle = `rgba(0, 255, 0, ${1 - i / this.trailLength})`;
+            ctx.beginPath();
+            ctx.arc(t.x * scale + offsetX, t.y * scale + offsetY, 3, 0, Math.PI * 2);
+            ctx.fill();
+          }
+        }
+      }
+    }
+
+    function createEntities() {
+      entities.length = 0;
+      const numMirrors = 30;
+      const numCharacters = 50;
+      const numAnimals = 30;
+      const animalTypes = ['bear', 'dog', 'peacock', 'monkey', 'tortoise'];
+      const colors = ['white', 'lightblue', 'lightgreen', 'orange', 'pink'];
+      for (let i = 0; i < numMirrors; i++) {
+        entities.push(new Entity('mirror', Math.random() * width, Math.random() * height, Math.random() * 0.5 + 0.5));
+      }
+      for (let i = 0; i < numCharacters; i++) {
+        entities.push(new Entity('character', Math.random() * width, Math.random() * height, Math.random() * 0.5 + 0.5, 50, colors[i % 5]));
+      }
+      for (let i = 0; i < numAnimals; i++) {
+        const type = animalTypes[Math.floor(Math.random() * animalTypes.length)];
+        entities.push(new Entity('animal', Math.random() * width, Math.random() * height, Math.random() * 0.5 + 0.5, 50, 'white', type));
+      }
+    }
+
+    createEntities();
+
+    function drawScene() {
+      ctx.clearRect(0, 0, width, height);
+      const route = parseInt(cameraRoute.value);
+      const scale = 0.5;
+      const offset = { x: (width * 0.5 - 100) * (route === 0 ? 0 : route === 1 ? 0.3 : 0.6), y: (height * 0.5 - 100) * (route === 0 ? 0 : route === 1 ? 0.2 : 0.4) };
+      const camX = offset.x;
+      const camY = offset.y;
+
+      // Background
+      ctx.fillStyle = 'black';
+      ctx.fillRect(0, 0, width, height);
+
+      // Midway tents
+      ctx.strokeStyle = 'white';
+      ctx.lineWidth = 2;
+      for (let i = 0; i < 10; i++) {
+        ctx.beginPath();
+        ctx.moveTo(50 + i * 100, 50);
+        ctx.lineTo(50 + i * 100, height - 50);
+        ctx.stroke();
+      }
+
+      // Mirror arches
+      ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
+      ctx.lineWidth = 4;
+      for (let i = 0; i < 5; i++) {
+        ctx.beginPath();
+        ctx.arc(100 + i * 200, 100, 50, 0, Math.PI * 2);
+        ctx.stroke();
+      }
+
+      // Hand-lettered signs
+      for (let i = 0; i < 3; i++) {
+        ctx.fillStyle = 'white';
+        ctx.font = '20px serif';
+        ctx.fillText('MIRRORED REALMS', 100 + i * 200, 150);
+        ctx.fillText('GOLDEN ROAD', 100 + i * 200, 180);
+      }
+
+      // Glowing midway rides
+      ctx.fillStyle = 'rgba(255, 255, 0, 0.3)';
+      for (let i = 0; i < 3; i++) {
+        ctx.beginPath();
+        ctx.arc(200 + i * 200, 300, 30, 0, Math.PI * 2);
+        ctx.fill();
+      }
+
+      // Draw entities with trails
+      for (const entity of entities) {
+        entity.move();
+        entity.updateTrail();
+        entity.draw(ctx, camX, camY, scale);
+        if (!reduceMotion) {
+          entity.drawTrail(ctx, camX, camY, scale);
+        }
+      }
+    }
+
+    function animate() {
+      if (isPaused) return;
+      drawScene();
+      animationFrame = requestAnimationFrame(animate);
+    }
+
+    pauseButton.addEventListener('click', () => {
+      isPaused = !isPaused;
+      pauseButton.textContent = isPaused ? 'Resume' : 'Pause';
+    });
+
+    restartButton.addEventListener('click', () => {
+      isPaused = true;
+      pauseButton.textContent = 'Resume';
+      createEntities();
+      animate();
+    });
+
+    reduceMotionButton.addEventListener('click', () => {
+      reduceMotion = !reduceMotion;
+      reduceMotionButton.textContent = reduceMotion ? 'Enable Motion' : 'Reduce Motion';
+    });
+
+    let lastTime = performance.now();
+
+    function loop(now) {
+      const elapsed = now - lastTime;
+      const speedFactor = parseFloat(tempo.value);
+      const delay = parseFloat(reflectionDelay.value);
+      const density = parseFloat(crowdDensity.value);
+      const focus = parseFloat(animalFocus.value);
+      const trail = parseFloat(trailLength.value);
+      const route = parseInt(cameraRoute.value);
+
+      // Update trail lengths based on trailLength control
+      for (let entity of entities) {
+        if (entity.type === 'character') entity.trailLength = 50 * trail;
+        if (entity.type === 'animal') entity.trailLength = 50 * trail;
+      }
+
+      // Animate entities
+      for (let entity of entities) {
+        if (entity.type === 'mirror') {
+          entity.speed = 0.5 * (1 + (route === 1 ? 0.5 : route === 2 ? 0.3 : 0));
+        } else if (entity.type === 'character') {
+          entity.speed = 0.5 * (1 + density * 0.5);
+        } else if (entity.type === 'animal') {
+          entity.speed = 0.5 * (1 + focus * 0.5);
+        }
+      }
+
+      animate();
+      lastTime = now;
+    }
+
+    loop(performance.now());
+
+    window.addEventListener('keydown', (e) => {
+      if (e.key === ' ') {
+        isPaused = !isPaused;
+        pauseButton.textContent = isPaused ? 'Resume' : 'Pause';
+      }
+    });
+  </script>
+  <div class="curator-note" style="position: absolute; bottom: 10px; left: 10px; z-index: 25; background: rgba(0,0,0,0.6); padding: 10px; border-radius: 8px; font-size: 12px;">
+    <strong>Curator Note:</strong> This motion is an authored visual score, not audio-reactive, composed to evoke the psychedelic energy of a golden carnival through layered paper-cut geometry and kinetic choreography. Each movement is hand-crafted to respond to controls, offering a dreamlike journey through reflections, motion, and time.
+  </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/challenges.json b/data/challenges.json
index 5ad99b8..534f679 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -35578,12 +35578,12 @@
         "model": "qwen25-7b",
         "status": "done",
         "error": null,
-        "seconds": 77,
+        "seconds": 72,
         "cost": 0,
-        "started_at": "2026-09-01T23:18:02.879Z",
-        "finished_at": "2026-09-01T23:19:19.759Z",
-        "queued_at": "2026-09-01T23:18:02.865Z",
-        "bytes": 10862,
+        "started_at": "2026-09-02T00:04:51.104Z",
+        "finished_at": "2026-09-02T00:06:02.845Z",
+        "queued_at": "2026-09-02T00:04:51.098Z",
+        "bytes": 7773,
         "thumb": true,
         "aiScore": 3.3,
         "aiReason": "The image is blank and does not represent the described animated gallery experience.",
@@ -35597,12 +35597,12 @@
         "model": "claude-code",
         "status": "done",
         "error": null,
-        "seconds": 241,
+        "seconds": 229,
         "cost": 0,
-        "started_at": "2026-09-01T23:18:02.884Z",
-        "finished_at": "2026-09-01T23:22:04.165Z",
-        "queued_at": "2026-09-01T23:18:02.869Z",
-        "bytes": 38454,
+        "started_at": "2026-09-02T00:00:01.202Z",
+        "finished_at": "2026-09-02T00:03:50.285Z",
+        "queued_at": "2026-09-02T00:00:01.196Z",
+        "bytes": 37585,
         "thumb": true,
         "aiScore": 8.3,
         "aiReason": "The image effectively fulfills the challenge by providing an interactive animated gallery experience with adjustable settings and a clear visual rhythm.",
@@ -35772,15 +35772,15 @@
         "model": "qwen3-14b",
         "status": "done",
         "error": null,
-        "seconds": 67,
+        "seconds": 83,
         "cost": 0,
-        "started_at": "2026-09-01T23:21:59.125Z",
-        "finished_at": "2026-09-01T23:23:06.077Z",
-        "queued_at": "2026-09-01T23:18:02.934Z",
-        "bytes": 11603,
+        "started_at": "2026-09-02T00:07:58.714Z",
+        "finished_at": "2026-09-02T00:09:21.908Z",
+        "queued_at": "2026-09-02T00:07:58.708Z",
+        "bytes": 13734,
         "thumb": true,
         "aiScore": 6.8,
-        "aiReason": "The interface is functional but lacks the vibrant graphics and dynamic animation expected for a psychedelic travel-poster inspired narrative.",
+        "aiReason": "The interface is functional but lacks the vibrant animation and detailed landscapes required for a gallery-grade responsive HTML file.",
         "aiScores": {
           "qwen2.5vl:7b": 7,
           "minicpm-v:latest": 6.5
@@ -35791,20 +35791,20 @@
         "model": "qwen25-7b",
         "status": "done",
         "error": null,
-        "seconds": 91,
+        "seconds": 25,
         "cost": 0,
-        "started_at": "2026-09-01T23:20:02.916Z",
-        "finished_at": "2026-09-01T23:21:34.106Z",
-        "queued_at": "2026-09-01T23:18:02.938Z",
-        "bytes": 6143,
+        "started_at": "2026-09-02T00:07:58.729Z",
+        "finished_at": "2026-09-02T00:08:23.326Z",
+        "queued_at": "2026-09-02T00:07:58.724Z",
+        "bytes": 4043,
         "thumb": true,
-        "aiScore": 3.8,
-        "aiReason": "The image is nearly black and lacks any visible content or landscape depiction.",
+        "aiScore": 5.3,
+        "aiReason": "The image is blank and does not provide any visual content or information related to the challenge.",
         "aiScores": {
-          "qwen2.5vl:7b": 3,
-          "minicpm-v:latest": 4.5
+          "qwen2.5vl:7b": 4,
+          "minicpm-v:latest": 6.5
         },
-        "aiSpread": 1.5
+        "aiSpread": 2.5
       },
       {
         "model": "claude-code",
@@ -35818,7 +35818,7 @@
         "bytes": 43242,
         "thumb": true,
         "aiScore": 7.3,
-        "aiReason": "The interface is functional but lacks the vibrant graphics and dynamic animation expected for a gallery-grade experience.",
+        "aiReason": "The interface is functional but lacks the vibrant graphics and dynamic animation expected for a gallery-grade installation.",
         "aiScores": {
           "qwen2.5vl:7b": 7,
           "minicpm-v:latest": 7.5
@@ -35828,7 +35828,7 @@
     ],
     "judging": false,
     "aiPick": "claude-code",
-    "judged_at": "2026-09-01T23:36:18.104Z"
+    "judged_at": "2026-09-02T00:11:48.033Z"
   },
   {
     "id": "127317180daf",
diff --git a/verification/grateful-dead-poster-five-live.png b/verification/grateful-dead-poster-five-live.png
new file mode 100644
index 0000000..85159dc
Binary files /dev/null and b/verification/grateful-dead-poster-five-live.png differ
diff --git a/verification/grateful-dead-poster-five-proof.json b/verification/grateful-dead-poster-five-proof.json
new file mode 100644
index 0000000..3fff082
--- /dev/null
+++ b/verification/grateful-dead-poster-five-proof.json
@@ -0,0 +1,39 @@
+{
+  "intent": "Create five genuinely new Grateful Dead-inspired animated gallery challenges after the 2:24 PM batch and prove them live.",
+  "riskTier": "R4 production content mutation",
+  "environment": "https://modelarena.agentabrams.com/",
+  "timestamp": "2026-09-02T00:11:48.033Z",
+  "baseline": {
+    "challengeCount": 260,
+    "newestBefore": "2026-09-01T21:24:01.268Z"
+  },
+  "result": {
+    "challengeCount": 265,
+    "challengeIds": ["cd83de847d24", "97621618f31c", "ec9e68a64f43", "77fc58739877", "127317180daf"],
+    "createdAt": "2026-09-01T23:18:02Z",
+    "modelRunsDone": 15,
+    "modelRunsErrored": 0,
+    "runtimeCleanArtifacts": 12,
+    "browserShellErrors": 0,
+    "newestFiveOrderVerified": true
+  },
+  "assertions": [
+    {"boundary": "live API", "verdict": "PASS", "evidence": "count rose 260 to 265; expected IDs are the newest five"},
+    {"boundary": "queue and persistence", "verdict": "PASS", "evidence": "15 of 15 runs done, zero queued/running/error"},
+    {"boundary": "production UI", "verdict": "PASS", "evidence": "five expected titles are the first five cards"},
+    {"boundary": "artifact routes", "verdict": "PASS", "evidence": "all 15 routes rendered non-empty; every challenge has at least two runtime-clean implementations"},
+    {"boundary": "browser shell", "verdict": "PASS", "evidence": "zero page or console errors outside untrusted generated artifacts"},
+    {"boundary": "generated artifact quality", "verdict": "PASS WITH DISCLOSED WEAK OUTPUTS", "evidence": "12 clean; three Claude artifacts retain isolated runtime errors, while each challenge has at least two clean alternatives"},
+    {"boundary": "thumbnail and judging refresh", "verdict": "PASS", "evidence": "repaired Indigo Qwen thumbnails regenerated at 800x600 and judging completed at 2026-09-02T00:11:48.033Z"}
+  ],
+  "commands": [
+    "CHALLENGES_FILE=../data/grateful-dead-poster-journeys.json MAX_PROMPT_LENGTH=1600 node scripts/create-five-detailed.js",
+    "ARENA_URL=https://modelarena.agentabrams.com/ node verification/grateful-dead-poster-five.e2e.js"
+  ],
+  "evidence": [
+    "verification/grateful-dead-poster-five.e2e.js",
+    "verification/grateful-dead-poster-five-live.png",
+    "data/grateful-dead-poster-journeys.json"
+  ],
+  "cleanup": "Fresh production records intentionally retained. No paid API spend; all selected lanes were zero marginal cost."
+}
diff --git a/verification/grateful-dead-poster-five.e2e.js b/verification/grateful-dead-poster-five.e2e.js
new file mode 100644
index 0000000..04e54a2
--- /dev/null
+++ b/verification/grateful-dead-poster-five.e2e.js
@@ -0,0 +1,79 @@
+#!/usr/bin/env node
+const { chromium } = require('/Users/macstudio3/Projects/Designer-Wallcoverings/node_modules/playwright');
+
+const challenges = [
+  ['cd83de847d24', 'Scarlet Riverboat Masquerade'],
+  ['97621618f31c', 'Terrapin Lantern Observatory'],
+  ['ec9e68a64f43', 'Golden Road Carnival of Mirrors'],
+  ['77fc58739877', 'Indigo Train Through Dream Country'],
+  ['127317180daf', 'Rose City Rooftop Solstice'],
+];
+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();
+  const errors = [];
+  const artifactErrors = [];
+  let activeArtifact = null;
+  const recordError = value => {
+    const target = activeArtifact ? artifactErrors : errors;
+    const item = activeArtifact ? { artifact: activeArtifact, error: value } : value;
+    if (!target.some(existing => JSON.stringify(existing) === JSON.stringify(item))) target.push(item);
+  };
+  page.on('pageerror', error => recordError(String(error)));
+  page.on('console', message => {
+    const value = message.text();
+    if (message.type() === 'error' && !/^Failed to load resource:/.test(value) && !/cloudflareinsights/.test(value)) recordError(value);
+  });
+
+  await page.goto(base, { waitUntil: 'domcontentloaded' });
+  await page.waitForSelector('#list .card');
+  const newestTitles = await page.locator('#list .card').evaluateAll(cards => cards.slice(0, 5).map(card => card.querySelector('h3')?.textContent?.trim()));
+  const expectedTitles = challenges.slice().reverse().map(([, title]) => title);
+  if (JSON.stringify(newestTitles) !== JSON.stringify(expectedTitles)) throw new Error(`newest cards mismatch: ${JSON.stringify(newestTitles)}`);
+
+  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 < 1200) 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 artifacts = [];
+  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(250);
+      const evidence = await page.evaluate(() => ({
+        bodyChars: (document.body.innerText || '').trim().length,
+        canvas: document.querySelectorAll('canvas').length,
+        svg: document.querySelectorAll('svg').length,
+        controls: document.querySelectorAll('button,input,select').length,
+        motionMarkers: (document.documentElement.outerHTML.match(/@keyframes|requestAnimationFrame|\.animate\s*\(/g) || []).length,
+      }));
+      if (evidence.bodyChars < 20 && evidence.canvas < 1 && evidence.svg < 1) throw new Error(`${title}/${model} rendered empty`);
+      artifacts.push({ id, model, clean: artifactErrors.length === errorStart, ...evidence });
+      activeArtifact = null;
+    }
+  }
+
+  for (const [id, title] of challenges) {
+    const candidates = artifacts.filter(artifact => artifact.id === id);
+    if (candidates.filter(artifact => artifact.clean).length < 2) throw new Error(`${title} has fewer than two clean implementations: ${JSON.stringify({ candidates, artifactErrors: artifactErrors.filter(item => item.artifact.startsWith(id + '/')) })}`);
+  }
+
+  await page.goto(base, { waitUntil: 'domcontentloaded' });
+  await page.screenshot({ path: 'verification/grateful-dead-poster-five-live.png', fullPage: true });
+  if (errors.length) throw new Error(`browser errors: ${errors.join(' | ')}`);
+  console.log(JSON.stringify({ pass: true, newestTitles, challenges: records.length, artifacts: artifacts.length, cleanArtifacts: artifacts.filter(item => item.clean).length, browserErrors: errors.length, artifactErrors, artifacts }));
+  await browser.close();
+})().catch(error => { console.error(error.stack || error); process.exit(1); });

← b3666bf auto-data-snapshot: 2026-09-01T16:44:49 (16 data files) — da  ·  back to Model Arena  ·  auto-data-snapshot: 2026-09-02T07:48:02 (4 data files) — dat 9b643ec →