Skip to content
Back to skills

Creative Code Lab

ASecurity

Use when user asks to create generative art, creative coding sketches, visual experiments, particle systems, shader effects, procedural graphics, interactive canvases, or any code-as-art project. Use when building animated backgrounds, flow fields, fractals, L-systems, noise-based visuals, text effects, fluid simulations, or interactive 3D scenes. Triggers on words like "generative", "sketch", "particles", "shader", "creative coding", "visual experiment", "procedural", "noise", "flow field".

  • 10 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 26, 2026
developmentjavascriptgojavareactnextjsrailsspringawsperformance

Works with

  • cursor
  • cli

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 27, 2026

npx -y skills add sacredvoid/skillkit --skill creative-code-lab --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Creative Code Lab?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Creative Code Lab
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/sacredvoid-creative-code-lab/badge)](https://www.skillsdirectory.com/skills/sacredvoid-creative-code-lab)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: creative-code-lab
description: Use when user asks to create generative art, creative coding sketches, visual experiments, particle systems, shader effects, procedural graphics, interactive canvases, or any code-as-art project. Use when building animated backgrounds, flow fields, fractals, L-systems, noise-based visuals, text effects, fluid simulations, or interactive 3D scenes. Triggers on words like "generative", "sketch", "particles", "shader", "creative coding", "visual experiment", "procedural", "noise", "flow field".
---

# Creative Code Lab

Generate interactive generative art and creative coding sketches. Inspired by [Leisure Lab](https://lab.xubh.top/) (43 sketches by KainXu).

## Overview

**Creative coding = algorithms made visible.** Every sketch transforms math into something you can see, touch, and feel.

The output format is flexible: single self-contained HTML file, React component, Next.js page, p5.js sketch, or raw Canvas/WebGL - whatever fits the user's project.

## When to Use

- User wants generative/procedural art
- Building animated backgrounds or hero sections
- Creating interactive visual experiments
- Implementing particle systems, flow fields, shaders, fractals
- "Make something cool/beautiful with code"
- Image-to-art transformations
- Creative 404 pages, loading screens, visual flourishes

## Technique Catalog

Pick the right approach for the visual:

```
What visual are you building?
|
+-- Flowing, organic motion?
|   +-- Particles following forces --> Flow Field (Perlin noise)
|   +-- Smooth color blending --> Fluid Shader (WebGL fragment)
|   +-- Swirling paint effect --> Fluid Swirl (iterative sine + spiral distortion)
|
+-- Nature / biology?
|   +-- Trees, plants, corals --> L-System (recursive branching)
|   +-- Cell division, growth --> Cellular Automata (neighbor rules)
|   +-- Veins, rivers, cracks --> Diffusion-Limited Aggregation
|
+-- Geometric / mathematical?
|   +-- Mondrian, grids --> Recursive Subdivision
|   +-- Space-filling patterns --> Hilbert/Peano Curves
|   +-- Spirals, roses --> Polar Coordinate Math
|   +-- Vortex patterns --> Parametric equations in (r, theta)
|
+-- Text effects?
|   +-- Text from particles --> Canvas pixel sampling + particle system
|   +-- Neon glow --> CSS text-shadow stacking or shader
|   +-- Glitch effect --> Random clip-path + color channel offset
|   +-- Blur/reveal --> CSS filter animation or shader blur
|
+-- Interactive / mouse-driven?
|   +-- Particles scatter from cursor --> Distance-based force repulsion
|   +-- Drawing / painting --> Canvas stroke with velocity-based width
|   +-- Cursor trail effects --> Ring buffer of positions + fade
|
+-- 3D scenes?
|   +-- Product showcase --> Three.js + GLTF + orbit controls
|   +-- Abstract geometry --> Three.js + custom shaders
|   +-- Camera depth blur --> Three.js postprocessing (DOF)
|
+-- Image transformation?
    +-- Photo to painting --> Algorithmic brush strokes on canvas
    +-- Pointillism / dots --> Pixel sampling + circle rendering
    +-- Pixel decomposition --> Grid sampling + animated scatter
```

## Core Algorithms Reference

### 1. Perlin Noise Flow Field

The foundation of organic-looking particle motion. Particles follow a vector field generated by noise.

```javascript
// Core concept: noise(x, y) returns smooth random value 0-1
// Map to angle: angle = noise(x * scale, y * scale) * TWO_PI
// Each particle follows the angle at its grid position

class FlowField {
  constructor(canvas, particleCount = 2000) {
    this.ctx = canvas.getContext('2d');
    this.w = canvas.width;
    this.h = canvas.height;
    this.scale = 0.005;       // Noise zoom (smaller = smoother)
    this.speed = 2;
    this.particles = Array.from({ length: particleCount }, () => ({
      x: Math.random() * this.w,
      y: Math.random() * this.h,
      prevX: 0, prevY: 0
    }));
  }

  // Attempt at simplex-like noise (for self-contained sketches)
  // For production, use a proper noise library
  noise2D(x, y) {
    const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
    return n - Math.floor(n);
  }

  update(time) {
    this.ctx.fillStyle = 'rgba(0, 0, 0, 0.02)'; // Trail fade
    this.ctx.fillRect(0, 0, this.w, this.h);

    for (const p of this.particles) {
      p.prevX = p.x;
      p.prevY = p.y;

      const angle = this.noise2D(
        p.x * this.scale + time * 0.0001,
        p.y * this.scale
      ) * Math.PI * 4;

      p.x += Math.cos(angle) * this.speed;
      p.y += Math.sin(angle) * this.speed;

      // Wrap around edges
      if (p.x < 0) p.x = this.w;
      if (p.x > this.w) p.x = 0;
      if (p.y < 0) p.y = this.h;
      if (p.y > this.h) p.y = 0;

      this.ctx.strokeStyle = `hsla(${angle * 30}, 70%, 60%, 0.3)`;
      this.ctx.beginPath();
      this.ctx.moveTo(p.prevX, p.prevY);
      this.ctx.lineTo(p.x, p.y);
      this.ctx.stroke();
    }
  }
}
```

**Key parameters to tune:**
- `scale` (0.001-0.01): Noise zoom. Smaller = wider, smoother curves
- `speed` (1-5): How fast particles move
- Trail fade alpha (0.01-0.1): Lower = longer trails
- `particleCount`: More = denser field, watch performance
- Color mapping: Map angle, position, or velocity to hue

### 2. L-System (Procedural Trees/Plants)

Recursive string rewriting that draws botanical structures.

```javascript
function lSystem(axiom, rules, iterations) {
  let current = axiom;
  for (let i = 0; i < iterations; i++) {
    current = current.split('').map(c => rules[c] || c).join('');
  }
  return current;
}

function drawLSystem(ctx, instructions, len, angle) {
  const stack = [];
  for (const char of instructions) {
    switch (char) {
      case 'F': // Draw forward
        ctx.beginPath();
        ctx.moveTo(0, 0);
        ctx.lineTo(0, -len);
        ctx.stroke();
        ctx.translate(0, -len);
        break;
      case '+': ctx.rotate(angle); break;   // Turn right
      case '-': ctx.rotate(-angle); break;   // Turn left
      case '[': // Save state (branch start)
        stack.push(ctx.getTransform());
        break;
      case ']': // Restore state (branch end)
        ctx.setTransform(stack.pop());
        break;
    }
  }
}

// Classic tree
const tree = lSystem('F', { 'F': 'FF+[+F-F-F]-[-F+F+F]' }, 4);
// Fern
const fern = lSystem('X', { 'X': 'F+[[X]-X]-F[-FX]+X', 'F': 'FF' }, 6);
```

**Common L-System presets:**
| Name | Axiom | Rules | Angle |
|------|-------|-------|-------|
| Binary tree | `F` | `F -> FF+[+F-F]-[-F+F]` | 25deg |
| Fern | `X` | `X -> F+[[X]-X]-F[-FX]+X`, `F -> FF` | 25deg |
| Bush | `F` | `F -> F[+FF][-FF]F[-F][+F]F` | 20deg |
| Seaweed | `F` | `F -> FF-[-F+F+F]+[+F-F-F]` | 22deg |

### 3. WebGL Fragment Shader (Fluid/Gradient Effects)

For GPU-accelerated visuals. Single HTML file with inline shader.

```html
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const gl = canvas.getContext('webgl');
canvas.width = innerWidth;
canvas.height = innerHeight;

const vertSrc = `attribute vec2 p; void main(){gl_Position=vec4(p,0,1);}`;
const fragSrc = `
precision mediump float;
uniform float t;
uniform vec2 r;    // resolution
uniform vec2 m;    // mouse

void main() {
  vec2 uv = gl_FragCoord.xy / r;
  vec2 mouse = m / r;

  // Fluid swirl: iterative sine distortion
  for (int i = 0; i < 8; i++) {
    uv = vec2(
      sin(uv.y * 4.0 + t + float(i)) * 0.4 + uv.x,
      cos(uv.x * 4.0 + t + float(i)) * 0.4 + uv.y
    );
  }

  // Distance from mouse adds interaction
  float d = length(uv - mouse) * 2.0;

  vec3 col = vec3(
    sin(uv.x * 3.0 + t) * 0.5 + 0.5,
    sin(uv.y * 3.0 + t * 1.3) * 0.5 + 0.5,
    sin((uv.x + uv.y) * 2.0 + t * 0.7) * 0.5 + 0.5
  );

  gl_FragColor = vec4(col * (1.0 - d * 0.3), 1.0);
}`;

// Boilerplate: compile, link, draw fullscreen quad
function compile(type, src) {
  const s = gl.createShader(type);
  gl.shaderSource(s, src);
  gl.compileShader(s);
  return s;
}
const prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, vertSrc));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, fragSrc));
gl.linkProgram(prog);
gl.useProgram(prog);

const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,1,1]), gl.STATIC_DRAW);
const p = gl.getAttribLocation(prog, 'p');
gl.enableVertexAttribArray(p);
gl.vertexAttribPointer(p, 2, gl.FLOAT, false, 0, 0);

const tLoc = gl.getUniformLocation(prog, 't');
const rLoc = gl.getUniformLocation(prog, 'r');
const mLoc = gl.getUniformLocation(prog, 'm');

let mx = 0, my = 0;
canvas.addEventListener('mousemove', e => { mx = e.clientX; my = canvas.height - e.clientY; });

(function loop(now) {
  gl.viewport(0, 0, canvas.width, canvas.height);
  gl.uniform1f(tLoc, now * 0.001);
  gl.uniform2f(rLoc, canvas.width, canvas.height);
  gl.uniform2f(mLoc, mx, my);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  requestAnimationFrame(loop);
})(0);
</script>
```

**Shader effect recipes (modify the `main()` body):**
- **Plasma**: `sin(uv.x*10+t) + sin(uv.y*10+t) + sin((uv.x+uv.y)*10+t)`
- **Voronoi cells**: Distance to nearest random point in grid
- **Fractal noise**: Layered `sin()` at increasing frequencies (octaves)
- **Distortion**: `texture2D(tex, uv + vec2(sin(uv.y*10+t)*0.02))`

### 4. Particle Text Effect

Sample pixels from text rendered on a hidden canvas, spawn particles at dark pixel positions.

```javascript
function createTextParticles(text, fontSize, canvas) {
  const ctx = canvas.getContext('2d');
  const w = canvas.width, h = canvas.height;

  // Render text to hidden canvas to sample pixels
  const offscreen = new OffscreenCanvas(w, h);
  const offCtx = offscreen.getContext('2d');
  offCtx.fillStyle = 'white';
  offCtx.font = `bold ${fontSize}px sans-serif`;
  offCtx.textAlign = 'center';
  offCtx.textBaseline = 'middle';
  offCtx.fillText(text, w / 2, h / 2);

  // Sample pixels at grid intervals
  const imageData = offCtx.getImageData(0, 0, w, h).data;
  const particles = [];
  const gap = 4; // Density: lower = more particles

  for (let y = 0; y < h; y += gap) {
    for (let x = 0; x < w; x += gap) {
      const alpha = imageData[(y * w + x) * 4 + 3];
      if (alpha > 128) {
        particles.push({
          targetX: x, targetY: y,
          x: Math.random() * w, y: Math.random() * h, // Start scattered
          vx: 0, vy: 0
        });
      }
    }
  }

  // Animate: spring toward target, repel from mouse
  function animate(mouseX, mouseY) {
    ctx.clearRect(0, 0, w, h);
    for (const p of particles) {
      const dx = mouseX - p.x, dy = mouseY - p.y;
      const dist = Math.sqrt(dx * dx + dy * dy);

      // Repel from mouse
      if (dist < 100) {
        p.vx -= dx / dist * 5;
        p.vy -= dy / dist * 5;
      }

      // Spring back to target
      p.vx += (p.targetX - p.x) * 0.05;
      p.vy += (p.targetY - p.y) * 0.05;
      p.vx *= 0.9; // Damping
      p.vy *= 0.9;
      p.x += p.vx;
      p.y += p.vy;

      ctx.fillStyle = '#fff';
      ctx.fillRect(p.x, p.y, 2, 2);
    }
  }

  return { animate, particles };
}
```

### 5. Cellular Automata / Vector Field

Emergent patterns from simple local rules.

```javascript
function cellularField(canvas, gridSize = 100) {
  const ctx = canvas.getContext('2d');
  const cols = gridSize, rows = gridSize;
  const cellW = canvas.width / cols, cellH = canvas.height / rows;

  // Initialize grid with random values
  let grid = Array.from({ length: cols }, () =>
    Array.from({ length: rows }, () => Math.random())
  );

  // Random locked anchor points
  const anchors = Array.from({ length: 20 }, () => ({
    x: Math.floor(Math.random() * cols),
    y: Math.floor(Math.random() * rows),
    value: Math.random()
  }));

  function step() {
    const next = grid.map(row => [...row]);
    for (let x = 1; x < cols - 1; x++) {
      for (let y = 1; y < rows - 1; y++) {
        // Average neighbors
        next[x][y] = (
          grid[x-1][y] + grid[x+1][y] +
          grid[x][y-1] + grid[x][y+1]
        ) / 4 + (Math.random() - 0.5) * 0.01;
      }
    }
    // Re-apply anchors
    for (const a of anchors) next[a.x][a.y] = a.value;
    grid = next;
  }

  function draw() {
    for (let x = 0; x < cols; x++) {
      for (let y = 0; y < rows; y++) {
        const v = grid[x][y];
        ctx.fillStyle = `hsl(${v * 360}, 70%, 50%)`;
        ctx.fillRect(x * cellW, y * cellH, cellW, cellH);
      }
    }
  }

  return { step, draw };
}
```

### 6. Polar Coordinate Art (Vortex/Spiral)

Mathematical beauty from `(r, theta)` parametric equations.

```javascript
function polarArt(canvas) {
  const ctx = canvas.getContext('2d');
  const cx = canvas.width / 2, cy = canvas.height / 2;

  function draw(time) {
    ctx.fillStyle = 'rgba(0,0,0,0.05)';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    for (let theta = 0; theta < Math.PI * 20; theta += 0.02) {
      // The art is in this formula - experiment!
      const r = 150 * Math.sin(theta * 3 + time * 0.001)
              + 50 * Math.cos(theta * 7 - time * 0.002);

      const x = cx + r * Math.cos(theta);
      const y = cy + r * Math.sin(theta);

      ctx.fillStyle = `hsl(${theta * 20 + time * 0.05}, 80%, 60%)`;
      ctx.fillRect(x, y, 2, 2);
    }
  }

  return { draw };
}
```

**Formula playground** (swap into the `r = ...` line):
- Rose: `r = 200 * cos(n * theta)` (n controls petals)
- Spiral: `r = theta * 5`
- Lissajous: `x = A*sin(a*t+d), y = B*sin(b*t)` (use cartesian)
- Butterfly: `r = exp(sin(theta)) - 2*cos(4*theta) + sin((2*theta-PI)/24)^5`

### 7. Three.js 3D Scene (Quick Setup)

For 3D creative coding with models, lighting, and camera.

```javascript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

function create3DScene(container) {
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
  const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setSize(innerWidth, innerHeight);
  renderer.setPixelRatio(devicePixelRatio);
  container.appendChild(renderer.domElement);

  // Lighting
  scene.add(new THREE.AmbientLight(0xffffff, 0.5));
  const dirLight = new THREE.DirectionalLight(0xffffff, 1);
  dirLight.position.set(5, 5, 5);
  scene.add(dirLight);

  // Controls
  const controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  camera.position.z = 5;

  // Animation loop
  function animate() {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
  }
  animate();

  // Handle resize
  window.addEventListener('resize', () => {
    camera.aspect = innerWidth / innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(innerWidth, innerHeight);
  });

  return { scene, camera, renderer };
}
```

### 8. Image-to-Art Transform

Upload an image, decompose into artistic representation.

```javascript
function imageToPointillism(sourceCanvas, outputCanvas, dotSize = 6) {
  const src = sourceCanvas.getContext('2d');
  const out = outputCanvas.getContext('2d');
  const w = sourceCanvas.width, h = sourceCanvas.height;
  const imageData = src.getImageData(0, 0, w, h).data;

  out.fillStyle = '#000';
  out.fillRect(0, 0, w, h);

  const dots = [];
  for (let y = 0; y < h; y += dotSize) {
    for (let x = 0; x < w; x += dotSize) {
      const i = (y * w + x) * 4;
      const r = imageData[i], g = imageData[i+1], b = imageData[i+2];
      const brightness = (r + g + b) / 3;
      dots.push({
        x: x + (Math.random() - 0.5) * dotSize * 0.5,
        y: y + (Math.random() - 0.5) * dotSize * 0.5,
        radius: (brightness / 255) * dotSize * 0.5 + 1,
        color: `rgb(${r},${g},${b})`
      });
    }
  }

  // Animate dots appearing
  let drawn = 0;
  function drawBatch() {
    const batch = Math.min(drawn + 200, dots.length);
    for (let i = drawn; i < batch; i++) {
      const d = dots[i];
      out.beginPath();
      out.arc(d.x, d.y, d.radius, 0, Math.PI * 2);
      out.fillStyle = d.color;
      out.fill();
    }
    drawn = batch;
    if (drawn < dots.length) requestAnimationFrame(drawBatch);
  }
  drawBatch();
}
```

**Art style variants** (change the rendering loop):
- **Pointillism**: Colored circles (above)
- **ASCII art**: Map brightness to character set ` .:-=+*#%@`
- **Mosaic**: Colored rectangles at grid positions
- **Brush strokes**: Short angled lines using local gradient direction

## Output Formats

Adapt output to the user's needs:

| User wants | Output format |
|-----------|---------------|
| Quick standalone demo | Single `.html` file, no dependencies |
| React/Next.js component | `.tsx` with `useRef` + `useEffect` + canvas |
| p5.js sketch | `sketch.js` with `setup()` / `draw()` |
| Background for existing site | CSS + minimal JS, or shader-only |
| npm package / reusable | ES module with config options |
| Three.js scene | Module with importmap or bundler setup |

**For self-contained HTML**: Inline all JS/CSS, use CDN imports via `<script type="importmap">` for Three.js, or raw Canvas/WebGL with no dependencies.

**For React**: Use `useRef` for canvas, `useEffect` for animation loop, cleanup on unmount. Use `'use client'` directive for Next.js.

## Design Principles

1. **Start simple, add complexity** - Get a dot moving before building a particle system
2. **Color is emotion** - HSL is your friend. Map data to hue for instant beauty
3. **Trails create history** - `fillRect` with low alpha instead of `clearRect`
4. **Noise is nature** - Perlin/simplex noise makes anything look organic
5. **Interaction creates connection** - Mouse influence makes it personal
6. **Frame rate is sacred** - Profile early, use `requestAnimationFrame`, offload to GPU
7. **Constraints breed creativity** - "What can I make with just circles?" beats "anything goes"

## Performance Tips

- **Canvas 2D**: Good for 1-5K particles. Use `fillRect` over `arc` for dots.
- **WebGL shaders**: Good for fullscreen effects, millions of calculations per frame
- **Three.js**: Good for 3D scenes, use instanced meshes for many objects
- **OffscreenCanvas**: Use web workers for heavy computation
- **`devicePixelRatio`**: Always set for sharp rendering on retina displays
- **`will-change: transform`**: Hint to browser for composited layers

## Common Mistakes

| Mistake | Fix |
|---------|-----|
| Clearing canvas every frame (no trails) | Use semi-transparent fill instead of `clearRect` |
| Noise that looks random, not smooth | Lower the scale factor (0.001-0.01 range) |
| Too many particles, low FPS | Start with 500, increase until you hit 30fps |
| Shader compiles but shows black | Check gl.getShaderInfoLog() for errors |
| Mouse coords wrong on canvas | Account for canvas offset and `devicePixelRatio` |
| Animation jerky on resize | Debounce resize handler, recalculate dimensions |
| Colors look muddy | Use HSL with fixed saturation/lightness, vary only hue |

## Inspiration Mapping

When the user describes a vibe, map to technique:

| Vibe | Technique |
|------|-----------|
| "Organic, flowing" | Perlin noise flow field |
| "Geometric, structured" | Recursive subdivision, Mondrian |
| "Natural, growing" | L-systems, fractal trees |
| "Psychedelic, trippy" | WebGL shader with iterative distortion |
| "Minimal, elegant" | Single curve, polar coordinates |
| "Chaotic, energetic" | High-count particles with physics |
| "Retro, pixelated" | Low-res canvas + nearest-neighbor scaling |
| "Dreamy, soft" | Gaussian blur + slow float + pastels |
| "Interactive, playful" | Mouse-reactive particles or deformation |
| "Data-driven" | Map dataset values to visual properties |

Files in this skill

  • SKILL.md19.3 KB
  • leisure-lab-catalog.md5.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.