← back to Model Arena
snapshot generated arena artifacts before UI verification
9152be0d88dc89b4b817e848fda6818cdec5d640 · 2026-08-31 12:41:07 -0700 · Steve Abrams
Files touched
A data/artifacts/5ed25e8a4aae/claude-code.htmlA data/artifacts/5ed25e8a4aae/codex.htmlA data/artifacts/5ed25e8a4aae/qwen25-7b.htmlA data/artifacts/5ed25e8a4aae/qwen3-14b.html
Diff
commit 9152be0d88dc89b4b817e848fda6818cdec5d640
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 31 12:41:07 2026 -0700
snapshot generated arena artifacts before UI verification
---
data/artifacts/5ed25e8a4aae/claude-code.html | 759 +++++++++++++++++++++++++++
data/artifacts/5ed25e8a4aae/codex.html | 331 ++++++++++++
data/artifacts/5ed25e8a4aae/qwen25-7b.html | 60 +++
data/artifacts/5ed25e8a4aae/qwen3-14b.html | 208 ++++++++
4 files changed, 1358 insertions(+)
diff --git a/data/artifacts/5ed25e8a4aae/claude-code.html b/data/artifacts/5ed25e8a4aae/claude-code.html
new file mode 100644
index 0000000..a11ee26
--- /dev/null
+++ b/data/artifacts/5ed25e8a4aae/claude-code.html
@@ -0,0 +1,759 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Baguette Brawl — Duel de Paris</title>
+<style>
+ :root{
+ --bg:#f7efe6; --surface:#fff9f1; --text:#3a2c22; --muted:#a08c7a;
+ --accent:#c65f38; --accent2:#e0a458;
+ --p1:#c65f38; --p2:#4b6b7a;
+ }
+ *{box-sizing:border-box;margin:0;padding:0}
+ html,body{height:100%}
+ body{
+ background:radial-gradient(120% 120% at 50% 0%,#20222e 0%,#12131a 55%,#0a0a0f 100%);
+ color:var(--text);
+ font-family:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
+ display:flex;align-items:center;justify-content:center;
+ min-height:100%;padding:20px;overflow:hidden;
+ }
+ #wrap{
+ position:relative;width:960px;max-width:100%;
+ aspect-ratio:960/540;border-radius:14px;overflow:hidden;
+ box-shadow:0 16px 48px rgba(0,0,0,.55), 0 0 0 1px rgba(224,164,88,.25);
+ background:#1a1720;
+ }
+ canvas{display:block;width:100%;height:100%;image-rendering:auto}
+
+ /* overlays */
+ .overlay{
+ position:absolute;inset:0;display:flex;flex-direction:column;
+ align-items:center;justify-content:center;text-align:center;
+ background:linear-gradient(160deg,rgba(18,15,22,.86),rgba(30,20,14,.92));
+ backdrop-filter:blur(2px);gap:16px;padding:32px;
+ color:var(--bg);z-index:10;
+ }
+ .overlay.hide{display:none}
+ .kicker{
+ font-size:12px;letter-spacing:.28em;text-transform:uppercase;
+ color:var(--accent2);font-weight:600;
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;
+ }
+ h1{
+ font-family:'Playfair Display',Georgia,'Bodoni MT',serif;
+ font-weight:800;font-size:clamp(38px,7vw,67px);line-height:.98;
+ color:var(--surface);letter-spacing:-.01em;
+ text-shadow:0 2px 24px rgba(224,164,88,.25);
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.09s;
+ }
+ h1 .em{color:var(--accent2);font-style:italic}
+ .sub{
+ max-width:520px;color:#e8dfd2;font-size:16px;line-height:1.5;
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.18s;
+ }
+ .btns{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.27s;}
+ button.play{
+ font-family:inherit;cursor:pointer;border:none;border-radius:12px;
+ padding:15px 26px;font-size:15px;font-weight:700;letter-spacing:.02em;
+ color:#fff;background:linear-gradient(135deg,var(--accent),#a8471f);
+ box-shadow:0 8px 24px rgba(198,95,56,.4);transition:transform .16s ease,box-shadow .16s ease;
+ }
+ button.play.alt{background:linear-gradient(135deg,#5c7d8f,#38505d);box-shadow:0 8px 24px rgba(75,107,122,.4)}
+ button.play:hover{transform:translateY(-2px);box-shadow:0 16px 40px rgba(198,95,56,.5)}
+ button.play.alt:hover{box-shadow:0 16px 40px rgba(75,107,122,.5)}
+ .controls{
+ display:flex;gap:32px;flex-wrap:wrap;justify-content:center;margin-top:6px;
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.36s;
+ }
+ .ctrlcard{
+ background:rgba(255,249,241,.06);border:1px solid rgba(224,164,88,.22);
+ border-radius:12px;padding:14px 18px;min-width:190px;text-align:left;
+ }
+ .ctrlcard h4{font-size:12px;letter-spacing:.15em;text-transform:uppercase;color:var(--accent2);margin-bottom:8px}
+ .ctrlcard .row{display:flex;justify-content:space-between;font-size:13px;color:#e0d7c9;margin:3px 0}
+ kbd{
+ background:#2a2530;border:1px solid #4a4150;border-bottom-width:2px;border-radius:5px;
+ padding:1px 7px;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#f2e9dd;
+ }
+ .result-name{font-family:'Playfair Display',Georgia,serif;font-style:italic}
+ .foot{position:absolute;bottom:12px;font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted)}
+ @keyframes fadeUp{from{opacity:0;transform:translateY(28px)}to{opacity:1;transform:none}}
+</style>
+</head>
+<body>
+<div id="wrap">
+ <canvas id="c" width="960" height="540"></canvas>
+
+ <!-- MENU -->
+ <div id="menu" class="overlay">
+ <div class="kicker">Sous la lune de Montmartre</div>
+ <h1>Baguette <span class="em">Brawl</span></h1>
+ <p class="sub">Two crusty duelists face off beneath the Eiffel Tower. Swing your loaf, guard your crust, and knock the crumbs out of your rival. First to <b>2 rounds</b> wins the boulangerie.</p>
+ <div class="btns">
+ <button class="play" data-mode="1">Solo vs. Chef IA</button>
+ <button class="play alt" data-mode="2">Deux Joueurs</button>
+ </div>
+ <div class="controls">
+ <div class="ctrlcard">
+ <h4>Pierre · Joueur 1</h4>
+ <div class="row"><span>Move / Jump</span><span><kbd>A</kbd><kbd>D</kbd><kbd>W</kbd></span></div>
+ <div class="row"><span>Jab · Heavy</span><span><kbd>J</kbd><kbd>K</kbd></span></div>
+ <div class="row"><span>Guard</span><span><kbd>L</kbd></span></div>
+ </div>
+ <div class="ctrlcard">
+ <h4>Gaston · Joueur 2</h4>
+ <div class="row"><span>Move / Jump</span><span><kbd>←</kbd><kbd>→</kbd><kbd>↑</kbd></span></div>
+ <div class="row"><span>Jab · Heavy</span><span><kbd>,</kbd><kbd>.</kbd></span></div>
+ <div class="row"><span>Guard</span><span><kbd>/</kbd></span></div>
+ </div>
+ </div>
+ <div class="foot">Un jeu de pain · Paris</div>
+ </div>
+
+ <!-- ROUND / RESULT -->
+ <div id="result" class="overlay hide">
+ <div class="kicker" id="resKicker">Manche terminée</div>
+ <h1 id="resTitle">—</h1>
+ <p class="sub" id="resSub"></p>
+ <div class="btns" id="resBtns"></div>
+ </div>
+</div>
+
+<script>
+(() => {
+const cv = document.getElementById('c');
+const ctx = cv.getContext('2d');
+const W = 960, H = 540;
+const GROUND = 470;
+
+const menuEl = document.getElementById('menu');
+const resEl = document.getElementById('result');
+const resTitle = document.getElementById('resTitle');
+const resSub = document.getElementById('resSub');
+const resKicker = document.getElementById('resKicker');
+const resBtns = document.getElementById('resBtns');
+
+// ---------- audio (tiny synth, no external files) ----------
+let AC = null;
+function actx(){ if(!AC){ try{AC=new (window.AudioContext||window.webkitAudioContext)();}catch(e){} } return AC; }
+function tone(freq, dur, type='sine', vol=.15, slideTo=null){
+ const a=actx(); if(!a) return;
+ const o=a.createOscillator(), g=a.createGain();
+ o.type=type; o.frequency.setValueAtTime(freq,a.currentTime);
+ if(slideTo) o.frequency.exponentialRampToValueAtTime(slideTo,a.currentTime+dur);
+ g.gain.setValueAtTime(vol,a.currentTime);
+ g.gain.exponentialRampToValueAtTime(.0001,a.currentTime+dur);
+ o.connect(g); g.connect(a.destination); o.start(); o.stop(a.currentTime+dur+.02);
+}
+function noise(dur, vol=.2, hp=800){
+ const a=actx(); if(!a) return;
+ const n=Math.floor(a.sampleRate*dur), buf=a.createBuffer(1,n,a.sampleRate), d=buf.getChannelData(0);
+ for(let i=0;i<n;i++) d[i]=(Math.random()*2-1)*(1-i/n);
+ const s=a.createBufferSource(); s.buffer=buf;
+ const f=a.createBiquadFilter(); f.type='highpass'; f.frequency.value=hp;
+ const g=a.createGain(); g.gain.value=vol;
+ s.connect(f); f.connect(g); g.connect(a.destination); s.start();
+}
+const sfx = {
+ swing(){ noise(.12,.12,1200); },
+ hit(){ noise(.16,.32,500); tone(120,.1,'square',.14,70); },
+ block(){ tone(880,.09,'triangle',.16,660); noise(.05,.08,2500); },
+ jump(){ tone(300,.12,'sine',.1,520); },
+ ko(){ tone(400,.5,'sawtooth',.2,80); },
+ win(){ tone(523,.12,'triangle',.16); setTimeout(()=>tone(659,.12,'triangle',.16),120); setTimeout(()=>tone(784,.2,'triangle',.18),240); }
+};
+
+// ---------- input ----------
+const keys = new Set();
+addEventListener('keydown', e=>{
+ const k=e.key.length===1?e.key.toLowerCase():e.key;
+ if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown',' ','/'].includes(e.key)) e.preventDefault();
+ keys.add(k);
+});
+addEventListener('keyup', e=>{
+ const k=e.key.length===1?e.key.toLowerCase():e.key;
+ keys.delete(k);
+});
+
+// ---------- fighter ----------
+function makeFighter(x, facing, cfg){
+ return {
+ x, y:GROUND, vx:0, vy:0, facing, onGround:true,
+ hp:100, maxhp:100,
+ state:'idle', // idle | attack | hurt | ko
+ atk:null, // 'light'|'heavy'
+ atkT:0, atkDur:0, atkHit:false,
+ hurtT:0, blockT:0, blocking:false,
+ swing:0, // swing anim 0..1
+ walkPhase:0,
+ color:cfg.color, beret:cfg.beret, name:cfg.name,
+ isAI:cfg.isAI||false, ctrl:cfg.ctrl,
+ hitFlash:0, wins:0
+ };
+}
+const P1CTRL = {left:'a',right:'d',jump:'w',light:'j',heavy:'k',block:'l'};
+const P2CTRL = {left:'ArrowLeft',right:'ArrowRight',jump:'ArrowUp',light:',',heavy:'.',block:'/'};
+
+let f1, f2, mode=1, state='menu';
+let roundNum=1, roundTime=60, timeLeft=60, roundActive=false, freeze=0;
+let shake=0, particles=[], clouds=[], stars=[];
+
+function initClouds(){
+ clouds=[]; for(let i=0;i<5;i++) clouds.push({x:Math.random()*W,y:40+Math.random()*120,s:.15+Math.random()*.25,w:80+Math.random()*90});
+ stars=[]; for(let i=0;i<70;i++) stars.push({x:Math.random()*W,y:Math.random()*300,r:Math.random()*1.4+.3,t:Math.random()*6});
+}
+initClouds();
+
+function startGame(m){
+ mode=m; state='fight'; roundNum=1;
+ f1=makeFighter(300,1,{color:'#e0a458',beret:'#c62f2f',name:'Pierre',ctrl:P1CTRL});
+ f2=makeFighter(660,-1,{color:'#d9974a',beret:'#2f4b6b',name:'Gaston',ctrl:P2CTRL,isAI:(m===1)});
+ f1.wins=0; f2.wins=0;
+ actx();
+ startRound();
+}
+function startRound(){
+ resEl.classList.add('hide'); menuEl.classList.add('hide');
+ Object.assign(f1,{x:300,y:GROUND,vx:0,vy:0,hp:100,state:'idle',atk:null,atkT:0,hurtT:0,swing:0,facing:1});
+ Object.assign(f2,{x:660,y:GROUND,vx:0,vy:0,hp:100,state:'idle',atk:null,atkT:0,hurtT:0,swing:0,facing:-1});
+ timeLeft=roundTime; roundActive=false; particles=[]; freeze=0;
+ showAnnounce=`MANCHE ${roundNum}`; announceT=1.6; countdownT=3.2;
+ state='fight';
+}
+
+let showAnnounce='', announceT=0, countdownT=0;
+
+// ---------- combat tuning ----------
+const ATK = {
+ light:{dur:.34, active:[.08,.18], reach:78, dmg:7, kb:3.2, stun:.28},
+ heavy:{dur:.62, active:[.22,.36], reach:96, dmg:16, kb:8.5, stun:.5}
+};
+
+function tryAttack(f, type){
+ if(f.state==='attack'||f.state==='hurt'||f.state==='ko') return;
+ if(!f.onGround && type==='heavy') return;
+ f.state='attack'; f.atk=type; f.atkT=0; f.atkDur=ATK[type].dur; f.atkHit=false;
+ sfx.swing();
+}
+
+function updateFighter(f, other, dt){
+ const c=f.ctrl;
+ const canAct = f.state!=='hurt' && f.state!=='ko';
+ f.blocking=false;
+
+ // face opponent when idle-ish on ground
+ if(f.onGround && f.state!=='attack'){
+ f.facing = other.x < f.x ? -1 : 1;
+ }
+
+ if(!f.isAI){
+ // horizontal
+ let mv=0;
+ if(canAct && f.state!=='attack'){
+ if(keys.has(c.left)) mv-=1;
+ if(keys.has(c.right)) mv+=1;
+ if(keys.has(c.block)){ f.blocking=true; mv=0; }
+ }
+ f.vx = mv*3.4;
+ if(canAct && keys.has(c.jump) && f.onGround){ f.vy=-13.4; f.onGround=false; sfx.jump(); }
+ if(canAct){
+ if(keys.has(c.light)) tryAttack(f,'light');
+ else if(keys.has(c.heavy)) tryAttack(f,'heavy');
+ }
+ } else {
+ aiControl(f, other, dt);
+ }
+
+ // block flash timer
+ if(f.blocking) f.blockT=Math.min(f.blockT+dt,.2);
+
+ // gravity
+ f.vy += 42*dt;
+ f.y += f.vy;
+ f.x += f.vx;
+ if(f.y>=GROUND){ f.y=GROUND; f.vy=0; if(!f.onGround){f.onGround=true;} }
+ else f.onGround=false;
+ f.x=Math.max(60,Math.min(W-60,f.x));
+
+ if(Math.abs(f.vx)>.2 && f.onGround) f.walkPhase+=dt*12; else f.walkPhase*=.8;
+
+ // attack timeline
+ if(f.state==='attack'){
+ f.atkT+=dt;
+ const a=ATK[f.atk];
+ const p=f.atkT/f.atkDur;
+ f.swing = swingCurve(p);
+ // active window hit
+ if(!f.atkHit && p>=a.active[0] && p<=a.active[1]){
+ const dx=other.x-f.x;
+ const inFront = (f.facing>0 && dx>0)||(f.facing<0 && dx<0);
+ const dist=Math.abs(dx);
+ const vgap=Math.abs(other.y-f.y);
+ if(inFront && dist< a.reach+40 && dist>10 && vgap<120){
+ f.atkHit=true; landHit(f, other, a);
+ }
+ }
+ if(f.atkT>=f.atkDur){ f.state='idle'; f.atk=null; f.swing=0; }
+ } else {
+ f.swing*=.7;
+ }
+
+ if(f.state==='hurt'){ f.hurtT-=dt; if(f.hurtT<=0) f.state='idle'; }
+ if(f.hitFlash>0) f.hitFlash-=dt;
+ if(f.blockT>0 && !f.blocking) f.blockT-=dt*.5;
+}
+
+function swingCurve(p){
+ // windup then fast swing
+ if(p<.35) return -(p/.35)*.5; // wind back
+ const q=(p-.35)/.65;
+ return -0.5 + q*q*(3-2*q)*1.9; // smooth forward
+}
+
+function landHit(attacker, target, a){
+ if(target.state==='ko') return;
+ let dmg=a.dmg, kb=a.kb;
+ const blocked = target.blocking && target.onGround && ((target.facing>0&&attacker.x>target.x)||(target.facing<0&&attacker.x<target.x));
+ if(blocked){
+ dmg*=0.18; kb*=.5;
+ sfx.block();
+ spawnParticles(target.x+target.facing*-20, target.y-70, 6, '#ffe7b0', 3);
+ shake=Math.max(shake,4);
+ target.blockT=.2;
+ } else {
+ sfx.hit();
+ target.state='hurt'; target.hurtT=a.stun;
+ spawnParticles(target.x, target.y-70, 16, attacker.color, 6);
+ spawnParticles(target.x, target.y-80, 8, '#fff3dd', 5);
+ target.hitFlash=.18;
+ shake=Math.max(shake, a.dmg>10?11:6);
+ freeze=a.dmg>10?.07:.035;
+ }
+ target.hp=Math.max(0,target.hp-dmg);
+ target.vx = attacker.facing*kb;
+ if(!blocked && a.dmg>10){ target.vy=-4; target.onGround=false; }
+ if(target.hp<=0){ target.state='ko'; target.vx=attacker.facing*5; target.vy=-8; target.onGround=false; sfx.ko(); }
+}
+
+// ---------- AI ----------
+function aiControl(f, o, dt){
+ f._t=(f._t||0)+dt;
+ const dist=Math.abs(o.x-f.x);
+ const dir=o.x<f.x?-1:1;
+ let mv=0;
+
+ if(f.state==='attack'||f.state==='hurt'){ f.vx*=.85; return; }
+
+ // guard if opponent is attacking & close
+ if(o.state==='attack' && dist<130 && Math.random()<0.14){ f.blocking=true; f.vx=0; return; }
+
+ const ideal=90;
+ if(dist>ideal+30) mv=dir;
+ else if(dist<ideal-30) mv=-dir;
+ else {
+ // in range: attack with some rhythm
+ if(f._t>0.35+Math.random()*.4){
+ f._t=0;
+ if(dist<100 && Math.random()<.55) tryAttack(f,'heavy');
+ else tryAttack(f,'light');
+ }
+ mv = (Math.random()<.3? -dir : 0);
+ }
+ // occasional jump
+ if(f.onGround && Math.random()<0.01 && dist<180){ f.vy=-13; f.onGround=false; }
+ f.vx=mv*3.0;
+}
+
+// ---------- particles ----------
+function spawnParticles(x,y,n,color,spd){
+ for(let i=0;i<n;i++){
+ const ang=Math.random()*Math.PI*2, s=Math.random()*spd+1;
+ particles.push({x,y,vx:Math.cos(ang)*s,vy:Math.sin(ang)*s-2,life:1,color,r:Math.random()*3+1.5});
+ }
+}
+function updateParticles(dt){
+ for(let i=particles.length-1;i>=0;i--){
+ const p=particles[i];
+ p.vy+=18*dt; p.x+=p.vx; p.y+=p.vy; p.life-=dt*1.7;
+ if(p.life<=0||p.y>GROUND+10) particles.splice(i,1);
+ }
+}
+
+// ---------- round flow ----------
+function checkRoundEnd(){
+ if(!roundActive) return;
+ let winner=null;
+ if(f1.hp<=0 && f2.hp<=0) winner='draw';
+ else if(f1.hp<=0) winner=f2;
+ else if(f2.hp<=0) winner=f1;
+ else if(timeLeft<=0){
+ if(f1.hp>f2.hp) winner=f1; else if(f2.hp>f1.hp) winner=f2; else winner='draw';
+ }
+ if(winner!==null){
+ roundActive=false;
+ setTimeout(()=>endRound(winner), 900);
+ }
+}
+function endRound(winner){
+ if(winner!=='draw' && winner) winner.wins++;
+ const matchOver = f1.wins>=2 || f2.wins>=2;
+ state='result';
+ if(matchOver){
+ const champ = f1.wins>f2.wins? f1 : f2;
+ sfx.win();
+ resKicker.textContent='Vainqueur du duel';
+ resTitle.innerHTML = `<span class="result-name">${champ.name}</span> l'emporte !`;
+ resSub.textContent = `${champ.name} remporte le match ${Math.max(f1.wins,f2.wins)}–${Math.min(f1.wins,f2.wins)} et devient le Roi de la Baguette.`;
+ resBtns.innerHTML='';
+ addBtn('Rejouer', ()=>startGame(mode), false);
+ addBtn('Menu', ()=>toMenu(), true);
+ } else {
+ resKicker.textContent='Manche terminée';
+ if(winner==='draw'){ resTitle.textContent='Égalité !'; resSub.textContent='Personne ne cède une miette.'; }
+ else { resTitle.innerHTML=`<span class="result-name">${winner.name}</span> gagne la manche`; resSub.textContent=`Score — Pierre ${f1.wins} · ${f2.wins} Gaston`; }
+ resBtns.innerHTML='';
+ roundNum++;
+ addBtn(`Manche ${roundNum} →`, ()=>startRound(), false);
+ addBtn('Menu', ()=>toMenu(), true);
+ }
+ resEl.classList.remove('hide');
+}
+function addBtn(label, fn, alt){
+ const b=document.createElement('button'); b.className='play'+(alt?' alt':''); b.textContent=label;
+ b.onclick=fn; resBtns.appendChild(b);
+}
+function toMenu(){ state='menu'; resEl.classList.add('hide'); menuEl.classList.remove('hide'); }
+
+// ---------- drawing ----------
+function draw(){
+ // sky gradient (dusk over Paris)
+ const g=ctx.createLinearGradient(0,0,0,H);
+ g.addColorStop(0,'#241a35'); g.addColorStop(.4,'#4a2e46'); g.addColorStop(.7,'#8a4a45'); g.addColorStop(1,'#c9754a');
+ ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
+
+ ctx.save();
+ if(shake>0){ ctx.translate((Math.random()-.5)*shake,(Math.random()-.5)*shake); }
+
+ // moon
+ ctx.save();
+ ctx.shadowColor='rgba(255,236,200,.7)'; ctx.shadowBlur=40;
+ ctx.fillStyle='#fdf1d6'; ctx.beginPath(); ctx.arc(770,110,46,0,7); ctx.fill();
+ ctx.restore();
+
+ // stars
+ for(const s of stars){ s.t+=.03; const tw=.5+.5*Math.sin(s.t);
+ ctx.globalAlpha=.3+tw*.5; ctx.fillStyle='#fff7e6'; ctx.fillRect(s.x,s.y,s.r,s.r); }
+ ctx.globalAlpha=1;
+
+ // clouds
+ for(const cl of clouds){ cl.x-=cl.s; if(cl.x<-cl.w) cl.x=W+cl.w;
+ ctx.fillStyle='rgba(60,40,55,.35)';
+ ctx.beginPath(); ctx.ellipse(cl.x,cl.y,cl.w,cl.w*.32,0,0,7); ctx.fill(); }
+
+ drawEiffel(480, GROUND+40, 300);
+ drawSkyline();
+
+ // ground
+ const gg=ctx.createLinearGradient(0,GROUND+18,0,H);
+ gg.addColorStop(0,'#3a2c22'); gg.addColorStop(1,'#241a14');
+ ctx.fillStyle=gg; ctx.fillRect(0,GROUND+18,W,H-GROUND);
+ // cobble line
+ ctx.strokeStyle='rgba(224,164,88,.15)'; ctx.lineWidth=2;
+ ctx.beginPath(); ctx.moveTo(0,GROUND+18); ctx.lineTo(W,GROUND+18); ctx.stroke();
+ for(let x=0;x<W;x+=46){ ctx.beginPath(); ctx.moveTo(x+((Math.floor((GROUND)/1))%2),GROUND+22); ctx.lineTo(x+20,H); ctx.strokeStyle='rgba(0,0,0,.18)'; ctx.stroke(); }
+
+ // fighters (draw back-most first by x? just draw both)
+ if(f1&&f2){
+ // shadows
+ drawShadow(f1); drawShadow(f2);
+ drawFighter(f2); drawFighter(f1);
+ }
+
+ // particles
+ for(const p of particles){ ctx.globalAlpha=Math.max(0,p.life); ctx.fillStyle=p.color;
+ ctx.fillRect(p.x-p.r/2,p.y-p.r/2,p.r,p.r); }
+ ctx.globalAlpha=1;
+
+ ctx.restore();
+
+ if(f1&&f2) drawHUD();
+ if(announceT>0) drawAnnounce();
+ if(countdownT>0 && countdownT<3.2) drawCountdown();
+}
+
+function drawShadow(f){
+ const sc=f.onGround?1:0.6;
+ ctx.fillStyle='rgba(0,0,0,.28)';
+ ctx.beginPath(); ctx.ellipse(f.x,GROUND+18,34*sc,9*sc,0,0,7); ctx.fill();
+}
+
+function drawEiffel(cx,baseY,h){
+ ctx.save();
+ ctx.strokeStyle='rgba(30,18,26,.65)'; ctx.fillStyle='rgba(30,18,26,.55)';
+ ctx.lineWidth=3;
+ const topY=baseY-h, topW=6, baseW=110, midY=baseY-h*.42, midW=52, plY=baseY-h*.14, plW=88;
+ // legs (curved)
+ function leg(sx){
+ ctx.beginPath();
+ ctx.moveTo(cx-baseW*sx, baseY);
+ ctx.quadraticCurveTo(cx-plW*sx*.7, plY, cx-midW*sx, midY);
+ ctx.quadraticCurveTo(cx-topW*sx*3, topY+h*.25, cx-topW*sx, topY);
+ ctx.stroke();
+ }
+ leg(1); leg(-1);
+ // arches
+ ctx.beginPath(); ctx.moveTo(cx-baseW*.72,plY); ctx.quadraticCurveTo(cx,plY-38,cx+baseW*.72,plY); ctx.stroke();
+ // platforms
+ ctx.fillRect(cx-plW,plY-4,plW*2,8);
+ ctx.fillRect(cx-midW,midY-3,midW*2,6);
+ // cross hatch body
+ ctx.lineWidth=1.5; ctx.strokeStyle='rgba(30,18,26,.4)';
+ for(let i=0;i<6;i++){ const y=plY-(plY-midY)*(i/6); const wl=plW-(plW-midW)*(i/6);
+ ctx.beginPath(); ctx.moveTo(cx-wl,y); ctx.lineTo(cx+wl,y); ctx.stroke(); }
+ // antenna + glow tip
+ ctx.strokeStyle='rgba(30,18,26,.65)'; ctx.lineWidth=3;
+ ctx.beginPath(); ctx.moveTo(cx,topY); ctx.lineTo(cx,topY-18); ctx.stroke();
+ ctx.fillStyle='rgba(255,220,150,.9)'; ctx.beginPath(); ctx.arc(cx,topY-20,3,0,7); ctx.fill();
+ ctx.restore();
+}
+function drawSkyline(){
+ ctx.fillStyle='rgba(25,16,24,.5)';
+ const b=[[40,60,120],[130,90,90],[210,40,150],[720,70,120],[810,110,100],[900,50,150]];
+ for(const [x,w,hh] of b){ ctx.fillRect(x,GROUND+18-hh,w,hh);
+ ctx.fillStyle='rgba(255,210,140,.18)';
+ for(let wy=GROUND+18-hh+10;wy<GROUND+8;wy+=18) for(let wx=x+6;wx<x+w-6;wx+=16) if(Math.random()<.5) ctx.fillRect(wx,wy,4,6);
+ ctx.fillStyle='rgba(25,16,24,.5)';
+ }
+}
+
+function drawFighter(f){
+ ctx.save();
+ ctx.translate(f.x, f.y);
+ ctx.scale(f.facing,1);
+
+ const hurt = f.hitFlash>0 && Math.floor(f.hitFlash*30)%2===0;
+ const kod = f.state==='ko';
+ if(kod) ctx.rotate(f.facing* -0.9);
+
+ // legs
+ const step=Math.sin(f.walkPhase)*8;
+ ctx.strokeStyle='#8a5a2a'; ctx.lineWidth=6; ctx.lineCap='round';
+ ctx.beginPath(); ctx.moveTo(-8,-8); ctx.lineTo(-10, 0-(f.onGround?0:-4)); ctx.stroke();
+ ctx.beginPath(); ctx.moveTo(8,-8); ctx.lineTo(10+step*0, 0); ctx.stroke();
+ // feet
+ ctx.fillStyle='#5c3a1a'; ctx.beginPath(); ctx.ellipse(-11,2,7,4,0,0,7); ctx.fill();
+ ctx.beginPath(); ctx.ellipse(11,2,7,4,0,0,7); ctx.fill();
+
+ // BODY — the baguette torso (vertical rounded loaf)
+ const bh=96, bw=40;
+ const bg=ctx.createLinearGradient(-bw/2,0,bw/2,0);
+ bg.addColorStop(0,'#a86a2e'); bg.addColorStop(.4,f.color); bg.addColorStop(.6,'#f2c987'); bg.addColorStop(1,'#a86a2e');
+ ctx.fillStyle=hurt?'#fff':bg;
+ roundedLoaf(0,-bh/2-8,bw,bh);
+ ctx.fill();
+ // crust score marks
+ ctx.strokeStyle='rgba(90,50,20,.55)'; ctx.lineWidth=2.5;
+ for(let i=0;i<4;i++){ const yy=-bh-2+ i*22+16;
+ ctx.beginPath(); ctx.moveTo(-6,yy); ctx.lineTo(8,yy-10); ctx.stroke(); }
+ // flour dust
+ ctx.fillStyle='rgba(255,250,235,.35)';
+ for(let i=0;i<7;i++) ctx.fillRect(-bw/2+4+Math.random()*bw-8, -bh-4+Math.random()*bh, 2,2);
+
+ // beret
+ ctx.save();
+ ctx.translate(0,-bh-6);
+ ctx.fillStyle=f.beret;
+ ctx.beginPath(); ctx.ellipse(2,-4,24,13,-.15,0,7); ctx.fill();
+ ctx.beginPath(); ctx.ellipse(0,2,20,7,0,0,7); ctx.fill();
+ ctx.fillStyle='#3a2c22'; ctx.beginPath(); ctx.arc(14,-11,2.5,0,7); ctx.fill(); // stalk
+ ctx.restore();
+
+ // eyes
+ ctx.fillStyle='#3a2c22';
+ const eyY=-bh+8;
+ if(kod){ // x eyes
+ ctx.strokeStyle='#3a2c22'; ctx.lineWidth=2.4;
+ xeye(6,eyY); xeye(20,eyY);
+ } else {
+ ctx.beginPath(); ctx.arc(8,eyY,3.4,0,7); ctx.fill();
+ ctx.beginPath(); ctx.arc(20,eyY,3.4,0,7); ctx.fill();
+ ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(9,eyY-1,1.1,0,7); ctx.fill();
+ ctx.beginPath(); ctx.arc(21,eyY-1,1.1,0,7); ctx.fill();
+ }
+ // little mustache / mouth
+ ctx.strokeStyle='#5c3a1a'; ctx.lineWidth=2;
+ ctx.beginPath();
+ if(f.state==='attack'){ ctx.arc(14,eyY+13,4,0,Math.PI); }
+ else { ctx.moveTo(9,eyY+12); ctx.quadraticCurveTo(14,eyY+15,20,eyY+12); }
+ ctx.stroke();
+
+ // ARM + sword baguette (the weapon)
+ ctx.save();
+ ctx.translate(16,-bh/2-4);
+ const base = -0.5, ang = base + f.swing*1.5;
+ ctx.rotate(ang);
+ // upper arm
+ ctx.strokeStyle='#8a5a2a'; ctx.lineWidth=6; ctx.lineCap='round';
+ ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(22,0); ctx.stroke();
+ // the weapon baguette
+ ctx.translate(22,0);
+ const wg=ctx.createLinearGradient(0,-6,0,6);
+ wg.addColorStop(0,'#e8b96e'); wg.addColorStop(.5,'#f2c987'); wg.addColorStop(1,'#b07a34');
+ ctx.fillStyle=wg;
+ roundedRect(0,-7,58,14,7); ctx.fill();
+ ctx.strokeStyle='rgba(90,50,20,.5)'; ctx.lineWidth=1.8;
+ for(let i=1;i<5;i++){ ctx.beginPath(); ctx.moveTo(6+i*10,-3); ctx.lineTo(10+i*10,3); ctx.stroke(); }
+ // swing trail
+ if(f.state==='attack' && f.swing>.3){
+ ctx.globalAlpha=.25; ctx.strokeStyle='#fff3dd'; ctx.lineWidth=10; ctx.lineCap='round';
+ ctx.beginPath(); ctx.arc(-22,0,64,-0.6,0.5); ctx.stroke(); ctx.globalAlpha=1;
+ }
+ ctx.restore();
+
+ // block shield shimmer
+ if(f.blocking && f.blockT>0){
+ ctx.globalAlpha=Math.min(.5,f.blockT*2.5);
+ ctx.strokeStyle='#ffe7b0'; ctx.lineWidth=3;
+ ctx.beginPath(); ctx.ellipse(f.facing*22,-bh/2-6,26,54,0,-1.2,1.2); ctx.stroke();
+ ctx.globalAlpha=1;
+ }
+
+ ctx.restore();
+}
+function xeye(x,y){ ctx.beginPath(); ctx.moveTo(x-3,y-3); ctx.lineTo(x+3,y+3); ctx.moveTo(x+3,y-3); ctx.lineTo(x-3,y+3); ctx.stroke(); }
+function roundedLoaf(cx,topY,w,h){
+ const r=w/2;
+ ctx.beginPath();
+ ctx.moveTo(cx-r,topY+r);
+ ctx.arc(cx,topY+r,r,Math.PI,0);
+ ctx.lineTo(cx+r,topY+h-r);
+ ctx.arc(cx,topY+h-r,r,0,Math.PI);
+ ctx.closePath();
+}
+function roundedRect(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 drawHUD(){
+ // HP bars
+ hpBar(30,26,360,f1,false,'Pierre',f1.wins);
+ hpBar(W-30-360,26,360,f2,true,'Gaston',f2.wins);
+ // timer
+ ctx.save();
+ ctx.fillStyle='rgba(18,15,22,.7)'; roundedRect(W/2-40,20,80,54,10); ctx.fill();
+ ctx.strokeStyle='rgba(224,164,88,.5)'; ctx.lineWidth=1.5; ctx.stroke();
+ ctx.fillStyle='#f5f2ea'; ctx.font='800 30px Georgia,serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.fillText(Math.ceil(Math.max(0,timeLeft)), W/2, 48);
+ ctx.restore();
+}
+function hpBar(x,y,w,f,right,name,wins){
+ const h=20;
+ ctx.save();
+ // name
+ ctx.fillStyle='#f5f2ea'; ctx.font='700 15px -apple-system,Arial'; ctx.textBaseline='alphabetic';
+ ctx.textAlign=right?'right':'left';
+ ctx.fillText(name, right? x+w : x, y-6);
+ // round pips
+ for(let i=0;i<2;i++){ const px=right? x+w-8-i*16 : x+8+i*16;
+ ctx.beginPath(); ctx.arc(px, y-11, 5, 0,7);
+ ctx.fillStyle = i<wins? '#e0a458':'rgba(255,255,255,.22)'; ctx.fill(); }
+ // frame
+ ctx.fillStyle='rgba(18,15,22,.6)'; roundedRect(x-3,y-3,w+6,h+6,7); ctx.fill();
+ ctx.fillStyle='rgba(0,0,0,.4)'; roundedRect(x,y,w,h,5); ctx.fill();
+ // fill
+ const pct=f.hp/f.maxhp;
+ const fw=w*pct;
+ const fx = right? x+w-fw : x;
+ const grad=ctx.createLinearGradient(0,y,0,y+h);
+ const col = pct>.4? ['#e0a458','#c65f38'] : ['#e07a45','#a8321f'];
+ grad.addColorStop(0,col[0]); grad.addColorStop(1,col[1]);
+ ctx.fillStyle=grad; roundedRect(fx,y,fw,h,5); ctx.fill();
+ ctx.strokeStyle='rgba(224,164,88,.4)'; ctx.lineWidth=1.5; roundedRect(x-3,y-3,w+6,h+6,7); ctx.stroke();
+ ctx.restore();
+}
+function drawAnnounce(){
+ ctx.save();
+ const a=Math.min(1,announceT*1.4)*Math.min(1,(1.6-announceT)*4+ .2);
+ ctx.globalAlpha=Math.max(0,Math.min(1,a));
+ ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.fillStyle='#f5f2ea'; ctx.font="italic 800 60px Georgia,'Playfair Display',serif";
+ ctx.shadowColor='rgba(198,95,56,.6)'; ctx.shadowBlur=24;
+ ctx.fillText(showAnnounce, W/2, 200);
+ ctx.restore();
+}
+function drawCountdown(){
+ const n=Math.ceil(countdownT-0.2);
+ if(n<=0) return;
+ const frac=(countdownT-0.2)%1;
+ ctx.save();
+ ctx.globalAlpha=Math.min(1,frac*2);
+ ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.fillStyle='#e0a458'; ctx.font="800 90px Georgia,serif";
+ ctx.shadowColor='rgba(0,0,0,.5)'; ctx.shadowBlur=18;
+ const sc=1+ (1-frac)*0.4;
+ ctx.translate(W/2,270); ctx.scale(sc,sc);
+ ctx.fillText(n>0?String(n):'', 0,0);
+ ctx.restore();
+}
+
+// ---------- loop ----------
+let last=performance.now();
+function loop(now){
+ let dt=(now-last)/1000; last=now;
+ if(dt>0.05) dt=0.05;
+
+ if(state==='fight'){
+ if(freeze>0){ freeze-=dt; }
+ else {
+ // announce / countdown
+ if(announceT>0){ announceT-=dt; }
+ if(countdownT>0){
+ countdownT-=dt;
+ if(countdownT<=0.2 && !roundActive){ roundActive=true; }
+ }
+ if(roundActive){
+ if(timeLeft>0) timeLeft-=dt;
+ updateFighter(f1,f2,dt);
+ updateFighter(f2,f1,dt);
+ checkRoundEnd();
+ } else if(f1&&f2){
+ // idle physics only during countdown
+ f1.vx*=.8; f2.vx*=.8;
+ updateFighter(f1,f2,dt*0); // freeze act but keep facing
+ f1.facing = f2.x<f1.x?-1:1; f2.facing=f1.x<f2.x?-1:1;
+ }
+ updateParticles(dt);
+ if(shake>0) shake=Math.max(0,shake-dt*40);
+ }
+ }
+ if(f1&&f2) draw();
+ else { drawMenuBg(); }
+ requestAnimationFrame(loop);
+}
+function drawMenuBg(){
+ const g=ctx.createLinearGradient(0,0,0,H);
+ g.addColorStop(0,'#241a35'); g.addColorStop(.4,'#4a2e46'); g.addColorStop(.7,'#8a4a45'); g.addColorStop(1,'#c9754a');
+ ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
+ ctx.save();
+ ctx.shadowColor='rgba(255,236,200,.7)'; ctx.shadowBlur=40;
+ ctx.fillStyle='#fdf1d6'; ctx.beginPath(); ctx.arc(770,110,46,0,7); ctx.fill(); ctx.restore();
+ for(const s of stars){ s.t+=.03; const tw=.5+.5*Math.sin(s.t); ctx.globalAlpha=.3+tw*.5; ctx.fillStyle='#fff7e6'; ctx.fillRect(s.x,s.y,s.r,s.r);} ctx.globalAlpha=1;
+ for(const cl of clouds){ cl.x-=cl.s; if(cl.x<-cl.w) cl.x=W+cl.w; ctx.fillStyle='rgba(60,40,55,.35)'; ctx.beginPath(); ctx.ellipse(cl.x,cl.y,cl.w,cl.w*.32,0,0,7); ctx.fill(); }
+ drawEiffel(480,GROUND+40,300); drawSkyline();
+ ctx.fillStyle='#241a14'; ctx.fillRect(0,GROUND+18,W,H-GROUND);
+}
+requestAnimationFrame(loop);
+
+// menu buttons
+document.querySelectorAll('.play[data-mode]').forEach(b=>{
+ b.addEventListener('click',()=>startGame(parseInt(b.dataset.mode)));
+});
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/5ed25e8a4aae/codex.html b/data/artifacts/5ed25e8a4aae/codex.html
new file mode 100644
index 0000000..c21e2a1
--- /dev/null
+++ b/data/artifacts/5ed25e8a4aae/codex.html
@@ -0,0 +1,331 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Baguette Duel: Paris</title>
+ <style>
+ :root{
+ --bg:#12002b;
+ --surface:#1e0b3d;
+ --text:#f3e9ff;
+ --muted:#8d7bb0;
+ --accent1:#ff2e88;
+ --accent2:#00e5ff;
+ --radius:14px;
+ --shadow:0 8px 24px rgba(0,0,0,.12);
+ --hover:0 16px 48px rgba(0,0,0,.18);
+ --glow:0 0 24px rgba(255,46,136,.35);
+ }
+ *{box-sizing:border-box}
+ html,body{height:100%}
+ body{
+ margin:0;
+ color:var(--text);
+ font:16px -apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;
+ background: linear-gradient(120deg,#12002b,#240a4d,#12002b);
+ background-size: 220% 220%;
+ animation:bgDrift 18s ease-in-out 1;
+ overflow:hidden;
+ }
+ @keyframes bgDrift{
+ 0%{background-position:0% 50%}
+ 50%{background-position:100% 50%}
+ 100%{background-position:0% 50%}
+ }
+ @keyframes fadeUp{
+ from{opacity:0;transform:translateY(28px)}
+ to{opacity:1;transform:none}
+ }
+ .wrap{
+ max-width:1180px;
+ margin:0 auto;
+ padding:32px 24px;
+ height:100%;
+ display:grid;
+ grid-template-rows:auto auto 1fr auto;
+ gap:16px;
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;
+ }
+ .title{
+ font-family:Didot,'Bodoni MT','Playfair Display',Georgia,serif;
+ font-size:50px;
+ line-height:1.06;
+ margin:0;
+ letter-spacing:.01em;
+ text-shadow:var(--glow);
+ }
+ .subtitle{
+ margin:0;
+ color:var(--muted);
+ font-size:21px;
+ }
+ .label{
+ font-size:12px;
+ letter-spacing:.15em;
+ text-transform:uppercase;
+ color:var(--muted);
+ }
+ .hud{
+ display:grid;
+ grid-template-columns: repeat(auto-fit,minmax(260px,1fr));
+ gap:24px;
+ }
+ .card{
+ background:rgba(30,11,61,.88);
+ border:1px solid rgba(255,255,255,.08);
+ border-radius:var(--radius);
+ padding:16px;
+ box-shadow:var(--shadow);
+ transition:transform .2s ease, box-shadow .2s ease, border-color .2s ease;
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;
+ }
+ .card:nth-child(1){animation-delay:.09s}
+ .card:nth-child(2){animation-delay:.18s}
+ .card:nth-child(3){animation-delay:.27s}
+ .card:hover{
+ transform:translateY(-2px);
+ box-shadow:var(--hover);
+ border-color:rgba(0,229,255,.45);
+ }
+ .bar{
+ width:100%;
+ height:12px;
+ border-radius:999px;
+ background:rgba(255,255,255,.08);
+ overflow:hidden;
+ margin-top:8px;
+ }
+ .fill{
+ height:100%;
+ width:100%;
+ background:linear-gradient(90deg,var(--accent1),var(--accent2));
+ transition:width .2s ease;
+ box-shadow:0 0 24px rgba(0,229,255,.35);
+ }
+ .arena{
+ background:rgba(30,11,61,.65);
+ border:1px solid rgba(255,255,255,.1);
+ border-radius:var(--radius);
+ box-shadow:var(--shadow);
+ position:relative;
+ overflow:hidden;
+ min-height:360px;
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both .36s;
+ }
+ .skyline{
+ position:absolute;inset:0;
+ background:
+ radial-gradient(circle at 20% 10%,rgba(255,46,136,.18),transparent 30%),
+ radial-gradient(circle at 80% 20%,rgba(0,229,255,.16),transparent 28%),
+ linear-gradient(to top,rgba(0,0,0,.35),transparent 45%);
+ pointer-events:none;
+ }
+ .fighter{
+ position:absolute;
+ width:120px;
+ height:120px;
+ display:grid;
+ place-items:center;
+ font-size:74px;
+ user-select:none;
+ transition:transform .2s ease;
+ filter:drop-shadow(0 10px 16px rgba(0,0,0,.35));
+ }
+ .name{
+ position:absolute;
+ top:-22px;
+ font-size:12px;
+ letter-spacing:.15em;
+ text-transform:uppercase;
+ color:var(--muted);
+ width:100%;
+ text-align:center;
+ }
+ .fx{
+ position:absolute;
+ font-size:34px;
+ pointer-events:none;
+ animation:pop .35s ease forwards;
+ }
+ @keyframes pop{
+ from{opacity:1;transform:translateY(0) scale(.8)}
+ to{opacity:0;transform:translateY(-24px) scale(1.2)}
+ }
+ .controls{
+ display:flex;
+ flex-wrap:wrap;
+ gap:12px;
+ align-items:center;
+ justify-content:space-between;
+ animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both .45s;
+ }
+ .btns{display:flex;gap:12px;flex-wrap:wrap}
+ button{
+ border:1px solid rgba(255,255,255,.16);
+ background:linear-gradient(90deg,rgba(255,46,136,.18),rgba(0,229,255,.18));
+ color:var(--text);
+ border-radius:12px;
+ padding:12px 16px;
+ font-weight:600;
+ cursor:pointer;
+ transition:all .18s ease;
+ box-shadow:var(--shadow);
+ }
+ button:hover{transform:translateY(-1px);box-shadow:var(--hover)}
+ button:active{transform:translateY(0)}
+ .log{
+ color:var(--muted);
+ min-height:24px;
+ font-size:16px;
+ }
+ .winner{
+ color:var(--accent2);
+ text-shadow:0 0 24px rgba(0,229,255,.45);
+ font-weight:700;
+ }
+ </style>
+</head>
+<body>
+ <div class="wrap">
+ <p class="label">Rue Royale • Neon Night Duel</p>
+ <h1 class="title">Baguette Battle: Paris</h1>
+ <p class="subtitle">Two Parisian champions settle glory with enchanted baguettes.</p>
+
+ <section class="hud">
+ <article class="card">
+ <p class="label">Fighter One</p>
+ <h3 style="margin:8px 0 0;font-family:Didot,'Bodoni MT','Playfair Display',Georgia,serif;font-size:28px;">Marcel “Crust”</h3>
+ <div class="bar"><div id="hp1" class="fill"></div></div>
+ <p id="hp1txt" style="margin:8px 0 0;color:var(--muted)">100 HP</p>
+ </article>
+ <article class="card">
+ <p class="label">Fighter Two</p>
+ <h3 style="margin:8px 0 0;font-family:Didot,'Bodoni MT','Playfair Display',Georgia,serif;font-size:28px;">Colette “Crumb”</h3>
+ <div class="bar"><div id="hp2" class="fill"></div></div>
+ <p id="hp2txt" style="margin:8px 0 0;color:var(--muted)">100 HP</p>
+ </article>
+ <article class="card">
+ <p class="label">Round</p>
+ <h3 id="round" style="margin:8px 0 0;font-family:Didot,'Bodoni MT','Playfair Display',Georgia,serif;font-size:38px;">1</h3>
+ <p id="status" style="margin:8px 0 0;color:var(--muted)">Choose an action to strike.</p>
+ </article>
+ </section>
+
+ <section class="arena" id="arena">
+ <div class="skyline"></div>
+ <div id="f1" class="fighter" style="left:12%;bottom:14%;">
+ <span class="name">Marcel</span>🥖
+ </div>
+ <div id="f2" class="fighter" style="right:12%;bottom:14%;">
+ <span class="name">Colette</span>🥖
+ </div>
+ </section>
+
+ <div class="controls">
+ <div class="btns">
+ <button data-move="quick">Quick Jab</button>
+ <button data-move="heavy">Heavy Swing</button>
+ <button data-move="parry">Parry & Riposte</button>
+ <button id="restart">New Duel</button>
+ </div>
+ <div class="log" id="log">Paris crowd hushes... 🕯️</div>
+ </div>
+ </div>
+
+ <script>
+ const state = { hp1:100, hp2:100, round:1, over:false };
+ const hp1El = document.getElementById('hp1');
+ const hp2El = document.getElementById('hp2');
+ const hp1Txt = document.getElementById('hp1txt');
+ const hp2Txt = document.getElementById('hp2txt');
+ const roundEl = document.getElementById('round');
+ const logEl = document.getElementById('log');
+ const statusEl = document.getElementById('status');
+ const f1 = document.getElementById('f1');
+ const f2 = document.getElementById('f2');
+ const arena = document.getElementById('arena');
+
+ function clamp(v){ return Math.max(0, Math.min(100, v)); }
+ function rand(a,b){ return Math.floor(Math.random()*(b-a+1))+a; }
+
+ function updateUI(){
+ hp1El.style.width = state.hp1 + '%';
+ hp2El.style.width = state.hp2 + '%';
+ hp1Txt.textContent = `${state.hp1} HP`;
+ hp2Txt.textContent = `${state.hp2} HP`;
+ roundEl.textContent = state.round;
+ }
+
+ function fx(x,y,text,color='#fff'){
+ const d = document.createElement('div');
+ d.className='fx';
+ d.style.left = x+'px';
+ d.style.top = y+'px';
+ d.style.color = color;
+ d.textContent = text;
+ arena.appendChild(d);
+ setTimeout(()=>d.remove(),360);
+ }
+
+ function botMove(){ return ['quick','heavy','parry'][rand(0,2)]; }
+
+ function resolve(player, bot){
+ let pD=0,bD=0,msg='';
+ if(player==='quick') pD = rand(10,16);
+ if(player==='heavy') pD = rand(16,28);
+ if(player==='parry') pD = rand(6,12);
+
+ if(bot==='quick') bD = rand(10,16);
+ if(bot==='heavy') bD = rand(16,28);
+ if(bot==='parry') bD = rand(6,12);
+
+ if(player==='parry' && (bot==='quick'||bot==='heavy')) { bD = Math.floor(bD*0.35); pD += 8; }
+ if(bot==='parry' && (player==='quick'||player==='heavy')) { pD = Math.floor(pD*0.35); bD += 8; }
+
+ if(player==='heavy' && Math.random()<0.22){ pD=0; msg+=' You whiffed!'; }
+ if(bot==='heavy' && Math.random()<0.22){ bD=0; msg+=' Colette whiffed!'; }
+
+ state.hp2 = clamp(state.hp2 - pD);
+ state.hp1 = clamp(state.hp1 - bD);
+
+ f1.style.transform='translateX(16px) rotate(-6deg)';
+ f2.style.transform='translateX(-16px) rotate(6deg)';
+ setTimeout(()=>{f1.style.transform='';f2.style.transform='';},180);
+
+ const r1 = f1.getBoundingClientRect(), r2 = f2.getBoundingClientRect(), ra=arena.getBoundingClientRect();
+ fx(r2.left-ra.left+50, r2.top-ra.top+35, `-${pD}`, 'var(--accent2)');
+ fx(r1.left-ra.left+50, r1.top-ra.top+35, `-${bD}`, 'var(--accent1)');
+
+ state.round++;
+ updateUI();
+ const map = {quick:'quick jab',heavy:'heavy swing',parry:'parry'};
+ logEl.textContent = `You used ${map[player]}, Colette used ${map[bot]}.${msg}`;
+ statusEl.textContent = `Damage dealt: ${pD} • Damage taken: ${bD}`;
+
+ if(state.hp1<=0 || state.hp2<=0){
+ state.over=true;
+ let w = state.hp1===state.hp2 ? "It's a draw in Paris!" : (state.hp1>state.hp2 ? "Marcel wins the baguette crown! 🏆" : "Colette conquers Rue Royale! 🏆");
+ logEl.innerHTML = `<span class="winner">${w}</span>`;
+ statusEl.textContent = "Press New Duel to play again.";
+ }
+ }
+
+ document.querySelectorAll('button[data-move]').forEach(btn=>{
+ btn.addEventListener('click',()=>{
+ if(state.over) return;
+ resolve(btn.dataset.move, botMove());
+ });
+ });
+
+ document.getElementById('restart').addEventListener('click',()=>{
+ state.hp1=100; state.hp2=100; state.round=1; state.over=false;
+ updateUI();
+ logEl.textContent='Fresh baguettes. Fresh rivalry.';
+ statusEl.textContent='Choose an action to strike.';
+ });
+
+ updateUI();
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/5ed25e8a4aae/qwen25-7b.html b/data/artifacts/5ed25e8a4aae/qwen25-7b.html
new file mode 100644
index 0000000..0a31726
--- /dev/null
+++ b/data/artifacts/5ed25e8a4aae/qwen25-7b.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Baguettes Battle</title>
+ <style>
+ body {
+ font-family: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+ color: #f5f2ea;
+ background-color: #0d0d0f;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ }
+ .container {
+ width: 80%;
+ max-width: 640px;
+ text-align: center;
+ color: #f5f2ea;
+ background-color: #17171c;
+ padding: 32px;
+ border-radius: 14px;
+ }
+ h1 {
+ font-size: 50px;
+ letter-spacing: .15em;
+ margin-bottom: 24px;
+ }
+ p {
+ font-size: 16px;
+ }
+ button {
+ padding: 16px 32px;
+ border: none;
+ background-color: #c9a961;
+ color: #f5f2ea;
+ cursor: pointer;
+ margin-top: 24px;
+ font-size: 18px;
+ }
+ </style>
+</head>
+<body>
+ <div class="container">
+ <h1>BAGUETTES BATTLE</h1>
+ <p>Welcome to the Parisian Baguette Combat Arena!</p>
+ <button onclick="startBattle()">Start Battle</button>
+ </div>
+
+ <script>
+ function startBattle() {
+ alert("Baguettes are about to fight! Click 'OK' to watch.");
+ }
+ </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/5ed25e8a4aae/qwen3-14b.html b/data/artifacts/5ed25e8a4aae/qwen3-14b.html
new file mode 100644
index 0000000..54dd5b1
--- /dev/null
+++ b/data/artifacts/5ed25e8a4aae/qwen3-14b.html
@@ -0,0 +1,208 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Baguette Arena</title>
+ <style>
+ :root {
+ --bg: #f7efe6;
+ --surface: #fff9f1;
+ --text: #3a2c22;
+ --muted: #a08c7a;
+ --accent: #c65f38;
+ --accent2: #e0a458;
+ --card-radius: 14px;
+ --card-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+ --hover-shadow: 0 16px 48px rgba(0, 0, 0, 0.18);
+ --glow: 0 0 24px rgba(202, 95, 56, 0.35);
+ --font-display: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+ --font-body: -apple-system, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
+ --spacing: 4px 8px 12px 16px 24px 32px 48px 64px 96px 128px;
+ --card-grid: repeat(auto-fit, minmax(260px, 1fr)) gap 24px;
+ --section-padding: 96px;
+ --letter-spacing: 0.15em;
+ }
+
+ * {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ }
+
+ body {
+ background: var(--bg);
+ color: var(--text);
+ font-family: var(--font-body);
+ padding: var(--section-padding);
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-height: 100vh;
+ overflow-x: hidden;
+ }
+
+ h1 {
+ font-family: var(--font-display);
+ font-size: 50px;
+ margin-bottom: 24px;
+ letter-spacing: var(--letter-spacing);
+ }
+
+ .arena {
+ display: grid;
+ grid-template-columns: var(--card-grid);
+ gap: 24px;
+ width: 100%;
+ max-width: 1200px;
+ }
+
+ .baguette-card {
+ background: var(--surface);
+ border-radius: var(--card-radius);
+ box-shadow: var(--card-shadow);
+ padding: 32px;
+ transition: box-shadow 150ms ease-in-out;
+ animation: fadeUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
+ }
+
+ .baguette-card:hover {
+ box-shadow: var(--hover-shadow);
+ }
+
+ .baguette-card::after {
+ content: '';
+ position: absolute;
+ top: -12px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 48px;
+ height: 48px;
+ background: var(--accent);
+ border-radius: 50%;
+ box-shadow: var(--glow);
+ animation: pulse 2s infinite;
+ }
+
+ .baguette-name {
+ font-family: var(--font-display);
+ font-size: 28px;
+ margin-bottom: 16px;
+ letter-spacing: var(--letter-spacing);
+ }
+
+ .baguette-health {
+ font-size: 24px;
+ margin-bottom: 16px;
+ }
+
+ .baguette-action {
+ display: flex;
+ justify-content: center;
+ margin-top: 24px;
+ }
+
+ .action-button {
+ background: var(--accent);
+ color: var(--surface);
+ border: none;
+ padding: 12px 24px;
+ border-radius: 8px;
+ font-size: 16px;
+ cursor: pointer;
+ transition: background 150ms ease-in-out;
+ }
+
+ .action-button:hover {
+ background: var(--accent2);
+ }
+
+ @keyframes fadeUp {
+ from {
+ opacity: 0;
+ transform: translateY(28px);
+ }
+ to {
+ opacity: 1;
+ transform: none;
+ }
+ }
+
+ @keyframes pulse {
+ 0% {
+ box-shadow: 0 0 24px rgba(202, 95, 56, 0.35);
+ }
+ 50% {
+ box-shadow: 0 0 48px rgba(202, 95, 56, 0.55);
+ }
+ 100% {
+ box-shadow: 0 0 24px rgba(202, 95, 56, 0.35);
+ }
+ }
+
+ @keyframes ambientGradient {
+ 0% {
+ background: linear-gradient(270deg, #f7efe6, #fff9f1, #f7efe6);
+ }
+ 100% {
+ background: linear-gradient(270deg, #fff9f1, #f7efe6, #fff9f1);
+ }
+ }
+
+ body {
+ background: linear-gradient(270deg, #f7efe6, #fff9f1, #f7efe6);
+ animation: ambientGradient 18s infinite;
+ }
+ </style>
+</head>
+<body>
+ <h1>Baguette Arena</h1>
+ <div class="arena" id="arena">
+ <div class="baguette-card" data-health="100" data-name="Le Croissant">
+ <div class="baguette-name">Le Croissant</div>
+ <div class="baguette-health">Health: <span id="health1">100</span></div>
+ <div class="baguette-action">
+ <button class="action-button" onclick="attack(0)">Attack</button>
+ </div>
+ </div>
+ <div class="baguette-card" data-health="100" data-name="La Baguette">
+ <div class="baguette-name">La Baguette</div>
+ <div class="baguette-health">Health: <span id="health2">100</span></div>
+ <div class="baguette-action">
+ <button class="action-button" onclick="attack(1)">Attack</button>
+ </div>
+ </div>
+ </div>
+
+ <script>
+ const baguettes = document.querySelectorAll('.baguette-card');
+ const healthElements = document.querySelectorAll('#health1, #health2');
+
+ function attack(attackerIndex) {
+ const attacker = baguettes[attackerIndex];
+ const defenderIndex = 1 - attackerIndex;
+ const defender = baguettes[defenderIndex];
+
+ const attackerHealth = parseInt(attacker.getAttribute('data-health'));
+ const defenderHealth = parseInt(defender.getAttribute('data-health'));
+
+ if (attackerHealth <= 0) {
+ alert("You can't attack when you're out of health!");
+ return;
+ }
+
+ const damage = 10;
+ const newDefenderHealth = Math.max(defenderHealth - damage, 0);
+ defender.setAttribute('data-health', newDefenderHealth);
+ healthElements[defenderIndex].textContent = newDefenderHealth;
+
+ if (newDefenderHealth === 0) {
+ defender.querySelector('.action-button').disabled = true;
+ defender.querySelector('.baguette-name').style.textDecoration = 'line-through';
+ defender.querySelector('.baguette-health').style.textDecoration = 'line-through';
+ alert(`${attacker.getAttribute('data-name')} wins!`);
+ }
+ }
+ </script>
+</body>
+</html>
\ No newline at end of file
← 716ee05 auto-data-snapshot: 2026-08-31T12:34:16 (6 data files) — dat
·
back to Model Arena
·
polish comparison and leaderboard workflows 92c23ba →