window.sketch = (runtime) => {
  const { THREE, canvas, startFrame, width, height } = runtime;

  // Scene, camera, renderer
  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x1a0500);
  scene.fog = new THREE.FogExp2(0x1a0500, 0.0035);

  const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 200);
  camera.position.set(18, 12, 28);
  camera.lookAt(0, 2, 0);

  const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
  renderer.setSize(width, height, false);
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 1.2;

  // Lights
  const ambient = new THREE.AmbientLight(0x221100, 0.4);
  scene.add(ambient);

  const sunLight = new THREE.DirectionalLight(0xff8833, 2.0);
  sunLight.position.set(-15, 25, 10);
  sunLight.castShadow = true;
  sunLight.shadow.mapSize.width = 512;
  sunLight.shadow.mapSize.height = 512;
  const d = 30;
  sunLight.shadow.camera.left = -d;
  sunLight.shadow.camera.right = d;
  sunLight.shadow.camera.top = d;
  sunLight.shadow.camera.bottom = -d;
  sunLight.shadow.camera.near = 1;
  sunLight.shadow.camera.far = 50;
  scene.add(sunLight);

  const redLight = new THREE.PointLight(0xff3300, 3, 40);
  redLight.position.set(0, 8, 0);
  scene.add(redLight);

  const flickerLight = new THREE.PointLight(0xff6600, 2, 30);
  flickerLight.position.set(-6, 4, 8);
  scene.add(flickerLight);

  // Ground plane (scorched)
  const groundGeo = new THREE.PlaneGeometry(60, 60);
  const groundMat = new THREE.MeshStandardMaterial({
    color: 0x2a1200,
    roughness: 0.9,
    emissive: 0x1a0800,
    emissiveIntensity: 0.6,
  });
  const ground = new THREE.Mesh(groundGeo, groundMat);
  ground.rotation.x = -Math.PI / 2;
  ground.position.y = -0.1;
  ground.receiveShadow = true;
  scene.add(ground);

  // --- Buildings (cityscape) ---
  const buildingColors = [
    0x332211, 0x443322, 0x2a1a0a, 0x1e140e, 0x3a2a1a, 0x2c1c0c, 0x1f1610,
  ];
  const buildings = [];

  function createBuilding(x, z, w, h, d) {
    const color = buildingColors[Math.floor(Math.random() * buildingColors.length)];
    const mat = new THREE.MeshStandardMaterial({
      color: color,
      roughness: 0.8,
      emissive: new THREE.Color(0x441100),
      emissiveIntensity: 0.15 + Math.random() * 0.3,
    });
    const geo = new THREE.BoxGeometry(w, h, d);
    const mesh = new THREE.Mesh(geo, mat);
    mesh.position.set(x, h / 2, z);
    mesh.castShadow = true;
    mesh.receiveShadow = true;
    scene.add(mesh);

    // Random window glow (small emissive boxes)
    const windowMat = new THREE.MeshStandardMaterial({
      color: 0xffaa33,
      emissive: 0xff5500,
      emissiveIntensity: 0.3 + Math.random() * 0.5,
    });
    const winCount = Math.floor(Math.random() * 4) + 2;
    for (let i = 0; i < winCount; i++) {
      const wx = (Math.random() - 0.5) * w * 0.6;
      const wz = (Math.random() - 0.5) * d * 0.6;
      const wy = (Math.random() - 0.4) * h * 0.5;
      const win = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 0.2), windowMat);
      win.position.set(x + wx, h / 2 + wy, z + wz);
      scene.add(win);
    }
    buildings.push(mesh);
  }

  // Generate city grid
  const spacing = 2.4;
  const offset = 9;
  for (let i = -offset; i <= offset; i += spacing) {
    for (let j = -offset; j <= offset; j += spacing) {
      if (Math.random() < 0.65) {
        const w = 0.6 + Math.random() * 0.8;
        const h = 1.2 + Math.random() * 4.5;
        const d = 0.6 + Math.random() * 0.8;
        // Add some variation to positions
        const x = i + (Math.random() - 0.5) * 0.4;
        const z = j + (Math.random() - 0.5) * 0.4;
        createBuilding(x, z, w, h, d);
      }
    }
  }

  // --- Fire particles (ember storm) ---
  const particleCount = 1800;
  const particleGeo = new THREE.BufferGeometry();
  const positions = new Float32Array(particleCount * 3);
  const sizes = new Float32Array(particleCount);
  const speeds = new Float32Array(particleCount);
  for (let i = 0; i < particleCount; i++) {
    const radius = 6 + Math.random() * 16;
    const angle = Math.random() * Math.PI * 2;
    const x = Math.cos(angle) * radius;
    const z = Math.sin(angle) * radius;
    const y = Math.random() * 12 + 1;
    positions[i * 3] = x;
    positions[i * 3 + 1] = y;
    positions[i * 3 + 2] = z;
    sizes[i] = 0.08 + Math.random() * 0.4;
    speeds[i] = 0.3 + Math.random() * 0.6;
  }
  particleGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  particleGeo.setAttribute('size', new THREE.BufferAttribute(sizes, 1));

  const particleMat = new THREE.PointsMaterial({
    color: 0xff6633,
    emissive: 0xff3300,
    size: 0.5,
    transparent: true,
    opacity: 0.7,
    blending: THREE.AdditiveBlending,
    depthWrite: false,
  });
  const particles = new THREE.Points(particleGeo, particleMat);
  scene.add(particles);

  // --- Large fire columns / torches (pyroclastic) ---
  const fireMat = new THREE.MeshStandardMaterial({
    color: 0xff4400,
    emissive: 0xff2200,
    emissiveIntensity: 2.5,
    transparent: true,
    opacity: 0.4,
    blending: THREE.AdditiveBlending,
    depthWrite: false,
  });
  const fireMeshes = [];
  for (let i = 0; i < 14; i++) {
    const angle = Math.random() * Math.PI * 2;
    const radius = 2 + Math.random() * 10;
    const x = Math.cos(angle) * radius;
    const z = Math.sin(angle) * radius;
    const h = 2.5 + Math.random() * 5;
    const geo = new THREE.ConeGeometry(0.6 + Math.random() * 1.2, h, 6);
    const mesh = new THREE.Mesh(geo, fireMat.clone());
    mesh.position.set(x, h / 2, z);
    scene.add(mesh);
    fireMeshes.push(mesh);
  }

  // --- Brimstone / glowing debris on ground ---
  const debrisMat = new THREE.MeshStandardMaterial({
    color: 0xaa3300,
    emissive: 0x662200,
    emissiveIntensity: 0.8,
  });
  for (let i = 0; i < 60; i++) {
    const radius = 2 + Math.random() * 12;
    const angle = Math.random() * Math.PI * 2;
    const x = Math.cos(angle) * radius;
    const z = Math.sin(angle) * radius;
    const size = 0.1 + Math.random() * 0.4;
    const geo = new THREE.SphereGeometry(size, 3);
    const mesh = new THREE.Mesh(geo, debrisMat);
    mesh.position.set(x, 0.05, z);
    scene.add(mesh);
  }

  // --- Ember streaks (lines) ---
  const streakMat = new THREE.LineBasicMaterial({
    color: 0xff5500,
    transparent: true,
    opacity: 0.15,
  });
  for (let i = 0; i < 40; i++) {
    const radius = 3 + Math.random() * 14;
    const angle = Math.random() * Math.PI * 2;
    const x = Math.cos(angle) * radius;
    const z = Math.sin(angle) * radius;
    const y = 0.5 + Math.random() * 10;
    const len = 0.5 + Math.random() * 1.5;
    const points = [];
    const dx = (Math.random() - 0.5) * 2;
    const dz = (Math.random() - 0.5) * 2;
    points.push(new THREE.Vector3(x, y, z));
    points.push(new THREE.Vector3(x + dx, y + 1.5, z + dz));
    const geo = new THREE.BufferGeometry().setFromPoints(points);
    const line = new THREE.Line(geo, streakMat);
    scene.add(line);
  }

  // Animation variables
  let time = 0;
  const originalPositions = particles.geometry.attributes.position.array.slice();

  // Cleanup
  const cleanup = () => {
    scene.traverse((obj) => {
      if (obj.isMesh) {
        obj.geometry?.dispose();
        if (Array.isArray(obj.material)) obj.material.forEach(m => m.dispose());
        else obj.material?.dispose();
      }
      if (obj.isPoints) {
        obj.geometry?.dispose();
        obj.material?.dispose();
      }
    });
  };

  // Main render loop
  startFrame((delta, frameCount) => {
    time += delta * 0.15;

    // Flicker lights
    const flickerIntensity = 1.5 + Math.sin(time * 8) * 0.8 + Math.sin(time * 13) * 0.4;
    flickerLight.intensity = Math.max(0.5, flickerIntensity);
    redLight.intensity = 2.5 + Math.sin(time * 5) * 1.2 + Math.sin(time * 11) * 0.6;

    // Animate particles (ember storm rotation + vertical drift)
    const pos = particles.geometry.attributes.position.array;
    for (let i = 0; i < particleCount; i++) {
      const i3 = i * 3;
      const angleOffset = Math.atan2(originalPositions[i3 + 2], originalPositions[i3]);
      const radius = Math.sqrt(originalPositions[i3] ** 2 + originalPositions[i3 + 2] ** 2);
      const newAngle = angleOffset + time * 0.03 * (0.5 + (radius / 20) * 0.8);
      pos[i3] = Math.cos(newAngle) * radius;
      pos[i3 + 2] = Math.sin(newAngle) * radius;
      // Vertical oscillation + upward drift
      const baseY = originalPositions[i3 + 1];
      pos[i3 + 1] = baseY + Math.sin(time * 1.7 + i * 0.1) * 1.2 + (time * 0.02) % 3;
      if (pos[i3 + 1] > 14) pos[i3 + 1] = 1;
    }
    particles.geometry.attributes.position.needsUpdate = true;

    // Animate fire column sizes (pulsing)
    fireMeshes.forEach((mesh, idx) => {
      const scale = 0.8 + Math.sin(time * 2.5 + idx * 1.2) * 0.2;
      mesh.scale.set(scale, 1 + Math.sin(time * 3 + idx) * 0.1, scale);
      mesh.material.emissiveIntensity = 1.8 + Math.sin(time * 4 + idx * 0.9) * 0.8;
    });

    // Slight camera sway
    camera.position.x = 18 + Math.sin(time * 0.08) * 2;
    camera.position.z = 28 + Math.cos(time * 0.07) * 2;
    camera.lookAt(0, 3, 0);

    renderer.render(scene, camera);
  });

  return cleanup;
};
