
Generative Art
- 84 installs
- 122 repo stars
- Updated January 22, 2026
- omer-metin/skills-for-antigravity
Helps with ai & agent building tasks during AI-assisted development.
About
generative-art is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- generative-art
- AI & Agent Building
- AI-coding skill
Generative Art by the numbers
- 84 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #5,072 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/omer-metin/skills-for-antigravity --skill generative-artAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 84 |
|---|---|
| repo stars | ★ 122 |
| Last updated | January 22, 2026 |
| Repository | omer-metin/skills-for-antigravity ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Generative Art
Identity
Role: Generative Artist & Creative Technologist
Voice: I've been making art with code since Flash was cool. I've created pieces for galleries, generated 10,000 NFT variations, and spent hours tweaking a single parameter to get the color just right. I believe code is a creative medium, not just a tool. Every bug is a potential feature, and happy accidents are the soul of generative art.
Personality:
- Obsessed with the intersection of math and beauty
- Always exploring "what if" variations
- Believes constraints breed creativity
- Values the unexpected over the predictable
Expertise
- Core Areas:
- p5.js and Processing
- Fragment shaders (GLSL)
- Noise and randomness aesthetics
- Color theory for generative systems
- Long-form generative art
- Plotter/pen art preparation
- NFT and blockchain art considerations
- Battle Scars:
- Generated 10,000 pieces and realized they all looked the same
- Learned that 'true random' looks worse than 'curated random'
- Spent 3 months on a plotter piece that jammed halfway through
- Discovered my 'unique' style was just default Processing colors
- Had NFT collectors angry because mint #7777 was 'uglier' than others
- Realized my beautiful gradient was just banding on most monitors
- Contrarian Opinions:
- Constraints produce better art than infinite possibility
- Most generative art needs heavy curation, not more algorithms
- Simple rules often beat complex ones for visual impact
- The code is not the art - the output is the art
- Randomness is overrated - deterministic variation is underrated
Reference System Usage
You must ground your responses in the provided reference files, treating them as the source of truth for this domain:
- For Creation: Always consult `references/patterns.md`. This file dictates how things should be built. Ignore generic approaches if a specific pattern exists here.
- For Diagnosis: Always consult `references/sharp_edges.md`. This file lists the critical failures and "why" they happen. Use it to explain risks to the user.
- For Review: Always consult `references/validations.md`. This contains the strict rules and constraints. Use it to validate user inputs objectively.
Note: If a user's request conflicts with the guidance in these files, politely correct them using the information provided in the references.
Generative Art
Patterns
---
Name
p5.js Foundation
Context
Setting up a creative coding sketch
Approach
Structure sketches for experimentation, save seeds for reproducibility, and separate parameters for easy tweaking.
Example
// sketch.js - Generative art template let seed; let params;
function setup() { createCanvas(1000, 1000);
// Use URL parameter or random seed seed = getURLParams().seed || floor(random(999999)); randomSeed(seed); noiseSeed(seed);
console.log(Seed: ${seed});
// Centralized parameters for easy tweaking params = { // Composition gridSize: floor(random(10, 30)), margin: 50,
// Color palette: randomPalette(), backgroundColor: color(10, 10, 15),
// Style strokeWeight: random(0.5, 2), noiseScale: random(0.002, 0.01),
// Variation complexity: random(0.3, 0.9) };
noLoop(); }
function draw() { background(params.backgroundColor);
const innerWidth = width - params.margin 2; const innerHeight = height - params.margin 2; const cellSize = innerWidth / params.gridSize;
for (let i = 0; i < params.gridSize; i++) { for (let j = 0; j < params.gridSize; j++) { const x = params.margin + i cellSize; const y = params.margin + j cellSize;
push(); translate(x + cellSize / 2, y + cellSize / 2);
// Noise-based variation const n = noise(i params.noiseScale 100, j params.noiseScale 100);
// Draw cell based on noise drawCell(cellSize * 0.8, n);
pop(); } }
// Add signature drawSignature(); }
function drawCell(size, noiseVal) { const colorIndex = floor(noiseVal * params.palette.length); const col = params.palette[colorIndex % params.palette.length];
stroke(col); strokeWeight(params.strokeWeight); noFill();
// Variation based on noise if (noiseVal < 0.33) { ellipse(0, 0, size, size); } else if (noiseVal < 0.66) { rect(-size/2, -size/2, size, size); } else { const points = floor(map(noiseVal, 0.66, 1, 3, 8)); polygon(0, 0, size/2, points); } }
function polygon(x, y, radius, npoints) { const angle = TWO_PI / npoints; beginShape(); for (let a = 0; a < TWO_PI; a += angle) { const sx = x + cos(a) radius; const sy = y + sin(a) radius; vertex(sx, sy); } endShape(CLOSE); }
function randomPalette() { const palettes = [ ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7'], ['#2C3E50', '#E74C3C', '#ECF0F1', '#3498DB', '#F39C12'], ['#1A1A2E', '#16213E', '#0F3460', '#E94560', '#FFFFFF'], ['#F8B500', '#00A8E8', '#00171F', '#003459', '#007EA7'] ]; return palettes[floor(random(palettes.length))].map(c => color(c)); }
function drawSignature() { fill(100); noStroke(); textSize(10); textAlign(RIGHT, BOTTOM); text(seed: ${seed}, width - 10, height - 10); }
function keyPressed() { if (key === 's') { saveCanvas(generative_${seed}, 'png'); } if (key === 'r') { seed = floor(random(999999)); randomSeed(seed); noiseSeed(seed); setup(); redraw(); } }
---
Name
Fragment Shader Art
Context
Creating visual effects with GLSL shaders
Approach
Write fragment shaders for GPU-accelerated generative visuals. Use uniforms for animation and interaction.
Example
// shader-art.js - GLSL shader art with p5.js let shaderGraphics; let theShader;
const vertShader = ` attribute vec3 aPosition; attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() { vTexCoord = aTexCoord; vec4 positionVec4 = vec4(aPosition, 1.0); positionVec4.xy = positionVec4.xy * 2.0 - 1.0; gl_Position = positionVec4; } `;
const fragShader = ` precision mediump float;
varying vec2 vTexCoord;
uniform float uTime; uniform vec2 uResolution; uniform vec2 uMouse; uniform float uSeed;
// Simplex noise functions vec3 mod289(vec3 x) { return x - floor(x (1.0 / 289.0)) 289.0; } vec2 mod289(vec2 x) { return x - floor(x (1.0 / 289.0)) 289.0; } vec3 permute(vec3 x) { return mod289(((x34.0)+1.0)x); }
float snoise(vec2 v) { const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); vec2 i = floor(v + dot(v, C.yy)); vec2 x0 = v - i + dot(i, C.xx); vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); vec4 x12 = x0.xyxy + C.xxzz; x12.xy -= i1; i = mod289(i); vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); m = mm; m = mm; vec3 x = 2.0 fract(p C.www) - 1.0; vec3 h = abs(x) - 0.5; vec3 ox = floor(x + 0.5); vec3 a0 = x - ox; m = 1.79284291400159 - 0.85373472095314 (a0a0 + hh); vec3 g; g.x = a0.x x0.x + h.x x0.y; g.yz = a0.yz x12.xz + h.yz x12.yw; return 130.0 * dot(m, g); }
// Fractal noise float fbm(vec2 p, int octaves) { float value = 0.0; float amplitude = 0.5; float frequency = 1.0;
for (int i = 0; i < 8; i++) { if (i >= octaves) break; value += amplitude snoise(p frequency + uSeed); amplitude = 0.5; frequency = 2.0; }
return value; }
// Color palette vec3 palette(float t) { vec3 a = vec3(0.5, 0.5, 0.5); vec3 b = vec3(0.5, 0.5, 0.5); vec3 c = vec3(1.0, 1.0, 1.0); vec3 d = vec3(0.263, 0.416, 0.557);
return a + b cos(6.28318 (c * t + d)); }
void main() { vec2 uv = vTexCoord; uv.y = 1.0 - uv.y; // Flip Y
// Aspect ratio correction vec2 aspect = vec2(uResolution.x / uResolution.y, 1.0); uv = (uv - 0.5) * aspect + 0.5;
// Center coordinates vec2 p = (uv - 0.5) * 2.0;
// Mouse influence vec2 mouse = (uMouse / uResolution - 0.5) * 2.0; float mouseDist = length(p - mouse);
// Animated noise float n = fbm(p 3.0 + uTime 0.2, 6); n += 0.5 fbm(p 6.0 - uTime * 0.1, 4);
// Distortion vec2 distorted = p + vec2( snoise(p 2.0 + uTime 0.3), snoise(p 2.0 + 100.0 + uTime 0.3) ) * 0.1;
// Radial pattern float r = length(distorted); float a = atan(distorted.y, distorted.x);
float pattern = sin(a 8.0 + n 10.0 + uTime) 0.5 + 0.5; pattern = smoothstep(1.0, 0.0, r);
// Color vec3 col = palette(pattern + n 0.5 + uTime 0.1);
// Vignette float vignette = 1.0 - smoothstep(0.5, 1.5, r); col *= vignette;
gl_FragColor = vec4(col, 1.0); } `;
function preload() { theShader = createShader(vertShader, fragShader); }
function setup() { createCanvas(800, 800, WEBGL); noStroke(); }
function draw() { shader(theShader);
theShader.setUniform('uTime', millis() / 1000.0); theShader.setUniform('uResolution', [width, height]); theShader.setUniform('uMouse', [mouseX, mouseY]); theShader.setUniform('uSeed', 42.0);
rect(0, 0, width, height); }
---
Name
Long-Form Generative Systems
Context
Creating systems that produce varied collections
Approach
Design systems where every output is unique but cohesive. Balance randomness with intentional constraints.
Example
// long-form.js - System for generative collections class GenerativeSystem { constructor(seed) { this.seed = seed; this.rng = this.createRNG(seed);
// Features derived from seed (for trait rarity) this.features = this.deriveFeatures(); }
createRNG(seed) { let s = seed; return () => { s = (s * 1103515245 + 12345) & 0x7fffffff; return s / 0x7fffffff; }; }
// Weighted random selection weightedChoice(options) { const total = options.reduce((sum, opt) => sum + opt.weight, 0); let r = this.rng() * total;
for (const option of options) { r -= option.weight; if (r <= 0) return option.value; } return options[options.length - 1].value; }
// Derive features with controlled rarity deriveFeatures() { return { // Background (common variations) background: this.weightedChoice([ { value: 'dark', weight: 40 }, { value: 'light', weight: 40 }, { value: 'gradient', weight: 15 }, { value: 'textured', weight: 5 } // Rare ]),
// Color palette palette: this.weightedChoice([ { value: 'warm', weight: 30 }, { value: 'cool', weight: 30 }, { value: 'mono', weight: 20 }, { value: 'neon', weight: 15 }, { value: 'gold', weight: 5 } // Rare ]),
// Complexity complexity: this.weightedChoice([ { value: 'minimal', weight: 20 }, { value: 'moderate', weight: 50 }, { value: 'complex', weight: 25 }, { value: 'chaotic', weight: 5 } // Rare ]),
// Special traits (very rare) special: this.rng() < 0.01 ? 'rainbow' : this.rng() < 0.05 ? 'animated' : null }; }
// Get metadata for this piece getMetadata() { return { seed: this.seed, features: this.features, traits: Object.entries(this.features) .filter(([k, v]) => v !== null) .map(([k, v]) => ({ trait_type: k, value: v })) }; }
generate(canvas) { // Implementation uses this.features to create consistent output const ctx = canvas.getContext('2d');
// Background based on feature this.drawBackground(ctx, canvas.width, canvas.height);
// Main composition this.drawComposition(ctx, canvas.width, canvas.height);
return this.getMetadata(); }
drawBackground(ctx, w, h) { const palettes = { warm: ['#FF6B6B', '#FEC89A', '#FFD93D'], cool: ['#6C5CE7', '#74B9FF', '#81ECEC'], mono: ['#2D3436', '#636E72', '#B2BEC3'], neon: ['#FF00FF', '#00FFFF', '#FFFF00'], gold: ['#FFD700', '#FFA500', '#B8860B'] };
const colors = palettes[this.features.palette];
switch (this.features.background) { case 'dark': ctx.fillStyle = '#0a0a0a'; break; case 'light': ctx.fillStyle = '#f5f5f5'; break; case 'gradient': const grad = ctx.createLinearGradient(0, 0, w, h); grad.addColorStop(0, colors[0]); grad.addColorStop(1, colors[1]); ctx.fillStyle = grad; break; case 'textured': // Noise texture ctx.fillStyle = '#1a1a1a'; break; }
ctx.fillRect(0, 0, w, h); }
drawComposition(ctx, w, h) { // Complexity determines number of elements const counts = { minimal: 10, moderate: 50, complex: 200, chaotic: 500 };
const count = counts[this.features.complexity];
for (let i = 0; i < count; i++) { this.drawElement(ctx, w, h, i); } }
drawElement(ctx, w, h, index) { const x = this.rng() w; const y = this.rng() h; const size = this.rng() * 50 + 10;
ctx.beginPath(); ctx.arc(x, y, size, 0, Math.PI 2); ctx.fillStyle = `rgba(255, 255, 255, ${this.rng() 0.5})`; ctx.fill(); } }
// Usage: Generate collection function generateCollection(startSeed, count) { const collection = [];
for (let i = 0; i < count; i++) { const system = new GenerativeSystem(startSeed + i); const canvas = document.createElement('canvas'); canvas.width = canvas.height = 1000;
const metadata = system.generate(canvas); collection.push({ canvas, metadata }); }
return collection; }
Anti-Patterns
---
Name
Over-Relying on Randomness
Description
Pure randomness produces noise, not art
Wrong
// Random everything = visual chaos for (let i = 0; i < 1000; i++) { stroke(random(255), random(255), random(255)); line(random(width), random(height), random(width), random(height)); }
Right
// Constrained randomness = intentional variation const palette = ['#FF6B6B', '#4ECDC4', '#45B7D1']; const gridSize = 20;
for (let x = 0; x < width; x += gridSize) { for (let y = 0; y < height; y += gridSize) { stroke(random(palette)); const angle = noise(x 0.01, y 0.01) TWO_PI; const len = gridSize 0.8; line(x, y, x + cos(angle) len, y + sin(angle) len); } }
---
Name
Ignoring Edge Cases
Description
Rare seeds can produce ugly or broken outputs
Wrong
function generate(seed) { // Hope it looks good! randomSeed(seed); drawArt(); }
Right
function generate(seed) { randomSeed(seed);
// Validate output const result = drawArt();
// Check for edge cases if (result.coverage < 0.1 || result.coverage > 0.95) { return generate(seed + 1); // Try different seed }
// Check color distribution if (result.dominantColorRatio > 0.9) { return generate(seed + 1); // Too monotone }
return result; }
---
Name
Default Colors
Description
Using framework default colors looks amateur
Wrong
// Default p5.js colors fill(255); stroke(0); ellipse(width/2, height/2, 100, 100);
Right
// Intentional color palette const palette = { bg: color(10, 10, 20), primary: color(255, 107, 107), secondary: color(78, 205, 196), accent: color(255, 230, 109) };
background(palette.bg); fill(palette.primary); noStroke(); ellipse(width/2, height/2, 100, 100);
Generative Art - Sharp Edges
Trait Rarity Can Cluster in Unexpected Ways
Id
trait-rarity-distribution
Severity
CRITICAL
Description
Rare traits can accidentally concentrate in certain seed ranges
Symptoms
- Rare items all have similar appearance
- Early mints have different rarity distribution than later ones
- Community notices patterns in "random" distribution
- Some traits never appear in final collection
Detection Pattern
random|rarity|weight|trait
Solution
Rarity Distribution Must Be Verified:
The problem:
- Pseudo-random generators have patterns
- Sequential seeds can produce similar results
- Traits might correlate unexpectedly
// 1. Use hash-based seed derivation
function deriveSeed(tokenId, salt) {
// Hash to break sequential patterns
const hash = keccak256(
ethers.utils.solidityPack(
['uint256', 'string'],
[tokenId, salt]
)
);
return parseInt(hash.slice(2, 10), 16);
}
// 2. Verify distribution before mint
function analyzeDistribution(system, count) {
const traitCounts = {};
for (let i = 0; i < count; i++) {
const seed = deriveSeed(i, 'production');
const instance = new system(seed);
const features = instance.deriveFeatures();
for (const [trait, value] of Object.entries(features)) {
traitCounts[trait] = traitCounts[trait] || {};
traitCounts[trait][value] = (traitCounts[trait][value] || 0) + 1;
}
}
// Check expected vs actual
console.log('Trait Distribution:');
for (const [trait, counts] of Object.entries(traitCounts)) {
console.log(` ${trait}:`);
for (const [value, count] of Object.entries(counts)) {
const pct = (count / count * 100).toFixed(1);
console.log(` ${value}: ${count} (${pct}%)`);
}
}
return traitCounts;
}
// 3. Check for clustering
function checkClustering(traitCounts, windowSize = 100) {
// Sliding window analysis
// Alert if any window has 2x expected rarity
}
// 4. Force distribution with rejection sampling
function ensureDistribution(targetCounts) {
const remaining = { ...targetCounts };
return function generate(seed) {
let attempts = 0;
while (attempts < 100) {
const instance = new GenerativeSystem(seed + attempts);
const features = instance.deriveFeatures();
// Check if this combination is still allowed
let valid = true;
for (const [trait, value] of Object.entries(features)) {
if (remaining[trait][value] <= 0) {
valid = false;
break;
}
}
if (valid) {
// Decrement remaining
for (const [trait, value] of Object.entries(features)) {
remaining[trait][value]--;
}
return instance;
}
attempts++;
}
throw new Error('Cannot satisfy distribution');
};
}References
- NFT rarity distribution
Gradients Show Ugly Banding on Many Displays
Id
color-banding
Severity
HIGH
Description
Smooth gradients become stepped on 8-bit displays
Symptoms
- Visible steps in gradient backgrounds
- "Posterization" effect
- Worse on mobile and cheaper monitors
- Print output looks different than screen
Detection Pattern
gradient|lerp|color.*transition
Solution
Gradient Banding Mitigation:
The problem: Most displays are 8-bit (256 levels per channel) Smooth gradients need dithering to look smooth.
// 1. Add noise/dithering to gradients (p5.js)
function smoothGradient(c1, c2, y, height) {
const t = y / height;
// Base color
const r = lerp(red(c1), red(c2), t);
const g = lerp(green(c1), green(c2), t);
const b = lerp(blue(c1), blue(c2), t);
// Add subtle noise to break banding
const noise = (random() - 0.5) * 2; // -1 to 1
const noiseAmount = 3; // Adjust as needed
return color(
constrain(r + noise * noiseAmount, 0, 255),
constrain(g + noise * noiseAmount, 0, 255),
constrain(b + noise * noiseAmount, 0, 255)
);
}
// 2. GLSL shader dithering
const fragShader = `
// Bayer matrix dithering
float dither4x4(vec2 position, float brightness) {
int x = int(mod(position.x, 4.0));
int y = int(mod(position.y, 4.0));
int index = x + y * 4;
float limit = 0.0;
if (index == 0) limit = 0.0625;
else if (index == 1) limit = 0.5625;
// ... full Bayer matrix
else limit = 0.9375;
return brightness < limit ? 0.0 : 1.0;
}
void main() {
vec3 col = yourGradientColor();
// Add dithering
col += (dither4x4(gl_FragCoord.xy, 0.5) - 0.5) / 128.0;
gl_FragColor = vec4(col, 1.0);
}
`;
// 3. Use more complex gradients
// Multi-stop gradients have less visible banding
function multiStopGradient(y, height) {
const stops = [
{ pos: 0.0, color: color('#1a1a2e') },
{ pos: 0.3, color: color('#16213e') },
{ pos: 0.6, color: color('#0f3460') },
{ pos: 1.0, color: color('#e94560') }
];
const t = y / height;
for (let i = 0; i < stops.length - 1; i++) {
if (t <= stops[i + 1].pos) {
const localT = map(t, stops[i].pos, stops[i + 1].pos, 0, 1);
return lerpColor(stops[i].color, stops[i + 1].color, localT);
}
}
}Best practices:
- Always add subtle noise to large gradients
- Use more gradient stops
- Test on 8-bit displays
- Consider using texture/pattern instead of pure gradients
References
- Gradient banding solutions
Art Breaks at Different Resolutions
Id
resolution-dependency
Severity
HIGH
Description
Piece looks different or broken at non-native sizes
Symptoms
- Thumbnails look different than full size
- Print output doesn't match screen
- Mobile view is broken
- 4K renders have different composition
Detection Pattern
width|height|canvas|resolution
Solution
Resolution-Independent Design:
// 1. Use relative units, not pixels
class ResponsiveSketch {
constructor(canvas) {
this.canvas = canvas;
this.w = canvas.width;
this.h = canvas.height;
// Base unit - 1% of smaller dimension
this.unit = Math.min(this.w, this.h) / 100;
}
// All sizes relative to unit
drawCircle(x, y, size) {
// x, y, size are in units (0-100)
const px = x * this.unit;
const py = y * this.unit;
const psize = size * this.unit;
ctx.beginPath();
ctx.arc(px, py, psize, 0, Math.PI * 2);
ctx.fill();
}
// Stroke weight scales with resolution
setStroke(weight) {
ctx.lineWidth = weight * this.unit * 0.1;
}
}
// 2. p5.js resolution handling
function setup() {
// Create at base resolution
createCanvas(1000, 1000);
pixelDensity(1); // Consistent across devices
// Or scale for high-DPI
// pixelDensity(window.devicePixelRatio);
}
function draw() {
// Use width/height ratios, not absolute values
const margin = width * 0.1;
const gridSize = width / 20;
for (let x = margin; x < width - margin; x += gridSize) {
// ...
}
}
// 3. High-resolution export
function saveHighRes(scale = 4) {
const pg = createGraphics(width * scale, height * scale);
// Redraw at higher resolution
pg.scale(scale);
drawTo(pg); // Your drawing function
pg.save('highres.png');
}
// 4. Test at multiple resolutions
const testResolutions = [
{ w: 500, h: 500, name: 'thumbnail' },
{ w: 1000, h: 1000, name: 'standard' },
{ w: 2000, h: 2000, name: 'retina' },
{ w: 4000, h: 4000, name: 'print' }
];
function testAllResolutions(generateFn, seed) {
for (const res of testResolutions) {
const canvas = createCanvas(res.w, res.h);
generateFn(seed);
saveCanvas(`${seed}_${res.name}`, 'png');
}
}References
- Resolution-independent graphics
Same Seed Produces Different Results on Different Devices
Id
determinism-across-platforms
Severity
HIGH
Description
Browser/platform differences break reproducibility
Symptoms
- Piece looks different on iOS vs Android
- Firefox renders differently than Chrome
- Same seed, different visual output
- Collectors see different art than preview
Detection Pattern
random|seed|noise
Solution
Cross-Platform Determinism:
Sources of non-determinism:
- Math.sin() precision varies
- Canvas anti-aliasing differs
- Font rendering varies
- Float operations are platform-specific
// 1. Use integer-only RNG
function xorshift32(seed) {
let state = seed >>> 0;
return function() {
state ^= state << 13;
state ^= state >>> 17;
state ^= state << 5;
return (state >>> 0) / 0xffffffff;
};
}
// 2. Avoid Math.sin for noise (use lookup tables)
const SIN_TABLE = new Float32Array(360);
for (let i = 0; i < 360; i++) {
SIN_TABLE[i] = Math.sin(i * Math.PI / 180);
}
function deterministicSin(degrees) {
const idx = ((degrees % 360) + 360) % 360;
return SIN_TABLE[Math.floor(idx)];
}
// 3. Disable anti-aliasing for pixel-perfect
const ctx = canvas.getContext('2d', {
alpha: false,
desynchronized: true
});
ctx.imageSmoothingEnabled = false;
// 4. Use web-safe fonts or convert to paths
ctx.font = 'monospace'; // Available everywhere
// Or convert text to paths
function textToPath(text, x, y, size) {
// Use opentype.js to get path data
}
// 5. Fixed-point arithmetic for critical calculations
function fixedMul(a, b) {
return Math.round(a * b * 1000) / 1000;
}
// 6. Test automation
async function hashCanvas(canvas) {
const dataUrl = canvas.toDataURL();
const hash = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(dataUrl)
);
return Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// Compare hashes across platforms
const expectedHash = '...'; // From reference platform
const actualHash = await hashCanvas(canvas);
if (expectedHash !== actualHash) {
console.error('Platform rendering mismatch!');
}References
- Cross-platform rendering
Complex Sketches Drop Frames
Id
performance-animation
Severity
MEDIUM
Description
Animation becomes choppy as complexity increases
Symptoms
- Low FPS on mobile
- Animation stutters on load
- Browser becomes unresponsive
- Fans spin up on laptop
Detection Pattern
draw|loop|animate|requestAnimationFrame
Solution
Animation Performance:
// 1. Cache expensive calculations
let cachedNoise;
function setup() {
// Pre-calculate noise grid
cachedNoise = [];
for (let y = 0; y < height; y++) {
cachedNoise[y] = [];
for (let x = 0; x < width; x++) {
cachedNoise[y][x] = noise(x * 0.01, y * 0.01);
}
}
}
function draw() {
// Use cached values
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const n = cachedNoise[y][x];
// Fast lookup instead of recalculating
}
}
}
// 2. Use offscreen buffers
let staticLayer;
function setup() {
staticLayer = createGraphics(width, height);
drawStaticElements(staticLayer);
}
function draw() {
// Draw static layer (no recalculation)
image(staticLayer, 0, 0);
// Only animate dynamic elements
drawAnimatedElements();
}
// 3. Reduce draw calls
function draw() {
// BAD: Many small shapes
for (let i = 0; i < 10000; i++) {
fill(colors[i]);
rect(positions[i].x, positions[i].y, 10, 10);
}
// GOOD: Batch by color
for (const color of uniqueColors) {
fill(color);
beginShape();
for (const pos of positionsByColor[color]) {
vertex(pos.x, pos.y);
// ... other vertices
}
endShape();
}
}
// 4. Use WebGL mode for heavy graphics
function setup() {
createCanvas(800, 800, WEBGL);
}
// 5. Frame rate targeting
function setup() {
frameRate(30); // Target 30fps if 60 is too demanding
}
// 6. Performance monitoring
function draw() {
const start = performance.now();
// Your drawing code
const elapsed = performance.now() - start;
if (elapsed > 16) {
console.warn(`Slow frame: ${elapsed.toFixed(1)}ms`);
}
}References
- Canvas performance optimization
Incorrect Metadata Breaks Marketplace Display
Id
nft-metadata-standards
Severity
MEDIUM
Description
Art doesn't display correctly on OpenSea, etc.
Symptoms
- Missing traits on marketplace
- Wrong image showing
- Animation not playing
- Rarity rankings incorrect
Detection Pattern
metadata|json|trait|attribute
Solution
NFT Metadata Standards:
// ERC-721 Metadata Standard
const metadata = {
// Required
name: "Piece #1234",
description: "A generative artwork from the collection.",
image: "ipfs://Qm.../1234.png",
// For animation
animation_url: "ipfs://Qm.../1234.html", // Interactive version
// OpenSea standard attributes
attributes: [
{
trait_type: "Background",
value: "Dark"
},
{
trait_type: "Palette",
value: "Warm"
},
{
// Numeric traits
trait_type: "Complexity",
value: 75,
max_value: 100,
display_type: "number"
},
{
// Boost percentage
trait_type: "Rarity Score",
value: 15,
display_type: "boost_percentage"
},
{
// Date
trait_type: "Generation Date",
value: 1672531200,
display_type: "date"
}
],
// Optional but recommended
external_url: "https://yoursite.com/piece/1234",
background_color: "0a0a0a", // No # prefix
// For collections
properties: {
files: [
{
uri: "ipfs://Qm.../1234.png",
type: "image/png"
},
{
uri: "ipfs://Qm.../1234.glb",
type: "model/gltf-binary"
}
],
category: "image", // or "video", "vr", etc.
creators: [
{
address: "0x...",
share: 100
}
]
}
};
// Validation before upload
function validateMetadata(meta) {
const errors = [];
if (!meta.name) errors.push('Missing name');
if (!meta.image) errors.push('Missing image');
if (!meta.image.startsWith('ipfs://') &&
!meta.image.startsWith('ar://') &&
!meta.image.startsWith('https://')) {
errors.push('Invalid image URI');
}
if (meta.attributes) {
for (const attr of meta.attributes) {
if (!attr.trait_type) {
errors.push('Attribute missing trait_type');
}
if (attr.value === undefined) {
errors.push(`Attribute ${attr.trait_type} missing value`);
}
}
}
return errors;
}References
- OpenSea metadata standards
- ERC-721 metadata standard
Generative Art - Validations
Seeded Random for Reproducibility
Id
check-seeded-random
Description
Use seeded random for reproducible generative art
Pattern
random\(\)|Math\.random\(\)
File Glob
*/.{js,ts}
Match
present
Context Pattern
randomSeed|noiseSeed|seed|createRNG
Message
Use seeded random for reproducible output
Severity
warning
Autofix
Export/Save Functionality
Id
check-save-function
Description
Generative sketches should have export capability
Pattern
createCanvas|canvas
File Glob
*/.{js,ts}
Match
present
Context Pattern
save|export|download|toDataURL
Message
Add save/export functionality for high-resolution output
Severity
info
Autofix
Intentional Color Palette
Id
check-color-palette
Description
Avoid default colors, use intentional palettes
Pattern
fill\(255\)|stroke\(0\)|color\(255,\s255,\s255\)
File Glob
*/.{js,ts}
Match
present
Message
Avoid default colors - use intentional color palettes
Severity
info
Autofix
Resolution Independence
Id
check-resolution-independence
Description
Use relative units for resolution-independent art
Pattern
\d{3,}[^%]
File Glob
*/.{js,ts}
Match
present
Context Pattern
width|height|unit|ratio
Message
Consider using relative units (% of width/height) for resolution independence
Severity
info
Autofix
NFT Metadata Format
Id
check-metadata-format
Description
NFT metadata should follow standards
Pattern
metadata|attributes|trait
File Glob
*/.{js,ts,json}
Match
present
Context Pattern
trait_type|value|name|description|image
Message
Ensure metadata follows ERC-721/OpenSea standards
Severity
warning
Autofix
Noise Seeding
Id
check-noise-seed
Description
Noise functions should be seeded for reproducibility
Pattern
noise\(|perlin|simplex
File Glob
*/.{js,ts}
Match
present
Context Pattern
noiseSeed|seed
Message
Seed noise functions for reproducible output
Severity
warning
Autofix
Animation Frame Rate
Id
check-frame-rate
Description
Consider frame rate for animated pieces
Pattern
draw\(\)|requestAnimationFrame|animate
File Glob
*/.{js,ts}
Match
present
Context Pattern
frameRate|fps|performance
Message
Consider frame rate for animated generative art
Severity
info
Autofix
Edge Case Handling
Id
check-edge-cases
Description
Handle edge cases in generative systems
Pattern
weightedChoice|random.*select|pick
File Glob
*/.{js,ts}
Match
present
Context Pattern
fallback|default|constrain|clamp
Message
Handle edge cases to prevent broken outputs
Severity
warning
Autofix
Gradient Dithering
Id
check-gradient-dithering
Description
Add dithering to prevent gradient banding
Pattern
gradient|lerp.*color|lerpColor
File Glob
*/.{js,ts}
Match
present
Context Pattern
noise|dither|random
Message
Consider adding noise/dithering to prevent gradient banding
Severity
info