← back to Model Arena
snapshot generated arena content before brief upgrade
b6699640558f971a950a690927db2ddb073ce526 · 2026-08-31 13:14:52 -0700 · Steve Abrams
Files touched
A data/artifacts/0f04410546ad/qwen25-7b.htmlA data/artifacts/0f04410546ad/qwen25-7b.pngA data/artifacts/0f04410546ad/qwen3-14b.htmlA data/artifacts/0f04410546ad/qwen3-14b.pngM data/challenges.json
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 →