[object Object]

← back to Model Arena

snapshot generated Claude arena landing

256567e18981bf734e7c10436822d247141c45d9 · 2026-08-31 12:44:55 -0700 · Steve Abrams

Files touched

Diff

commit 256567e18981bf734e7c10436822d247141c45d9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 31 12:44:55 2026 -0700

    snapshot generated Claude arena landing
---
 data/landings/5ed25e8a4aae-claude-code.html | 768 ++++++++++++++++++++++++++++
 1 file changed, 768 insertions(+)

diff --git a/data/landings/5ed25e8a4aae-claude-code.html b/data/landings/5ed25e8a4aae-claude-code.html
new file mode 100644
index 0000000..ca7030f
--- /dev/null
+++ b/data/landings/5ed25e8a4aae-claude-code.html
@@ -0,0 +1,768 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Neon City Driving — built by Claude Opus (Max plan) · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>Neon City Driving</h1><span class="by">built by <b>Claude Opus (Max plan)</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="&lt;!DOCTYPE html&gt;
+&lt;html lang=&quot;en&quot;&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;UTF-8&quot;&gt;
+&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
+&lt;title&gt;Baguette Brawl — Duel de Paris&lt;/title&gt;
+&lt;style&gt;
+  :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}}
+&lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;div id=&quot;wrap&quot;&gt;
+  &lt;canvas id=&quot;c&quot; width=&quot;960&quot; height=&quot;540&quot;&gt;&lt;/canvas&gt;
+
+  &lt;!-- MENU --&gt;
+  &lt;div id=&quot;menu&quot; class=&quot;overlay&quot;&gt;
+    &lt;div class=&quot;kicker&quot;&gt;Sous la lune de Montmartre&lt;/div&gt;
+    &lt;h1&gt;Baguette &lt;span class=&quot;em&quot;&gt;Brawl&lt;/span&gt;&lt;/h1&gt;
+    &lt;p class=&quot;sub&quot;&gt;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 &lt;b&gt;2 rounds&lt;/b&gt; wins the boulangerie.&lt;/p&gt;
+    &lt;div class=&quot;btns&quot;&gt;
+      &lt;button class=&quot;play&quot; data-mode=&quot;1&quot;&gt;Solo vs. Chef IA&lt;/button&gt;
+      &lt;button class=&quot;play alt&quot; data-mode=&quot;2&quot;&gt;Deux Joueurs&lt;/button&gt;
+    &lt;/div&gt;
+    &lt;div class=&quot;controls&quot;&gt;
+      &lt;div class=&quot;ctrlcard&quot;&gt;
+        &lt;h4&gt;Pierre · Joueur 1&lt;/h4&gt;
+        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;Move / Jump&lt;/span&gt;&lt;span&gt;&lt;kbd&gt;A&lt;/kbd&gt;&lt;kbd&gt;D&lt;/kbd&gt;&lt;kbd&gt;W&lt;/kbd&gt;&lt;/span&gt;&lt;/div&gt;
+        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;Jab · Heavy&lt;/span&gt;&lt;span&gt;&lt;kbd&gt;J&lt;/kbd&gt;&lt;kbd&gt;K&lt;/kbd&gt;&lt;/span&gt;&lt;/div&gt;
+        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;Guard&lt;/span&gt;&lt;span&gt;&lt;kbd&gt;L&lt;/kbd&gt;&lt;/span&gt;&lt;/div&gt;
+      &lt;/div&gt;
+      &lt;div class=&quot;ctrlcard&quot;&gt;
+        &lt;h4&gt;Gaston · Joueur 2&lt;/h4&gt;
+        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;Move / Jump&lt;/span&gt;&lt;span&gt;&lt;kbd&gt;←&lt;/kbd&gt;&lt;kbd&gt;→&lt;/kbd&gt;&lt;kbd&gt;↑&lt;/kbd&gt;&lt;/span&gt;&lt;/div&gt;
+        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;Jab · Heavy&lt;/span&gt;&lt;span&gt;&lt;kbd&gt;,&lt;/kbd&gt;&lt;kbd&gt;.&lt;/kbd&gt;&lt;/span&gt;&lt;/div&gt;
+        &lt;div class=&quot;row&quot;&gt;&lt;span&gt;Guard&lt;/span&gt;&lt;span&gt;&lt;kbd&gt;/&lt;/kbd&gt;&lt;/span&gt;&lt;/div&gt;
+      &lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div class=&quot;foot&quot;&gt;Un jeu de pain · Paris&lt;/div&gt;
+  &lt;/div&gt;
+
+  &lt;!-- ROUND / RESULT --&gt;
+  &lt;div id=&quot;result&quot; class=&quot;overlay hide&quot;&gt;
+    &lt;div class=&quot;kicker&quot; id=&quot;resKicker&quot;&gt;Manche terminée&lt;/div&gt;
+    &lt;h1 id=&quot;resTitle&quot;&gt;—&lt;/h1&gt;
+    &lt;p class=&quot;sub&quot; id=&quot;resSub&quot;&gt;&lt;/p&gt;
+    &lt;div class=&quot;btns&quot; id=&quot;resBtns&quot;&gt;&lt;/div&gt;
+  &lt;/div&gt;
+&lt;/div&gt;
+
+&lt;script&gt;
+(() =&gt; {
+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&lt;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(()=&gt;tone(659,.12,'triangle',.16),120); setTimeout(()=&gt;tone(784,.2,'triangle',.18),240); }
+};
+
+// ---------- input ----------
+const keys = new Set();
+addEventListener('keydown', e=&gt;{
+  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=&gt;{
+  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&lt;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&lt;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 &amp;&amp; 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' &amp;&amp; f.state!=='ko';
+  f.blocking=false;
+
+  // face opponent when idle-ish on ground
+  if(f.onGround &amp;&amp; f.state!=='attack'){
+    f.facing = other.x &lt; f.x ? -1 : 1;
+  }
+
+  if(!f.isAI){
+    // horizontal
+    let mv=0;
+    if(canAct &amp;&amp; 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 &amp;&amp; keys.has(c.jump) &amp;&amp; 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&gt;=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)&gt;.2 &amp;&amp; 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 &amp;&amp; p&gt;=a.active[0] &amp;&amp; p&lt;=a.active[1]){
+      const dx=other.x-f.x;
+      const inFront = (f.facing&gt;0 &amp;&amp; dx&gt;0)||(f.facing&lt;0 &amp;&amp; dx&lt;0);
+      const dist=Math.abs(dx);
+      const vgap=Math.abs(other.y-f.y);
+      if(inFront &amp;&amp; dist&lt; a.reach+40 &amp;&amp; dist&gt;10 &amp;&amp; vgap&lt;120){
+        f.atkHit=true; landHit(f, other, a);
+      }
+    }
+    if(f.atkT&gt;=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&lt;=0) f.state='idle'; }
+  if(f.hitFlash&gt;0) f.hitFlash-=dt;
+  if(f.blockT&gt;0 &amp;&amp; !f.blocking) f.blockT-=dt*.5;
+}
+
+function swingCurve(p){
+  // windup then fast swing
+  if(p&lt;.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 &amp;&amp; target.onGround &amp;&amp; ((target.facing&gt;0&amp;&amp;attacker.x&gt;target.x)||(target.facing&lt;0&amp;&amp;attacker.x&lt;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&gt;10?11:6);
+    freeze=a.dmg&gt;10?.07:.035;
+  }
+  target.hp=Math.max(0,target.hp-dmg);
+  target.vx = attacker.facing*kb;
+  if(!blocked &amp;&amp; a.dmg&gt;10){ target.vy=-4; target.onGround=false; }
+  if(target.hp&lt;=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&lt;f.x?-1:1;
+  let mv=0;
+
+  if(f.state==='attack'||f.state==='hurt'){ f.vx*=.85; return; }
+
+  // guard if opponent is attacking &amp; close
+  if(o.state==='attack' &amp;&amp; dist&lt;130 &amp;&amp; Math.random()&lt;0.14){ f.blocking=true; f.vx=0; return; }
+
+  const ideal=90;
+  if(dist&gt;ideal+30) mv=dir;
+  else if(dist&lt;ideal-30) mv=-dir;
+  else {
+    // in range: attack with some rhythm
+    if(f._t&gt;0.35+Math.random()*.4){
+      f._t=0;
+      if(dist&lt;100 &amp;&amp; Math.random()&lt;.55) tryAttack(f,'heavy');
+      else tryAttack(f,'light');
+    }
+    mv = (Math.random()&lt;.3? -dir : 0);
+  }
+  // occasional jump
+  if(f.onGround &amp;&amp; Math.random()&lt;0.01 &amp;&amp; dist&lt;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&lt;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&gt;=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&lt;=0||p.y&gt;GROUND+10) particles.splice(i,1);
+  }
+}
+
+// ---------- round flow ----------
+function checkRoundEnd(){
+  if(!roundActive) return;
+  let winner=null;
+  if(f1.hp&lt;=0 &amp;&amp; f2.hp&lt;=0) winner='draw';
+  else if(f1.hp&lt;=0) winner=f2;
+  else if(f2.hp&lt;=0) winner=f1;
+  else if(timeLeft&lt;=0){
+    if(f1.hp&gt;f2.hp) winner=f1; else if(f2.hp&gt;f1.hp) winner=f2; else winner='draw';
+  }
+  if(winner!==null){
+    roundActive=false;
+    setTimeout(()=&gt;endRound(winner), 900);
+  }
+}
+function endRound(winner){
+  if(winner!=='draw' &amp;&amp; winner) winner.wins++;
+  const matchOver = f1.wins&gt;=2 || f2.wins&gt;=2;
+  state='result';
+  if(matchOver){
+    const champ = f1.wins&gt;f2.wins? f1 : f2;
+    sfx.win();
+    resKicker.textContent='Vainqueur du duel';
+    resTitle.innerHTML = `&lt;span class=&quot;result-name&quot;&gt;${champ.name}&lt;/span&gt; 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', ()=&gt;startGame(mode), false);
+    addBtn('Menu', ()=&gt;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=`&lt;span class=&quot;result-name&quot;&gt;${winner.name}&lt;/span&gt; gagne la manche`; resSub.textContent=`Score — Pierre ${f1.wins} · ${f2.wins} Gaston`; }
+    resBtns.innerHTML='';
+    roundNum++;
+    addBtn(`Manche ${roundNum} →`, ()=&gt;startRound(), false);
+    addBtn('Menu', ()=&gt;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&gt;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&lt;-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&lt;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&amp;&amp;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&amp;&amp;f2) drawHUD();
+  if(announceT&gt;0) drawAnnounce();
+  if(countdownT&gt;0 &amp;&amp; countdownT&lt;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&lt;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&lt;GROUND+8;wy+=18) for(let wx=x+6;wx&lt;x+w-6;wx+=16) if(Math.random()&lt;.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&gt;0 &amp;&amp; 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&lt;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&lt;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&lt;5;i++){ ctx.beginPath(); ctx.moveTo(6+i*10,-3); ctx.lineTo(10+i*10,3); ctx.stroke(); }
+  // swing trail
+  if(f.state==='attack' &amp;&amp; f.swing&gt;.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 &amp;&amp; f.blockT&gt;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&lt;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&lt;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&gt;.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=&quot;italic 800 60px Georgia,'Playfair Display',serif&quot;;
+  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&lt;=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=&quot;800 90px Georgia,serif&quot;;
+  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&gt;0?String(n):'', 0,0);
+  ctx.restore();
+}
+
+// ---------- loop ----------
+let last=performance.now();
+function loop(now){
+  let dt=(now-last)/1000; last=now;
+  if(dt&gt;0.05) dt=0.05;
+
+  if(state==='fight'){
+    if(freeze&gt;0){ freeze-=dt; }
+    else {
+      // announce / countdown
+      if(announceT&gt;0){ announceT-=dt; }
+      if(countdownT&gt;0){
+        countdownT-=dt;
+        if(countdownT&lt;=0.2 &amp;&amp; !roundActive){ roundActive=true; }
+      }
+      if(roundActive){
+        if(timeLeft&gt;0) timeLeft-=dt;
+        updateFighter(f1,f2,dt);
+        updateFighter(f2,f1,dt);
+        checkRoundEnd();
+      } else if(f1&amp;&amp;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&lt;f1.x?-1:1; f2.facing=f1.x&lt;f2.x?-1:1;
+      }
+      updateParticles(dt);
+      if(shake&gt;0) shake=Math.max(0,shake-dt*40);
+    }
+  }
+  if(f1&amp;&amp;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&lt;-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=&gt;{
+  b.addEventListener('click',()=&gt;startGame(parseInt(b.dataset.mode)));
+});
+})();
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" title="Neon City Driving"></iframe></div>
+</body></html>
\ No newline at end of file

← 92c23ba polish comparison and leaderboard workflows  ·  back to Model Arena  ·  snapshot generated Qwen arena landing 84d5249 →