[object Object]

← back to Graphics Agentabrams

graphics.aa: add Qwen3-14B Generative Art Canvas (Model Arena landing, self-hosted)

fe6f36c3c1546c06d6aa690582c87ec31816ff64 · 2026-07-24 23:37:56 -0700 · Steve Abrams

Files touched

Diff

commit fe6f36c3c1546c06d6aa690582c87ec31816ff64
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 24 23:37:56 2026 -0700

    graphics.aa: add Qwen3-14B Generative Art Canvas (Model Arena landing, self-hosted)
---
 graphics.json                                      |   8 +
 .../GenerativeArtCanvasQwen.aa                     | 195 +++++++++++++++++++++
 graphics/generative-art-canvas-qwen/index.html     | 195 +++++++++++++++++++++
 3 files changed, 398 insertions(+)

diff --git a/graphics.json b/graphics.json
index 8a1bf60..6393a02 100644
--- a/graphics.json
+++ b/graphics.json
@@ -31,6 +31,14 @@
       "desc": "Interactive Voronoi tessellation — click or drag to scatter seed points and watch cells re-tessellate with a smooth power-diagram grow-in animation; each shard gets a radial gradient fill. Shift/right-click removes the nearest seed, Explode flies the shards apart and reassembles, cycle color palettes, toggle seeds/cracks, undo/scatter/clear, and save-PNG.",
       "path": "graphics/voronoi-shatter/",
       "added": "2026-07-24"
+    },
+    {
+      "id": "generative-art-canvas-qwen",
+      "file": "GenerativeArtCanvasQwen.aa",
+      "title": "Generative Art Canvas (Qwen3 14B)",
+      "desc": "Flow-field generative art — thousands of Perlin-noise particles paint flowing colored trails; a button reseeds a fresh palette + field, and you can save a PNG. The winning Model Arena artifact built by Qwen3 14B.",
+      "path": "graphics/generative-art-canvas-qwen/",
+      "added": "2026-07-24"
     }
   ]
 }
diff --git a/graphics/generative-art-canvas-qwen/GenerativeArtCanvasQwen.aa b/graphics/generative-art-canvas-qwen/GenerativeArtCanvasQwen.aa
new file mode 100644
index 0000000..73e5581
--- /dev/null
+++ b/graphics/generative-art-canvas-qwen/GenerativeArtCanvasQwen.aa
@@ -0,0 +1,195 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Generative Art Canvas — built by Qwen3 14B · 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>Generative Art Canvas</h1><span class="by">built by <b>Qwen3 14B</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+  &lt;meta charset=&quot;UTF-8&quot;&gt;
+  &lt;title&gt;Flow Field Art&lt;/title&gt;
+  &lt;style&gt;
+    html, body {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      background: #000;
+    }
+    canvas {
+      display: block;
+    }
+    #controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      z-index: 10;
+      color: white;
+      background: rgba(0,0,0,0.5);
+      padding: 10px;
+      border-radius: 5px;
+    }
+    button {
+      margin: 5px;
+      padding: 8px 12px;
+      font-size: 14px;
+      background: #444;
+      color: white;
+      border: none;
+      border-radius: 4px;
+      cursor: pointer;
+    }
+    button:hover {
+      background: #666;
+    }
+  &lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+  &lt;div id=&quot;controls&quot;&gt;
+    &lt;button onclick=&quot;resetField()&quot;&gt;New Field&lt;/button&gt;
+    &lt;button onclick=&quot;saveCanvas()&quot;&gt;Save as PNG&lt;/button&gt;
+  &lt;/div&gt;
+  &lt;canvas id=&quot;canvas&quot;&gt;&lt;/canvas&gt;
+  &lt;script&gt;
+    const canvas = document.getElementById('canvas');
+    const ctx = canvas.getContext('2d');
+    let width, height;
+    let particles = [];
+    let palette;
+
+    function resizeCanvas() {
+      width = canvas.width = window.innerWidth;
+      height = canvas.height = window.innerHeight;
+    }
+    window.addEventListener('resize', resizeCanvas);
+    resizeCanvas();
+
+    function resetField() {
+      palette = generatePalette();
+      particles = [];
+      for (let i = 0; i &lt; 1000; i++) {
+        particles.push(new Particle());
+      }
+    }
+
+    function generatePalette() {
+      const hues = [];
+      for (let i = 0; i &lt; 10; i++) {
+        hues.push(Math.random() * 360);
+      }
+      return hues;
+    }
+
+    function mapValue(value, min1, max1, min2, max2) {
+      return (value - min1) / (max1 - min1) * (max2 - min2) + min2;
+    }
+
+    function noise(x, y) {
+      const nx = x / 100;
+      const ny = y / 100;
+      const sinx = Math.sin(nx);
+      const siny = Math.sin(ny);
+      return Math.sin(sinx + siny) * 0.5 + 0.5;
+    }
+
+    function getDirection(x, y) {
+      const step = 10;
+      const dx = noise(x + step, y) - noise(x - step, y);
+      const dy = noise(x, y + step) - noise(x, y - step);
+      const angle = Math.atan2(dy, dx);
+      return { angle, magnitude: 1 };
+    }
+
+    function lerpColor(a, b, t) {
+      return [
+        a[0] + t * (b[0] - a[0]),
+        a[1] + t * (b[1] - a[1]),
+        a[2] + t * (b[2] - a[2])
+      ];
+    }
+
+    function getColor(x, y) {
+      const nx = x / 100;
+      const ny = y / 100;
+      const t = (Math.sin(nx) + Math.cos(ny)) * 0.5 + 0.5;
+      const hue = palette[Math.floor(t * palette.length)];
+      return [hue, 1, 1];
+    }
+
+    function Particle() {
+      this.x = Math.random() * width;
+      this.y = Math.random() * height;
+      this.vx = 0;
+      this.vy = 0;
+      this.alpha = 1;
+    }
+
+    Particle.prototype.update = function() {
+      const { angle } = getDirection(this.x, this.y);
+      this.vx = Math.cos(angle);
+      this.vy = Math.sin(angle);
+      this.x += this.vx;
+      this.y += this.vy;
+      this.alpha -= 0.005;
+      if (this.alpha &lt;= 0) {
+        this.x = Math.random() * width;
+        this.y = Math.random() * height;
+        this.alpha = 1;
+      }
+    };
+
+    Particle.prototype.draw = function(ctx) {
+      const color = getColor(this.x, this.y);
+      const [h, s, l] = color;
+      const rgb = hsvToRgb(h, s, l);
+      ctx.fillStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${this.alpha})`;
+      ctx.fillRect(this.x, this.y, 2, 2);
+    };
+
+    function hsvToRgb(h, s, l) {
+      let r, g, b;
+      if (s === 0) {
+        r = g = b = l;
+      } else {
+        const hue2rgb = function (p, q, t) {
+          if (t &lt; 0) t += 1;
+          if (t &gt; 1) t -= 1;
+          if (t &lt; 1/6) return p + (q - p) * 6 * t;
+          if (t &lt; 1/2) return q;
+          if (t &lt; 2/3) return p + (q - p) * (2/3 - t) * 6;
+          return p;
+        };
+        const q = l &lt; 0.5 ? l * (1 + s) : l + s - l * s;
+        const p = 2 * l - q;
+        r = hue2rgb(p, q, h + 1/3);
+        g = hue2rgb(p, q, h);
+        b = hue2rgb(p, q, h - 1/3);
+      }
+      return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
+    }
+
+    function draw() {
+      ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
+      ctx.fillRect(0, 0, width, height);
+      for (let i = 0; i &lt; particles.length; i++) {
+        particles[i].update();
+        particles[i].draw(ctx);
+      }
+      requestAnimationFrame(draw);
+    }
+
+    resetField();
+    draw();
+
+    function saveCanvas() {
+      const link = document.createElement('a');
+      link.download = 'flow-field-art.png';
+      link.href = canvas.toDataURL();
+      link.click();
+    }
+  &lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" title="Generative Art Canvas"></iframe></div>
+</body></html>
\ No newline at end of file
diff --git a/graphics/generative-art-canvas-qwen/index.html b/graphics/generative-art-canvas-qwen/index.html
new file mode 100644
index 0000000..73e5581
--- /dev/null
+++ b/graphics/generative-art-canvas-qwen/index.html
@@ -0,0 +1,195 @@
+<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Generative Art Canvas — built by Qwen3 14B · 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>Generative Art Canvas</h1><span class="by">built by <b>Qwen3 14B</b></span></header>
+<div class="wrap"><iframe sandbox="allow-scripts allow-pointer-lock" srcdoc="&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+  &lt;meta charset=&quot;UTF-8&quot;&gt;
+  &lt;title&gt;Flow Field Art&lt;/title&gt;
+  &lt;style&gt;
+    html, body {
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      background: #000;
+    }
+    canvas {
+      display: block;
+    }
+    #controls {
+      position: absolute;
+      top: 10px;
+      left: 10px;
+      z-index: 10;
+      color: white;
+      background: rgba(0,0,0,0.5);
+      padding: 10px;
+      border-radius: 5px;
+    }
+    button {
+      margin: 5px;
+      padding: 8px 12px;
+      font-size: 14px;
+      background: #444;
+      color: white;
+      border: none;
+      border-radius: 4px;
+      cursor: pointer;
+    }
+    button:hover {
+      background: #666;
+    }
+  &lt;/style&gt;
+&lt;/head&gt;
+&lt;body&gt;
+  &lt;div id=&quot;controls&quot;&gt;
+    &lt;button onclick=&quot;resetField()&quot;&gt;New Field&lt;/button&gt;
+    &lt;button onclick=&quot;saveCanvas()&quot;&gt;Save as PNG&lt;/button&gt;
+  &lt;/div&gt;
+  &lt;canvas id=&quot;canvas&quot;&gt;&lt;/canvas&gt;
+  &lt;script&gt;
+    const canvas = document.getElementById('canvas');
+    const ctx = canvas.getContext('2d');
+    let width, height;
+    let particles = [];
+    let palette;
+
+    function resizeCanvas() {
+      width = canvas.width = window.innerWidth;
+      height = canvas.height = window.innerHeight;
+    }
+    window.addEventListener('resize', resizeCanvas);
+    resizeCanvas();
+
+    function resetField() {
+      palette = generatePalette();
+      particles = [];
+      for (let i = 0; i &lt; 1000; i++) {
+        particles.push(new Particle());
+      }
+    }
+
+    function generatePalette() {
+      const hues = [];
+      for (let i = 0; i &lt; 10; i++) {
+        hues.push(Math.random() * 360);
+      }
+      return hues;
+    }
+
+    function mapValue(value, min1, max1, min2, max2) {
+      return (value - min1) / (max1 - min1) * (max2 - min2) + min2;
+    }
+
+    function noise(x, y) {
+      const nx = x / 100;
+      const ny = y / 100;
+      const sinx = Math.sin(nx);
+      const siny = Math.sin(ny);
+      return Math.sin(sinx + siny) * 0.5 + 0.5;
+    }
+
+    function getDirection(x, y) {
+      const step = 10;
+      const dx = noise(x + step, y) - noise(x - step, y);
+      const dy = noise(x, y + step) - noise(x, y - step);
+      const angle = Math.atan2(dy, dx);
+      return { angle, magnitude: 1 };
+    }
+
+    function lerpColor(a, b, t) {
+      return [
+        a[0] + t * (b[0] - a[0]),
+        a[1] + t * (b[1] - a[1]),
+        a[2] + t * (b[2] - a[2])
+      ];
+    }
+
+    function getColor(x, y) {
+      const nx = x / 100;
+      const ny = y / 100;
+      const t = (Math.sin(nx) + Math.cos(ny)) * 0.5 + 0.5;
+      const hue = palette[Math.floor(t * palette.length)];
+      return [hue, 1, 1];
+    }
+
+    function Particle() {
+      this.x = Math.random() * width;
+      this.y = Math.random() * height;
+      this.vx = 0;
+      this.vy = 0;
+      this.alpha = 1;
+    }
+
+    Particle.prototype.update = function() {
+      const { angle } = getDirection(this.x, this.y);
+      this.vx = Math.cos(angle);
+      this.vy = Math.sin(angle);
+      this.x += this.vx;
+      this.y += this.vy;
+      this.alpha -= 0.005;
+      if (this.alpha &lt;= 0) {
+        this.x = Math.random() * width;
+        this.y = Math.random() * height;
+        this.alpha = 1;
+      }
+    };
+
+    Particle.prototype.draw = function(ctx) {
+      const color = getColor(this.x, this.y);
+      const [h, s, l] = color;
+      const rgb = hsvToRgb(h, s, l);
+      ctx.fillStyle = `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${this.alpha})`;
+      ctx.fillRect(this.x, this.y, 2, 2);
+    };
+
+    function hsvToRgb(h, s, l) {
+      let r, g, b;
+      if (s === 0) {
+        r = g = b = l;
+      } else {
+        const hue2rgb = function (p, q, t) {
+          if (t &lt; 0) t += 1;
+          if (t &gt; 1) t -= 1;
+          if (t &lt; 1/6) return p + (q - p) * 6 * t;
+          if (t &lt; 1/2) return q;
+          if (t &lt; 2/3) return p + (q - p) * (2/3 - t) * 6;
+          return p;
+        };
+        const q = l &lt; 0.5 ? l * (1 + s) : l + s - l * s;
+        const p = 2 * l - q;
+        r = hue2rgb(p, q, h + 1/3);
+        g = hue2rgb(p, q, h);
+        b = hue2rgb(p, q, h - 1/3);
+      }
+      return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
+    }
+
+    function draw() {
+      ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
+      ctx.fillRect(0, 0, width, height);
+      for (let i = 0; i &lt; particles.length; i++) {
+        particles[i].update();
+        particles[i].draw(ctx);
+      }
+      requestAnimationFrame(draw);
+    }
+
+    resetField();
+    draw();
+
+    function saveCanvas() {
+      const link = document.createElement('a');
+      link.download = 'flow-field-art.png';
+      link.href = canvas.toDataURL();
+      link.click();
+    }
+  &lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;" title="Generative Art Canvas"></iframe></div>
+</body></html>
\ No newline at end of file

← 6f5aeff Add Flow Field graphic to the hub (FlowField.aa)  ·  back to Graphics Agentabrams  ·  graphics.aa: unwrap Qwen3 canvas to standalone (strip Model 35c6035 →