← 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
M graphics.jsonA graphics/generative-art-canvas-qwen/GenerativeArtCanvasQwen.aaA graphics/generative-art-canvas-qwen/index.html
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="<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Flow Field Art</title>
+ <style>
+ 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;
+ }
+ </style>
+</head>
+<body>
+ <div id="controls">
+ <button onclick="resetField()">New Field</button>
+ <button onclick="saveCanvas()">Save as PNG</button>
+ </div>
+ <canvas id="canvas"></canvas>
+ <script>
+ 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 < 1000; i++) {
+ particles.push(new Particle());
+ }
+ }
+
+ function generatePalette() {
+ const hues = [];
+ for (let i = 0; i < 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 <= 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 < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1/6) return p + (q - p) * 6 * t;
+ if (t < 1/2) return q;
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+ return p;
+ };
+ const q = l < 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 < 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();
+ }
+ </script>
+</body>
+</html>" 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="<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Flow Field Art</title>
+ <style>
+ 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;
+ }
+ </style>
+</head>
+<body>
+ <div id="controls">
+ <button onclick="resetField()">New Field</button>
+ <button onclick="saveCanvas()">Save as PNG</button>
+ </div>
+ <canvas id="canvas"></canvas>
+ <script>
+ 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 < 1000; i++) {
+ particles.push(new Particle());
+ }
+ }
+
+ function generatePalette() {
+ const hues = [];
+ for (let i = 0; i < 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 <= 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 < 0) t += 1;
+ if (t > 1) t -= 1;
+ if (t < 1/6) return p + (q - p) * 6 * t;
+ if (t < 1/2) return q;
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
+ return p;
+ };
+ const q = l < 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 < 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();
+ }
+ </script>
+</body>
+</html>" 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 →