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

  // Scene setup
  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
  camera.position.set(0, 5, 15);
  camera.lookAt(0, 0, 0);

  const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
  renderer.setSize(width, height, false);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  renderer.shadowMap.enabled = true;

  // Fog for atmospheric depth
  scene.fog = new THREE.FogExp2(0x331100, 0.08);

  // Lights
  const ambientLight = new THREE.AmbientLight(0xff6600, 0.5);
  scene.add(ambientLight);

  const directionalLight = new THREE.DirectionalLight(0xff4400, 1);
  directionalLight.position.set(5, 10, 7);
  directionalLight.castShadow = true;
  directionalLight.shadow.mapSize.width = 2048;
  directionalLight.shadow.mapSize.height = 2048;
  scene.add(directionalLight);

  // Sky gradient (smoke-choked)
  const skyGeometry = new THREE.SphereGeometry(50, 32, 32);
  const skyMaterial = new THREE.ShaderMaterial({
    uniforms: {
      time: { value: 0 },
      color1: { value: new THREE.Color(0x111111) },
      color2: { value: new THREE.Color(0x442200) },
      color3: { value: new THREE.Color(0xff4400) }
    },
    vertexShader: `
      varying vec3 vPosition;
      void main() {
        vPosition = position;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `,
    fragmentShader: `
      uniform float time;
      uniform vec3 color1;
      uniform vec3 color2;
      uniform vec3 color3;
      varying vec3 vPosition;
      void main() {
        float h = normalize(vPosition).y;
        vec3 color = mix(color1, color2, h * 0.5 + 0.5);
        color = mix(color, color3, pow(1.0 - h, 2.0) * 0.3);
        // Add flickering embers effect
        float noise = sin(vPosition.x * 10.0 + time * 2.0) * sin(vPosition.z * 8.0 + time * 1.5);
        color += vec3(1.0, 0.5, 0.1) * noise * 0.1;
        gl_FragColor = vec4(color, 1.0);
      }
    `,
    side: THREE.BackSide
  });
  const sky = new THREE.Mesh(skyGeometry, skyMaterial);
  scene.add(sky);

  // Ground (burning city base)
  const groundGeometry = new THREE.PlaneGeometry(100, 100);
  const groundMaterial = new THREE.MeshStandardMaterial({
    color: 0x221100,
    roughness: 1,
    metalness: 0
  });
  const ground = new THREE.Mesh(groundGeometry, groundMaterial);
  ground.rotation.x = -Math.PI / 2;
  ground.receiveShadow = true;
  scene.add(ground);

  // Create burning buildings
  const buildings = [];
  const buildingCount = 15;
  const buildingGeometry = new THREE.BoxGeometry(1, 1, 1);

  for (let i = 0; i < buildingCount; i++) {
    const height = 2 + Math.random() * 8;
    const buildingMaterial = new THREE.MeshStandardMaterial({
      color: 0x333333,
      roughness: 0.7,
      metalness: 0.1
    });

    const building = new THREE.Mesh(
      new THREE.BoxGeometry(0.5 + Math.random() * 0.5, height, 0.5 + Math.random() * 0.5),
      buildingMaterial
    );

    building.position.x = (Math.random() - 0.5) * 20;
    building.position.z = (Math.random() - 0.5) * 20;
    building.position.y = height / 2;
    building.castShadow = true;
    building.receiveShadow = true;
    scene.add(building);
    buildings.push({
      mesh: building,
      height: height,
      fireIntensity: Math.random() * 0.5 + 0.2,
      fireOffset: Math.random() * Math.PI * 2
    });
  }

  // Fire particles
  const fireParticles = [];
  const particleCount = 200;
  const particleGeometry = new THREE.BufferGeometry();
  const particleMaterial = new THREE.PointsMaterial({
    color: 0xff4400,
    size: 0.1,
    transparent: true,
    opacity: 0.8,
    blending: THREE.AdditiveBlending
  });

  const positions = new Float32Array(particleCount * 3);
  const colors = new Float32Array(particleCount * 3);
  const sizes = new Float32Array(particleCount);

  for (let i = 0; i < particleCount; i++) {
    positions[i * 3] = (Math.random() - 0.5) * 40;
    positions[i * 3 + 1] = Math.random() * 10;
    positions[i * 3 + 2] = (Math.random() - 0.5) * 40;

    colors[i * 3] = 0.8 + Math.random() * 0.2;
    colors[i * 3 + 1] = 0.3 + Math.random() * 0.2;
    colors[i * 3 + 2] = 0.1;

    sizes[i] = 0.05 + Math.random() * 0.15;
  }

  particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  particleGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
  particleGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));

  const particles = new THREE.Points(particleGeometry, particleMaterial);
  scene.add(particles);

  // Explosions in distance
  const explosionGroup = new THREE.Group();
  scene.add(explosionGroup);

  // Heat distortion effect (post-processing would be better, but we'll simulate with a plane)
  const distortionPlaneGeometry = new THREE.PlaneGeometry(40, 20);
  const distortionPlaneMaterial = new THREE.ShaderMaterial({
    uniforms: {
      time: { value: 0 },
      distortionScale: { value: 0.5 }
    },
    vertexShader: `
      uniform float time;
      uniform float distortionScale;
      varying vec2 vUv;
      void main() {
        vUv = uv;
        vec3 pos = position;
        float distortion = sin(pos.x * 2.0 + time) * cos(pos.y * 2.0 + time * 0.7) * distortionScale;
        pos.z += distortion;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
      }
    `,
    fragmentShader: `
      uniform float time;
      varying vec2 vUv;
      void main() {
        float alpha = 0.1 + sin(vUv.x * 10.0 + time) * sin(vUv.y * 10.0 + time * 0.5) * 0.05;
        gl_FragColor = vec4(1.0, 0.3, 0.1, alpha);
      }
    `,
    transparent: true,
    side: THREE.DoubleSide
  });
  const distortionPlane = new THREE.Mesh(distortionPlaneGeometry, distortionPlaneMaterial);
  distortionPlane.position.set(0, 5, -5);
  distortionPlane.rotation.x = Math.PI / 4;
  scene.add(distortionPlane);

  // Animation
  let time = 0;
  const explosions = [];

  startFrame((dt) => {
    time += dt * 0.001;

    // Update sky
    skyMaterial.uniforms.time.value = time;

    // Update buildings with fire
    buildings.forEach((building, i) => {
      // Sway buildings as if they're about to collapse
      building.mesh.rotation.x = Math.sin(time * 2 + i) * 0.02;
      building.mesh.rotation.z = Math.cos(time * 1.5 + i) * 0.02;

      // Pulsing fire effect on buildings
      building.mesh.material.emissive.setHex(0xff4400);
      building.mesh.material.emissiveIntensity = Math.sin(time * 3 + building.fireOffset) * 0.3 + 0.2;
    });

    // Update fire particles
    const positions = particleGeometry.attributes.position.array;
    const colors = particleGeometry.attributes.color.array;
    for (let i = 0; i < particleCount; i++) {
      const i3 = i * 3;
      // Move particles upward with some randomness
      positions[i3 + 1] += (0.01 + Math.random() * 0.02) * dt * 0.06;

      // Reset particles that go too high
      if (positions[i3 + 1] > 15) {
        positions[i3] = (Math.random() - 0.5) * 40;
        positions[i3 + 1] = 0;
        positions[i3 + 2] = (Math.random() - 0.5) * 40;
        colors[i3] = 0.8 + Math.random() * 0.2;
        colors[i3 + 1] = 0.3 + Math.random() * 0.2;
        colors[i3 + 2] = 0.1;
      }

      // Fade out particles as they rise
      const life = positions[i3 + 1] / 15;
      colors[i3] = 0.8 * (1 - life * 0.7);
      colors[i3 + 1] = 0.3 * (1 - life * 0.5);
      colors[i3 + 2] = 0.1 * (1 - life * 0.3);
    }
    particleGeometry.attributes.position.needsUpdate = true;
    particleGeometry.attributes.color.needsUpdate = true;

    // Random explosions
    if (Math.random() < 0.02) {
      const explosion = {
        position: new THREE.Vector3(
          (Math.random() - 0.5) * 40,
          Math.random() * 5,
          (Math.random() - 0.5) * 40
        ),
        size: 0.1,
        maxSize: 0.5 + Math.random() * 0.5,
        life: 0,
        maxLife: 0.5 + Math.random() * 0.5,
        color: new THREE.Color().setHSL(0.05 + Math.random() * 0.1, 0.9, 0.5)
      };
      explosions.push(explosion);
    }

    // Update explosions
    explosionGroup.children = [];
    explosions.forEach((explosion, i) => {
      explosion.life += dt * 0.001;
      explosion.size = explosion.maxSize * (explosion.life / explosion.maxLife);

      if (explosion.life > explosion.maxLife) {
        explosions.splice(i, 1);
        return;
      }

      const explosionGeometry = new THREE.SphereGeometry(explosion.size, 16, 16);
      const explosionMaterial = new THREE.MeshBasicMaterial({
        color: explosion.color,
        transparent: true,
        opacity: 1 - (explosion.life / explosion.maxLife)
      });
      const explosionMesh = new THREE.Mesh(explosionGeometry, explosionMaterial);
      explosionMesh.position.copy(explosion.position);
      explosionGroup.add(explosionMesh);
    });

    // Update heat distortion
    distortionPlaneMaterial.uniforms.time.value = time;
    distortionPlaneMaterial.uniforms.distortionScale.value = 0.3 + Math.sin(time * 0.5) * 0.2;

    // Camera movement
    camera.position.x = Math.sin(time * 0.3) * 2;
    camera.position.y = 5 + Math.sin(time * 0.2) * 0.5;
    camera.position.z = 15 + Math.cos(time * 0.3) * 2;
    camera.lookAt(0, 2, 0);

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

  return () => {
    // Cleanup
    renderer.dispose();
    scene.traverse((obj) => {
      if (obj.isMesh) {
        obj.geometry.dispose();
        if (obj.material instanceof Array) {
          obj.material.forEach(material => material.dispose());
        } else {
          obj.material.dispose();
        }
      }
    });
  };
};
