← back to Model Arena
auto-save: 2026-07-23T15:52:21 (4 files) — data/landings/48026228790d-claude-code.html data/landings/556ee195503a-claude-code.html data/landings/556ee195503a-gpt.html data/landings/556ee195503a-grok.html
68491015f94ee8a0f0bf387f44b6d3c560d736d3 · 2026-07-23 15:52:28 -0700 · Steve Abrams
Files touched
A data/landings/48026228790d-claude-code.htmlA data/landings/556ee195503a-claude-code.htmlA data/landings/556ee195503a-gpt.htmlA data/landings/556ee195503a-grok.html
Diff
commit 68491015f94ee8a0f0bf387f44b6d3c560d736d3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 23 15:52:28 2026 -0700
auto-save: 2026-07-23T15:52:21 (4 files) — data/landings/48026228790d-claude-code.html data/landings/556ee195503a-claude-code.html data/landings/556ee195503a-gpt.html data/landings/556ee195503a-grok.html
---
data/landings/48026228790d-claude-code.html | 327 ++++++++++++++++
data/landings/556ee195503a-claude-code.html | 558 ++++++++++++++++++++++++++++
data/landings/556ee195503a-gpt.html | 548 +++++++++++++++++++++++++++
data/landings/556ee195503a-grok.html | 514 +++++++++++++++++++++++++
4 files changed, 1947 insertions(+)
diff --git a/data/landings/48026228790d-claude-code.html b/data/landings/48026228790d-claude-code.html
new file mode 100644
index 0000000..53ce1e6
--- /dev/null
+++ b/data/landings/48026228790d-claude-code.html
@@ -0,0 +1,327 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>All-Models — Voronoi Shatter — built by Claude Opus (Max plan) · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>All-Models — Voronoi Shatter</h1><span class="by">built by <b>Claude Opus (Max plan)</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Voronoi Shatter</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ overflow: hidden;
+ background: #0b0e14;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+ }
+ #stage {
+ display: block;
+ width: 100vw;
+ height: 100vh;
+ cursor: crosshair;
+ }
+ #panel {
+ position: fixed;
+ top: 14px;
+ left: 14px;
+ background: rgba(12, 15, 24, 0.78);
+ border: 1px solid rgba(255, 255, 255, 0.14);
+ border-radius: 10px;
+ padding: 12px 14px;
+ color: #e8ecf4;
+ backdrop-filter: blur(6px);
+ -webkit-backdrop-filter: blur(6px);
+ user-select: none;
+ box-shadow: 0 6px 24px rgba(0,0,0,0.45);
+ }
+ #panel h1 {
+ margin: 0 0 4px 0;
+ font-size: 14px;
+ font-weight: 700;
+ letter-spacing: 0.4px;
+ }
+ #panel p {
+ margin: 0 0 10px 0;
+ font-size: 11.5px;
+ color: #9aa4b8;
+ }
+ #panel .row {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ }
+ button {
+ font: inherit;
+ font-size: 12px;
+ padding: 6px 12px;
+ border-radius: 7px;
+ border: 1px solid rgba(255,255,255,0.2);
+ background: rgba(255,255,255,0.08);
+ color: #e8ecf4;
+ cursor: pointer;
+ transition: background 0.15s ease;
+ }
+ button:hover { background: rgba(255,255,255,0.17); }
+ button.active {
+ background: #4f7cff;
+ border-color: #6c92ff;
+ }
+ #count {
+ font-size: 11.5px;
+ color: #9aa4b8;
+ min-width: 60px;
+ }
+</style>
+</head>
+<body>
+<canvas id="stage"></canvas>
+<div id="panel">
+ <h1>Voronoi Shatter</h1>
+ <p>Click anywhere to add a point &amp; re-shatter</p>
+ <div class="row">
+ <button id="toggleSeeds" class="active">Seeds: on</button>
+ <button id="reset">Reset</button>
+ <span id="count"></span>
+ </div>
+</div>
+<script>
+(function () {
+ "use strict";
+
+ var canvas = document.getElementById("stage");
+ var ctx = canvas.getContext("2d");
+ var toggleBtn = document.getElementById("toggleSeeds");
+ var resetBtn = document.getElementById("reset");
+ var countEl = document.getElementById("count");
+
+ var W = 0, H = 0, DPR = 1;
+ var showSeeds = true;
+ var sites = []; // {x, y, hue, birth, w0} w0 = starting (negative) power weight magnitude
+ var ANIM_MS = 700;
+ var MAX_SITES = 400;
+ var needsDraw = true;
+
+ function resize() {
+ DPR = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
+ W = window.innerWidth;
+ H = window.innerHeight;
+ canvas.width = Math.round(W * DPR);
+ canvas.height = Math.round(H * DPR);
+ ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
+ needsDraw = true;
+ }
+
+ function easeOutCubic(t) {
+ t = Math.min(1, Math.max(0, t));
+ var u = 1 - t;
+ return 1 - u * u * u;
+ }
+
+ // Current power-diagram weight for a site. Animates from -w0 (cell = a point)
+ // up to 0 (plain Voronoi), which makes the new cell grow smoothly while
+ // neighbors yield ground — the re-tessellation animation.
+ function weightOf(s, now) {
+ var t = (now - s.birth) / ANIM_MS;
+ if (t >= 1) return 0;
+ return (easeOutCubic(t) - 1) * s.w0;
+ }
+
+ function nearestDist2(x, y, excludeIdx) {
+ var best = Infinity;
+ for (var i = 0; i < sites.length; i++) {
+ if (i === excludeIdx) continue;
+ var dx = sites[i].x - x, dy = sites[i].y - y;
+ var d2 = dx * dx + dy * dy;
+ if (d2 < best) best = d2;
+ }
+ return best;
+ }
+
+ function addSite(x, y, birth) {
+ var d2 = sites.length ? nearestDist2(x, y, -1) : (W * W + H * H) / 4;
+ d2 = Math.max(d2, 400); // never start fully degenerate
+ sites.push({
+ x: x,
+ y: y,
+ hue: (sites.length * 137.508) % 360,
+ birth: birth,
+ w0: d2
+ });
+ }
+
+ // Sutherland–Hodgman clip of polygon against half-plane dot(p, d) <= k
+ function clipHalfPlane(poly, dx, dy, k) {
+ var out = [];
+ var n = poly.length;
+ if (n === 0) return out;
+ var px = poly[n - 1][0], py = poly[n - 1][1];
+ var pIn = (px * dx + py * dy) <= k;
+ for (var i = 0; i < n; i++) {
+ var cx = poly[i][0], cy = poly[i][1];
+ var cIn = (cx * dx + cy * dy) <= k;
+ if (cIn !== pIn) {
+ var denom = dx * (cx - px) + dy * (cy - py);
+ if (denom !== 0) {
+ var t = (k - (dx * px + dy * py)) / denom;
+ out.push([px + t * (cx - px), py + t * (cy - py)]);
+ }
+ }
+ if (cIn) out.push([cx, cy]);
+ px = cx; py = cy; pIn = cIn;
+ }
+ return out;
+ }
+
+ // Compute the power-diagram cell for site i (weights make growth animatable).
+ // Bisector of |x-pi|^2 - wi <= |x-pj|^2 - wj is the half-plane:
+ // dot(x, 2(pj-pi)) <= |pj|^2 - |pi|^2 + wi - wj
+ function cellFor(i, weights) {
+ var s = sites[i];
+ var poly = [[0, 0], [W, 0], [W, H], [0, H]];
+ var mi = s.x * s.x + s.y * s.y;
+ for (var j = 0; j < sites.length && poly.length; j++) {
+ if (j === i) continue;
+ var o = sites[j];
+ var dx = 2 * (o.x - s.x);
+ var dy = 2 * (o.y - s.y);
+ var k = (o.x * o.x + o.y * o.y) - mi + weights[i] - weights[j];
+ poly = clipHalfPlane(poly, dx, dy, k);
+ }
+ return poly;
+ }
+
+ function draw(now) {
+ var animating = false;
+ var weights = new Array(sites.length);
+ for (var i = 0; i < sites.length; i++) {
+ weights[i] = weightOf(sites[i], now);
+ if (weights[i] !== 0) animating = true;
+ }
+
+ ctx.clearRect(0, 0, W, H);
+ ctx.fillStyle = "#0b0e14";
+ ctx.fillRect(0, 0, W, H);
+
+ ctx.lineJoin = "round";
+
+ for (var s = 0; s < sites.length; s++) {
+ var poly = cellFor(s, weights);
+ if (poly.length < 3) continue;
+ var site = sites[s];
+
+ // gradient radius = farthest vertex from the seed
+ var r2max = 0;
+ for (var v = 0; v < poly.length; v++) {
+ var vx = poly[v][0] - site.x, vy = poly[v][1] - site.y;
+ var d2 = vx * vx + vy * vy;
+ if (d2 > r2max) r2max = d2;
+ }
+ var r = Math.sqrt(r2max) || 1;
+
+ var g = ctx.createRadialGradient(site.x, site.y, 0, site.x, site.y, r);
+ g.addColorStop(0, "hsl(" + site.hue.toFixed(1) + ", 70%, 66%)");
+ g.addColorStop(0.55, "hsl(" + site.hue.toFixed(1) + ", 62%, 48%)");
+ g.addColorStop(1, "hsl(" + site.hue.toFixed(1) + ", 60%, 26%)");
+
+ ctx.beginPath();
+ ctx.moveTo(poly[0][0], poly[0][1]);
+ for (var p = 1; p < poly.length; p++) ctx.lineTo(poly[p][0], poly[p][1]);
+ ctx.closePath();
+ ctx.fillStyle = g;
+ ctx.fill();
+
+ // crack lines between shards
+ ctx.strokeStyle = "rgba(8, 10, 16, 0.9)";
+ ctx.lineWidth = 1.6;
+ ctx.stroke();
+ // subtle inner highlight edge
+ ctx.strokeStyle = "rgba(255, 255, 255, 0.10)";
+ ctx.lineWidth = 0.6;
+ ctx.stroke();
+ }
+
+ if (showSeeds) {
+ for (var q = 0; q < sites.length; q++) {
+ var st = sites[q];
+ var age = Math.min(1, (now - st.birth) / ANIM_MS);
+ var pr = 3 + 2 * (1 - easeOutCubic(age)); // slight pop on birth
+ ctx.beginPath();
+ ctx.arc(st.x, st.y, pr + 1.6, 0, Math.PI * 2);
+ ctx.fillStyle = "rgba(0, 0, 0, 0.55)";
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(st.x, st.y, pr, 0, Math.PI * 2);
+ ctx.fillStyle = "#ffffff";
+ ctx.fill();
+ }
+ }
+
+ countEl.textContent = sites.length + " cells";
+ return animating;
+ }
+
+ var rafId = null;
+ function frame() {
+ rafId = null;
+ var now = performance.now();
+ var animating = draw(now);
+ needsDraw = false;
+ if (animating) schedule();
+ }
+ function schedule() {
+ if (rafId === null) rafId = requestAnimationFrame(frame);
+ }
+
+ function seedInitial() {
+ sites = [];
+ var now = performance.now();
+ var n = 22;
+ var margin = 40;
+ for (var i = 0; i < n; i++) {
+ var x = margin + Math.random() * (W - margin * 2);
+ var y = margin + Math.random() * (H - margin * 2);
+ // staggered births for a rippling intro shatter
+ addSite(x, y, now + i * 28);
+ }
+ schedule();
+ }
+
+ canvas.addEventListener("pointerdown", function (e) {
+ if (sites.length >= MAX_SITES) return;
+ var rect = canvas.getBoundingClientRect();
+ var x = e.clientX - rect.left;
+ var y = e.clientY - rect.top;
+ addSite(x, y, performance.now());
+ schedule();
+ });
+
+ toggleBtn.addEventListener("click", function () {
+ showSeeds = !showSeeds;
+ toggleBtn.textContent = "Seeds: " + (showSeeds ? "on" : "off");
+ toggleBtn.classList.toggle("active", showSeeds);
+ schedule();
+ });
+
+ resetBtn.addEventListener("click", function () {
+ seedInitial();
+ });
+
+ window.addEventListener("resize", function () {
+ resize();
+ schedule();
+ });
+
+ resize();
+ seedInitial();
+})();
+</script>
+</body>
+</html>" title="All-Models — Voronoi Shatter"></iframe></div>
+</body></html>
\ No newline at end of file
diff --git a/data/landings/556ee195503a-claude-code.html b/data/landings/556ee195503a-claude-code.html
new file mode 100644
index 0000000..f82737a
--- /dev/null
+++ b/data/landings/556ee195503a-claude-code.html
@@ -0,0 +1,558 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>All-Models — Flappy Clone — built by Claude Opus (Max plan) · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>All-Models — Flappy Clone</h1><span class="by">built by <b>Claude Opus (Max plan)</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+<title>Flappy Flight</title>
+<style>
+ * { margin: 0; padding: 0; box-sizing: border-box; }
+ html, body {
+ width: 100%; height: 100%;
+ background: #0e1420;
+ overflow: hidden;
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
+ -webkit-user-select: none; user-select: none;
+ touch-action: manipulation;
+ }
+ #wrap {
+ position: absolute; inset: 0;
+ display: flex; align-items: center; justify-content: center;
+ }
+ canvas {
+ display: block;
+ border-radius: 12px;
+ box-shadow: 0 20px 60px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,.06);
+ cursor: pointer;
+ }
+</style>
+</head>
+<body>
+<div id="wrap"><canvas id="game"></canvas></div>
+<script>
+(() => {
+'use strict';
+
+const canvas = document.getElementById('game');
+const ctx = canvas.getContext('2d');
+
+// ---------- sizing ----------
+const GW = 420, GH = 640; // logical game size
+let scale = 1;
+function resize() {
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
+ const availW = window.innerWidth, availH = window.innerHeight;
+ scale = Math.min(availW / GW, availH / GH) * 0.98;
+ canvas.style.width = (GW * scale) + 'px';
+ canvas.style.height = (GH * scale) + 'px';
+ canvas.width = Math.round(GW * scale * dpr);
+ canvas.height = Math.round(GH * scale * dpr);
+ ctx.setTransform(canvas.width / GW, 0, 0, canvas.height / GH, 0, 0);
+}
+window.addEventListener('resize', resize);
+resize();
+
+// ---------- audio (generated, no assets) ----------
+let actx = null;
+function audio() {
+ if (!actx) { try { actx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e){} }
+ if (actx && actx.state === 'suspended') actx.resume();
+ return actx;
+}
+function beep(freq, dur, type, vol, slide) {
+ const a = audio(); if (!a) return;
+ const o = a.createOscillator(), g = a.createGain();
+ o.type = type || 'sine';
+ o.frequency.setValueAtTime(freq, a.currentTime);
+ if (slide) o.frequency.exponentialRampToValueAtTime(Math.max(slide, 1), a.currentTime + dur);
+ g.gain.setValueAtTime(vol || 0.08, a.currentTime);
+ g.gain.exponentialRampToValueAtTime(0.0001, a.currentTime + dur);
+ o.connect(g); g.connect(a.destination);
+ o.start(); o.stop(a.currentTime + dur);
+}
+const sfx = {
+ flap: () => beep(430, 0.09, 'triangle', 0.06, 620),
+ score: () => { beep(880, 0.09, 'sine', 0.07); setTimeout(() => beep(1320, 0.12, 'sine', 0.07), 70); },
+ hit: () => beep(200, 0.25, 'sawtooth', 0.10, 60),
+ swoosh:() => beep(320, 0.18, 'sine', 0.04, 90)
+};
+
+// ---------- constants ----------
+const GROUND_H = 88;
+const SKY_H = GH - GROUND_H;
+const GRAVITY = 1350; // px/s^2
+const FLAP_V = -410; // px/s
+const MAX_FALL = 620;
+const BIRD_X = GW * 0.30;
+const BIRD_R = 14;
+const PIPE_W = 66;
+
+// difficulty ramps with score
+function diff(score) {
+ const t = Math.min(score / 30, 1);
+ return {
+ speed: 145 + 95 * t, // scroll speed px/s
+ gap: Math.max(196 - score * 2.2, 128), // gap height
+ spacing: Math.max(258 - score * 1.6, 205) // px between pipes
+ };
+}
+
+// ---------- state ----------
+const STATE = { READY: 0, PLAY: 1, DYING: 2, OVER: 3 };
+let state = STATE.READY;
+let bird, pipes, score, best, distSincePipe, shake, flash, wingT, deadT, overT, groundX;
+let clouds = [], stars = [];
+best = 0;
+try { best = +localStorage.getItem('ff_best') || 0; } catch(e) {}
+
+function seedScenery() {
+ clouds = [];
+ for (let i = 0; i < 7; i++) clouds.push({
+ x: Math.random() * GW, y: 30 + Math.random() * 200,
+ s: 0.5 + Math.random() * 0.9, layer: Math.random() < 0.5 ? 0 : 1
+ });
+ stars = [];
+ for (let i = 0; i < 26; i++) stars.push({
+ x: Math.random() * GW, y: Math.random() * SKY_H * 0.7,
+ r: 0.6 + Math.random() * 1.3, tw: Math.random() * Math.PI * 2
+ });
+}
+seedScenery();
+
+function reset() {
+ bird = { y: SKY_H * 0.45, v: 0, rot: 0 };
+ pipes = [];
+ score = 0;
+ distSincePipe = diff(0).spacing * 0.55;
+ shake = 0; flash = 0; wingT = 0; deadT = 0; overT = 0; groundX = 0;
+}
+reset();
+
+function spawnPipe() {
+ const d = diff(score);
+ const margin = 56;
+ const gapY = margin + Math.random() * (SKY_H - d.gap - margin * 2);
+ pipes.push({ x: GW + PIPE_W, gapY, gap: d.gap, scored: false, wob: Math.random() * Math.PI * 2 });
+}
+
+// ---------- input ----------
+function flap() {
+ if (state === STATE.READY) {
+ state = STATE.PLAY;
+ bird.v = FLAP_V;
+ sfx.flap();
+ return;
+ }
+ if (state === STATE.PLAY) {
+ bird.v = FLAP_V;
+ wingT = 0;
+ sfx.flap();
+ return;
+ }
+ if (state === STATE.OVER && overT > 0.45) {
+ reset();
+ state = STATE.READY;
+ sfx.swoosh();
+ }
+}
+window.addEventListener('keydown', e => {
+ if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'KeyW') {
+ e.preventDefault();
+ if (!e.repeat) flap();
+ }
+});
+canvas.addEventListener('pointerdown', e => { e.preventDefault(); flap(); });
+
+// ---------- collision ----------
+function circleRect(cx, cy, r, rx, ry, rw, rh) {
+ const nx = Math.max(rx, Math.min(cx, rx + rw));
+ const ny = Math.max(ry, Math.min(cy, ry + rh));
+ const dx = cx - nx, dy = cy - ny;
+ return dx * dx + dy * dy < r * r;
+}
+
+function die() {
+ state = STATE.DYING;
+ deadT = 0;
+ shake = 1;
+ flash = 1;
+ bird.v = Math.min(bird.v, -220);
+ sfx.hit();
+ if (score > best) {
+ best = score;
+ try { localStorage.setItem('ff_best', best); } catch(e) {}
+ }
+}
+
+// ---------- update ----------
+function update(dt) {
+ const d = diff(score);
+ wingT += dt;
+ if (shake > 0) shake = Math.max(0, shake - dt * 2.6);
+ if (flash > 0) flash = Math.max(0, flash - dt * 3.2);
+
+ // parallax scenery always drifts a little
+ const sceneSpeed = (state === STATE.PLAY || state === STATE.READY) ? d.speed : 20;
+ for (const c of clouds) {
+ c.x -= sceneSpeed * (c.layer === 0 ? 0.18 : 0.32) * c.s * dt;
+ if (c.x < -120) { c.x = GW + 60 + Math.random() * 60; c.y = 30 + Math.random() * 200; }
+ }
+
+ if (state === STATE.READY) {
+ bird.y = SKY_H * 0.45 + Math.sin(performance.now() / 380) * 9;
+ bird.rot = 0;
+ groundX = (groundX - d.speed * dt) % 24;
+ return;
+ }
+
+ if (state === STATE.PLAY) {
+ groundX = (groundX - d.speed * dt) % 24;
+
+ // bird physics
+ bird.v = Math.min(bird.v + GRAVITY * dt, MAX_FALL);
+ bird.y += bird.v * dt;
+ const target = bird.v < 0 ? -0.42 : Math.min(bird.v / MAX_FALL * 1.45, 1.35);
+ bird.rot += (target - bird.rot) * Math.min(1, dt * (bird.v < 0 ? 14 : 6));
+
+ // ceiling clamp
+ if (bird.y < BIRD_R) { bird.y = BIRD_R; bird.v = 0; }
+
+ // pipes
+ distSincePipe += d.speed * dt;
+ if (distSincePipe >= d.spacing) { distSincePipe = 0; spawnPipe(); }
+ for (const p of pipes) {
+ p.x -= d.speed * dt;
+ if (!p.scored && p.x + PIPE_W < BIRD_X - BIRD_R) {
+ p.scored = true;
+ score++;
+ sfx.score();
+ }
+ // collide
+ if (circleRect(BIRD_X, bird.y, BIRD_R - 1, p.x, -10, PIPE_W, p.gapY + 10) ||
+ circleRect(BIRD_X, bird.y, BIRD_R - 1, p.x, p.gapY + p.gap, PIPE_W, SKY_H - p.gapY - p.gap)) {
+ die();
+ }
+ }
+ pipes = pipes.filter(p => p.x > -PIPE_W - 10);
+
+ // ground
+ if (bird.y > SKY_H - BIRD_R) { bird.y = SKY_H - BIRD_R; die(); }
+ return;
+ }
+
+ if (state === STATE.DYING) {
+ deadT += dt;
+ bird.v = Math.min(bird.v + GRAVITY * 1.15 * dt, MAX_FALL * 1.2);
+ bird.y += bird.v * dt;
+ bird.rot = Math.min(bird.rot + dt * 7, Math.PI / 2);
+ if (bird.y >= SKY_H - BIRD_R) {
+ bird.y = SKY_H - BIRD_R;
+ state = STATE.OVER;
+ overT = 0;
+ sfx.swoosh();
+ }
+ return;
+ }
+
+ if (state === STATE.OVER) overT += dt;
+}
+
+// ---------- drawing ----------
+function roundRect(x, y, w, h, r) {
+ ctx.beginPath();
+ ctx.moveTo(x + r, y);
+ ctx.arcTo(x + w, y, x + w, y + h, r);
+ ctx.arcTo(x + w, y + h, x, y + h, r);
+ ctx.arcTo(x, y + h, x, y, r);
+ ctx.arcTo(x, y, x + w, y, r);
+ ctx.closePath();
+}
+
+function drawSky() {
+ const g = ctx.createLinearGradient(0, 0, 0, SKY_H);
+ g.addColorStop(0, '#1b2a52');
+ g.addColorStop(0.55, '#3f5f9e');
+ g.addColorStop(1, '#7fa8d9');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, 0, GW, SKY_H);
+
+ // stars (subtle, twinkling in the upper sky)
+ const t = performance.now() / 1000;
+ for (const s of stars) {
+ const a = 0.25 + 0.25 * Math.sin(t * 2 + s.tw);
+ ctx.fillStyle = `rgba(255,255,240,${a.toFixed(3)})`;
+ ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2); ctx.fill();
+ }
+
+ // moon
+ ctx.fillStyle = 'rgba(255,248,220,0.9)';
+ ctx.beginPath(); ctx.arc(GW - 74, 78, 26, 0, Math.PI * 2); ctx.fill();
+ ctx.fillStyle = 'rgba(63,95,158,0.55)';
+ ctx.beginPath(); ctx.arc(GW - 84, 70, 22, 0, Math.PI * 2); ctx.fill();
+}
+
+function drawCloudShape(x, y, s, alpha) {
+ ctx.fillStyle = `rgba(255,255,255,${alpha})`;
+ ctx.beginPath();
+ ctx.arc(x, y, 18 * s, 0, Math.PI * 2);
+ ctx.arc(x + 22 * s, y - 8 * s, 14 * s, 0, Math.PI * 2);
+ ctx.arc(x + 42 * s, y, 16 * s, 0, Math.PI * 2);
+ ctx.arc(x + 20 * s, y + 6 * s, 15 * s, 0, Math.PI * 2);
+ ctx.fill();
+}
+
+function drawParallax() {
+ // far clouds
+ for (const c of clouds) if (c.layer === 0) drawCloudShape(c.x, c.y, c.s * 0.8, 0.16);
+
+ // far mountain ridge (drifts with a slow phase tied to ground scroll)
+ const t = performance.now() / 1000;
+ ctx.fillStyle = 'rgba(38,58,102,0.85)';
+ ctx.beginPath();
+ ctx.moveTo(0, SKY_H);
+ for (let x = 0; x <= GW; x += 8) {
+ const y = SKY_H - 96 - Math.sin((x + t * 12) * 0.012) * 34 - Math.sin((x + t * 12) * 0.031) * 16;
+ ctx.lineTo(x, y);
+ }
+ ctx.lineTo(GW, SKY_H); ctx.closePath(); ctx.fill();
+
+ // near hills
+ ctx.fillStyle = 'rgba(52,84,128,0.95)';
+ ctx.beginPath();
+ ctx.moveTo(0, SKY_H);
+ for (let x = 0; x <= GW; x += 8) {
+ const y = SKY_H - 44 - Math.sin((x + t * 34) * 0.02) * 22 - Math.cos((x + t * 34) * 0.043) * 10;
+ ctx.lineTo(x, y);
+ }
+ ctx.lineTo(GW, SKY_H); ctx.closePath(); ctx.fill();
+
+ // near clouds
+ for (const c of clouds) if (c.layer === 1) drawCloudShape(c.x, c.y, c.s, 0.28);
+}
+
+function drawPipes() {
+ for (const p of pipes) {
+ const bodyG = ctx.createLinearGradient(p.x, 0, p.x + PIPE_W, 0);
+ bodyG.addColorStop(0, '#3f9d4e');
+ bodyG.addColorStop(0.35, '#6fce74');
+ bodyG.addColorStop(0.6, '#57b95f');
+ bodyG.addColorStop(1, '#2f7a3b');
+
+ // top pipe
+ ctx.fillStyle = bodyG;
+ ctx.fillRect(p.x + 4, 0, PIPE_W - 8, p.gapY - 24);
+ roundRect(p.x, p.gapY - 26, PIPE_W, 26, 5); ctx.fill();
+ // bottom pipe
+ ctx.fillRect(p.x + 4, p.gapY + p.gap + 24, PIPE_W - 8, SKY_H - p.gapY - p.gap - 24);
+ roundRect(p.x, p.gapY + p.gap, PIPE_W, 26, 5); ctx.fill();
+
+ // rim highlights
+ ctx.fillStyle = 'rgba(255,255,255,0.22)';
+ ctx.fillRect(p.x + 9, 0, 6, p.gapY - 26);
+ ctx.fillRect(p.x + 9, p.gapY + p.gap + 26, 6, SKY_H - p.gapY - p.gap - 26);
+ ctx.fillStyle = 'rgba(0,0,0,0.18)';
+ ctx.fillRect(p.x + PIPE_W - 12, 0, 6, p.gapY - 26);
+ ctx.fillRect(p.x + PIPE_W - 12, p.gapY + p.gap + 26, 6, SKY_H - p.gapY - p.gap - 26);
+ // cap shading
+ ctx.fillStyle = 'rgba(255,255,255,0.18)';
+ ctx.fillRect(p.x + 3, p.gapY - 23, PIPE_W - 6, 5);
+ ctx.fillRect(p.x + 3, p.gapY + p.gap + 3, PIPE_W - 6, 5);
+ }
+}
+
+function drawGround() {
+ const g = ctx.createLinearGradient(0, SKY_H, 0, GH);
+ g.addColorStop(0, '#c9b26a');
+ g.addColorStop(0.12, '#9ac162');
+ g.addColorStop(0.16, '#77a94e');
+ g.addColorStop(1, '#5b8340');
+ ctx.fillStyle = g;
+ ctx.fillRect(0, SKY_H, GW, GROUND_H);
+
+ // striped turf edge
+ ctx.save();
+ ctx.beginPath(); ctx.rect(0, SKY_H, GW, 14); ctx.clip();
+ for (let x = groundX - 24; x < GW + 24; x += 24) {
+ ctx.fillStyle = 'rgba(255,255,255,0.16)';
+ ctx.beginPath();
+ ctx.moveTo(x, SKY_H); ctx.lineTo(x + 12, SKY_H);
+ ctx.lineTo(x + 4, SKY_H + 14); ctx.lineTo(x - 8, SKY_H + 14);
+ ctx.closePath(); ctx.fill();
+ }
+ ctx.restore();
+ ctx.fillStyle = 'rgba(0,0,0,0.20)';
+ ctx.fillRect(0, SKY_H, GW, 3);
+
+ // pebbles
+ ctx.fillStyle = 'rgba(0,0,0,0.10)';
+ for (let i = 0; i < 9; i++) {
+ const x = ((i * 97 + groundX * 2.5) % (GW + 40)) - 20;
+ ctx.beginPath(); ctx.arc(x, SKY_H + 34 + (i * 37 % 40), 3 + (i % 3), 0, Math.PI * 2); ctx.fill();
+ }
+}
+
+function drawBird() {
+ ctx.save();
+ ctx.translate(BIRD_X, bird.y);
+ ctx.rotate(bird.rot);
+
+ // body
+ const g = ctx.createRadialGradient(-4, -5, 3, 0, 0, BIRD_R + 4);
+ g.addColorStop(0, '#ffe38a');
+ g.addColorStop(0.6, '#ffc73d');
+ g.addColorStop(1, '#f0a020');
+ ctx.fillStyle = g;
+ ctx.beginPath(); ctx.ellipse(0, 0, BIRD_R + 2, BIRD_R, 0, 0, Math.PI * 2); ctx.fill();
+ ctx.strokeStyle = 'rgba(120,70,0,0.35)'; ctx.lineWidth = 1.5; ctx.stroke();
+
+ // wing (flaps)
+ const wingA = state === STATE.PLAY || state === STATE.READY
+ ? Math.sin(wingT * 22) * 0.9 - 0.2 : 0.6;
+ ctx.save();
+ ctx.translate(-3, 1);
+ ctx.rotate(wingA * 0.6);
+ ctx.fillStyle = '#f7b32b';
+ ctx.beginPath(); ctx.ellipse(-2, 2, 9, 6, -0.4, 0, Math.PI * 2); ctx.fill();
+ ctx.strokeStyle = 'rgba(120,70,0,0.3)'; ctx.lineWidth = 1; ctx.stroke();
+ ctx.restore();
+
+ // belly
+ ctx.fillStyle = 'rgba(255,250,230,0.85)';
+ ctx.beginPath(); ctx.ellipse(1, 5, 8, 5.5, 0, 0, Math.PI * 2); ctx.fill();
+
+ // eye
+ ctx.fillStyle = '#fff';
+ ctx.beginPath(); ctx.arc(6, -5, 5.4, 0, Math.PI * 2); ctx.fill();
+ ctx.fillStyle = '#222';
+ ctx.beginPath(); ctx.arc(7.6, -5, 2.5, 0, Math.PI * 2); ctx.fill();
+ ctx.fillStyle = '#fff';
+ ctx.beginPath(); ctx.arc(8.4, -6, 0.9, 0, Math.PI * 2); ctx.fill();
+
+ // beak
+ ctx.fillStyle = '#ff7043';
+ ctx.beginPath();
+ ctx.moveTo(12, -1); ctx.lineTo(21, 1.5); ctx.lineTo(12, 5);
+ ctx.closePath(); ctx.fill();
+ ctx.strokeStyle = 'rgba(150,40,0,0.4)'; ctx.lineWidth = 1; ctx.stroke();
+
+ ctx.restore();
+}
+
+function text(str, x, y, size, fill, align, weight) {
+ ctx.font = `${weight || 800} ${size}px 'Segoe UI', system-ui, sans-serif`;
+ ctx.textAlign = align || 'center';
+ ctx.textBaseline = 'middle';
+ ctx.lineWidth = Math.max(3, size * 0.14);
+ ctx.strokeStyle = 'rgba(20,30,50,0.85)';
+ ctx.strokeText(str, x, y);
+ ctx.fillStyle = fill;
+ ctx.fillText(str, x, y);
+}
+
+function drawUI() {
+ if (state === STATE.PLAY || state === STATE.DYING) {
+ text(String(score), GW / 2, 64, 52, '#fff');
+ }
+
+ if (state === STATE.READY) {
+ text('FLAPPY FLIGHT', GW / 2, 130, 40, '#ffe066');
+ const pulse = 0.75 + 0.25 * Math.sin(performance.now() / 300);
+ ctx.globalAlpha = pulse;
+ text('Tap / Click / Space to flap', GW / 2, SKY_H * 0.66, 20, '#fff', 'center', 600);
+ ctx.globalAlpha = 1;
+ if (best > 0) text('Best: ' + best, GW / 2, SKY_H * 0.66 + 34, 17, '#cfe3ff', 'center', 600);
+
+ // hint arrow near bird
+ ctx.strokeStyle = 'rgba(255,255,255,0.7)';
+ ctx.lineWidth = 3;
+ const ay = bird.y - 40 - Math.sin(performance.now() / 250) * 5;
+ ctx.beginPath();
+ ctx.moveTo(BIRD_X, ay + 16); ctx.lineTo(BIRD_X, ay);
+ ctx.moveTo(BIRD_X - 7, ay + 8); ctx.lineTo(BIRD_X, ay);
+ ctx.lineTo(BIRD_X + 7, ay + 8);
+ ctx.stroke();
+ }
+
+ if (state === STATE.OVER) {
+ const slide = Math.min(overT / 0.35, 1);
+ const ease = 1 - Math.pow(1 - slide, 3);
+ ctx.save();
+ ctx.globalAlpha = ease;
+ ctx.translate(0, (1 - ease) * -30);
+
+ // panel
+ const pw = 280, ph = 190, px = (GW - pw) / 2, py = 150;
+ ctx.fillStyle = 'rgba(18,28,48,0.92)';
+ roundRect(px, py, pw, ph, 14); ctx.fill();
+ ctx.strokeStyle = 'rgba(255,255,255,0.15)'; ctx.lineWidth = 2; ctx.stroke();
+
+ text('GAME OVER', GW / 2, py + 36, 30, '#ff8a65');
+ text('SCORE', GW / 2 - 66, py + 82, 14, '#9fb4d8', 'center', 600);
+ text(String(score), GW / 2 - 66, py + 112, 34, '#fff');
+ text('BEST', GW / 2 + 66, py + 82, 14, '#9fb4d8', 'center', 600);
+ text(String(best), GW / 2 + 66, py + 112, 34, score >= best && score > 0 ? '#ffe066' : '#fff');
+
+ // medal
+ if (score >= 10) {
+ const medal = score >= 40 ? '#ffd700' : score >= 25 ? '#c0c0c0' : '#cd7f32';
+ ctx.fillStyle = medal;
+ ctx.beginPath(); ctx.arc(GW / 2, py + 148, 13, 0, Math.PI * 2); ctx.fill();
+ ctx.strokeStyle = 'rgba(0,0,0,0.3)'; ctx.lineWidth = 2; ctx.stroke();
+ ctx.fillStyle = 'rgba(255,255,255,0.55)';
+ ctx.beginPath(); ctx.arc(GW / 2 - 4, py + 144, 4, 0, Math.PI * 2); ctx.fill();
+ }
+
+ if (overT > 0.45) {
+ const pulse = 0.7 + 0.3 * Math.sin(performance.now() / 280);
+ ctx.globalAlpha = ease * pulse;
+ text('Tap or press Space to restart', GW / 2, py + ph + 40, 18, '#fff', 'center', 600);
+ }
+ ctx.restore();
+ }
+}
+
+function draw() {
+ ctx.save();
+ if (shake > 0) {
+ const s = shake * shake * 9;
+ ctx.translate((Math.random() - 0.5) * s, (Math.random() - 0.5) * s);
+ }
+ drawSky();
+ drawParallax();
+ drawPipes();
+ drawGround();
+ drawBird();
+ ctx.restore();
+
+ drawUI();
+
+ if (flash > 0) {
+ ctx.fillStyle = `rgba(255,255,255,${(flash * 0.75).toFixed(3)})`;
+ ctx.fillRect(0, 0, GW, GH);
+ }
+}
+
+// ---------- main loop ----------
+let last = performance.now();
+function frame(now) {
+ let dt = (now - last) / 1000;
+ last = now;
+ if (dt > 0.05) dt = 0.05; // clamp after tab-switch
+ update(dt);
+ draw();
+ requestAnimationFrame(frame);
+}
+requestAnimationFrame(frame);
+
+})();
+</script>
+</body>
+</html>" title="All-Models — Flappy Clone"></iframe></div>
+</body></html>
\ No newline at end of file
diff --git a/data/landings/556ee195503a-gpt.html b/data/landings/556ee195503a-gpt.html
new file mode 100644
index 0000000..787bfcf
--- /dev/null
+++ b/data/landings/556ee195503a-gpt.html
@@ -0,0 +1,548 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>All-Models — Flappy Clone — built by GPT-5.1 · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>All-Models — Flappy Clone</h1><span class="by">built by <b>GPT-5.1</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<title>Flappy Sky</title>
+<style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ background: #2b2e3b;
+ overflow: hidden;
+ height: 100%;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ }
+
+ canvas {
+ display: block;
+ background: radial-gradient(circle at 20% 20%, #586df2 0, #243046 40%, #0e1018 100%);
+ }
+
+ #overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+ user-select: none;
+ }
+
+ #ui {
+ color: #f8f8ff;
+ text-align: center;
+ text-shadow: 0 0 8px rgba(0,0,0,0.7);
+ }
+
+ #title {
+ font-size: 42px;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ margin-bottom: 10px;
+ }
+
+ #subtitle {
+ font-size: 16px;
+ opacity: 0.85;
+ margin-bottom: 28px;
+ }
+
+ #prompt {
+ font-size: 17px;
+ opacity: 0.95;
+ }
+
+ #scoreTop {
+ position: absolute;
+ top: 14px;
+ left: 50%;
+ transform: translateX(-50%);
+ color: #fefefe;
+ font-size: 22px;
+ font-weight: 700;
+ text-shadow: 0 0 6px rgba(0,0,0,0.8);
+ pointer-events: none;
+ }
+
+ #scoreTop span {
+ font-weight: 900;
+ }
+
+ #smallInfo {
+ position: absolute;
+ bottom: 10px;
+ right: 12px;
+ font-size: 11px;
+ color: rgba(255,255,255,0.45);
+ text-shadow: 0 0 4px rgba(0,0,0,0.8);
+ pointer-events: none;
+ }
+
+ .hidden {
+ display: none;
+ }
+</style>
+</head>
+<body>
+<canvas id="game"></canvas>
+
+<div id="overlay">
+ <div id="ui">
+ <div id="title">FLAPPY SKY</div>
+ <div id="subtitle">Tap / Click / Space to flap through the neon gates.</div>
+ <div id="prompt">Press Space, Click, or Tap to Start</div>
+ </div>
+</div>
+
+<div id="scoreTop" class="hidden">Score: <span id="scoreVal">0</span> &nbsp; • &nbsp; Best: <span id="bestVal">0</span></div>
+<div id="smallInfo">Space / Click / Tap to flap</div>
+
+<script>
+(function () {
+ const canvas = document.getElementById("game");
+ const ctx = canvas.getContext("2d");
+
+ let W, H, scale;
+ function resize() {
+ const dpr = window.devicePixelRatio || 1;
+ canvas.width = window.innerWidth * dpr;
+ canvas.height = window.innerHeight * dpr;
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ W = window.innerWidth;
+ H = window.innerHeight;
+ scale = Math.min(W, H) / 600;
+ }
+ resize();
+ window.addEventListener("resize", resize);
+
+ const overlay = document.getElementById("overlay");
+ const uiPrompt = document.getElementById("prompt");
+ const scoreTop = document.getElementById("scoreTop");
+ const scoreVal = document.getElementById("scoreVal");
+ const bestVal = document.getElementById("bestVal");
+
+ const GRAVITY = 1400; // px/s^2
+ const FLAP = -440; // impulse
+ const PIPE_INTERVAL_START = 1600; // ms
+ const PIPE_INTERVAL_MIN = 850; // ms
+ const GAP_START = 190; // px
+ const GAP_MIN = 135;
+ const SPEED_START = 190; // px/s
+ const SPEED_MAX = 410;
+
+ let state = "ready"; // ready, playing, gameover
+ let bird, pipes, particles, stars, hills;
+ let lastTime = 0;
+ let timeSincePipe = 0;
+ let score = 0;
+ let best = +localStorage.getItem("flappy_sky_best") || 0;
+ bestVal.textContent = best;
+ let pipeInterval = PIPE_INTERVAL_START;
+ let gapSize = GAP_START;
+ let speed = SPEED_START;
+
+ function initBackground() {
+ stars = [];
+ const starCount = 70;
+ for (let i = 0; i < starCount; i++) {
+ stars.push({
+ x: Math.random() * W,
+ y: Math.random() * H,
+ r: Math.random() * 1.4 + 0.4,
+ speed: 8 + Math.random() * 10,
+ phase: Math.random() * Math.PI * 2
+ });
+ }
+ hills = [
+ { y: H * 0.72, amp: 40, freq: 0.004, speed: 18, offset: 0, color: "rgba(12,17,40,0.9)" },
+ { y: H * 0.78, amp: 55, freq: 0.003, speed: 30, offset: 200, color: "rgba(10,10,25,0.9)" }
+ ];
+ }
+
+ function resetGame() {
+ bird = {
+ x: W * 0.3,
+ y: H * 0.45,
+ vy: 0,
+ r: 16 * scale,
+ rot: 0
+ };
+ pipes = [];
+ particles = [];
+ timeSincePipe = 0;
+ score = 0;
+ scoreVal.textContent = score;
+ pipeInterval = PIPE_INTERVAL_START;
+ gapSize = GAP_START;
+ speed = SPEED_START;
+ }
+
+ function spawnPipe() {
+ const margin = 80;
+ const maxTop = H - margin - gapSize;
+ const minTop = margin;
+ const top = minTop + Math.random() * (maxTop - minTop);
+ pipes.push({
+ x: W + 60,
+ top: top,
+ gap: gapSize,
+ scored: false
+ });
+ }
+
+ function addParticles(x, y, count, color, spread) {
+ for (let i = 0; i < count; i++) {
+ const ang = Math.random() * Math.PI * 2;
+ const mag = (Math.random() * 160 + 40) * scale;
+ particles.push({
+ x,
+ y,
+ vx: Math.cos(ang) * mag,
+ vy: Math.sin(ang) * mag,
+ life: 0.6 + Math.random() * 0.2,
+ age: 0,
+ size: (Math.random() * 3 + 2) * scale,
+ color
+ });
+ }
+ }
+
+ function flap() {
+ if (state === "ready") {
+ state = "playing";
+ overlay.classList.add("hidden");
+ scoreTop.classList.remove("hidden");
+ bird.vy = FLAP;
+ addParticles(bird.x - bird.r * 0.8, bird.y, 10, "rgba(200,220,255,0.9)", 1);
+ } else if (state === "playing") {
+ bird.vy = FLAP;
+ addParticles(bird.x - bird.r * 0.8, bird.y, 8, "rgba(210,230,255,0.9)", 1);
+ } else if (state === "gameover") {
+ state = "ready";
+ uiPrompt.textContent = "Press Space, Click, or Tap to Start";
+ overlay.classList.remove("hidden");
+ resetGame();
+ }
+ }
+
+ document.addEventListener("keydown", (e) => {
+ if (e.code === "Space" || e.key === " ") {
+ e.preventDefault();
+ flap();
+ }
+ });
+ document.addEventListener("mousedown", flap);
+ document.addEventListener("touchstart", (e) => {
+ e.preventDefault();
+ flap();
+ }, {passive:false});
+
+ function update(dt) {
+ // background stars
+ stars.forEach(s => {
+ s.x -= s.speed * dt;
+ if (s.x < -5) {
+ s.x = W + 5;
+ s.y = Math.random() * H;
+ }
+ s.phase += dt * 2;
+ });
+ hills.forEach(h => { h.offset += h.speed * dt; });
+
+ if (state === "playing") {
+ // difficulty scaling
+ const t = Math.min(score / 20, 1);
+ pipeInterval = PIPE_INTERVAL_START - (PIPE_INTERVAL_START - PIPE_INTERVAL_MIN) * t;
+ gapSize = GAP_START - (GAP_START - GAP_MIN) * t;
+ speed = SPEED_START + (SPEED_MAX - SPEED_START) * t;
+
+ // bird physics
+ bird.vy += GRAVITY * dt;
+ bird.y += bird.vy * dt;
+ bird.rot = Math.atan2(bird.vy, 260);
+
+ // pipes
+ timeSincePipe += dt * 1000;
+ if (timeSincePipe > pipeInterval) {
+ timeSincePipe = 0;
+ spawnPipe();
+ }
+ for (let i = pipes.length - 1; i >= 0; i--) {
+ const p = pipes[i];
+ p.x -= speed * dt;
+ if (p.x + 80 < 0) pipes.splice(i, 1);
+ }
+
+ // score & collisions
+ const bx = bird.x;
+ const by = bird.y;
+ const br = bird.r * 0.85;
+
+ // bounds
+ if (by - br < 0 || by + br > H) {
+ triggerGameOver();
+ return;
+ }
+
+ for (const p of pipes) {
+ const pipeW = 80;
+ if (!p.scored && p.x + pipeW < bx) {
+ p.scored = true;
+ score++;
+ scoreVal.textContent = score;
+ }
+ if (bx + br > p.x && bx - br < p.x + pipeW) {
+ if (by - br < p.top || by + br > p.top + p.gap) {
+ triggerGameOver();
+ return;
+ }
+ }
+ }
+ } else {
+ // idle bird bob
+ bird.y += Math.sin(performance.now() / 400) * 0.08 * (H / 600);
+ bird.rot = Math.sin(performance.now() / 600) * 0.2;
+ }
+
+ // particles
+ for (let i = particles.length - 1; i >= 0; i--) {
+ const p = particles[i];
+ p.age += dt;
+ if (p.age >= p.life) {
+ particles.splice(i, 1);
+ continue;
+ }
+ p.x += p.vx * dt;
+ p.y += p.vy * dt;
+ p.vy += 400 * dt * 0.6;
+ p.vx *= 0.985;
+ p.vy *= 0.985;
+ }
+ }
+
+ function triggerGameOver() {
+ if (state !== "playing") return;
+ state = "gameover";
+ addParticles(bird.x, bird.y, 26, "rgba(255,220,190,0.9)", 1.6);
+ if (score > best) {
+ best = score;
+ localStorage.setItem("flappy_sky_best", best);
+ }
+ bestVal.textContent = best;
+ overlay.classList.remove("hidden");
+ uiPrompt.textContent = "Game Over • Score " + score + " • Click / Tap / Space to Restart";
+ }
+
+ function drawBackground() {
+ // subtly dark overlay for depth
+ const grd = ctx.createLinearGradient(0, 0, 0, H);
+ grd.addColorStop(0, "rgba(40,50,90,0.2)");
+ grd.addColorStop(1, "rgba(0,0,0,0.55)");
+ ctx.fillStyle = grd;
+ ctx.fillRect(0, 0, W, H);
+
+ // stars
+ stars.forEach(s => {
+ const alpha = 0.3 + 0.5 * (0.5 + 0.5 * Math.sin(s.phase));
+ ctx.fillStyle = "rgba(230,240,255," + alpha + ")";
+ ctx.beginPath();
+ ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
+ ctx.fill();
+ });
+
+ // subtle moving clouds / haze
+ const cloudY = H * 0.32;
+ const cloudGrad = ctx.createLinearGradient(0, cloudY - 60, 0, cloudY + 120);
+ cloudGrad.addColorStop(0, "rgba(160,190,255,0.0)");
+ cloudGrad.addColorStop(0.3, "rgba(160,190,255,0.08)");
+ cloudGrad.addColorStop(1, "rgba(80,110,170,0)");
+ ctx.fillStyle = cloudGrad;
+ ctx.fillRect(0, cloudY - 80, W, 160);
+
+ // hills
+ hills.forEach(h => {
+ ctx.fillStyle = h.color;
+ ctx.beginPath();
+ ctx.moveTo(0, H);
+ for (let x = 0; x <= W + 40; x += 22) {
+ const y = h.y + Math.sin((x + h.offset) * h.freq) * h.amp;
+ ctx.lineTo(x, y);
+ }
+ ctx.lineTo(W, H);
+ ctx.closePath();
+ ctx.fill();
+ });
+ }
+
+ function drawPipes() {
+ pipes.forEach(p => {
+ const pipeW = 80;
+ const glow = ctx.createLinearGradient(p.x, 0, p.x + pipeW, 0);
+ glow.addColorStop(0, "#11ffd4");
+ glow.addColorStop(0.5, "#72fffb");
+ glow.addColorStop(1, "#0df2a6");
+
+ const topBottomGrad = ctx.createLinearGradient(0, 0, 0, H);
+ topBottomGrad.addColorStop(0, "rgba(0,0,0,0.8)");
+ topBottomGrad.addColorStop(1, "rgba(0,0,0,0.95)");
+
+ ctx.save();
+ ctx.shadowColor = "rgba(0,255,200,0.7)";
+ ctx.shadowBlur = 18;
+
+ // top pipe
+ ctx.fillStyle = topBottomGrad;
+ ctx.fillRect(p.x, 0, pipeW, p.top);
+
+ ctx.strokeStyle = glow;
+ ctx.lineWidth = 6;
+ ctx.strokeRect(p.x + 2, 0, pipeW - 4, p.top);
+
+ // bottom pipe
+ const bottomY = p.top + p.gap;
+ ctx.fillStyle = topBottomGrad;
+ ctx.fillRect(p.x, bottomY, pipeW, H - bottomY);
+
+ ctx.strokeStyle = glow;
+ ctx.lineWidth = 6;
+ ctx.strokeRect(p.x + 2, bottomY, pipeW - 4, H - bottomY - 2);
+
+ // neon inner edges
+ ctx.lineWidth = 2;
+ ctx.strokeStyle = "rgba(0,255,200,0.65)";
+ ctx.beginPath();
+ ctx.moveTo(p.x + pipeW * 0.5, p.top - 10);
+ ctx.lineTo(p.x + pipeW * 0.5, p.top + 10);
+ ctx.moveTo(p.x + pipeW * 0.5, bottomY - 10);
+ ctx.lineTo(p.x + pipeW * 0.5, bottomY + 10);
+ ctx.stroke();
+
+ ctx.restore();
+ });
+ }
+
+ function drawBird() {
+ const b = bird;
+
+ ctx.save();
+ ctx.translate(b.x, b.y);
+ ctx.rotate(b.rot);
+
+ // body
+ const bodyR = b.r;
+ const bodyGrad = ctx.createRadialGradient(-bodyR * 0.2, -bodyR * 0.2, bodyR * 0.2, 0, 0, bodyR);
+ bodyGrad.addColorStop(0, "#fffbe6");
+ bodyGrad.addColorStop(0.5, "#ffe07a");
+ bodyGrad.addColorStop(1, "#ff9f3f");
+ ctx.fillStyle = bodyGrad;
+ ctx.beginPath();
+ ctx.ellipse(0, 0, bodyR * 1.15, bodyR * 0.9, 0, 0, Math.PI * 2);
+ ctx.fill();
+
+ // belly
+ ctx.fillStyle = "rgba(255,255,255,0.85)";
+ ctx.beginPath();
+ ctx.ellipse(-bodyR * 0.1, bodyR * 0.1, bodyR * 0.7, bodyR * 0.6, 0, 0, Math.PI * 2);
+ ctx.fill();
+
+ // wing (animated)
+ const wingPhase = Math.sin(performance.now() / 90) * 0.4;
+ ctx.save();
+ ctx.translate(-bodyR * 0.1, 0);
+ ctx.rotate(-0.9 + wingPhase);
+ const wingGrad = ctx.createLinearGradient(-bodyR * 0.2, -bodyR * 0.4, bodyR * 0.5, bodyR * 0.6);
+ wingGrad.addColorStop(0, "#ffe7a3");
+ wingGrad.addColorStop(1, "#ffb25a");
+ ctx.fillStyle = wingGrad;
+ ctx.beginPath();
+ ctx.ellipse(0, 0, bodyR * 0.9, bodyR * 0.5, 0, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.restore();
+
+ // eye
+ ctx.fillStyle = "#ffffff";
+ ctx.beginPath();
+ ctx.arc(bodyR * 0.4, -bodyR * 0.2, bodyR * 0.25, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.fillStyle = "#1c1f2b";
+ ctx.beginPath();
+ ctx.arc(bodyR * 0.47, -bodyR * 0.2, bodyR * 0.13, 0, Math.PI * 2);
+ ctx.fill();
+
+ ctx.fillStyle = "rgba(255,255,255,0.7)";
+ ctx.beginPath();
+ ctx.arc(bodyR * 0.52, -bodyR * 0.23, bodyR * 0.05, 0, Math.PI * 2);
+ ctx.fill();
+
+ // beak
+ ctx.fillStyle = "#ffdd66";
+ ctx.beginPath();
+ ctx.moveTo(bodyR * 0.9, 0);
+ ctx.lineTo(bodyR * 1.35, bodyR * 0.13);
+ ctx.lineTo(bodyR * 0.9, bodyR * 0.26);
+ ctx.closePath();
+ ctx.fill();
+
+ ctx.fillStyle = "#ffb347";
+ ctx.beginPath();
+ ctx.moveTo(bodyR * 0.9, 0);
+ ctx.lineTo(bodyR * 1.35, 0);
+ ctx.lineTo(bodyR * 0.9, bodyR * 0.26);
+ ctx.closePath();
+ ctx.fill();
+
+ // tail
+ ctx.fillStyle = "#ffb562";
+ ctx.beginPath();
+ ctx.moveTo(-bodyR * 1.1, -bodyR * 0.1);
+ ctx.lineTo(-bodyR * 1.55, -bodyR * 0.3);
+ ctx.lineTo(-bodyR * 1.2, bodyR * 0.15);
+ ctx.closePath();
+ ctx.fill();
+
+ ctx.restore();
+ }
+
+ function drawParticles() {
+ particles.forEach(p => {
+ const t = p.age / p.life;
+ ctx.globalAlpha = (1 - t) * 0.9;
+ ctx.fillStyle = p.color;
+ ctx.beginPath();
+ ctx.arc(p.x, p.y, p.size * (1 - t * 0.6), 0, Math.PI * 2);
+ ctx.fill();
+ ctx.globalAlpha = 1;
+ });
+ }
+
+ function loop(timestamp) {
+ if (!lastTime) lastTime = timestamp;
+ const dt = Math.min((timestamp - lastTime) / 1000, 0.035);
+ lastTime = timestamp;
+
+ update(dt);
+ drawBackground();
+ drawPipes();
+ drawBird();
+ drawParticles();
+
+ requestAnimationFrame(loop);
+ }
+
+ initBackground();
+ resetGame();
+ requestAnimationFrame(loop);
+})();
+</script>
+</body>
+</html>" title="All-Models — Flappy Clone"></iframe></div>
+</body></html>
\ No newline at end of file
diff --git a/data/landings/556ee195503a-grok.html b/data/landings/556ee195503a-grok.html
new file mode 100644
index 0000000..e4da9e8
--- /dev/null
+++ b/data/landings/556ee195503a-grok.html
@@ -0,0 +1,514 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>All-Models — Flappy Clone — built by Grok 4.5 · Model Arena</title>
+<style>*{box-sizing:border-box}html,body{margin:0;height:100%;background:#0b0d10;color:#e6ecf2;font:14px/1.5 ui-monospace,Menlo,monospace}
+header{display:flex;align-items:center;gap:14px;padding:13px 20px;border-bottom:1px solid #1c2430;background:#0e1116}
+header .k{color:#7cf;font-weight:700;letter-spacing:2px;text-transform:uppercase;font-size:12px;white-space:nowrap}
+header h1{font-size:15px;margin:0;font-weight:600}header .by{color:#8a93a0;font-size:12px;margin-left:auto;white-space:nowrap}
+.wrap{height:calc(100% - 51px)}iframe{width:100%;height:100%;border:0;background:#000;display:block}</style></head><body>
+<header><span class="k">⚔ Model Arena</span><h1>All-Models — Flappy Clone</h1><span class="by">built by <b>Grok 4.5</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+<title>Flappy Bird</title>
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+html,body{width:100%;height:100%;overflow:hidden;background:#1a1a2e;font-family:system-ui,-apple-system,sans-serif}
+canvas{display:block;margin:0 auto;background:#70c5ce;cursor:pointer;touch-action:none}
+</style>
+</head>
+<body>
+<canvas id="c"></canvas>
+<script>
+(function(){
+const canvas=document.getElementById('c');
+const ctx=canvas.getContext('2d');
+
+let W,H,scale;
+function resize(){
+ const maxW=480,maxH=720;
+ const rw=window.innerWidth,rh=window.innerHeight;
+ scale=Math.min(rw/maxW,rh/maxH,1.5);
+ W=Math.floor(maxW*scale);
+ H=Math.floor(maxH*scale);
+ canvas.width=W;
+ canvas.height=H;
+ canvas.style.width=W+'px';
+ canvas.style.height=H+'px';
+}
+resize();
+window.addEventListener('resize',resize);
+
+const GRAVITY=0.35;
+const FLAP=-6.8;
+const PIPE_W=60;
+const PIPE_GAP=150;
+const PIPE_SPEED=2.2;
+const BIRD_R=16;
+const GROUND_H=80;
+
+let bird,pipes,score,best=0,state,frame,particles,clouds,hills,speed,gapSize;
+
+function init(){
+ bird={x:W*0.28,y:H*0.4,vy:0,rot:0,wing:0};
+ pipes=[];
+ score=0;
+ state='ready';
+ frame=0;
+ particles=[];
+ speed=PIPE_SPEED*scale;
+ gapSize=PIPE_GAP*scale;
+ spawnClouds();
+ spawnHills();
+}
+
+function spawnClouds(){
+ clouds=[];
+ for(let i=0;i<6;i++){
+ clouds.push({
+ x:Math.random()*W*1.5,
+ y:H*0.05+Math.random()*H*0.35,
+ w:40+Math.random()*60,
+ speed:0.15+Math.random()*0.25,
+ op:0.4+Math.random()*0.4
+ });
+ }
+}
+
+function spawnHills(){
+ hills=[];
+ for(let i=0;i<8;i++){
+ hills.push({
+ x:i*W*0.4+Math.random()*50,
+ y:H-GROUND_H*scale,
+ w:120+Math.random()*100,
+ h:40+Math.random()*80,
+ layer:i%2,
+ speed:0.3+i%2*0.2
+ });
+ }
+}
+
+function addPipe(){
+ const minY=100*scale;
+ const maxY=H-GROUND_H*scale-gapSize-80*scale;
+ const top=minY+Math.random()*(maxY-minY);
+ pipes.push({x:W+10,top:top,gap:gapSize,passed:false});
+}
+
+function flap(){
+ if(state==='ready'){state='play';bird.vy=FLAP*scale;}
+ else if(state==='play'){bird.vy=FLAP*scale;burst(bird.x,bird.y,6,'#fff');}
+ else if(state==='over'){init();}
+}
+
+function burst(x,y,n,col){
+ for(let i=0;i<n;i++){
+ particles.push({
+ x,y,
+ vx:(Math.random()-0.5)*4,
+ vy:(Math.random()-0.5)*4-1,
+ life:20+Math.random()*15,
+ max:35,
+ col:col||'#ffd700',
+ r:2+Math.random()*3
+ });
+ }
+}
+
+canvas.addEventListener('mousedown',e=>{e.preventDefault();flap();});
+canvas.addEventListener('touchstart',e=>{e.preventDefault();flap();},{passive:false});
+window.addEventListener('keydown',e=>{
+ if(e.code==='Space'||e.key===' '){e.preventDefault();flap();}
+});
+
+function update(){
+ frame++;
+ // parallax
+ clouds.forEach(c=>{
+ c.x-=c.speed*scale*(state==='play'?1:0.3);
+ if(c.x+c.w<-10){c.x=W+Math.random()*100;c.y=H*0.05+Math.random()*H*0.35;}
+ });
+ hills.forEach(h=>{
+ h.x-=h.speed*scale*(state==='play'?1:0.2);
+ if(h.x+h.w<-10){h.x=W+Math.random()*80;h.h=40+Math.random()*80;h.w=120+Math.random()*100;}
+ });
+
+ particles=particles.filter(p=>{
+ p.x+=p.vx;p.y+=p.vy;p.vy+=0.08;p.life--;
+ return p.life>0;
+ });
+
+ if(state!=='play')return;
+
+ bird.vy+=GRAVITY*scale;
+ bird.y+=bird.vy;
+ bird.rot=Math.min(Math.PI/3,Math.max(-Math.PI/4,bird.vy*0.06));
+ bird.wing=Math.sin(frame*0.4)*0.4;
+
+ // difficulty
+ const diff=Math.min(score*0.08,2.5);
+ speed=(PIPE_SPEED+diff)*scale;
+ gapSize=Math.max((PIPE_GAP-score*2.5)*scale,100*scale);
+
+ if(pipes.length===0||pipes[pipes.length-1].x<W-180*scale-diff*15){
+ addPipe();
+ }
+
+ for(let i=pipes.length-1;i>=0;i--){
+ const p=pipes[i];
+ p.x-=speed;
+ if(!p.passed&&p.x+PIPE_W*scale<bird.x){
+ p.passed=true;
+ score++;
+ burst(bird.x+20,bird.y,8,'#ffd700');
+ if(score>best)best=score;
+ }
+ if(p.x+PIPE_W*scale<-10)pipes.splice(i,1);
+
+ // collision
+ const bx=bird.x,by=bird.y,br=BIRD_R*scale*0.85;
+ const px=p.x,pw=PIPE_W*scale;
+ if(bx+br>px&&bx-br<px+pw){
+ if(by-br<p.top||by+br>p.top+p.gap){
+ die();
+ }
+ }
+ }
+
+ const groundY=H-GROUND_H*scale;
+ if(bird.y+BIRD_R*scale>groundY||bird.y-BIRD_R*scale<0)die();
+}
+
+function die(){
+ state='over';
+ burst(bird.x,bird.y,20,'#ff6b6b');
+ burst(bird.x,bird.y,12,'#fff');
+}
+
+function roundRect(x,y,w,h,r){
+ ctx.beginPath();
+ ctx.moveTo(x+r,y);
+ ctx.arcTo(x+w,y,x+w,y+h,r);
+ ctx.arcTo(x+w,y+h,x,y+h,r);
+ ctx.arcTo(x,y+h,x,y,r);
+ ctx.arcTo(x,y,x+w,y,r);
+ ctx.closePath();
+}
+
+function drawBg(){
+ // sky gradient
+ const g=ctx.createLinearGradient(0,0,0,H);
+ g.addColorStop(0,'#4ec0ca');
+ g.addColorStop(0.5,'#70c5ce');
+ g.addColorStop(1,'#b8e0e8');
+ ctx.fillStyle=g;
+ ctx.fillRect(0,0,W,H);
+
+ // sun
+ ctx.beginPath();
+ ctx.arc(W*0.82,H*0.12,30*scale,0,Math.PI*2);
+ ctx.fillStyle='rgba(255,240,180,0.7)';
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(W*0.82,H*0.12,18*scale,0,Math.PI*2);
+ ctx.fillStyle='rgba(255,250,220,0.9)';
+ ctx.fill();
+
+ // clouds
+ clouds.forEach(c=>{
+ ctx.globalAlpha=c.op;
+ ctx.fillStyle='#fff';
+ const s=c.w*scale/50;
+ drawCloud(c.x,c.y,s);
+ });
+ ctx.globalAlpha=1;
+
+ // hills far
+ hills.filter(h=>h.layer===0).forEach(h=>{
+ ctx.fillStyle='#5aad5a';
+ ctx.beginPath();
+ ctx.ellipse(h.x+h.w/2,h.y,h.w/2,h.h,0,Math.PI,0);
+ ctx.fill();
+ });
+ // hills near
+ hills.filter(h=>h.layer===1).forEach(h=>{
+ ctx.fillStyle='#4a9a4a';
+ ctx.beginPath();
+ ctx.ellipse(h.x+h.w/2,h.y,h.w/2,h.h*1.1,0,Math.PI,0);
+ ctx.fill();
+ });
+}
+
+function drawCloud(x,y,s){
+ ctx.beginPath();
+ ctx.arc(x,y,12*s,0,Math.PI*2);
+ ctx.arc(x+16*s,y-6*s,14*s,0,Math.PI*2);
+ ctx.arc(x+34*s,y,13*s,0,Math.PI*2);
+ ctx.arc(x+16*s,y+4*s,11*s,0,Math.PI*2);
+ ctx.fill();
+}
+
+function drawGround(){
+ const gy=H-GROUND_H*scale;
+ // dirt
+ ctx.fillStyle='#ded895';
+ ctx.fillRect(0,gy,W,GROUND_H*scale);
+ // grass strip
+ ctx.fillStyle='#5ee05e';
+ ctx.fillRect(0,gy,W,14*scale);
+ ctx.fillStyle='#4ec04e';
+ ctx.fillRect(0,gy+10*scale,W,6*scale);
+ // scrolling grass pattern
+ const off=(frame*(state==='play'?speed:0.3*scale)*0.5)%(24*scale);
+ ctx.fillStyle='#3da83d';
+ for(let x=-off;x<W;x+=24*scale){
+ ctx.beginPath();
+ ctx.moveTo(x,gy+14*scale);
+ ctx.lineTo(x+8*scale,gy);
+ ctx.lineTo(x+16*scale,gy+14*scale);
+ ctx.fill();
+ }
+ // ground line
+ ctx.strokeStyle='#c4a84a';
+ ctx.lineWidth=2;
+ ctx.beginPath();
+ ctx.moveTo(0,gy);
+ ctx.lineTo(W,gy);
+ ctx.stroke();
+}
+
+function drawPipes(){
+ const pw=PIPE_W*scale;
+ pipes.forEach(p=>{
+ const cap=pw*0.15;
+ // top pipe body
+ const grad1=ctx.createLinearGradient(p.x,0,p.x+pw,0);
+ grad1.addColorStop(0,'#3d9e2f');
+ grad1.addColorStop(0.3,'#5ed14a');
+ grad1.addColorStop(0.7,'#4caf3a');
+ grad1.addColorStop(1,'#2d7a22');
+ ctx.fillStyle=grad1;
+ ctx.fillRect(p.x+2,0,pw-4,p.top);
+ // top cap
+ ctx.fillStyle=grad1;
+ roundRect(p.x-cap,p.top-28*scale,pw+cap*2,28*scale,4);
+ ctx.fill();
+ // rim highlight
+ ctx.fillStyle='rgba(255,255,255,0.15)';
+ ctx.fillRect(p.x+4,0,6*scale,p.top-28*scale);
+ // bottom pipe
+ const by=p.top+p.gap;
+ ctx.fillStyle=grad1;
+ ctx.fillRect(p.x+2,by,pw-4,H-by-GROUND_H*scale);
+ roundRect(p.x-cap,by,pw+cap*2,28*scale,4);
+ ctx.fill();
+ ctx.fillStyle='rgba(255,255,255,0.15)';
+ ctx.fillRect(p.x+4,by+28*scale,6*scale,H-by-GROUND_H*scale-28*scale);
+ // dark edges
+ ctx.strokeStyle='rgba(0,0,0,0.2)';
+ ctx.lineWidth=2;
+ ctx.strokeRect(p.x+2,0,pw-4,p.top-28*scale);
+ ctx.strokeRect(p.x+2,by+28*scale,pw-4,H-by-GROUND_H*scale-28*scale);
+ });
+}
+
+function drawBird(){
+ const r=BIRD_R*scale;
+ ctx.save();
+ ctx.translate(bird.x,bird.y);
+ ctx.rotate(bird.rot);
+
+ // body
+ ctx.beginPath();
+ ctx.ellipse(0,0,r*1.1,r*0.95,0,0,Math.PI*2);
+ const bg=ctx.createRadialGradient(-r*0.3,-r*0.3,0,0,0,r);
+ bg.addColorStop(0,'#ffe566');
+ bg.addColorStop(1,'#f0c020');
+ ctx.fillStyle=bg;
+ ctx.fill();
+ ctx.strokeStyle='#d4a010';
+ ctx.lineWidth=1.5;
+ ctx.stroke();
+
+ // wing
+ ctx.save();
+ ctx.rotate(bird.wing);
+ ctx.beginPath();
+ ctx.ellipse(-2*scale,2*scale,r*0.65,r*0.4,0.3,0,Math.PI*2);
+ ctx.fillStyle='#f5d040';
+ ctx.fill();
+ ctx.strokeStyle='#d4a010';
+ ctx.lineWidth=1;
+ ctx.stroke();
+ ctx.restore();
+
+ // eye white
+ ctx.beginPath();
+ ctx.arc(r*0.4,-r*0.25,r*0.38,0,Math.PI*2);
+ ctx.fillStyle='#fff';
+ ctx.fill();
+ ctx.strokeStyle='#333';
+ ctx.lineWidth=1;
+ ctx.stroke();
+ // pupil
+ ctx.beginPath();
+ ctx.arc(r*0.5,-r*0.25,r*0.18,0,Math.PI*2);
+ ctx.fillStyle='#222';
+ ctx.fill();
+ // eye shine
+ ctx.beginPath();
+ ctx.arc(r*0.55,-r*0.35,r*0.08,0,Math.PI*2);
+ ctx.fillStyle='#fff';
+ ctx.fill();
+
+ // beak
+ ctx.beginPath();
+ ctx.moveTo(r*0.7,0);
+ ctx.lineTo(r*1.45,r*0.12);
+ ctx.lineTo(r*0.7,r*0.3);
+ ctx.closePath();
+ ctx.fillStyle='#ff8c00';
+ ctx.fill();
+ ctx.strokeStyle='#e07000';
+ ctx.lineWidth=1;
+ ctx.stroke();
+ // beak line
+ ctx.beginPath();
+ ctx.moveTo(r*0.7,r*0.12);
+ ctx.lineTo(r*1.3,r*0.12);
+ ctx.strokeStyle='#e07000';
+ ctx.stroke();
+
+ // belly
+ ctx.beginPath();
+ ctx.ellipse(2*scale,r*0.35,r*0.55,r*0.4,0,0,Math.PI*2);
+ ctx.fillStyle='rgba(255,255,255,0.35)';
+ ctx.fill();
+
+ ctx.restore();
+}
+
+function drawParticles(){
+ particles.forEach(p=>{
+ ctx.globalAlpha=p.life/p.max;
+ ctx.fillStyle=p.col;
+ ctx.beginPath();
+ ctx.arc(p.x,p.y,p.r*scale*0.6,0,Math.PI*2);
+ ctx.fill();
+ });
+ ctx.globalAlpha=1;
+}
+
+function drawUI(){
+ ctx.textAlign='center';
+ ctx.textBaseline='middle';
+
+ if(state==='ready'){
+ // title
+ ctx.font='bold '+Math.floor(42*scale)+'px system-ui,sans-serif';
+ ctx.fillStyle='rgba(0,0,0,0.25)';
+ ctx.fillText('FLAPPY',W/2+2,H*0.22+2);
+ ctx.fillStyle='#fff';
+ ctx.strokeStyle='#e87a20';
+ ctx.lineWidth=4*scale;
+ ctx.strokeText('FLAPPY',W/2,H*0.22);
+ ctx.fillText('FLAPPY',W/2,H*0.22);
+
+ ctx.font='bold '+Math.floor(28*scale)+'px system-ui,sans-serif';
+ ctx.fillStyle='rgba(0,0,0,0.25)';
+ ctx.fillText('BIRD',W/2+2,H*0.29+2);
+ ctx.fillStyle='#ffd700';
+ ctx.strokeStyle='#c49000';
+ ctx.lineWidth=3*scale;
+ ctx.strokeText('BIRD',W/2,H*0.29);
+ ctx.fillText('BIRD',W/2,H*0.29);
+
+ // prompt
+ const pulse=0.7+Math.sin(frame*0.08)*0.3;
+ ctx.globalAlpha=pulse;
+ ctx.font=Math.floor(16*scale)+'px system-ui,sans-serif';
+ ctx.fillStyle='#fff';
+ ctx.strokeStyle='rgba(0,0,0,0.3)';
+ ctx.lineWidth=3;
+ ctx.strokeText('Click / Space to flap',W/2,H*0.55);
+ ctx.fillText('Click / Space to flap',W/2,H*0.55);
+ ctx.globalAlpha=1;
+
+ if(best>0){
+ ctx.font=Math.floor(14*scale)+'px system-ui,sans-serif';
+ ctx.fillStyle='rgba(255,255,255,0.7)';
+ ctx.fillText('Best: '+best,W/2,H*0.62);
+ }
+ }
+
+ if(state==='play'||state==='over'){
+ // score
+ ctx.font='bold '+Math.floor(48*scale)+'px system-ui,sans-serif';
+ ctx.fillStyle='rgba(0,0,0,0.2)';
+ ctx.fillText(String(score),W/2+2,H*0.12+2);
+ ctx.fillStyle='#fff';
+ ctx.strokeStyle='rgba(0,0,0,0.4)';
+ ctx.lineWidth=4*scale;
+ ctx.strokeText(String(score),W/2,H*0.12);
+ ctx.fillText(String(score),W/2,H*0.12);
+ }
+
+ if(state==='over'){
+ // panel
+ const pw=220*scale,ph=180*scale;
+ const px=(W-pw)/2,py=H*0.28;
+ ctx.fillStyle='rgba(0,0,0,0.45)';
+ roundRect(px+3,py+3,pw,ph,12*scale);
+ ctx.fill();
+ ctx.fillStyle='rgba(255,255,255,0.92)';
+ roundRect(px,py,pw,ph,12*scale);
+ ctx.fill();
+ ctx.strokeStyle='#e87a20';
+ ctx.lineWidth=3*scale;
+ roundRect(px,py,pw,ph,12*scale);
+ ctx.stroke();
+
+ ctx.font='bold '+Math.floor(28*scale)+'px system-ui,sans-serif';
+ ctx.fillStyle='#e74c3c';
+ ctx.fillText('GAME OVER',W/2,py+35*scale);
+
+ ctx.font=Math.floor(15*scale)+'px system-ui,sans-serif';
+ ctx.fillStyle='#666';
+ ctx.fillText('Score',W/2-50*scale,py+70*scale);
+ ctx.fillText('Best',W/2+50*scale,py+70*scale);
+
+ ctx.font='bold '+Math.floor(32*scale)+'px system-ui,sans-serif';
+ ctx.fillStyle='#333';
+ ctx.fillText(String(score),W/2-50*scale,py+100*scale);
+ ctx.fillStyle=score>=best&&score>0?'#e87a20':'#333';
+ ctx.fillText(String(best),W/2+50*scale,py+100*scale);
+
+ const bp=0.85+Math.sin(frame*0.1)*0.15;
+ ctx.globalAlpha=bp;
+ ctx.font=Math.floor(14*scale)+'px system-ui,sans-serif';
+ ctx.fillStyle='#e87a20';
+ ctx.fillText('Click / Space to restart',W/2,py+145*scale);
+ ctx.globalAlpha=1;
+ }
+}
+
+function loop(){
+ update();
+ drawBg();
+ drawPipes();
+ drawGround();
+ drawBird();
+ drawParticles();
+ drawUI();
+ requestAnimationFrame(loop);
+}
+
+init();
+loop();
+})();
+</script>
+</body>
+</html>" title="All-Models — Flappy Clone"></iframe></div>
+</body></html>
\ No newline at end of file
← 4440a4a auto-save: 2026-07-23T13:21:35 (7 files) — data/challenges.j
·
back to Model Arena
·
auto-save: 2026-07-23T18:22:59 (1 files) — data/landings/5a4 7ad4f5d →