Skip to content

Getting Started

Open the editor to try everything on this page live.

Mental Model

The engine executes your entry file every animation frame (~60 FPS):

  1. Injects time, dt, state, w, h
  2. Runs your code
  3. Reads the object you return
  4. Draws those primitives on a 2D canvas

Input → Code → Output Primitives → Canvas

You never touch the canvas context. Describe what to draw; the engine renders it.

App Tour

AreaPurpose
File explorerMulti-file projects; main.js is the entry (badge). New files are modules.
Monaco editorEdit code. Recompiles ~400ms after you stop typing — clears state and time.
CanvasLive render with grid and centered axes for reference.
Pause / PlayFreeze or resume the frame loop.
RestartClear state and time; keep your current code.
ResetRestore the starter main.js project.
ProjectsSave and load through Supabase-backed accounts.
DocsThis documentation (served at /docs).

30-Second Quick Start

Open the editor, then paste this into main.js:

javascript
// 1. State setup — run once
if (!state.init) {
  state.x = w * 0.3;
  state.y = h * 0.3;
  state.vx = 120;
  state.vy = 0;
  state.init = true;
}

// 2. Physics — Euler integration
state.vy += 200 * dt;        // gravity (px/s²)
state.x += state.vx * dt;
state.y += state.vy * dt;

// Floor bounce
if (state.y > h - 20) {
  state.y = h - 20;
  state.vy *= -0.8;
}

// Wall bounce
if (state.x > w - 20 || state.x < 20) {
  state.vx *= -1;
}

// 3. Render
return {
  particles: [
    { x: state.x, y: state.y, r: 14, color: '#8B5CF6' },
  ],
  text: {
    time: time.toFixed(1) + 's',
    velocity: Math.sqrt(state.vx ** 2 + state.vy ** 2).toFixed(0),
  },
};

You should see a purple ball bouncing under gravity with a live velocity readout.

What Just Happened

StepWhatWhy
if (!state.init)Guard clauseSetup runs once; values persist on state
state.vy += 200 * dtGravityAcceleration in px/s², scaled by frame time
state.y > h - 20Floor collisionStop 20px above the bottom
vy *= -0.8BounceCoefficient of restitution (0 = stop, 1 = elastic)
return { particles }OutputEngine draws whatever you return

Injected Globals

VariableTypeDescription
timenumberSeconds since simulation start (cumulative)
dtnumberSeconds since last frame (max 0.05)
stateobjectPersistent storage across frames
wnumberCanvas width in CSS pixels
hnumberCanvas height in CSS pixels

TIP

dt is clamped to 50ms. After a long tab blur, the sim advances at most one capped step — it won't jump by seconds.

WARNING

Editing code recompiles after ~400ms and resets state and time, same as Restart. Only Pause freezes without clearing.

Output Primitives

javascript
return {
  particles: [{ x, y, r, color, label }],
  lines:     [{ x1, y1, x2, y2, color, dashed, width }],
  vectors:   [{ x, y, ox, oy, color, label }],
  circles:   [{ cx, cy, r, color, fill }],
  arcs:      [{ cx, cy, r, startAngle, endAngle, color, width }],
  bars:      [{ x, y, w, h, color, label }],
  text:      { label: value },
}

All properties are optional. Return only what you need.

See Output Schema for defaults and draw order.

State Persistence

Anything on state survives until Restart, Reset, or a code recompile:

javascript
if (!state.init) {
  state.particles = [];
  state.count = 0;
  state.init = true;
}

state.count++;

WARNING

let x = 100 resets every frame. Use state.x for values that must accumulate.

Click Interaction

Clicks write canvas coordinates onto state. Always consume them after handling:

javascript
if (state.clickX !== undefined) {
  state.particles.push({
    x: state.clickX,
    y: state.clickY,
    vx: (Math.random() - 0.5) * 100,
    vy: -100,
  });
  state.clickX = undefined;
  state.clickY = undefined;
}

See Mouse & Click Tracking.

Coordinates

Origin is the top-left; Y increases downward. The grid axes are visual guides centered at (w/2, h/2) — sketch coordinates are still raw canvas pixels. Prefer:

javascript
const cx = w / 2, cy = h / 2;

Details: Coordinate System.

Next Steps