How nittygrid animations work, the five ways to build one, and the complete render_code JavaScript API and all built-in generators.
Every animation in nittygrid, no matter how you made it, does the same simple thing: on every frame it fills a grid of colored cells. That grid is called the PixelMatrix. Your display then shows those colors.
The grid and its colors
The PixelMatrix has W columns and H rows. Cell (0, 0) is the top-left. Each cell holds four numbers from 0 to 255: red, green, blue, and alpha ([r, g, b, a]).
Five ways to build an animation
Each animation node stores a small recipe (so you can always edit it), made in one of five ways:
Way
Good for
Stored as
Built-in generator
Fast effects such as Fire, Plasma, Rain, or Text, with ready-made sliders.
type: "generator" + generatorId + params
JavaScript render_code
Custom scenes, or anything the AI writes for you.
type: "code" + code + optional codeParams
Drawn frames and sprites
Hand-painted pixel art and short loops.
generatorId: "draw-frame" or "sprite-anim"
Vector path
Light sweeps and glows travelling along a line.
generatorId: "path_animation"
Video
Sampling a video clip across your LEDs, live or baked into frames.
generatorId: "video-input" or "flattened-animation"
Write your own effect with render_code
render_code is a short piece of JavaScript that runs once per frame. The AI writes it for you, and you can also edit it in the Animation Editor. This complete example draws a soft breathing glow in the middle of any display:
js
// 1. Start every frame with opaque black
clear([0, 0, 0, 255]);
// 2. A smooth 0 → 1 → 0 pulse every 2 seconds
const pulse = easeInOut(pingpong(t, 2.0));
const radius = map(pulse, 0, 1, 2, Math.min(W, H) * 0.35);
// 3. Draw a glow at the center
glow(W / 2, H / 2, [80, 180, 255, 255], radius);
Combine drawing and AI (_targetFrame)
You can lock a picture you drew as the exact start or end of an AI animation:
Paint a logo, icon, or pattern with the Draw tool.
Open the AI chat on that animation node and ask for an intro or transition, for example "assemble this icon from drifting embers".
nittygrid locks your painted pixels as _targetFrame, so both the preview and the export land exactly on your artwork.
Technical reference
The tables below are generated from the code, so they always match the app. They are for technicians and AI agents.
render_code API
The same reference as the in-app References modal.
Core scope
Name
Type
Description
matrix
PixelMatrix
Full matrix object — use top-level helpers instead where possible
frame
number
Frame counter, increments ~30×/s (normalized to 60 fps ticks during sequence export)
t
number
Elapsed seconds
W, H
number
Matrix width and height in pixels
state
object
Plain object persisting between frames — store particles, flags, etc. here
params
object
Live values of any codeParams you declared
Drawing
Call
Description
set(x, y, [r,g,b,a])
Overwrite a single pixel. a=255 for fully opaque.
clear([r,g,b,a])
Fill the whole matrix. Use at frame start for clean effects.
fade(factor)
Darken all pixels (0–1). Creates decay trails. Affected by global Trail slider.
Radial additive glow, brightness falls off with distance². Sub-pixel center OK. Affected by global Glow Intensity/Radius sliders.
Shapes
Call
Description
line(x0, y0, x1, y1, color)
Bresenham pixel line between two points.
circle(cx, cy, r, color, filled=false)
Rasterized circle — outline by default, pass true for filled.
rect(x, y, w, h, color, filled=true)
Rectangle — filled by default, pass false for outline only.
js
// Spinning clock hand
clear([0,0,0,255]);
const angle = t * 2;
line(W/2, H/2, W/2 + Math.cos(angle)*6, H/2 + Math.sin(angle)*6, [0,255,180,255]);
Easings
Function
Behaviour
easeIn(t)
Slow start, accelerates (quadratic)
easeOut(t)
Fast start, decelerates — most natural feel
easeInOut(t)
Smooth S-curve
easeElastic(t)
Springy overshoot at end
easeBounce(t)
Bouncy landing
easeBack(t)
Slight recoil before moving forward
js
// Pulsing circle with eased size
const phase = (t % 1.5) / 1.5; // 0→1 over 1.5s
const r = map(easeInOut(phase), 0, 1, 1, 8);
clear([0,0,0,255]);
circle(W/2, H/2, r, [255,120,0,255], true);
Spring physics
Parameter
Default
Effect
stiffness
0.1
How strongly it pulls toward target — higher = snappier
damping
0.8
How quickly it settles — lower = more oscillation
js
// Dot that springs to a moving target
if (!state.s) state.s = { val: W/2, vel: 0 };
const target = W/2 + Math.sin(t * 0.8) * (W/2 - 2);
const r = spring(state.s.val, target, state.s.vel, 0.08, 0.75);
state.s.val = r.value; state.s.vel = r.velocity;
clear([0,0,0,255]);
glow(state.s.val, H/2, [100,200,255,255], 3);
Math helpers
Call
Description
map(v, i0, i1, o0, o1)
Remap v from input range to output range
lerp(a, b, t)
Linear interpolate between a and b
constrain(v, lo, hi)
Clamp v to [lo, hi]
dist(x1, y1, x2, y2)
2D Euclidean distance
lerpColor(a, b, t)
Interpolate two [r,g,b,a] colors
hsb(h, s, b, a?)
h 0–360 · s/b 0–100 → [r,g,b,a]. Great for hue cycling.
js
// Rainbow hue cycle across columns
clear([0,0,0,255]);
for (let x = 0; x < W; x++) {
const hue = (x / W * 360 + t * 60) % 360;
for (let y = 0; y < H; y++) set(x, y, hsb(hue, 100, 80));
}
Noise & random
Call
Description
noise(x, y, z=0)
Perlin-style smooth noise → -1..1. Pass t as z to animate.
noise01(x, y, z=0)
Same but → 0..1, easier for brightness and opacity.
random(lo=0, hi=1)
Random float in [lo, hi).
js
// Organic flowing colour field
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
const v = noise01(x * 0.15, y * 0.15, t * 0.4);
set(x, y, hsb(v * 240, 80, v * 100));
}
Time & loop helpers
Call
Description
phase(t, period)
0..1 sawtooth — resets every period seconds. Clean loop phase.
pingpong(t, period)
0..1..0 triangle — breathe/pulse/bounce without writing modulo math.
palette(t, colors[])
Sample a multi-stop gradient at t (0..1). Each stop is [r,g,b,a].
js
// Pulsing fire-palette glow at centre
const fireColors = [[0,0,0,255],[180,20,0,255],[255,120,0,255],[255,255,100,255]];
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
const d = 1 - constrain(dist(x, y, W/2, H/2) / (H/2), 0, 1);
const pulse = pingpong(t, 1.2); // 0→1→0 every 1.2s
set(x, y, palette(d * easeOut(pulse), fireColors));
}
Scales the decay factor of every fade() call (0 = clear, 1 = original, 2 = very persistent)
Scale / Offset X-Y / Rotation
Pixel-level backward-mapping transform applied after each frame
Built-in generators
Every built-in generator with its parameter names, types, ranges, and defaults, read live from the app.
Basic
Generator ID
Name
Parameters (key: type = default)
solid-color
Solid Color
color: color = "#ffffff", brightness: number (0..1) = 1
gradient-sweep
Gradient Sweep
colorA: color = "#ffffff", colorB: color = "#aaaaaa", colorC: color = "#555555", useThreeColors: boolean = false, angle: number (0..360) = 0, speed: number (0..3) = 0.5, loop: boolean = true
draw-frame
Draw Frame
frame: pixeldata = ""
flattened-animation
Flattened Animation
data: flatteneddata = ""
Effects
Generator ID
Name
Parameters (key: type = default)
rain
Rain
color: color = "#ffffff", speed: number (0.05..1) = 0.3, density: number (0.1..1) = 0.6, direction: select = "down", trailLength: number (1..20) = 4
twinkle
Twinkle / Stars
color: color = "#ffffff", density: number (0.01..1) = 0.3, speed: number (0.005..0.2) = 0.04, minBrightness: number (0..1) = 0.05, maxBrightness: number (0..1) = 1
fire
Fire
intensity: number (0.1..1) = 0.7, wind: number (-2..2) = 0, palette: select = "classic", cooling: number (0.1..1) = 0.4
matrix-rain
Matrix Rain
color: color = "#ffffff", speed: number (0.05..2) = 0.4, density: number (0.1..1) = 0.7, trailLength: number (2..32) = 8, glitch: number (0..0.5) = 0.05
pulse
Pulse / Ripple
color: color = "#ffffff", bgColor: color = "#000000", speed: number (0.1..5) = 1, rings: number (1..10) = 3, decay: number (0.05..1) = 0.5, originX: number (0..1) = 0.5, originY: number (0..1) = 0.5
bouncing-balls
Bouncing Balls
count: number (1..20) = 3, radius: number (0.5..8) = 1.5, speed: number (0.05..3) = 0.5, trailLength: number (0..30) = 5, rainbow: boolean = true, color: color = "#ffffff"
physics-balls
Physics Balls
count: number (1..15) = 4, radius: number (1..8) = 2, restitution: number (0.1..1) = 0.9, gravity: number (-2..3) = 0.5, trailLength: number (0..20) = 4
Procedural
Generator ID
Name
Parameters (key: type = default)
plasma
Plasma
speed: number (0.1..5) = 1, scale: number (0.2..5) = 1, palette: select = "rainbow", phase: number (0..6.28) = 0
perlin-flow
Perlin Flow
scale: number (0.2..6) = 1.5, speed: number (0.05..3) = 0.5, colorA: color = "#1a0533", colorB: color = "#ff6bff", contrast: number (0.1..3) = 1
waveform
Waveform
amplitude: number (0.05..1) = 0.7, frequency: number (0.1..10) = 2, speed: number (0.1..5) = 1, layers: number (1..5) = 2, palette: select = "viridis", direction: select = "horizontal", filled: boolean = true
heatmap
Heatmap / Cellular
palette: select = "hot", scale: number (0.2..8) = 2, speed: number (0.01..2) = 0.3, spread: number (0.1..4) = 1
Cellular
Generator ID
Name
Parameters (key: type = default)
game-of-life
Game of Life
density: number (0.05..0.8) = 0.35, speed: number (0.02..1) = 0.15, liveColor: color = "#ffffff", deadColor: color = "#000000", trailColor: color = "#666666", trailLength: number (0..20) = 4
Creative
Generator ID
Name
Parameters (key: type = default)
text
Text
text: textarea = "HELLO", color: color = "#ffffff", bg: color = "#000000", scroll: boolean = true, speed: number (0.1..6) = 0.5, size: number (0.3..2) = 0.75, bold: boolean = true
emoji
Emoji
emoji: textarea = "🔥", size: number (0.2..2) = 0.85, anim: select = "none", speed: number (0.1..5) = 1, bg: color = "#000000"
pixel-icon
Pixel Icon
icon: select = "heart", color: color = "#ffffff", size: number (0.1..2) = 0.8, anim: select = "none", speed: number (0.1..5) = 1, bg: color = "#000000"
keyframe-anim
Keyframe Anim
data: flatteneddata = ""
formula
Formula (tixy-style)
expression: textarea = "(t,i,x,y,a,b,c,d) => ...", a: number (-16..16) = 2, b: number (-16..16) = 8, c: number (-1..1) = 0, d: number (0..1) = 0.35, fgColor: color = "#ffffff", bgColor: color = "#000000", negColor: color = "#ff3333", scale: number (0.1..5) = 1