window.sketch = (runtime) => {
  const { c2, canvas, startFrame } = runtime;
  const renderer = new c2.Renderer(canvas);

  // ----- utility helpers -----
  const rand = (min, max) => Math.random() * (max - min) + min;
  const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
  const easeInOut = (t) => t * t * (3 - 2 * t);

  // ----- pedestrian prototype -----
  function createPedestrian(startX, startY, speed, layer) {
    const headRadius = rand(3, 5);
    const bodyHeight = rand(10, 16);
    const shoulderWidth = rand(6, 10);
    return {
      x: startX,
      y: startY,
      speed: speed,
      layer: layer,            // 0 = far (small), 1 = mid, 2 = near (large)
      headRadius: headRadius,
      bodyHeight: bodyHeight,
      shoulderWidth: shoulderWidth,
      phase: rand(0, Math.PI * 2),
      strideCycle: rand(0.8, 1.4),
      armSwing: rand(0.4, 0.7),
      hue: Math.floor(rand(20, 50)),   // warm skin & clothing hues
      clothingSat: rand(30, 80),
      clothingLight: rand(40, 70),
    };
  }

  // ----- spawn initial crowd -----
  const pedestrians = [];
  const CROWD_COUNT = 80;
  for (let i = 0; i < CROWD_COUNT; i++) {
    // layer: 0 = back, 1 = middle, 2 = front (larger)
    const layer = Math.floor(rand(0, 3));
    const scale = 0.5 + layer * 0.35;  // 0.5, 0.85, 1.2
    const baseY = canvas.height * 0.6 + layer * canvas.height * 0.08;
    const startX = rand(-canvas.width * 0.2, canvas.width * 1.2);
    const startY = baseY + rand(-8, 8);
    const speed = (0.4 + layer * 0.25) * (0.8 + rand(0, 0.4));
    const p = createPedestrian(startX, startY, speed, layer);
    p.y = startY;
    p.baseY = baseY;
    p.scale = scale;
    // randomise some visual traits per individual
    p.hue = Math.floor(rand(15, 45));
    p.clothingSat = Math.floor(rand(25, 75));
    p.clothingLight = Math.floor(rand(35, 70));
    p.headRadius *= scale;
    p.bodyHeight *= scale;
    p.shoulderWidth *= scale;
    p.armSwing *= scale;
    pedestrians.push(p);
  }

  // ----- draw one pedestrian (simple figurative stick + shape) -----
  function drawPedestrian(p, frameCount) {
    const { x, y, headRadius, bodyHeight, shoulderWidth, phase, strideCycle, armSwing, scale, layer } = p;

    // skip if too far off screen (culling)
    if (x < -canvas.width * 0.25 || x > canvas.width * 1.25) return;

    // walking bob & stride
    const step = Math.sin(frameCount * 0.06 * strideCycle + phase);
    const bob = Math.abs(step) * 2.5 * scale;
    const bodyY = y - bob;

    // leg swing
    const legAngle = step * 0.5;

    // arm swing (opposite phase)
    const armAngle = -step * 0.45 * armSwing / scale;

    // --- SKIN TONE ---
    const skin = `hsl(30, 55%, ${50 + 10 * Math.sin(phase + 1)}%)`;
    // --- CLOTHING colour (varied) ---
    const cloth = `hsl(${p.hue + 10 * Math.sin(frameCount * 0.01 + phase)}, ${p.clothingSat}%, ${p.clothingLight}%)`;

    // ---- draw legs (two lines) ----
    renderer.stroke(skin);
    renderer.lineWidth(1.8 * scale);
    const legLen = bodyHeight * 0.7;
    renderer.line(x - shoulderWidth * 0.3, bodyY + bodyHeight * 0.2,
                  x - shoulderWidth * 0.3 - Math.sin(legAngle) * legLen * 0.5,
                  bodyY + bodyHeight * 0.2 + Math.cos(legAngle) * legLen);
    renderer.line(x + shoulderWidth * 0.3, bodyY + bodyHeight * 0.2,
                  x + shoulderWidth * 0.3 - Math.sin(-legAngle) * legLen * 0.5,
                  bodyY + bodyHeight * 0.2 + Math.cos(-legAngle) * legLen);

    // ---- torso (rectangle as jacket / shirt) ----
    renderer.fill(cloth);
    renderer.stroke(false);
    const torsoW = shoulderWidth * 0.7;
    const torsoH = bodyHeight * 0.7;
    renderer.rect(x - torsoW / 2, bodyY - bodyHeight * 0.2, torsoW, torsoH);

    // ---- arms (two lines with swing) ----
    renderer.stroke(skin);
    renderer.lineWidth(1.4 * scale);
    const armLen = bodyHeight * 0.6;
    // left arm
    renderer.line(x - shoulderWidth * 0.45, bodyY - bodyHeight * 0.15,
                  x - shoulderWidth * 0.45 - Math.sin(armAngle) * armLen * 0.6,
                  bodyY - bodyHeight * 0.15 + Math.cos(armAngle) * armLen);
    // right arm
    renderer.line(x + shoulderWidth * 0.45, bodyY - bodyHeight * 0.15,
                  x + shoulderWidth * 0.45 - Math.sin(-armAngle) * armLen * 0.6,
                  bodyY - bodyHeight * 0.15 + Math.cos(-armAngle) * armLen);

    // ---- head (circle) ----
    renderer.fill(skin);
    renderer.stroke(false);
    renderer.circle(x, bodyY - bodyHeight * 0.45 - headRadius * 0.3, headRadius);

    // ---- hair (small dark cap / hair patch) ----
    renderer.fill('#1f1a17');
    renderer.circle(x, bodyY - bodyHeight * 0.45 - headRadius * 0.55, headRadius * 0.75);
    // eyes (tiny dots)
    renderer.fill('#111');
    renderer.circle(x - headRadius * 0.3, bodyY - bodyHeight * 0.45 - headRadius * 0.2, headRadius * 0.15);
    renderer.circle(x + headRadius * 0.3, bodyY - bodyHeight * 0.45 - headRadius * 0.2, headRadius * 0.15);
  }

  // ----- background : street & buildings -----
  function drawBackground(frameCount) {
    // sky gradient (warm dusk)
    renderer.clear('#3a3f5c');

    // buildings (far silhouette)
    renderer.fill('#2d2f45');
    renderer.stroke(false);
    const bCount = 14;
    for (let i = 0; i < bCount; i++) {
      const bx = (i / bCount) * canvas.width + Math.sin(i * 2.3 + frameCount * 0.001) * 15;
      const bw = canvas.width / bCount * 0.85;
      const bh = canvas.height * 0.35 + Math.sin(i * 3.1 + 0.5) * canvas.height * 0.08;
      renderer.rect(bx, canvas.height * 0.1, bw, canvas.height * 0.7);
    }
    // windows (tiny lit rectangles)
    renderer.fill('#f7d89c');
    for (let i = 0; i < bCount; i++) {
      for (let j = 0; j < 6; j++) {
        const wx = (i / bCount) * canvas.width + 8 + j * 16;
        const wy = canvas.height * 0.2 + j * 22 + Math.sin(i * 0.7 + j) * 8;
        renderer.rect(wx, wy, 7, 9);
      }
    }

    // street (dark ground with perspective)
    renderer.fill('#2b2a2e');
    renderer.stroke(false);
    renderer.rect(0, canvas.height * 0.65, canvas.width, canvas.height * 0.35);

    // road markings (dashed line)
    renderer.stroke('#f0d48b');
    renderer.lineWidth(3);
    for (let i = 0; i < 20; i++) {
      const lx = (i * 40 + (frameCount * 2) % 40) % canvas.width;
      renderer.line(lx, canvas.height * 0.73, lx + 20, canvas.height * 0.73);
    }

    // ambient light glow (warm)
    renderer.fill('#ffd18e');
    renderer.alpha(0.04);
    renderer.circle(canvas.width * 0.3, canvas.height * 0.15, canvas.width * 0.5);
    renderer.alpha(1);
  }

  // ----- animation loop -----
  startFrame((frameCount) => {
    // 1. background
    drawBackground(frameCount);

    // 2. sort pedestrians by layer (far drawn first)
    const sorted = [...pedestrians].sort((a, b) => a.layer - b.layer);

    // 3. draw each pedestrian & update position (move left → right)
    for (const p of sorted) {
      drawPedestrian(p, frameCount);

      // move right
      p.x += p.speed * 0.9;

      // wrap around: if off right edge, respawn left
      if (p.x > canvas.width * 1.3) {
        p.x = -canvas.width * 0.3;
        p.y = p.baseY + rand(-6, 6);
        // recycle visual traits slightly
        p.hue = Math.floor(rand(15, 45));
        p.clothingSat = Math.floor(rand(25, 75));
        p.clothingLight = Math.floor(rand(35, 70));
      }
    }
  });
};
