[object Object]

← 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

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="&lt;!DOCTYPE html&gt;
+&lt;html lang=&quot;en&quot;&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;UTF-8&quot;&gt;
+&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
+&lt;title&gt;Voronoi Shatter&lt;/title&gt;
+&lt;style&gt;
+  html, body {
+    margin: 0;
+    padding: 0;
+    height: 100%;
+    overflow: hidden;
+    background: #0b0e14;
+    font-family: -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, 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;
+  }
+&lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;canvas id=&quot;stage&quot;&gt;&lt;/canvas&gt;
+&lt;div id=&quot;panel&quot;&gt;
+  &lt;h1&gt;Voronoi Shatter&lt;/h1&gt;
+  &lt;p&gt;Click anywhere to add a point &amp;amp; re-shatter&lt;/p&gt;
+  &lt;div class=&quot;row&quot;&gt;
+    &lt;button id=&quot;toggleSeeds&quot; class=&quot;active&quot;&gt;Seeds: on&lt;/button&gt;
+    &lt;button id=&quot;reset&quot;&gt;Reset&lt;/button&gt;
+    &lt;span id=&quot;count&quot;&gt;&lt;/span&gt;
+  &lt;/div&gt;
+&lt;/div&gt;
+&lt;script&gt;
+(function () {
+  &quot;use strict&quot;;
+
+  var canvas = document.getElementById(&quot;stage&quot;);
+  var ctx = canvas.getContext(&quot;2d&quot;);
+  var toggleBtn = document.getElementById(&quot;toggleSeeds&quot;);
+  var resetBtn = document.getElementById(&quot;reset&quot;);
+  var countEl = document.getElementById(&quot;count&quot;);
+
+  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 &gt;= 1) return 0;
+    return (easeOutCubic(t) - 1) * s.w0;
+  }
+
+  function nearestDist2(x, y, excludeIdx) {
+    var best = Infinity;
+    for (var i = 0; i &lt; 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 &lt; 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) &lt;= 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) &lt;= k;
+    for (var i = 0; i &lt; n; i++) {
+      var cx = poly[i][0], cy = poly[i][1];
+      var cIn = (cx * dx + cy * dy) &lt;= 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 &lt;= |x-pj|^2 - wj is the half-plane:
+  //   dot(x, 2(pj-pi)) &lt;= |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 &lt; sites.length &amp;&amp; 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 &lt; sites.length; i++) {
+      weights[i] = weightOf(sites[i], now);
+      if (weights[i] !== 0) animating = true;
+    }
+
+    ctx.clearRect(0, 0, W, H);
+    ctx.fillStyle = &quot;#0b0e14&quot;;
+    ctx.fillRect(0, 0, W, H);
+
+    ctx.lineJoin = &quot;round&quot;;
+
+    for (var s = 0; s &lt; sites.length; s++) {
+      var poly = cellFor(s, weights);
+      if (poly.length &lt; 3) continue;
+      var site = sites[s];
+
+      // gradient radius = farthest vertex from the seed
+      var r2max = 0;
+      for (var v = 0; v &lt; poly.length; v++) {
+        var vx = poly[v][0] - site.x, vy = poly[v][1] - site.y;
+        var d2 = vx * vx + vy * vy;
+        if (d2 &gt; 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, &quot;hsl(&quot; + site.hue.toFixed(1) + &quot;, 70%, 66%)&quot;);
+      g.addColorStop(0.55, &quot;hsl(&quot; + site.hue.toFixed(1) + &quot;, 62%, 48%)&quot;);
+      g.addColorStop(1, &quot;hsl(&quot; + site.hue.toFixed(1) + &quot;, 60%, 26%)&quot;);
+
+      ctx.beginPath();
+      ctx.moveTo(poly[0][0], poly[0][1]);
+      for (var p = 1; p &lt; poly.length; p++) ctx.lineTo(poly[p][0], poly[p][1]);
+      ctx.closePath();
+      ctx.fillStyle = g;
+      ctx.fill();
+
+      // crack lines between shards
+      ctx.strokeStyle = &quot;rgba(8, 10, 16, 0.9)&quot;;
+      ctx.lineWidth = 1.6;
+      ctx.stroke();
+      // subtle inner highlight edge
+      ctx.strokeStyle = &quot;rgba(255, 255, 255, 0.10)&quot;;
+      ctx.lineWidth = 0.6;
+      ctx.stroke();
+    }
+
+    if (showSeeds) {
+      for (var q = 0; q &lt; 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 = &quot;rgba(0, 0, 0, 0.55)&quot;;
+        ctx.fill();
+        ctx.beginPath();
+        ctx.arc(st.x, st.y, pr, 0, Math.PI * 2);
+        ctx.fillStyle = &quot;#ffffff&quot;;
+        ctx.fill();
+      }
+    }
+
+    countEl.textContent = sites.length + &quot; cells&quot;;
+    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 &lt; 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(&quot;pointerdown&quot;, function (e) {
+    if (sites.length &gt;= 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(&quot;click&quot;, function () {
+    showSeeds = !showSeeds;
+    toggleBtn.textContent = &quot;Seeds: &quot; + (showSeeds ? &quot;on&quot; : &quot;off&quot;);
+    toggleBtn.classList.toggle(&quot;active&quot;, showSeeds);
+    schedule();
+  });
+
+  resetBtn.addEventListener(&quot;click&quot;, function () {
+    seedInitial();
+  });
+
+  window.addEventListener(&quot;resize&quot;, function () {
+    resize();
+    schedule();
+  });
+
+  resize();
+  seedInitial();
+})();
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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="&lt;!DOCTYPE html&gt;
+&lt;html lang=&quot;en&quot;&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;UTF-8&quot;&gt;
+&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0, user-scalable=no&quot;&gt;
+&lt;title&gt;Flappy Flight&lt;/title&gt;
+&lt;style&gt;
+  * { 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;
+  }
+&lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;div id=&quot;wrap&quot;&gt;&lt;canvas id=&quot;game&quot;&gt;&lt;/canvas&gt;&lt;/div&gt;
+&lt;script&gt;
+(() =&gt; {
+'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 &amp;&amp; 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:  () =&gt; beep(430, 0.09, 'triangle', 0.06, 620),
+  score: () =&gt; { beep(880, 0.09, 'sine', 0.07); setTimeout(() =&gt; beep(1320, 0.12, 'sine', 0.07), 70); },
+  hit:   () =&gt; beep(200, 0.25, 'sawtooth', 0.10, 60),
+  swoosh:() =&gt; 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 &lt; 7; i++) clouds.push({
+    x: Math.random() * GW, y: 30 + Math.random() * 200,
+    s: 0.5 + Math.random() * 0.9, layer: Math.random() &lt; 0.5 ? 0 : 1
+  });
+  stars = [];
+  for (let i = 0; i &lt; 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 &amp;&amp; overT &gt; 0.45) {
+    reset();
+    state = STATE.READY;
+    sfx.swoosh();
+  }
+}
+window.addEventListener('keydown', e =&gt; {
+  if (e.code === 'Space' || e.code === 'ArrowUp' || e.code === 'KeyW') {
+    e.preventDefault();
+    if (!e.repeat) flap();
+  }
+});
+canvas.addEventListener('pointerdown', e =&gt; { 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 &lt; r * r;
+}
+
+function die() {
+  state = STATE.DYING;
+  deadT = 0;
+  shake = 1;
+  flash = 1;
+  bird.v = Math.min(bird.v, -220);
+  sfx.hit();
+  if (score &gt; best) {
+    best = score;
+    try { localStorage.setItem('ff_best', best); } catch(e) {}
+  }
+}
+
+// ---------- update ----------
+function update(dt) {
+  const d = diff(score);
+  wingT += dt;
+  if (shake &gt; 0) shake = Math.max(0, shake - dt * 2.6);
+  if (flash &gt; 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 &lt; -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 &lt; 0 ? -0.42 : Math.min(bird.v / MAX_FALL * 1.45, 1.35);
+    bird.rot += (target - bird.rot) * Math.min(1, dt * (bird.v &lt; 0 ? 14 : 6));
+
+    // ceiling clamp
+    if (bird.y &lt; BIRD_R) { bird.y = BIRD_R; bird.v = 0; }
+
+    // pipes
+    distSincePipe += d.speed * dt;
+    if (distSincePipe &gt;= d.spacing) { distSincePipe = 0; spawnPipe(); }
+    for (const p of pipes) {
+      p.x -= d.speed * dt;
+      if (!p.scored &amp;&amp; p.x + PIPE_W &lt; 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 =&gt; p.x &gt; -PIPE_W - 10);
+
+    // ground
+    if (bird.y &gt; 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 &gt;= 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 &lt;= 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 &lt;= 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 &lt; 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 &lt; 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 &gt; 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 &gt;= best &amp;&amp; score &gt; 0 ? '#ffe066' : '#fff');
+
+    // medal
+    if (score &gt;= 10) {
+      const medal = score &gt;= 40 ? '#ffd700' : score &gt;= 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 &gt; 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 &gt; 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 &gt; 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 &gt; 0.05) dt = 0.05;   // clamp after tab-switch
+  update(dt);
+  draw();
+  requestAnimationFrame(frame);
+}
+requestAnimationFrame(frame);
+
+})();
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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="&lt;!DOCTYPE html&gt;
+&lt;html lang=&quot;en&quot;&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;UTF-8&quot; /&gt;
+&lt;title&gt;Flappy Sky&lt;/title&gt;
+&lt;style&gt;
+  html, body {
+    margin: 0;
+    padding: 0;
+    background: #2b2e3b;
+    overflow: hidden;
+    height: 100%;
+    font-family: system-ui, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, 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;
+  }
+&lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;canvas id=&quot;game&quot;&gt;&lt;/canvas&gt;
+
+&lt;div id=&quot;overlay&quot;&gt;
+  &lt;div id=&quot;ui&quot;&gt;
+    &lt;div id=&quot;title&quot;&gt;FLAPPY SKY&lt;/div&gt;
+    &lt;div id=&quot;subtitle&quot;&gt;Tap / Click / Space to flap through the neon gates.&lt;/div&gt;
+    &lt;div id=&quot;prompt&quot;&gt;Press Space, Click, or Tap to Start&lt;/div&gt;
+  &lt;/div&gt;
+&lt;/div&gt;
+
+&lt;div id=&quot;scoreTop&quot; class=&quot;hidden&quot;&gt;Score: &lt;span id=&quot;scoreVal&quot;&gt;0&lt;/span&gt; &amp;nbsp; • &amp;nbsp; Best: &lt;span id=&quot;bestVal&quot;&gt;0&lt;/span&gt;&lt;/div&gt;
+&lt;div id=&quot;smallInfo&quot;&gt;Space / Click / Tap to flap&lt;/div&gt;
+
+&lt;script&gt;
+(function () {
+  const canvas = document.getElementById(&quot;game&quot;);
+  const ctx = canvas.getContext(&quot;2d&quot;);
+
+  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(&quot;resize&quot;, resize);
+
+  const overlay = document.getElementById(&quot;overlay&quot;);
+  const uiPrompt = document.getElementById(&quot;prompt&quot;);
+  const scoreTop = document.getElementById(&quot;scoreTop&quot;);
+  const scoreVal = document.getElementById(&quot;scoreVal&quot;);
+  const bestVal = document.getElementById(&quot;bestVal&quot;);
+
+  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 = &quot;ready&quot;; // ready, playing, gameover
+  let bird, pipes, particles, stars, hills;
+  let lastTime = 0;
+  let timeSincePipe = 0;
+  let score = 0;
+  let best = +localStorage.getItem(&quot;flappy_sky_best&quot;) || 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 &lt; 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: &quot;rgba(12,17,40,0.9)&quot; },
+      { y: H * 0.78, amp: 55, freq: 0.003, speed: 30, offset: 200, color: &quot;rgba(10,10,25,0.9)&quot; }
+    ];
+  }
+
+  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 &lt; 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 === &quot;ready&quot;) {
+      state = &quot;playing&quot;;
+      overlay.classList.add(&quot;hidden&quot;);
+      scoreTop.classList.remove(&quot;hidden&quot;);
+      bird.vy = FLAP;
+      addParticles(bird.x - bird.r * 0.8, bird.y, 10, &quot;rgba(200,220,255,0.9)&quot;, 1);
+    } else if (state === &quot;playing&quot;) {
+      bird.vy = FLAP;
+      addParticles(bird.x - bird.r * 0.8, bird.y, 8, &quot;rgba(210,230,255,0.9)&quot;, 1);
+    } else if (state === &quot;gameover&quot;) {
+      state = &quot;ready&quot;;
+      uiPrompt.textContent = &quot;Press Space, Click, or Tap to Start&quot;;
+      overlay.classList.remove(&quot;hidden&quot;);
+      resetGame();
+    }
+  }
+
+  document.addEventListener(&quot;keydown&quot;, (e) =&gt; {
+    if (e.code === &quot;Space&quot; || e.key === &quot; &quot;) {
+      e.preventDefault();
+      flap();
+    }
+  });
+  document.addEventListener(&quot;mousedown&quot;, flap);
+  document.addEventListener(&quot;touchstart&quot;, (e) =&gt; {
+    e.preventDefault();
+    flap();
+  }, {passive:false});
+
+  function update(dt) {
+    // background stars
+    stars.forEach(s =&gt; {
+      s.x -= s.speed * dt;
+      if (s.x &lt; -5) {
+        s.x = W + 5;
+        s.y = Math.random() * H;
+      }
+      s.phase += dt * 2;
+    });
+    hills.forEach(h =&gt; { h.offset += h.speed * dt; });
+
+    if (state === &quot;playing&quot;) {
+      // 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 &gt; pipeInterval) {
+        timeSincePipe = 0;
+        spawnPipe();
+      }
+      for (let i = pipes.length - 1; i &gt;= 0; i--) {
+        const p = pipes[i];
+        p.x -= speed * dt;
+        if (p.x + 80 &lt; 0) pipes.splice(i, 1);
+      }
+
+      // score &amp; collisions
+      const bx = bird.x;
+      const by = bird.y;
+      const br = bird.r * 0.85;
+
+      // bounds
+      if (by - br &lt; 0 || by + br &gt; H) {
+        triggerGameOver();
+        return;
+      }
+
+      for (const p of pipes) {
+        const pipeW = 80;
+        if (!p.scored &amp;&amp; p.x + pipeW &lt; bx) {
+          p.scored = true;
+          score++;
+          scoreVal.textContent = score;
+        }
+        if (bx + br &gt; p.x &amp;&amp; bx - br &lt; p.x + pipeW) {
+          if (by - br &lt; p.top || by + br &gt; 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 &gt;= 0; i--) {
+      const p = particles[i];
+      p.age += dt;
+      if (p.age &gt;= 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 !== &quot;playing&quot;) return;
+    state = &quot;gameover&quot;;
+    addParticles(bird.x, bird.y, 26, &quot;rgba(255,220,190,0.9)&quot;, 1.6);
+    if (score &gt; best) {
+      best = score;
+      localStorage.setItem(&quot;flappy_sky_best&quot;, best);
+    }
+    bestVal.textContent = best;
+    overlay.classList.remove(&quot;hidden&quot;);
+    uiPrompt.textContent = &quot;Game Over • Score &quot; + score + &quot; • Click / Tap / Space to Restart&quot;;
+  }
+
+  function drawBackground() {
+    // subtly dark overlay for depth
+    const grd = ctx.createLinearGradient(0, 0, 0, H);
+    grd.addColorStop(0, &quot;rgba(40,50,90,0.2)&quot;);
+    grd.addColorStop(1, &quot;rgba(0,0,0,0.55)&quot;);
+    ctx.fillStyle = grd;
+    ctx.fillRect(0, 0, W, H);
+
+    // stars
+    stars.forEach(s =&gt; {
+      const alpha = 0.3 + 0.5 * (0.5 + 0.5 * Math.sin(s.phase));
+      ctx.fillStyle = &quot;rgba(230,240,255,&quot; + alpha + &quot;)&quot;;
+      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, &quot;rgba(160,190,255,0.0)&quot;);
+    cloudGrad.addColorStop(0.3, &quot;rgba(160,190,255,0.08)&quot;);
+    cloudGrad.addColorStop(1, &quot;rgba(80,110,170,0)&quot;);
+    ctx.fillStyle = cloudGrad;
+    ctx.fillRect(0, cloudY - 80, W, 160);
+
+    // hills
+    hills.forEach(h =&gt; {
+      ctx.fillStyle = h.color;
+      ctx.beginPath();
+      ctx.moveTo(0, H);
+      for (let x = 0; x &lt;= 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 =&gt; {
+      const pipeW = 80;
+      const glow = ctx.createLinearGradient(p.x, 0, p.x + pipeW, 0);
+      glow.addColorStop(0, &quot;#11ffd4&quot;);
+      glow.addColorStop(0.5, &quot;#72fffb&quot;);
+      glow.addColorStop(1, &quot;#0df2a6&quot;);
+
+      const topBottomGrad = ctx.createLinearGradient(0, 0, 0, H);
+      topBottomGrad.addColorStop(0, &quot;rgba(0,0,0,0.8)&quot;);
+      topBottomGrad.addColorStop(1, &quot;rgba(0,0,0,0.95)&quot;);
+
+      ctx.save();
+      ctx.shadowColor = &quot;rgba(0,255,200,0.7)&quot;;
+      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 = &quot;rgba(0,255,200,0.65)&quot;;
+      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, &quot;#fffbe6&quot;);
+    bodyGrad.addColorStop(0.5, &quot;#ffe07a&quot;);
+    bodyGrad.addColorStop(1, &quot;#ff9f3f&quot;);
+    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 = &quot;rgba(255,255,255,0.85)&quot;;
+    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, &quot;#ffe7a3&quot;);
+    wingGrad.addColorStop(1, &quot;#ffb25a&quot;);
+    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 = &quot;#ffffff&quot;;
+    ctx.beginPath();
+    ctx.arc(bodyR * 0.4, -bodyR * 0.2, bodyR * 0.25, 0, Math.PI * 2);
+    ctx.fill();
+    ctx.fillStyle = &quot;#1c1f2b&quot;;
+    ctx.beginPath();
+    ctx.arc(bodyR * 0.47, -bodyR * 0.2, bodyR * 0.13, 0, Math.PI * 2);
+    ctx.fill();
+
+    ctx.fillStyle = &quot;rgba(255,255,255,0.7)&quot;;
+    ctx.beginPath();
+    ctx.arc(bodyR * 0.52, -bodyR * 0.23, bodyR * 0.05, 0, Math.PI * 2);
+    ctx.fill();
+
+    // beak
+    ctx.fillStyle = &quot;#ffdd66&quot;;
+    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 = &quot;#ffb347&quot;;
+    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 = &quot;#ffb562&quot;;
+    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 =&gt; {
+      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);
+})();
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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="&lt;!DOCTYPE html&gt;
+&lt;html lang=&quot;en&quot;&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;UTF-8&quot;&gt;
+&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0, user-scalable=no&quot;&gt;
+&lt;title&gt;Flappy Bird&lt;/title&gt;
+&lt;style&gt;
+*{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}
+&lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+&lt;canvas id=&quot;c&quot;&gt;&lt;/canvas&gt;
+&lt;script&gt;
+(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&lt;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&lt;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&lt;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=&gt;{e.preventDefault();flap();});
+canvas.addEventListener('touchstart',e=&gt;{e.preventDefault();flap();},{passive:false});
+window.addEventListener('keydown',e=&gt;{
+  if(e.code==='Space'||e.key===' '){e.preventDefault();flap();}
+});
+
+function update(){
+  frame++;
+  // parallax
+  clouds.forEach(c=&gt;{
+    c.x-=c.speed*scale*(state==='play'?1:0.3);
+    if(c.x+c.w&lt;-10){c.x=W+Math.random()*100;c.y=H*0.05+Math.random()*H*0.35;}
+  });
+  hills.forEach(h=&gt;{
+    h.x-=h.speed*scale*(state==='play'?1:0.2);
+    if(h.x+h.w&lt;-10){h.x=W+Math.random()*80;h.h=40+Math.random()*80;h.w=120+Math.random()*100;}
+  });
+
+  particles=particles.filter(p=&gt;{
+    p.x+=p.vx;p.y+=p.vy;p.vy+=0.08;p.life--;
+    return p.life&gt;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&lt;W-180*scale-diff*15){
+    addPipe();
+  }
+
+  for(let i=pipes.length-1;i&gt;=0;i--){
+    const p=pipes[i];
+    p.x-=speed;
+    if(!p.passed&amp;&amp;p.x+PIPE_W*scale&lt;bird.x){
+      p.passed=true;
+      score++;
+      burst(bird.x+20,bird.y,8,'#ffd700');
+      if(score&gt;best)best=score;
+    }
+    if(p.x+PIPE_W*scale&lt;-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&gt;px&amp;&amp;bx-br&lt;px+pw){
+      if(by-br&lt;p.top||by+br&gt;p.top+p.gap){
+        die();
+      }
+    }
+  }
+
+  const groundY=H-GROUND_H*scale;
+  if(bird.y+BIRD_R*scale&gt;groundY||bird.y-BIRD_R*scale&lt;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=&gt;{
+    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=&gt;h.layer===0).forEach(h=&gt;{
+    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=&gt;h.layer===1).forEach(h=&gt;{
+    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&lt;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=&gt;{
+    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=&gt;{
+    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&gt;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&gt;=best&amp;&amp;score&gt;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();
+})();
+&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" 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 →