[object Object]

← back to Model Arena

snapshot generated arena content before brief upgrade

b6699640558f971a950a690927db2ddb073ce526 · 2026-08-31 13:14:52 -0700 · Steve Abrams

Files touched

Diff

commit b6699640558f971a950a690927db2ddb073ce526
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 31 13:14:52 2026 -0700

    snapshot generated arena content before brief upgrade
---
 data/artifacts/0f04410546ad/qwen25-7b.html | 409 +++++++++++++++++++
 data/artifacts/0f04410546ad/qwen25-7b.png  | Bin 0 -> 213235 bytes
 data/artifacts/0f04410546ad/qwen3-14b.html | 634 +++++++++++++++++++++++++++++
 data/artifacts/0f04410546ad/qwen3-14b.png  | Bin 0 -> 215276 bytes
 data/challenges.json                       |  86 ++++
 5 files changed, 1129 insertions(+)

diff --git a/data/artifacts/0f04410546ad/qwen25-7b.html b/data/artifacts/0f04410546ad/qwen25-7b.html
new file mode 100644
index 0000000..a2bda63
--- /dev/null
+++ b/data/artifacts/0f04410546ad/qwen25-7b.html
@@ -0,0 +1,409 @@
+<!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;
+  }
+  *{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"><button class="play" data-mode="1">Rejouer</button></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');
+
+// ---------- 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,
+    hp: 100, maxhp: 100,
+    state: 'idle', // idle | attack | hurt | ko
+    atk: null, 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: '#c65f38', beret: '#c62f2f', name: 'Pierre', ctrl: P1CTRL });
+  f2 = makeFighter(660, -1, { color: '#e0a458', beret: '#2f4b6b', name: 'Gaston', ctrl: P2CTRL, isAI: (m === 1) });
+  f1.wins = 0; f2.wins = 0;
+  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;
+  ctx.fillStyle = type === 'light' ? '#c65f38' : '#e0a458';
+  ctx.fillRect(f.x - 10, GROUND + 20, 20, 20);
+}
+
+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 movement
+    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; ctx.fillStyle = '#c65f38'; ctx.fillRect(f.x, GROUND + 20, 20, 20); }
+    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 and collision with ground
+  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;
+    ctx.fillStyle = '#ffe7b0';
+    ctx.fillRect(target.x + target.facing * -20, target.y - 70, 6, 3);
+    shake = Math.max(shake, 4);
+    target.blockT = .2;
+  } else {
+    ctx.fillStyle = '#c65f38'; // attacker color
+    ctx.fillRect(target.x, target.y - 70, 16, 6); // hit flash
+    ctx.fillStyle = '#fff3dd';
+    ctx.fillRect(target.x, target.y - 80, 8, 5); // particle effect
+    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;
+    ctx.fillStyle = '#c65f38';
+    ctx.fillRect(target.x, GROUND + 20, 20, 20); // KO flash
+    shake = Math.max(shake, 14);
+    freeze = .075;
+  }
+}
+
+// ---------- 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;
+
+  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.abs(dist - ideal) > 20 ? dir : 0);
+  }
+
+  f.vx += mv * 3.4;
+}
+
+// ---------- round logic ----------
+function updateRound() {
+  timeLeft -= dt;
+
+  if (timeLeft <= 0 && !roundActive) {
+    roundActive = true;
+    setTimeout(() => {
+      if (f1.hp > f2.hp) {
+        resTitle.textContent = 'Pierre';
+        resSub.textContent = `Gagne la manche ${roundNum} !`;
+      } else if (f2.hp > f1.hp) {
+        resTitle.textContent = 'Gaston';
+        resSub.textContent = `Gagne la manche ${roundNum} !`;
+      }
+      resBtns.classList.remove('hide');
+      roundActive = false;
+    }, 500);
+  }
+
+  if (!roundActive && timeLeft < 3) {
+    ctx.fillStyle = '#c65f38';
+    ctx.fillRect(W / 2 - 100, H - 70, 200, 40);
+    resKicker.textContent = 'Fin de la manche !';
+    resTitle.classList.remove('hide');
+    resSub.classList.remove('hide');
+    resBtns.classList.remove('hide');
+  }
+
+  if (timeLeft < 3 && timeLeft > 1) {
+    ctx.fillStyle = '#c65f38';
+    ctx.fillRect(W / 2 - 100, H - 70, 200, 40);
+    resKicker.textContent = 'Fin de la manche !';
+    resTitle.classList.remove('hide');
+    resSub.classList.remove('hide');
+    resBtns.classList.remove('hide');
+  }
+}
+
+// ---------- game loop ----------
+const dt = .016;
+setInterval(() => {
+  if (state === 'fight') {
+    updateFighter(f1, f2, dt);
+    updateFighter(f2, f1, dt);
+    updateRound();
+  } else if (state === 'menu') {
+    initClouds();
+  }
+}, 1000 / 60);
+
+document.querySelector('.play[data-mode="1"]').addEventListener('click', () => startGame(1));
+document.querySelector('.play[data-mode="2"]').addEventListener('click', () => startGame(2));
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0f04410546ad/qwen25-7b.png b/data/artifacts/0f04410546ad/qwen25-7b.png
new file mode 100644
index 0000000..8f5c53f
Binary files /dev/null and b/data/artifacts/0f04410546ad/qwen25-7b.png differ
diff --git a/data/artifacts/0f04410546ad/qwen3-14b.html b/data/artifacts/0f04410546ad/qwen3-14b.html
new file mode 100644
index 0000000..a621419
--- /dev/null
+++ b/data/artifacts/0f04410546ad/qwen3-14b.html
@@ -0,0 +1,634 @@
+<!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;
+      --shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+      --shadow-hover: 0 16px 48px rgba(0, 0, 0, 0.18);
+      --glow: 0 0 24px rgba(198, 95, 56, 0.35);
+      --font-display: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
+      --font-body: -apple-system, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
+    }
+
+    * {
+      box-sizing: border-box;
+      margin: 0;
+      padding: 0;
+    }
+
+    html,
+    body {
+      height: 100%;
+      background: radial-gradient(120% 120% at 50% 0%, #20222e 0%, #12131a 55%, #0a0a0f 100%);
+      color: var(--text);
+      font-family: var(--font-body);
+      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: var(--shadow);
+      background: #1a1720;
+    }
+
+    canvas {
+      display: block;
+      width: 100%;
+      height: 100%;
+      image-rendering: auto;
+    }
+
+    .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, 0.86), rgba(30, 20, 14, 0.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: 0.15em;
+      text-transform: uppercase;
+      color: var(--accent2);
+      font-weight: 600;
+      animation: fadeUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
+    }
+
+    h1 {
+      font-family: var(--font-display);
+      font-weight: 800;
+      font-size: clamp(38px, 7vw, 67px);
+      line-height: 0.98;
+      color: var(--surface);
+      letter-spacing: -0.01em;
+      text-shadow: 0 2px 24px rgba(224, 164, 88, 0.25);
+      animation: fadeUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
+      animation-delay: 0.09s;
+    }
+
+    h1 .em {
+      color: var(--accent2);
+      font-style: italic;
+    }
+
+    .sub {
+      max-width: 520px;
+      color: #e8dfd2;
+      font-size: 16px;
+      line-height: 1.5;
+      animation: fadeUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
+      animation-delay: 0.18s;
+    }
+
+    .btns {
+      display: flex;
+      gap: 14px;
+      flex-wrap: wrap;
+      justify-content: center;
+      animation: fadeUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
+      animation-delay: 0.27s;
+    }
+
+    button.play {
+      font-family: inherit;
+      cursor: pointer;
+      border: none;
+      border-radius: 12px;
+      padding: 15px 26px;
+      font-size: 15px;
+      font-weight: 700;
+      letter-spacing: 0.02em;
+      color: #fff;
+      background: linear-gradient(135deg, var(--accent), #a8471f);
+      box-shadow: var(--shadow);
+      transition: transform 0.16s ease, box-shadow 0.16s ease;
+    }
+
+    button.play.alt {
+      background: linear-gradient(135deg, #5c7d8f, #38505d);
+      box-shadow: var(--shadow);
+    }
+
+    button.play:hover {
+      transform: translateY(-2px);
+      box-shadow: 0 16px 40px rgba(198, 95, 56, 0.5);
+    }
+
+    button.play.alt:hover {
+      box-shadow: 0 16px 40px rgba(75, 107, 122, 0.5);
+    }
+
+    .controls {
+      display: flex;
+      gap: 32px;
+      flex-wrap: wrap;
+      justify-content: center;
+      margin-top: 6px;
+      animation: fadeUp 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
+      animation-delay: 0.36s;
+    }
+
+    .ctrlcard {
+      background: rgba(255, 249, 241, 0.06);
+      border: 1px solid rgba(224, 164, 88, 0.22);
+      border-radius: 12px;
+      padding: 14px 18px;
+      min-width: 190px;
+      text-align: left;
+    }
+
+    .ctrlcard h4 {
+      font-size: 12px;
+      letter-spacing: 0.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: var(--font-display);
+      font-style: italic;
+    }
+
+    .foot {
+      position: absolute;
+      bottom: 12px;
+      font-size: 11px;
+      letter-spacing: 0.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');
+
+      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 = 0.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(0.0001, a.currentTime + dur);
+        o.connect(g); g.connect(a.destination); o.start(); o.stop(a.currentTime + dur + 0.02);
+      }
+
+      function noise(dur, vol = 0.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(0.12, 0.12, 1200); },
+        hit() { noise(0.16, 0.32, 500); tone(120, 0.1, 'square', 0.14, 70); },
+        block() { tone(880, 0.09, 'triangle', 0.16, 660); noise(0.05, 0.08, 2500); },
+        jump() { tone(300, 0.12, 'sine', 0.1, 520); },
+        ko() { tone(400, 0.5, 'sawtooth', 0.2, 80); },
+        win() {
+          tone(523, 0.12, 'triangle', 0.16);
+          setTimeout(() => tone(659, 0.12, 'triangle', 0.16), 120);
+          setTimeout(() => tone(784, 0.2, 'triangle', 0.18), 240);
+        }
+      };
+
+      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);
+      });
+
+      function makeFighter(x, facing, cfg) {
+        return {
+          x, y: GROUND, vx: 0, vy: 0, facing, onGround: true,
+          hp: 100, maxhp: 100,
+          state: 'idle', atk: null, atkT: 0, atkDur: 0, atkHit: false,
+          hurtT: 0, blockT: 0, blocking: false,
+          swing: 0, 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: 0.15 + Math.random() * 0.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 + 0.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;
+
+      const ATK = {
+        light: { dur: 0.34, active: [0.08, 0.18], reach: 78, dmg: 7, kb: 3.2, stun: 0.28 },
+        heavy: { dur: 0.62, active: [0.22, 0.36], reach: 96, dmg: 16, kb: 8.5, stun: 0.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;
+
+        if (f.onGround && f.state !== 'attack') {
+          f.facing = other.x < f.x ? -1 : 1;
+        }
+
+        if (!f.isAI) {
+          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);
+        }
+
+        if (f.blocking) f.blockT = Math.min(f.blockT + dt, 0.2);
+
+        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) > 0.2 && f.onGround) {
+          f.walkPhase += dt * 12;
+        } else {
+          f.walkPhase *= 0.8;
+        }
+
+        if (f.state === 'attack') {
+          f.atkT += dt;
+          const a = ATK[f.atk];
+          const p = f.atkT / f.atkDur;
+          f.swing = swingCurve(p);
+          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 *= 0.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 * 0.5;
+      }
+
+      function swingCurve(p) {
+        if (p < 0.35) return -(p / 0.35) * 0.5;
+        const q = (p - 0.35) / 0.65;
+        return -0.5 + q * q * (3 - 2 * q) * 1.9;
+      }
+
+      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 *= 0.5;
+          sfx.block();
+          spawnParticles(target.x + target.facing * -20, target.y - 70, 6, '#ffe7b0', 3);
+          shake = Math.max(shake, 4);
+          target.blockT = 0.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 = 0.18;
+          shake = Math.max(shake, a.dmg > 10 ? 11 : 6);
+          freeze = a.dmg > 10 ? 0.07 : 0.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();
+        }
+      }
+
+      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 *= 0.85;
+          return;
+        }
+
+        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 {
+          if (f._t > 0.35 + Math.random() * 0.4) {
+            f._t = 0;
+            if (dist < 100 && Math.random() < 0.55) tryAttack(f, 'heavy');
+            else tryAttack(f, 'light');
+          }
+          mv = 0;
+        }
+        f.vx = mv * 3.4;
+      }
+
+      function draw(ctx, W, H) {
+        ctx.clearRect(0, 0, W, H);
+
+        // background
+        ctx.fillStyle = '#0d0d0f';
+        ctx.fillRect(0, 0, W, H);
+
+        // clouds and stars
+        for (const c of clouds) {
+          ctx.save();
+          ctx.translate(c.x, c.y);
+          ctx.scale(c.s, c.s);
+          ctx.fillStyle = 'white';
+          ctx.beginPath();
+          ctx.arc(0, 0, 50, 0, Math.PI * 2);
+          ctx.fill();
+          ctx.restore();
+        }
+
+        for (const s of stars) {
+          ctx.beginPath();
+          ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
+          ctx.fillStyle = 'white';
+          ctx.fill();
+        }
+
+        // draw fighters
+        for (const f of [f1, f2]) {
+          ctx.save();
+          ctx.translate(f.x, f.y);
+          ctx.scale(f.facing, 1);
+          ctx.strokeStyle = 'white';
+          ctx.lineWidth = 2;
+          ctx.beginPath();
+          ctx.arc(0, 0, 24, 0, Math.PI * 2);
+          ctx.stroke();
+          ctx.beginPath();
+          ctx.arc(0, -24, 10, 0, Math.PI * 2);
+          ctx.fillStyle = f.color;
+          ctx.fill();
+          ctx.restore();
+        }
+
+        // draw text
+        ctx.fillStyle = 'white';
+        ctx.font = '20px serif';
+        ctx.fillText(`Round ${roundNum}`, 400, 40);
+        ctx.fillText(`Time: ${Math.floor(timeLeft)}`, 400, 60);
+        ctx.fillText(`${f1.name}: ${f1.hp}`, 100, 40);
+        ctx.fillText(`${f2.name}: ${f2.hp}`, 800, 40);
+
+        if (showAnnounce) {
+          ctx.fillStyle = 'white';
+          ctx.font = '40px serif';
+          ctx.fillText(showAnnounce, W / 2 - 120, H / 2);
+        }
+      }
+
+      function update() {
+        if (state === 'fight') {
+          if (!roundActive) {
+            timeLeft -= 0.03;
+            if (timeLeft <= 0) {
+              roundActive = true;
+              startRound();
+            }
+          } else {
+            updateFighter(f1, f2, 0.03);
+            updateFighter(f2, f1, 0.03);
+          }
+        }
+        draw(ctx, W, H);
+        requestAnimationFrame(update);
+      }
+
+      const buttons = document.querySelectorAll('button.play');
+      buttons.forEach(button => {
+        button.addEventListener('click', () => {
+          const mode = parseInt(button.getAttribute('data-mode'));
+          startGame(mode);
+        });
+      });
+
+      update();
+    })();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/0f04410546ad/qwen3-14b.png b/data/artifacts/0f04410546ad/qwen3-14b.png
new file mode 100644
index 0000000..8d440c1
Binary files /dev/null and b/data/artifacts/0f04410546ad/qwen3-14b.png differ
diff --git a/data/challenges.json b/data/challenges.json
index 3e34a95..573bf8b 100644
--- a/data/challenges.json
+++ b/data/challenges.json
@@ -33617,5 +33617,91 @@
     "judging": false,
     "aiPick": "claude-code",
     "judged_at": "2026-08-31T19:36:14.308Z"
+  },
+  {
+    "id": "0f04410546ad",
+    "title": "Remix: Neon City Driving",
+    "prompt": "Build a game that uses baguettes in paris to fight each other\n\nHere is a strong previous attempt at this exact challenge. IMPROVE on it — keep what works, fix its weaknesses, and elevate the visual quality and polish. Return a complete, improved single-file HTML document.\n\n=== PREVIOUS ATTEMPT (Claude Opus (Max plan)) ===\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Baguette Brawl — Duel de Paris</title>\n<style>\n  :root{\n    --bg:#f7efe6; --surface:#fff9f1; --text:#3a2c22; --muted:#a08c7a;\n    --accent:#c65f38; --accent2:#e0a458;\n    --p1:#c65f38; --p2:#4b6b7a;\n  }\n  *{box-sizing:border-box;margin:0;padding:0}\n  html,body{height:100%}\n  body{\n    background:radial-gradient(120% 120% at 50% 0%,#20222e 0%,#12131a 55%,#0a0a0f 100%);\n    color:var(--text);\n    font-family:-apple-system,'Segoe UI','Helvetica Neue',Arial,sans-serif;\n    display:flex;align-items:center;justify-content:center;\n    min-height:100%;padding:20px;overflow:hidden;\n  }\n  #wrap{\n    position:relative;width:960px;max-width:100%;\n    aspect-ratio:960/540;border-radius:14px;overflow:hidden;\n    box-shadow:0 16px 48px rgba(0,0,0,.55), 0 0 0 1px rgba(224,164,88,.25);\n    background:#1a1720;\n  }\n  canvas{display:block;width:100%;height:100%;image-rendering:auto}\n\n  /* overlays */\n  .overlay{\n    position:absolute;inset:0;display:flex;flex-direction:column;\n    align-items:center;justify-content:center;text-align:center;\n    background:linear-gradient(160deg,rgba(18,15,22,.86),rgba(30,20,14,.92));\n    backdrop-filter:blur(2px);gap:16px;padding:32px;\n    color:var(--bg);z-index:10;\n  }\n  .overlay.hide{display:none}\n  .kicker{\n    font-size:12px;letter-spacing:.28em;text-transform:uppercase;\n    color:var(--accent2);font-weight:600;\n    animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;\n  }\n  h1{\n    font-family:'Playfair Display',Georgia,'Bodoni MT',serif;\n    font-weight:800;font-size:clamp(38px,7vw,67px);line-height:.98;\n    color:var(--surface);letter-spacing:-.01em;\n    text-shadow:0 2px 24px rgba(224,164,88,.25);\n    animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.09s;\n  }\n  h1 .em{color:var(--accent2);font-style:italic}\n  .sub{\n    max-width:520px;color:#e8dfd2;font-size:16px;line-height:1.5;\n    animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.18s;\n  }\n  .btns{display:flex;gap:14px;flex-wrap:wrap;justify-content:center;\n    animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.27s;}\n  button.play{\n    font-family:inherit;cursor:pointer;border:none;border-radius:12px;\n    padding:15px 26px;font-size:15px;font-weight:700;letter-spacing:.02em;\n    color:#fff;background:linear-gradient(135deg,var(--accent),#a8471f);\n    box-shadow:0 8px 24px rgba(198,95,56,.4);transition:transform .16s ease,box-shadow .16s ease;\n  }\n  button.play.alt{background:linear-gradient(135deg,#5c7d8f,#38505d);box-shadow:0 8px 24px rgba(75,107,122,.4)}\n  button.play:hover{transform:translateY(-2px);box-shadow:0 16px 40px rgba(198,95,56,.5)}\n  button.play.alt:hover{box-shadow:0 16px 40px rgba(75,107,122,.5)}\n  .controls{\n    display:flex;gap:32px;flex-wrap:wrap;justify-content:center;margin-top:6px;\n    animation:fadeUp .7s cubic-bezier(.22,1,.36,1) both;animation-delay:.36s;\n  }\n  .ctrlcard{\n    background:rgba(255,249,241,.06);border:1px solid rgba(224,164,88,.22);\n    border-radius:12px;padding:14px 18px;min-width:190px;text-align:left;\n  }\n  .ctrlcard h4{font-size:12px;letter-spacing:.15em;text-transform:uppercase;color:var(--accent2);margin-bottom:8px}\n  .ctrlcard .row{display:flex;justify-content:space-between;font-size:13px;color:#e0d7c9;margin:3px 0}\n  kbd{\n    background:#2a2530;border:1px solid #4a4150;border-bottom-width:2px;border-radius:5px;\n    padding:1px 7px;font-family:ui-monospace,Menlo,monospace;font-size:12px;color:#f2e9dd;\n  }\n  .result-name{font-family:'Playfair Display',Georgia,serif;font-style:italic}\n  .foot{position:absolute;bottom:12px;font-size:11px;letter-spacing:.15em;text-transform:uppercase;color:var(--muted)}\n  @keyframes fadeUp{from{opacity:0;transform:translateY(28px)}to{opacity:1;transform:none}}\n</style>\n</head>\n<body>\n<div id=\"wrap\">\n  <canvas id=\"c\" width=\"960\" height=\"540\"></canvas>\n\n  <!-- MENU -->\n  <div id=\"menu\" class=\"overlay\">\n    <div class=\"kicker\">Sous la lune de Montmartre</div>\n    <h1>Baguette <span class=\"em\">Brawl</span></h1>\n    <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>\n    <div class=\"btns\">\n      <button class=\"play\" data-mode=\"1\">Solo vs. Chef IA</button>\n      <button class=\"play alt\" data-mode=\"2\">Deux Joueurs</button>\n    </div>\n    <div class=\"controls\">\n      <div class=\"ctrlcard\">\n        <h4>Pierre · Joueur 1</h4>\n        <div class=\"row\"><span>Move / Jump</span><span><kbd>A</kbd><kbd>D</kbd><kbd>W</kbd></span></div>\n        <div class=\"row\"><span>Jab · Heavy</span><span><kbd>J</kbd><kbd>K</kbd></span></div>\n        <div class=\"row\"><span>Guard</span><span><kbd>L</kbd></span></div>\n      </div>\n      <div class=\"ctrlcard\">\n        <h4>Gaston · Joueur 2</h4>\n        <div class=\"row\"><span>Move / Jump</span><span><kbd>←</kbd><kbd>→</kbd><kbd>↑</kbd></span></div>\n        <div class=\"row\"><span>Jab · Heavy</span><span><kbd>,</kbd><kbd>.</kbd></span></div>\n        <div class=\"row\"><span>Guard</span><span><kbd>/</kbd></span></div>\n      </div>\n    </div>\n    <div class=\"foot\">Un jeu de pain · Paris</div>\n  </div>\n\n  <!-- ROUND / RESULT -->\n  <div id=\"result\" class=\"overlay hide\">\n    <div class=\"kicker\" id=\"resKicker\">Manche terminée</div>\n    <h1 id=\"resTitle\">—</h1>\n    <p class=\"sub\" id=\"resSub\"></p>\n    <div class=\"btns\" id=\"resBtns\"></div>\n  </div>\n</div>\n\n<script>\n(() => {\nconst cv = document.getElementById('c');\nconst ctx = cv.getContext('2d');\nconst W = 960, H = 540;\nconst GROUND = 470;\n\nconst menuEl = document.getElementById('menu');\nconst resEl = document.getElementById('result');\nconst resTitle = document.getElementById('resTitle');\nconst resSub = document.getElementById('resSub');\nconst resKicker = document.getElementById('resKicker');\nconst resBtns = document.getElementById('resBtns');\n\n// ---------- audio (tiny synth, no external files) ----------\nlet AC = null;\nfunction actx(){ if(!AC){ try{AC=new (window.AudioContext||window.webkitAudioContext)();}catch(e){} } return AC; }\nfunction tone(freq, dur, type='sine', vol=.15, slideTo=null){\n  const a=actx(); if(!a) return;\n  const o=a.createOscillator(), g=a.createGain();\n  o.type=type; o.frequency.setValueAtTime(freq,a.currentTime);\n  if(slideTo) o.frequency.exponentialRampToValueAtTime(slideTo,a.currentTime+dur);\n  g.gain.setValueAtTime(vol,a.currentTime);\n  g.gain.exponentialRampToValueAtTime(.0001,a.currentTime+dur);\n  o.connect(g); g.connect(a.destination); o.start(); o.stop(a.currentTime+dur+.02);\n}\nfunction noise(dur, vol=.2, hp=800){\n  const a=actx(); if(!a) return;\n  const n=Math.floor(a.sampleRate*dur), buf=a.createBuffer(1,n,a.sampleRate), d=buf.getChannelData(0);\n  for(let i=0;i<n;i++) d[i]=(Math.random()*2-1)*(1-i/n);\n  const s=a.createBufferSource(); s.buffer=buf;\n  const f=a.createBiquadFilter(); f.type='highpass'; f.frequency.value=hp;\n  const g=a.createGain(); g.gain.value=vol;\n  s.connect(f); f.connect(g); g.connect(a.destination); s.start();\n}\nconst sfx = {\n  swing(){ noise(.12,.12,1200); },\n  hit(){ noise(.16,.32,500); tone(120,.1,'square',.14,70); },\n  block(){ tone(880,.09,'triangle',.16,660); noise(.05,.08,2500); },\n  jump(){ tone(300,.12,'sine',.1,520); },\n  ko(){ tone(400,.5,'sawtooth',.2,80); },\n  win(){ tone(523,.12,'triangle',.16); setTimeout(()=>tone(659,.12,'triangle',.16),120); setTimeout(()=>tone(784,.2,'triangle',.18),240); }\n};\n\n// ---------- input ----------\nconst keys = new Set();\naddEventListener('keydown', e=>{\n  const k=e.key.length===1?e.key.toLowerCase():e.key;\n  if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown',' ','/'].includes(e.key)) e.preventDefault();\n  keys.add(k);\n});\naddEventListener('keyup', e=>{\n  const k=e.key.length===1?e.key.toLowerCase():e.key;\n  keys.delete(k);\n});\n\n// ---------- fighter ----------\nfunction makeFighter(x, facing, cfg){\n  return {\n    x, y:GROUND, vx:0, vy:0, facing, onGround:true,\n    hp:100, maxhp:100,\n    state:'idle',      // idle | attack | hurt | ko\n    atk:null,          // 'light'|'heavy'\n    atkT:0, atkDur:0, atkHit:false,\n    hurtT:0, blockT:0, blocking:false,\n    swing:0,           // swing anim 0..1\n    walkPhase:0,\n    color:cfg.color, beret:cfg.beret, name:cfg.name,\n    isAI:cfg.isAI||false, ctrl:cfg.ctrl,\n    hitFlash:0, wins:0\n  };\n}\nconst P1CTRL = {left:'a',right:'d',jump:'w',light:'j',heavy:'k',block:'l'};\nconst P2CTRL = {left:'ArrowLeft',right:'ArrowRight',jump:'ArrowUp',light:',',heavy:'.',block:'/'};\n\nlet f1, f2, mode=1, state='menu';\nlet roundNum=1, roundTime=60, timeLeft=60, roundActive=false, freeze=0;\nlet shake=0, particles=[], clouds=[], stars=[];\n\nfunction initClouds(){\n  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});\n  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});\n}\ninitClouds();\n\nfunction startGame(m){\n  mode=m; state='fight'; roundNum=1;\n  f1=makeFighter(300,1,{color:'#e0a458',beret:'#c62f2f',name:'Pierre',ctrl:P1CTRL});\n  f2=makeFighter(660,-1,{color:'#d9974a',beret:'#2f4b6b',name:'Gaston',ctrl:P2CTRL,isAI:(m===1)});\n  f1.wins=0; f2.wins=0;\n  actx();\n  startRound();\n}\nfunction startRound(){\n  resEl.classList.add('hide'); menuEl.classList.add('hide');\n  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});\n  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});\n  timeLeft=roundTime; roundActive=false; particles=[]; freeze=0;\n  showAnnounce=`MANCHE ${roundNum}`; announceT=1.6; countdownT=3.2;\n  state='fight';\n}\n\nlet showAnnounce='', announceT=0, countdownT=0;\n\n// ---------- combat tuning ----------\nconst ATK = {\n  light:{dur:.34, active:[.08,.18], reach:78, dmg:7, kb:3.2, stun:.28},\n  heavy:{dur:.62, active:[.22,.36], reach:96, dmg:16, kb:8.5, stun:.5}\n};\n\nfunction tryAttack(f, type){\n  if(f.state==='attack'||f.state==='hurt'||f.state==='ko') return;\n  if(!f.onGround && type==='heavy') return;\n  f.state='attack'; f.atk=type; f.atkT=0; f.atkDur=ATK[type].dur; f.atkHit=false;\n  sfx.swing();\n}\n\nfunction updateFighter(f, other, dt){\n  const c=f.ctrl;\n  const canAct = f.state!=='hurt' && f.state!=='ko';\n  f.blocking=false;\n\n  // face opponent when idle-ish on ground\n  if(f.onGround && f.state!=='attack'){\n    f.facing = other.x < f.x ? -1 : 1;\n  }\n\n  if(!f.isAI){\n    // horizontal\n    let mv=0;\n    if(canAct && f.state!=='attack'){\n      if(keys.has(c.left)) mv-=1;\n      if(keys.has(c.right)) mv+=1;\n      if(keys.has(c.block)){ f.blocking=true; mv=0; }\n    }\n    f.vx = mv*3.4;\n    if(canAct && keys.has(c.jump) && f.onGround){ f.vy=-13.4; f.onGround=false; sfx.jump(); }\n    if(canAct){\n      if(keys.has(c.light)) tryAttack(f,'light');\n      else if(keys.has(c.heavy)) tryAttack(f,'heavy');\n    }\n  } else {\n    aiControl(f, other, dt);\n  }\n\n  // block flash timer\n  if(f.blocking) f.blockT=Math.min(f.blockT+dt,.2);\n\n  // gravity\n  f.vy += 42*dt;\n  f.y += f.vy;\n  f.x += f.vx;\n  if(f.y>=GROUND){ f.y=GROUND; f.vy=0; if(!f.onGround){f.onGround=true;} }\n  else f.onGround=false;\n  f.x=Math.max(60,Math.min(W-60,f.x));\n\n  if(Math.abs(f.vx)>.2 && f.onGround) f.walkPhase+=dt*12; else f.walkPhase*=.8;\n\n  // attack timeline\n  if(f.state==='attack'){\n    f.atkT+=dt;\n    const a=ATK[f.atk];\n    const p=f.atkT/f.atkDur;\n    f.swing = swingCurve(p);\n    // active window hit\n    if(!f.atkHit && p>=a.active[0] && p<=a.active[1]){\n      const dx=other.x-f.x;\n      const inFront = (f.facing>0 && dx>0)||(f.facing<0 && dx<0);\n      const dist=Math.abs(dx);\n      const vgap=Math.abs(other.y-f.y);\n      if(inFront && dist< a.reach+40 && dist>10 && vgap<120){\n        f.atkHit=true; landHit(f, other, a);\n      }\n    }\n    if(f.atkT>=f.atkDur){ f.state='idle'; f.atk=null; f.swing=0; }\n  } else {\n    f.swing*=.7;\n  }\n\n  if(f.state==='hurt'){ f.hurtT-=dt; if(f.hurtT<=0) f.state='idle'; }\n  if(f.hitFlash>0) f.hitFlash-=dt;\n  if(f.blockT>0 && !f.blocking) f.blockT-=dt*.5;\n}\n\nfunction swingCurve(p){\n  // windup then fast swing\n  if(p<.35) return -(p/.35)*.5;         // wind back\n  const q=(p-.35)/.65;\n  return -0.5 + q*q*(3-2*q)*1.9;         // smooth forward\n}\n\nfunction landHit(attacker, target, a){\n  if(target.state==='ko') return;\n  let dmg=a.dmg, kb=a.kb;\n  const blocked = target.blocking && target.onGround && ((target.facing>0&&attacker.x>target.x)||(target.facing<0&&attacker.x<target.x));\n  if(blocked){\n    dmg*=0.18; kb*=.5;\n    sfx.block();\n    spawnParticles(target.x+target.facing*-20, target.y-70, 6, '#ffe7b0', 3);\n    shake=Math.max(shake,4);\n    target.blockT=.2;\n  } else {\n    sfx.hit();\n    target.state='hurt'; target.hurtT=a.stun;\n    spawnParticles(target.x, target.y-70, 16, attacker.color, 6);\n    spawnParticles(target.x, target.y-80, 8, '#fff3dd', 5);\n    target.hitFlash=.18;\n    shake=Math.max(shake, a.dmg>10?11:6);\n    freeze=a.dmg>10?.07:.035;\n  }\n  target.hp=Math.max(0,target.hp-dmg);\n  target.vx = attacker.facing*kb;\n  if(!blocked && a.dmg>10){ target.vy=-4; target.onGround=false; }\n  if(target.hp<=0){ target.state='ko'; target.vx=attacker.facing*5; target.vy=-8; target.onGround=false; sfx.ko(); }\n}\n\n// ---------- AI ----------\nfunction aiControl(f, o, dt){\n  f._t=(f._t||0)+dt;\n  const dist=Math.abs(o.x-f.x);\n  const dir=o.x<f.x?-1:1;\n  let mv=0;\n\n  if(f.state==='attack'||f.state==='hurt'){ f.vx*=.85; return; }\n\n  // guard if opponent is attacking & close\n  if(o.state==='attack' && dist<130 && Math.random()<0.14){ f.blocking=true; f.vx=0; return; }\n\n  const ideal=90;\n  if(dist>ideal+30) mv=dir;\n  else if(dist<ideal-30) mv=-dir;\n  else {\n    // in range: attack with some rhythm\n    if(f._t>0.35+Math.random()*.4){\n      f._t=0;\n      if(dist<100 && Math.random()<.55) tryAttack(f,'heavy');\n      else tryAttack(f,'light');\n    }\n    mv = (Math.",
+    "remixOf": "5ed25e8a4aae",
+    "remixSource": "claude-code",
+    "designTools": true,
+    "created_at": "2026-08-31T19:45:41.517Z",
+    "winner": null,
+    "runs": [
+      {
+        "model": "qwen3-14b",
+        "status": "done",
+        "error": null,
+        "seconds": 175,
+        "cost": 0,
+        "started_at": "2026-08-31T19:45:41.555Z",
+        "finished_at": "2026-08-31T19:48:36.764Z",
+        "queued_at": "2026-08-31T19:45:41.523Z",
+        "bytes": 19808,
+        "thumb": true
+      },
+      {
+        "model": "gemma3-12b",
+        "status": "error",
+        "error": "ollama bad response: 404",
+        "seconds": 0,
+        "cost": null,
+        "started_at": "2026-08-31T19:48:36.770Z",
+        "finished_at": "2026-08-31T19:48:36.802Z",
+        "queued_at": "2026-08-31T19:45:41.528Z"
+      },
+      {
+        "model": "hermes3-8b",
+        "status": "error",
+        "error": "ollama bad response: 404",
+        "seconds": 0,
+        "cost": null,
+        "started_at": "2026-08-31T19:48:36.808Z",
+        "finished_at": "2026-08-31T19:48:36.815Z",
+        "queued_at": "2026-08-31T19:45:41.534Z"
+      },
+      {
+        "model": "qwen25-7b",
+        "status": "done",
+        "error": null,
+        "seconds": 175,
+        "cost": 0,
+        "started_at": "2026-08-31T19:45:41.561Z",
+        "finished_at": "2026-08-31T19:48:36.426Z",
+        "queued_at": "2026-08-31T19:45:41.539Z",
+        "bytes": 15089,
+        "thumb": true,
+        "aiScore": 8,
+        "aiReason": "The game is visually appealing and well-structured, effectively conveying the theme of baguette combat in Paris.",
+        "aiScores": {
+          "qwen2.5vl:7b": 9,
+          "minicpm-v:latest": 7
+        },
+        "aiSpread": 2
+      },
+      {
+        "model": "hf-qwen-coder-32b",
+        "status": "error",
+        "error": "ollama bad response: 404",
+        "seconds": 0,
+        "cost": null,
+        "started_at": "2026-08-31T19:48:36.821Z",
+        "finished_at": "2026-08-31T19:48:36.828Z",
+        "queued_at": "2026-08-31T19:45:41.544Z"
+      },
+      {
+        "model": "muse",
+        "status": "error",
+        "error": "ollama bad response: 404",
+        "seconds": 0,
+        "cost": null,
+        "started_at": "2026-08-31T19:48:36.834Z",
+        "finished_at": "2026-08-31T19:48:36.840Z",
+        "queued_at": "2026-08-31T19:45:41.549Z"
+      }
+    ],
+    "judging": false,
+    "aiPick": "qwen25-7b",
+    "judged_at": "2026-08-31T19:49:08.451Z"
   }
 ]
\ No newline at end of file

← 84d5249 snapshot generated Qwen arena landing  ·  back to Model Arena  ·  add sophisticated Model Arena benchmark briefs 1d9f6ba →