Models 44

MiniMax M3.1 Flash

by MiniMaxReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
2 min 36 s
Cost
$0.017
Output tokens
28,795
Spent thinking
18,512 tokens
Speed
184.8 tok/s
Console errors
0
First token after
611 ms
First visible text after
2 min 6 s
Prompt tokens
730
Code size
34.3 KB
Finish reason
stop
Served by
DigitalOcean
Model id
minimax/minimax-m3.1-flash
Reasoning sent
{"effort":"xhigh"}
Max output
64,000 tokens
Generated
Nov 11, 2026

What it was asked

The prompt
Build a low-poly 3D scene of a campfire in a forest clearing at night, with Three.js in a single HTML file. Load Three.js only through this import map (do not use any other URL or version): <script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}</script> Requirements: - A ground with a few low-poly rocks and logs around a campfire in the center. - At least 30 low-poly trees placed randomly around the clearing (not on the fire). - The campfire has animated flames and a warm point light that flickers and casts shadows. - Sparks rising from the fire as a simple particle system. - A dark night sky with stars and a moon, plus fog that fades the distant trees. - OrbitControls to look around, with limits so the camera stays above the ground. - The renderer fills the window and handles resizing correctly.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 948 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Emberlight — Night Forest Clearing</title>
  <style>
    * { box-sizing: border-box; }
    html, body {
      width: 100%;
      height: 100%;
      margin: 0;
      overflow: hidden;
      background: #05080d;
    }
    body {
      color: #eef3f5;
      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }
    canvas {
      display: block;
      width: 100%;
      height: 100%;
      touch-action: none;
    }
    .vignette {
      position: fixed;
      inset: 0;
      pointer-events: none;
      background:
        radial-gradient(ellipse at 50% 48%, transparent 34%, rgba(1, 4, 9, .13) 66%, rgba(1, 3, 7, .62) 100%),
        linear-gradient(to bottom, rgba(2, 5, 12, .28), transparent 22%, transparent 76%, rgba(2, 5, 10, .42));
    }
    .caption {
      position: fixed;
      top: 28px;
      left: 30px;
      pointer-events: none;
      user-select: none;
      text-shadow: 0 2px 14px rgba(0, 0, 0, .75);
    }
    .kicker {
      display: flex;
      align-items: center;
      gap: 9px;
      margin-bottom: 7px;
      color: #c4d2da;
      font-size: 9px;
      font-weight: 650;
      letter-spacing: .28em;
      text-transform: uppercase;
    }
    .kicker::before {
      content: "";
      width: 5px;
      height: 5px;
      border-radius: 50%;
      background: #ff9b4a;
      box-shadow: 0 0 10px #ff6b24;
    }
    .title {
      font-family: Georgia, "Times New Roman", serif;
      font-size: clamp(21px, 2.4vw, 31px);
      font-weight: 400;
      letter-spacing: .055em;
      line-height: 1;
    }
    .subtitle {
      margin-top: 7px;
      color: rgba(211, 222, 228, .62);
      font-size: 10px;
      letter-spacing: .13em;
      text-transform: uppercase;
    }
    .hint {
      position: fixed;
      right: 26px;
      bottom: 23px;
      padding: 9px 13px;
      border: 1px solid rgba(207, 222, 232, .13);
      border-radius: 999px;
      background: rgba(5, 11, 17, .34);
      color: rgba(224, 235, 241, .72);
      font-size: 10px;
      letter-spacing: .08em;
      pointer-events: none;
      user-select: none;
      backdrop-filter: blur(8px);
    }
    .hint span { color: #f3f6f7; }
    .hint i {
      margin: 0 7px;
      color: #e78a48;
      font-style: normal;
    }
    @media (max-width: 600px) {
      .caption { top: 19px; left: 19px; }
      .subtitle { font-size: 8px; }
      .hint {
        right: 50%;
        bottom: 16px;
        transform: translateX(50%);
        white-space: nowrap;
        font-size: 9px;
      }
    }
  </style>

  <script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}</script>
</head>
<body>
  <div class="vignette"></div>
  <div class="caption">
    <div class="kicker">Nightfall</div>
    <div class="title">Emberlight</div>
    <div class="subtitle">Forest clearing · after midnight</div>
  </div>
  <div class="hint"><span>Drag</span> to orbit <i>•</i> <span>Scroll</span> to zoom</div>

  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

    // ------------------------------------------------------------
    // Renderer, camera, and controls
    // ------------------------------------------------------------
    const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: 'high-performance' });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.8));
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.15;
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    renderer.domElement.setAttribute('aria-label', 'Low-poly campfire in a moonlit forest clearing');
    document.body.prepend(renderer.domElement);

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x050a12);
    scene.fog = new THREE.FogExp2(0x08131d, 0.024);

    const narrow = window.innerWidth < 600;
    const camera = new THREE.PerspectiveCamera(narrow ? 60 : 52, window.innerWidth / window.innerHeight, 0.1, 150);
    if (narrow) camera.position.set(18, 12, 22);
    else camera.position.set(12, 7, 15);

    const controls = new OrbitControls(camera, renderer.domElement);
    controls.target.set(0, 1.25, 0);
    controls.enableDamping = true;
    controls.dampingFactor = 0.055;
    controls.enablePan = false;
    controls.minDistance = 7.5;
    controls.maxDistance = 38;
    controls.minPolarAngle = 0.18;
    controls.maxPolarAngle = Math.PI / 2 - 0.075;
    controls.rotateSpeed = 0.55;
    controls.zoomSpeed = 0.7;
    controls.update();

    // Deterministic randomness keeps the composition reproducible.
    let seed = 738491;
    function random() {
      seed = (seed * 1664525 + 1013904223) >>> 0;
      return seed / 4294967296;
    }
    const range = (a, b) => a + (b - a) * random();

    // ------------------------------------------------------------
    // Sky dome and moonlight
    // ------------------------------------------------------------
    const skyMaterial = new THREE.ShaderMaterial({
      side: THREE.BackSide,
      depthWrite: false,
      uniforms: {
        topColor: { value: new THREE.Color(0x02050b) },
        horizonColor: { value: new THREE.Color(0x102333) }
      },
      vertexShader: `
        varying vec3 vLocal;
        void main() {
          vLocal = position;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        uniform vec3 topColor;
        uniform vec3 horizonColor;
        varying vec3 vLocal;
        void main() {
          float h = normalize(vLocal).y;
          float blend = smoothstep(-0.18, 0.72, h);
          vec3 color = mix(horizonColor, topColor, blend);
          gl_FragColor = vec4(color, 1.0);
        }
      `
    });
    const sky = new THREE.Mesh(new THREE.SphereGeometry(78, 32, 18), skyMaterial);
    sky.renderOrder = -20;
    scene.add(sky);

    const hemisphere = new THREE.HemisphereLight(0x7698c4, 0x10160e, 0.67);
    scene.add(hemisphere);

    const moonLight = new THREE.DirectionalLight(0xa7c2ec, 0.9);
    moonLight.position.set(-18, 22, -30);
    scene.add(moonLight);

    // ------------------------------------------------------------
    // Procedural textures
    // ------------------------------------------------------------
    function makeGlowTexture() {
      const canvas = document.createElement('canvas');
      canvas.width = canvas.height = 128;
      const ctx = canvas.getContext('2d');
      const gradient = ctx.createRadialGradient(64, 64, 0, 64, 64, 64);
      gradient.addColorStop(0, 'rgba(255,255,255,1)');
      gradient.addColorStop(0.13, 'rgba(255,255,255,.82)');
      gradient.addColorStop(0.38, 'rgba(255,255,255,.2)');
      gradient.addColorStop(1, 'rgba(255,255,255,0)');
      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, 128, 128);
      const texture = new THREE.CanvasTexture(canvas);
      texture.colorSpace = THREE.SRGBColorSpace;
      return texture;
    }

    function makeMoonTexture() {
      const canvas = document.createElement('canvas');
      canvas.width = 512;
      canvas.height = 256;
      const ctx = canvas.getContext('2d');
      ctx.fillStyle = '#d9dfda';
      ctx.fillRect(0, 0, 512, 256);

      let moonSeed = 91;
      const moonRandom = () => {
        moonSeed = (moonSeed * 1103515245 + 12345) & 0x7fffffff;
        return moonSeed / 0x7fffffff;
      };

      for (let i = 0; i < 95; i++) {
        const x = moonRandom() * 512;
        const y = moonRandom() * 256;
        const r = 2 + moonRandom() * 17;
        const g = ctx.createRadialGradient(x, y, 0, x, y, r);
        g.addColorStop(0, 'rgba(76,91,93,.22)');
        g.addColorStop(0.65, 'rgba(91,105,105,.09)');
        g.addColorStop(1, 'rgba(91,105,105,0)');
        ctx.fillStyle = g;
        ctx.beginPath();
        ctx.arc(x, y, r, 0, Math.PI * 2);
        ctx.fill();
      }
      const texture = new THREE.CanvasTexture(canvas);
      texture.colorSpace = THREE.SRGBColorSpace;
      texture.wrapS = THREE.RepeatWrapping;
      return texture;
    }

    const glowTexture = makeGlowTexture();

    const moon = new THREE.Group();
    moon.position.set(-18, 11, -30);
    const moonBody = new THREE.Mesh(
      new THREE.SphereGeometry(2.45, 24, 16),
      new THREE.MeshBasicMaterial({ map: makeMoonTexture(), fog: false, toneMapped: false })
    );
    moonBody.rotation.y = 0.65;
    moon.add(moonBody);

    const moonHalo = new THREE.Sprite(new THREE.SpriteMaterial({
      map: glowTexture,
      color: 0x9fbcdf,
      transparent: true,
      opacity: 0.22,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
      fog: false,
      toneMapped: false
    }));
    moonHalo.scale.set(10, 10, 1);
    moon.add(moonHalo);
    scene.add(moon);

    // ------------------------------------------------------------
    // Stars
    // ------------------------------------------------------------
    const starCount = 900;
    const starPositions = new Float32Array(starCount * 3);
    const starColors = new Float32Array(starCount * 3);
    const starColor = new THREE.Color();

    for (let i = 0; i < starCount; i++) {
      const azimuth = range(0, Math.PI * 2);
      const elevation = range(0.025, 0.98);
      const horizontal = Math.sqrt(1 - elevation * elevation);
      const radius = range(61, 67);
      starPositions[i * 3] = Math.cos(azimuth) * horizontal * radius;
      starPositions[i * 3 + 1] = elevation * radius;
      starPositions[i * 3 + 2] = Math.sin(azimuth) * horizontal * radius;

      const cool = random();
      starColor.setRGB(
        0.62 + cool * 0.38,
        0.72 + cool * 0.28,
        0.82 + cool * 0.18
      );
      starColors[i * 3] = starColor.r;
      starColors[i * 3 + 1] = starColor.g;
      starColors[i * 3 + 2] = starColor.b;
    }

    const starGeometry = new THREE.BufferGeometry();
    starGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
    starGeometry.setAttribute('color', new THREE.BufferAttribute(starColors, 3));

    const starMaterial = new THREE.PointsMaterial({
      size: 1.45,
      sizeAttenuation: false,
      vertexColors: true,
      transparent: true,
      opacity: 0.85,
      depthWrite: false,
      fog: false,
      toneMapped: false
    });
    const stars = new THREE.Points(starGeometry, starMaterial);
    scene.add(stars);

    // ------------------------------------------------------------
    // Faceted ground and clearing
    // ------------------------------------------------------------
    function terrainHeight(x, z) {
      const radius = Math.hypot(x, z);
      if (radius < 10.5) return 0;
      return (
        Math.sin(x * 0.23) * 0.07 +
        Math.cos(z * 0.19) * 0.055 +
        Math.sin((x + z) * 0.11) * 0.06
      );
    }

    const terrainGeometry = new THREE.PlaneGeometry(100, 100, 44, 44);
    terrainGeometry.rotateX(-Math.PI / 2);
    const terrainPositions = terrainGeometry.attributes.position;
    for (let i = 0; i < terrainPositions.count; i++) {
      const x = terrainPositions.getX(i);
      const z = terrainPositions.getZ(i);
      terrainPositions.setY(i, terrainHeight(x, z));
    }
    terrainGeometry.computeVertexNormals();
    const facetedTerrain = terrainGeometry.toNonIndexed();
    facetedTerrain.computeVertexNormals();

    const terrainColors = new Float32Array(facetedTerrain.attributes.position.count * 3);
    const faceColor = new THREE.Color();
    for (let i = 0; i < facetedTerrain.attributes.position.count; i += 3) {
      faceColor.setHSL(range(0.32, 0.38), range(0.22, 0.31), range(0.075, 0.115));
      for (let j = 0; j < 3; j++) {
        const index = i + j;
        terrainColors[index * 3] = faceColor.r;
        terrainColors[index * 3 + 1] = faceColor.g;
        terrainColors[index * 3 + 2] = faceColor.b;
      }
    }
    facetedTerrain.setAttribute('color', new THREE.BufferAttribute(terrainColors, 3));
    const ground = new THREE.Mesh(
      facetedTerrain,
      new THREE.MeshStandardMaterial({
        vertexColors: true,
        roughness: 1,
        metalness: 0,
        flatShading: true
      })
    );
    ground.receiveShadow = true;
    scene.add(ground);

    function makeGroundPatch(radius, y, innerHex, outerHex, rings = 4, segments = 40) {
      const positions = [];
      const colors = [];
      const innerColor = new THREE.Color(innerHex);
      const outerColor = new THREE.Color(outerHex);
      const mixedColor = new THREE.Color();

      function pointAt(ringRadius, angle, ringIndex) {
        const edge = 1 + 0.035 * Math.sin(angle * 3 + 0.7) + 0.018 * Math.cos(angle * 8);
        const r = ringRadius * edge;
        return new THREE.Vector3(
          Math.cos(angle) * r,
          y + (ringIndex ? range(-0.012, 0.012) : 0),
          Math.sin(angle) * r
        );
      }

      function pushTriangle(a, b, c, ratio) {
        mixedColor.copy(innerColor).lerp(outerColor, ratio);
        mixedColor.offsetHSL(range(-0.012, 0.012), 0, range(-0.012, 0.012));
        for (const p of [a, b, c]) {
          positions.push(p.x, p.y, p.z);
          colors.push(mixedColor.r, mixedColor.g, mixedColor.b);
        }
      }

      const center = new THREE.Vector3(0, y, 0);
      for (let s = 0; s < segments; s++) {
        const a0 = (s / segments) * Math.PI * 2;
        const a1 = ((s + 1) / segments) * Math.PI * 2;
        pushTriangle(center, pointAt(radius / rings, a0, 1), pointAt(radius / rings, a1, 1), 0.05);
      }

      for (let ring = 1; ring < rings; ring++) {
        const r0 = radius * ring / rings;
        const r1 = radius * (ring + 1) / rings;
        for (let s = 0; s < segments; s++) {
          const a0 = (s / segments) * Math.PI * 2;
          const a1 = ((s + 1) / segments) * Math.PI * 2;
          const p00 = pointAt(r0, a0, ring);
          const p01 = pointAt(r0, a1, ring);
          const p10 = pointAt(r1, a0, ring + 1);
          const p11 = pointAt(r1, a1, ring + 1);
          pushTriangle(p00, p10, p11, ring / rings);
          pushTriangle(p00, p11, p01, ring / rings);
        }
      }

      const geometry = new THREE.BufferGeometry();
      geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
      geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
      geometry.computeVertexNormals();
      const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({
        vertexColors: true,
        roughness: 1,
        flatShading: true
      }));
      mesh.receiveShadow = true;
      scene.add(mesh);
      return mesh;
    }

    makeGroundPatch(9.5, 0.035, 0x352a1e, 0x25251a, 4, 44);
    makeGroundPatch(4.25, 0.075, 0x251a11, 0x15110e, 3, 32);

    // ------------------------------------------------------------
    // Shared materials and low-poly tree construction
    // ------------------------------------------------------------
    const barkMaterials = [
      new THREE.MeshStandardMaterial({ color: 0x493326, roughness: 1, flatShading: true }),
      new THREE.MeshStandardMaterial({ color: 0x38291f, roughness: 1, flatShading: true })
    ];
    const pineMaterials = [
      new THREE.MeshStandardMaterial({ color: 0x173b2e, roughness: 0.95, flatShading: true }),
      new THREE.MeshStandardMaterial({ color: 0x204735, roughness: 0.95, flatShading: true }),
      new THREE.MeshStandardMaterial({ color: 0x28543c, roughness: 0.95, flatShading: true }),
      new THREE.MeshStandardMaterial({ color: 0x18372d, roughness: 0.95, flatShading: true })
    ];
    const broadleafMaterials = [
      new THREE.MeshStandardMaterial({ color: 0x244936, roughness: 1, flatShading: true }),
      new THREE.MeshStandardMaterial({ color: 0x30513a, roughness: 1, flatShading: true }),
      new THREE.MeshStandardMaterial({ color: 0x203f32, roughness: 1, flatShading: true })
    ];

    const trunkGeometry = new THREE.CylinderGeometry(0.13, 0.21, 1, 5);
    const coneGeometry = new THREE.ConeGeometry(1, 1, 7);
    coneGeometry.translate(0, 0.5, 0);
    const crownGeometry = new THREE.IcosahedronGeometry(1, 0);

    function makeTree(height, broadleaf) {
      const group = new THREE.Group();
      const trunkHeight = height * (broadleaf ? 0.63 : 0.66);
      const trunk = new THREE.Mesh(trunkGeometry, barkMaterials[random() > 0.5 ? 0 : 1]);
      trunk.scale.y = trunkHeight;
      trunk.position.y = trunkHeight * 0.5;
      group.add(trunk);

      if (broadleaf) {
        const material = broadleafMaterials[Math.floor(random() * broadleafMaterials.length)];
        const crownHeight = height * 0.72;
        const crown = new THREE.Mesh(crownGeometry, material);
        crown.position.set(range(-0.12, 0.12) * height, crownHeight, range(-0.1, 0.1) * height);
        crown.scale.set(height * 0.29, height * 0.25, height * 0.27);
        group.add(crown);

        for (let i = 0; i < 3; i++) {
          const lobe = new THREE.Mesh(crownGeometry, material);
          const angle = i * Math.PI * 2 / 3 + range(-0.35, 0.35);
          lobe.position.set(
            Math.cos(angle) * height * 0.16,
            crownHeight + range(-0.05, 0.11) * height,
            Math.sin(angle) * height * 0.16
          );
          lobe.scale.set(height * range(0.17, 0.23), height * range(0.17, 0.23), height * range(0.17, 0.23));
          group.add(lobe);
        }

        const branch = new THREE.Mesh(trunkGeometry, barkMaterials[0]);
        branch.scale.set(0.8, height * 0.24, 0.8);
        branch.position.set(height * 0.08, height * 0.53, 0);
        branch.rotation.z = -0.72;
        group.add(branch);
      } else {
        const radii = [height * 0.28, height * 0.225, height * 0.165];
        const heights = [height * 0.52, height * 0.43, height * 0.36];
        const bases = [height * 0.25, height * 0.43, height * 0.59];
        for (let i = 0; i < 3; i++) {
          const foliage = new THREE.Mesh(coneGeometry, pineMaterials[Math.floor(random() * pineMaterials.length)]);
          foliage.position.set(range(-0.07, 0.07) * height, bases[i], range(-0.05, 0.05) * height);
          foliage.scale.set(radii[i], heights[i], radii[i]);
          foliage.rotation.y = range(0, Math.PI * 2);
          group.add(foliage);
        }
      }
      return group;
    }

    let treeCount = 0;
    const cameraAzimuth = Math.atan2(camera.position.z, camera.position.x);
    for (let i = 0; i < 64; i++) {
      const angle = i * 2.399963 + range(-0.28, 0.28);
      const radius = i < 46 ? range(10.8, 23.5) : range(26, 43);
      const angleDifference = Math.atan2(Math.sin(angle - cameraAzimuth), Math.cos(angle - cameraAzimuth));

      // Leave the foreground view into the clearing open.
      if (Math.abs(angleDifference) < 0.48 && radius < 30) continue;

      const tree = makeTree(range(4.4, 7.2), random() < 0.2);
      tree.position.set(Math.cos(angle) * radius, terrainHeight(Math.cos(angle) * radius, Math.sin(angle) * radius), Math.sin(angle) * radius);
      tree.rotation.y = range(0, Math.PI * 2);
      tree.traverse(object => {
        if (object.isMesh) {
          object.castShadow = radius < 19;
          object.receiveShadow = true;
        }
      });
      scene.add(tree);
      treeCount++;
    }
    scene.userData.treeCount = treeCount;

    // ------------------------------------------------------------
    // Rocks, grass, and fallen logs
    // ------------------------------------------------------------
    const rockMaterials = [
      new THREE.MeshStandardMaterial({ color: 0x48504e, roughness: 1, flatShading: true }),
      new THREE.MeshStandardMaterial({ color: 0x353e3d, roughness: 1, flatShading: true }),
      new THREE.MeshStandardMaterial({ color: 0x535854, roughness: 1, flatShading: true })
    ];
    const rockGeometry = new THREE.IcosahedronGeometry(1, 0);

    for (let i = 0; i < 20; i++) {
      const angle = range(0, Math.PI * 2);
      const radius = range(2.9, 8.7);
      const rock = new THREE.Mesh(rockGeometry, rockMaterials[Math.floor(random() * rockMaterials.length)]);
      const size = range(0.25, 0.78);
      rock.scale.set(size * range(0.8, 1.35), size * range(0.55, 0.95), size * range(0.8, 1.3));
      rock.position.set(Math.cos(angle) * radius, rock.scale.y * 0.55, Math.sin(angle) * radius);
      rock.rotation.set(range(0, 1), range(0, Math.PI * 2), range(0, 1));
      rock.castShadow = true;
      rock.receiveShadow = true;
      scene.add(rock);
    }

    // Small stones loosely ring the fire.
    for (let i = 0; i < 11; i++) {
      const angle = i / 11 * Math.PI * 2 + range(-0.12, 0.12);
      const stone = new THREE.Mesh(rockGeometry, rockMaterials[i % rockMaterials.length]);
      const size = range(0.14, 0.25);
      stone.scale.set(size * 1.25, size * 0.72, size);
      stone.position.set(Math.cos(angle) * 1.12, 0.14, Math.sin(angle) * 1.12);
      stone.rotation.set(range(0, 1), range(0, Math.PI * 2), range(0, 1));
      stone.castShadow = true;
      stone.receiveShadow = true;
      scene.add(stone);
    }

    const grassGeometry = new THREE.ConeGeometry(0.095, 0.52, 3);
    grassGeometry.translate(0, 0.26, 0);
    const grassMaterial = new THREE.MeshStandardMaterial({
      color: 0x294333,
      roughness: 1,
      flatShading: true
    });
    const grassCount = 88;
    const grass = new THREE.InstancedMesh(grassGeometry, grassMaterial, grassCount);
    const dummy = new THREE.Object3D();
    const grassColor = new THREE.Color();
    for (let i = 0; i < grassCount; i++) {
      const angle = range(0, Math.PI * 2);
      const radius = range(5.1, 10.4);
      dummy.position.set(Math.cos(angle) * radius, 0.06, Math.sin(angle) * radius);
      dummy.rotation.set(range(-0.18, 0.18), range(0, Math.PI * 2), range(-0.18, 0.18));
      dummy.scale.setScalar(range(0.55, 1.3));
      dummy.updateMatrix();
      grass.setMatrixAt(i, dummy.matrix);
      grassColor.setHSL(range(0.32, 0.39), range(0.2, 0.35), range(0.11, 0.19));
      grass.setColorAt(i, grassColor);
    }
    grass.instanceMatrix.needsUpdate = true;
    grass.instanceColor.needsUpdate = true;
    grass.receiveShadow = true;
    scene.add(grass);

    const logGeometry = new THREE.CylinderGeometry(0.83, 1, 1, 7, 1, false);
    const logBark = new THREE.MeshStandardMaterial({ color: 0x473023, roughness: 1, flatShading: true });
    const logEnd = new THREE.MeshStandardMaterial({ color: 0x755037, roughness: 1, flatShading: true });
    const charBark = new THREE.MeshStandardMaterial({ color: 0x251b15, roughness: 1, flatShading: true });
    const charEnd = new THREE.MeshStandardMaterial({ color: 0x39251a, roughness: 1, flatShading: true });

    function makeLog(length, radius, angle, x, z, y, materials) {
      const group = new THREE.Group();
      const log = new THREE.Mesh(logGeometry, [materials[0], materials[1], materials[1]]);
      log.scale.set(radius, length, radius);
      log.rotation.z = Math.PI / 2;
      log.castShadow = true;
      log.receiveShadow = true;
      group.add(log);
      group.position.set(x, y, z);
      group.rotation.y = angle;
      scene.add(group);
      return group;
    }

    // Crossed, partially charred logs form the campfire.
    for (let i = 0; i < 5; i++) {
      makeLog(
        range(2.15, 2.7),
        range(0.14, 0.19),
        i * Math.PI / 5 + range(-0.17, 0.17),
        range(-0.15, 0.15),
        range(-0.15, 0.15),
        0.22 + i * 0.025,
        [charBark, charEnd]
      );
    }

    // Larger fallen logs around the clearing.
    const fallenLogs = [
      { a: 0.25, r: 4.4, length: 3.8 },
      { a: 1.75, r: 5.4, length: 4.3 },
      { a: 3.45, r: 4.6, length: 3.4 },
      { a: 5.15, r: 6.0, length: 4.0 }
    ];
    for (const item of fallenLogs) {
      makeLog(
        item.length,
        range(0.21, 0.29),
        item.a + Math.PI / 2 + range(-0.18, 0.18),
        Math.cos(item.a) * item.r,
        Math.sin(item.a) * item.r,
        0.24,
        [logBark, logEnd]
      );
    }

    // ------------------------------------------------------------
    // Campfire flames
    // ------------------------------------------------------------
    function makeFlameGeometry(variant) {
      const segments = 6;
      const rings = [
        { y: 0.00, r: 0.31, x: 0.00 },
        { y: 0.31, r: 0.46, x: 0.01 },
        { y: 0.79, r: 0.33, x: 0.06 + variant * 0.012 },
        { y: 1.31, r: 0.15, x: 0.14 + variant * 0.01 }
      ];
      const positions = [];
      const indices = [];

      for (let ring = 0; ring < rings.length; ring++) {
        const data = rings[ring];
        for (let segment = 0; segment < segments; segment++) {
          const angle = segment / segments * Math.PI * 2 + ring * 0.16 + variant * 0.07;
          positions.push(
            Math.cos(angle) * data.r + data.x,
            data.y,
            Math.sin(angle) * data.r * 0.82
          );
        }
      }

      for (let ring = 0; ring < rings.length - 1; ring++) {
        for (let segment = 0; segment < segments; segment++) {
          const next = (segment + 1) % segments;
          const a = ring * segments + segment;
          const b = ring * segments + next;
          const c = (ring + 1) * segments + segment;
          const d = (ring + 1) * segments + next;
          indices.push(a, c, b, b, c, d);
        }
      }

      const tipIndex = positions.length / 3;
      positions.push(0.22 + variant * 0.014, 1.75, 0);
      const lastRing = (rings.length - 1) * segments;
      for (let segment = 0; segment < segments; segment++) {
        indices.push(lastRing + segment, tipIndex, lastRing + (segment + 1) % segments);
      }

      const geometry = new THREE.BufferGeometry();
      geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
      geometry.setIndex(indices);
      geometry.computeVertexNormals();
      return geometry;
    }

    const outerFlameMaterial = new THREE.MeshStandardMaterial({
      color: 0xff4108,
      emissive: 0xff2600,
      emissiveIntensity: 2.25,
      roughness: 0.72,
      flatShading: true,
      transparent: true,
      opacity: 0.79,
      depthWrite: false,
      side: THREE.DoubleSide
    });
    const orangeFlameMaterial = new THREE.MeshStandardMaterial({
      color: 0xff9818,
      emissive: 0xff6a00,
      emissiveIntensity: 2.45,
      roughness: 0.65,
      flatShading: true,
      transparent: true,
      opacity: 0.86,
      depthWrite: false,
      side: THREE.DoubleSide
    });
    const coreFlameMaterial = new THREE.MeshStandardMaterial({
      color: 0xffeda0,
      emissive: 0xffbf35,
      emissiveIntensity: 2.8,
      roughness: 0.6,
      flatShading: true,
      transparent: true,
      opacity: 0.94,
      depthWrite: false,
      side: THREE.DoubleSide
    });

    const flameRoot = new THREE.Group();
    flameRoot.position.y = 0.28;
    scene.add(flameRoot);

    const flames = [];
    function addFlame(material, x, z, sx, sy, sz, phase) {
      const flame = new THREE.Mesh(makeFlameGeometry(flames.length), material);
      flame.position.set(x, 0, z);
      flame.scale.set(sx, sy, sz);
      flame.userData.baseScale = flame.scale.clone();
      flame.userData.basePosition = flame.position.clone();
      flame.userData.phase = phase;
      flame.renderOrder = 4;
      flameRoot.add(flame);
      flames.push(flame);
    }

    addFlame(outerFlameMaterial, -0.22, 0.05, 0.86, 1.13, 0.78, 0.3);
    addFlame(outerFlameMaterial, 0.20, 0.16, 0.76, 1.30, 0.72, 1.8);
    addFlame(outerFlameMaterial, 0.02, -0.23, 0.74, 1.04, 0.69, 3.1);
    addFlame(orangeFlameMaterial, -0.05, 0.02, 0.69, 1.05, 0.65, 4.2);
    addFlame(orangeFlameMaterial, 0.22, -0.02, 0.56, 0.88, 0.54, 5.0);
    addFlame(orangeFlameMaterial, -0.20, -0.08, 0.50, 0.83, 0.49, 2.2);
    addFlame(coreFlameMaterial, 0.00, 0.01, 0.38, 0.68, 0.36, 0.8);

    const fireLight = new THREE.PointLight(0xff7a24, 115, 23, 2);
    fireLight.position.set(0, 1.4, 0);
    fireLight.castShadow = true;
    fireLight.shadow.mapSize.set(1024, 1024);
    fireLight.shadow.camera.near = 0.25;
    fireLight.shadow.camera.far = 22;
    fireLight.shadow.bias = -0.00035;
    fireLight.shadow.normalBias = 0.035;
    scene.add(fireLight);

    const fireGlow = new THREE.Sprite(new THREE.SpriteMaterial({
      map: glowTexture,
      color: 0xff641c,
      transparent: true,
      opacity: 0.19,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
      fog: false,
      toneMapped: false
    }));
    fireGlow.position.set(0, 1.08, 0);
    fireGlow.scale.set(5.2, 4.2, 1);
    scene.add(fireGlow);

    // Glowing embers nestled among the charred wood.
    const emberMaterial = new THREE.MeshBasicMaterial({ color: 0xff5418, toneMapped: false });
    for (let i = 0; i < 9; i++) {
      const ember = new THREE.Mesh(new THREE.IcosahedronGeometry(range(0.035, 0.075), 0), emberMaterial);
      const angle = range(0, Math.PI * 2);
      const radius = range(0.05, 0.52);
      ember.position.set(Math.cos(angle) * radius, 0.24, Math.sin(angle) * radius);
      scene.add(ember);
    }

    // ------------------------------------------------------------
    // Rising spark particle system
    // ------------------------------------------------------------
    const sparkCount = 100;
    const sparkPositions = new Float32Array(sparkCount * 3);
    const sparkColors = new Float32Array(sparkCount * 3);
    const sparks = [];
    const hotColor = new THREE.Color(0xffd06a);
    const coolColor = new THREE.Color(0xff4a12);

    for (let i = 0; i < sparkCount; i++) {
      const spark = {
        x: range(-0.38, 0.38),
        y: 0,
        z: range(-0.38, 0.38),
        vx: range(-0.22, 0.22),
        vy: range(1.45, 2.75),
        vz: range(-0.22, 0.22),
        age: 0,
        life: range(1.5, 3.1),
        phase: range(0, Math.PI * 2)
      };
      spark.age = random() * spark.life;
      spark.y = 0.35 + spark.vy * spark.age - 0.11 * spark.age * spark.age;
      spark.x += spark.vx * spark.age + Math.sin(spark.age * 5 + spark.phase) * 0.08;
      spark.z += spark.vz * spark.age + Math.cos(spark.age * 4 + spark.phase) * 0.06;
      sparks.push(spark);

      const color = hotColor.clone().lerp(coolColor, random() * 0.55);
      sparkColors[i * 3] = color.r;
      sparkColors[i * 3 + 1] = color.g;
      sparkColors[i * 3 + 2] = color.b;
    }

    const sparkGeometry = new THREE.BufferGeometry();
    const sparkPositionAttribute = new THREE.BufferAttribute(sparkPositions, 3);
    sparkPositionAttribute.setUsage(THREE.DynamicDrawUsage);
    sparkGeometry.setAttribute('position', sparkPositionAttribute);
    sparkGeometry.setAttribute('color', new THREE.BufferAttribute(sparkColors, 3));

    const sparkMaterial = new THREE.PointsMaterial({
      map: glowTexture,
      size: 0.07,
      sizeAttenuation: true,
      vertexColors: true,
      transparent: true,
      opacity: 0.95,
      blending: THREE.AdditiveBlending,
      depthWrite: false,
      fog: false,
      toneMapped: false
    });
    const sparkPoints = new THREE.Points(sparkGeometry, sparkMaterial);
    sparkPoints.renderOrder = 5;
    scene.add(sparkPoints);

    // The shadow geometry is static; only the light intensity flickers.
    // Rendering the shadow map once avoids six expensive point-light passes per frame.
    renderer.shadowMap.autoUpdate = false;
    renderer.shadowMap.needsUpdate = true;

    // ------------------------------------------------------------
    // Animation
    // ------------------------------------------------------------
    const clock = new THREE.Clock();
    let elapsed = 0;

    function animate() {
      const delta = Math.min(clock.getDelta(), 0.05);
      elapsed += delta;
      controls.update();

      for (const flame of flames) {
        const { baseScale, basePosition, phase } = flame.userData;
        const pulse = Math.sin(elapsed * 8.1 + phase);
        const quickPulse = Math.sin(elapsed * 13.7 + phase * 1.9);
        flame.scale.set(
          baseScale.x * (0.94 + pulse * 0.065 + quickPulse * 0.025),
          baseScale.y * (0.91 + pulse * 0.105 + quickPulse * 0.045),
          baseScale.z * (0.95 - pulse * 0.045)
        );
        flame.position.x = basePosition.x + Math.sin(elapsed * 2.4 + phase) * 0.035;
        flame.position.z = basePosition.z + Math.cos(elapsed * 2.1 + phase) * 0.028;
        flame.rotation.y = elapsed * 0.28 + phase;
        flame.rotation.z = Math.sin(elapsed * 2.8 + phase) * 0.105;
      }

      const flicker =
        104 +
        Math.sin(elapsed * 11.3) * 15 +
        Math.sin(elapsed * 19.7 + 0.7) * 9 +
        Math.sin(elapsed * 31.1 + 1.8) * 5;
      fireLight.intensity = flicker;
      fireGlow.material.opacity = 0.17 + (flicker - 95) / 650;
      const glowScale = 4.8 + Math.sin(elapsed * 8.6) * 0.22;
      fireGlow.scale.set(glowScale, glowScale * 0.82, 1);

      for (let i = 0; i < sparkCount; i++) {
        const spark = sparks[i];
        spark.age += delta;

        if (spark.age > spark.life || spark.y > 6.2) {
          spark.x = range(-0.34, 0.34);
          spark.z = range(-0.34, 0.34);
          spark.y = 0.35;
          spark.vx = range(-0.24, 0.24);
          spark.vy = range(1.5, 2.8);
          spark.vz = range(-0.24, 0.24);
          spark.age = 0;
          spark.life = range(1.5, 3.1);
        }

        spark.vy += 0.13 * delta;
        spark.x += (spark.vx + Math.sin(elapsed * 4.5 + spark.phase) * 0.19) * delta;
        spark.y += spark.vy * delta;
        spark.z += (spark.vz + Math.cos(elapsed * 3.8 + spark.phase) * 0.15) * delta;

        sparkPositions[i * 3] = spark.x;
        sparkPositions[i * 3 + 1] = spark.y;
        sparkPositions[i * 3 + 2] = spark.z;
      }
      sparkPositionAttribute.needsUpdate = true;

      starMaterial.opacity = 0.79 + Math.sin(elapsed * 0.7) * 0.055;
      stars.rotation.y = elapsed * 0.0012;
      moonHalo.material.opacity = 0.205 + Math.sin(elapsed * 0.55) * 0.025;

      renderer.render(scene, camera);
    }

    renderer.setAnimationLoop(animate);

    // ------------------------------------------------------------
    // Responsive resizing
    // ------------------------------------------------------------
    function onResize() {
      const width = window.innerWidth;
      const height = window.innerHeight;
      camera.aspect = width / height;
      camera.fov = width < 600 ? 60 : 52;
      camera.updateProjectionMatrix();
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.8));
      renderer.setSize(width, height);
    }

    window.addEventListener('resize', onResize);
    onResize();
  </script>
</body>
</html>
Reasoning trace (18,512 tokens)
We need craft final exactly one fenced code block html complete. Need build robust attractive scene Three.js. Need ensure all requirements. Need use import map exact. No external beyond CDN specified. Single html. Need self-contained no assets. Need design.

We need think dimensions 1280x800. Low-poly scene campfire forest clearing night, warm fire, point light shadows, sparks particles, stars moon fog, 30+ trees random not fire, OrbitControls limits above ground, renderer fill window resize. Need perhaps overlay title/UI tasteful but scene visible. Need no interaction requirement except orbit. We can include instructions.

Need make high quality. Three.js ES module import. Need ensure no errors. Use THREE, OrbitControls from addons.

Create scene background dark navy, fog exp2 maybe 0.035. Renderer antialias, alpha false, shadows PCFSoft, ACESFilmic, output SRGB. Camera Perspective 50, position (13,8,15) look at 0,2,0.

Ground circular clearing? Ground as low-poly circle radius 28 with vertex irregular? Need scene. Could use CircleGeometry 34, 48 and color. Make slight vertex variations. Outer terrain large plane perhaps Circle 70 with muted green. Clearing disk radius 9 with dirt, rough edges. Use CircleGeometry with vertices randomized? Circle vertices center+radial; alter outer positions and y. Rotate -pi/2. But Circle only triangle fan gives no interior vertices; can create PlaneGeometry 80,80,40,40, rotate and color per vertex, add broad clearing? Low-poly ground can be plane. For clearing shape use circle with 48 segments and additional ring maybe use custom geometry with concentric rings and random radial subdivisions. Could use CircleGeometry(9, 48) slightly irregular by modifying outer. But center center. Add second outer patch maybe irregular CircleGeometry(13,48) under? Better terrain plane triangulated with heights low, colors, dark. Clearing circle radius 8.4, polygon irregular, slightly raised 0.02; scene ground plane 90x90 48 subdivisions. Position trees within radius 33 excluding clearing & fire. Some background silhouettes 45 maybe. At least 30 count visible.

Trees low-poly: procedural trees. Need variety. Use trunk CylinderGeometry 0.2/0.35 height, 5 radial segments, brown. Foliage cones with 6-8 radial segments layers, maybe 3 stacked conical canopies, varied greens. Need random placement 46 trees. Ensure not clearing/fire. Position angle radius 10 to 34. Avoid camera blocking foreground? camera (13,9,15), trees around clearing perhaps some behind camera or foreground obscure. We can exclude front arc? Camera high enough. Trees can be tall 4-7, perhaps can block. Need scene aesthetic.

Could make tree groups. Trunk Cylinder with top/bottom and foliage. For low poly use ConeGeometry with flatShading. Add 2-3 cone tiers. tree scale random .8-1.5. Group orientation random. Trees placed 42 around. Fog distant.

Ground rocks around campfire: low-poly IcosahedronGeometry(1,0) scale irregular gray, cast/receive shadows. 14 rocks radius 3-7, avoid logs perhaps. Logs around campfire, perhaps three crossed? "logs around a campfire" use 5 fallen logs at radius 3.3-5 oriented. Log geometry cylinder 5-6 radial segments horizontal, cut end circles maybe. Add branch stubs. Could make log groups. Also wood logs stacked around fire? Campfire usually logs arranged teepee. Requirement logs around. We can add 3 charred logs as crossed pile near center and 4 fallen logs around.

Campfire center at (0,0,0). Flames animated. Need likely iconic orange/yellow low-poly flame geometry. Could create group with 5 flame meshes each custom geometry resembling tapered twisted flame using BufferGeometry with cross-section rings maybe. Animated scale/rotation, colors emissive. Use MeshBasicMaterial maybe transparent additive; but geometry low-poly. Need flame visible. Could use cones with weird vertices, but low-poly flame stylized. Create custom flame geometry with segments radial 5, vertical rings 4, y positions and radii; top tip. Geometry based around y. Each flame mesh, group; scale animate. Color inner yellow white, outer orange. Emissive materials maybe MeshBasic enough bright. Since fog? MeshBasic fog perhaps fog fades; set fog false. Flame meshes around center at y=.25. PointLight at (0,1.8,0), color orange intensity maybe 110 physically correct units? Three r170 uses physically correct by default and light intensity units. PointLight intensity e.g. 80, distance 20, decay 2. At 2m gives ~20. Fine. Need shadows point light 6 maps expensive but okay 512/1024. PointLight.castShadow true, shadow.mapSize 1024, bias. Flicker intensity with sin/noise 65-115 and position slight. Cast shadows from trees/rocks/logs/flames. Flame basic won't cast maybe irrelevant.

Could create flame shader? No need. But animated shapes should look good. Geometry per flame with points maybe shape bending. For each flame, update rotation z/x, scale y. Some appear. Create outer flame orange at 0.6 scale, inner yellow. Need flames maybe use custom shape with y local 0 to 2, width .5. Rings:
y 0: radius .35, x offset 0
y .35: radius .5, offset .05
y .9: radius .35 offset .1
y1.5 radius .12 offset .15
tip y1.8 offset .2.
Ring radial 6 with angle rotation random; triangulate quads. Base cap? skip or add. Flat shading, MeshStandardMaterial with color and emissive, roughness. Maybe if emissive too bright but no bloom. Set material emissiveIntensity 1.8, toneMapped false? Basic material toneMapped false gives bright. Use MeshBasicMaterial transparent opacity .85, depthWrite false, blending Additive? Additive can appear. Outer red/orange and inner yellow. Layer arrangement. Perhaps MeshStandard emissive with color orange and emissive #ff5a00 intensity 1.7, but emissive only in lit area. Add inner core MeshBasic yellow.

Need animated time. Flames each has base scale, phase. update at delta. set y scale pulsing, x scale, rotation; tip shape static but animate. Could custom flame group with outer mesh and inner? We can create 6 flames each unique geometry and material colors. Each flame group? Could have mesh only, scale.

Particle sparks: simple particle system. Use Points with BufferGeometry positions/colors, PointsMaterial size .075 transparent vertexColors blending Additive depthWrite false. Count 90. Update positions upward with turbulence, life; reset at fire base. Need first screenshot sparks distributed, not all at origin. initialize randomized ages in lifetimes and compute positions using deterministic-ish random. Particle velocity, age. Update each frame dt; pos x,y,z; if age>life reset near base. Since points material fog? false perhaps.

Also embers with larger. points sizeAttenuation true.

Fire glow? point light.

Stars: Points geometry 900 distributed hemisphere/whole sky radius 70. Need visible from camera and Orbit. Star positions on sphere radius 65, y positive maybe entire hemisphere. Camera at radius ~20 looking center, background sky radius. fog may affect Points if material fog false. Make varying sizes impossible with PointsMaterial single size. Could custom shader for per-star size/twinkle, but no need; 700 stars with white/blue colors and size .1 maybe. Star field visible. PointsMaterial size .12, transparent opacity .85, sizeAttenuation false? At perspective PointsMaterial size in px if false; choose 1.5 px. Better size .12, sizeAttenuation false? In three, points size material size pixels if attenuation false? yes. Need visible. 900 points might be too many bright. 550 size 1.25. Stars at radius 65 around origin, lower y excluded. Random x/z on circle and y 12-60. Use sphere theta/phi; horizon dark. Need camera look around, stars in all directions perhaps distribute sphere y -? Distant terrain blocks lower half. Generate hemisphere: theta random 0..2pi, y 5..55 based radius. Use random spherical with y positive 0.05..1, so hemisphere.

Moon: sphere low poly? Moon at position (-18, 23, -30) or (-20,25,-28). SphereGeometry(3, 20,14) MeshBasic pale #cdd8df, maybe glow with sprite made canvas? External images no, can generate canvas texture inline. But "everything inline" canvas okay. Moon disk with crater circles? Sphere with flat shading maybe low-poly. Add glow sprite via CanvasTexture radial gradient; created in JS. Moon should face camera? Sphere no. At far position radius 50. Need fog? material fog false. Use group. Moon sphere 2.4 maybe apparent 3 deg. With stars behind. Add glow sprite with additive transparent at same pos scale 9. canvas radial gradient. Canvas texture is internal no external. Fine. Could add crescent? full moon with small crater low poly circles? At distance not necessary. Maybe moon use CircleGeometry billboard? Sphere better and phase visible. Moon position (-24,26,-35), radius 2.6. Direction camera at (12,7,14) looking origin, this moon maybe in view? Camera view forward target from camera to origin vector (-12,-7,-14), horizontal. Moon vector from camera (-36,19,-49) roughly similar xz ratio 0.73 vs camera 0.86, so appears left-ish, high. FOV 50 vertical, aspect 1.6, moon elevation from camera 22°, camera looking down angle ~23°, vertical relative +45°, maybe outside top! Need calculate camera target y 1.5? camera pos (12,7.5,14), target (0,1.4,0), direction downward arctan 6.1/18.4=18°. Moon vector (-36,18.5,-49), elevation 17.2°, relative to camera forward is about +35° (forward downward -18, moon +17 => +35) outside vertical half 25 maybe top. Put moon lower? Moon at (-21,16,-30), elevation vector (-33,8.5,-44)=9.7°, relative +27.7, near top. Need target perhaps y=1, camera y=6.5 down 17°, FOV 50. To get moon within upper third, elevation should +5 to +10 relative horizontal, relative +22 to +27. Camera default should show it near top. position (-18,15,-28): vector (-30,8.5,-42), elevation 10.4°, relative forward down 17 => 27.4 >25, just outside. increase FOV 55 or camera less elevated. Camera pos (12,6.5,15), target (0,1,0): down angle 16.3; moon (-18,14,-27): vector(-30,7.5,-42)=9.3, relative 25.6, edge. FOV 55 half27.5 => visible. Could position moon (-15,11,-30): vector(-27,4.5,-45) elevation 5°, relative 21°, visible upper. But moon near horizon only 11 high and distance 40. Fine. Maybe moon at (-20,13,-32), relative ~26. Default visible top-left. FOV 55. Camera (12,7,15), target (0,1,0): down 17.8; moon vector (-32,6,-47) elev6.4, rel24.2, visible top 4%. size 2.7 at distance 58 -> 2.7deg, visible near edge. Better camera target (0,1.7,0), down angle 15.6; moon relative 22°, visible 10% from top. Do that. Fire center maybe target y1.3. Default screenshot show fire at center lower, trees. Camera distance 19, angle downward 16, horizon maybe upper third. Moon at position(-17,11,-31) dist 40 vector from cam (-29,4,-46) elev 4.4 rel20, visible ~14% from top. Great.

Need perhaps use CSS overlay for vignette, title minimal and instructions. Requirement scene. Could overlay top-left with "NIGHTFALL" / "Forest Clearing" in modern. But task likely judged visual. An unobtrusive bottom centered hint "Drag to orbit · Scroll to zoom". Maybe top left badge. Use CSS only fonts system. Add gradient vignette pointer-events none. Also maybe dark scene should not be too dark.

Lighting: ambient/hemisphere very low blue. HemisphereLight(0x7289b8,0x11160f,0.55) maybe enough. Directional moonlight intensity 1.3 cool from moon direction, castShadow maybe point enough. Requirements warm point shadow. We can also directional moon shadows? not necessary. Point light shadows works. Ambient so geometry visible. Ground dark teal. Add cool rim from directional light. Use Hemisphere intensity .6. Three color management. MeshStandard materials.

Point light shadows: all tree foliage cast shadows maybe 48* meshes = 150; point light shadow rendering 6 faces heavy but okay. 42 trees*4=168 draw calls x6 =1000 per frame, potentially performance but static shadow map needs update? point light shadow map every frame due flicker, expensive. At 1280x800 sandbox maybe still okay but could lag. Need optimize. Could set pointLight.shadow.autoUpdate = false? But flicker changes position/intensity, shadows don't need change if position static. Intensity flicker doesn't affect shadows; if light position static, shadow map can be static. Requirement point light flickers and casts shadows; yes. Set renderer.shadowMap.autoUpdate maybe false? But directional and point static. However flame animation doesn't matter. Can render one shadow map then autoUpdate false to performance, but if camera doesn't change shadow map camera independent. Point shadow map only needs initial. `renderer.shadowMap.autoUpdate = false; renderer.shadowMap.needsUpdate=true;` after? But with WebGLRenderer shadowMap needsUpdate property can set true and autoUpdate false. Then point shadow static. Yet renderer may not update until first frame? needsUpdate true initial. If lights maybe loaded. Works. But subtle flame flicker position no need. Keep light position fixed (0,1.6,0), intensity only flickers. Cast shadows static. Directional shadow static. Great.

Maybe set only near trees cast point shadows? all. Draw calls still scene 200 plus initial shadow 1200. Fine. If update disabled, good. If point light intensity flicker but not position, shadows same.

Need trees at least 30. Could use InstancedMesh for performance but each tree varying layered; can create each tree group. At least 42. More detail. Static. Need "placed randomly around clearing not on fire". We can ensure radius > 10.5. Maybe 48.

Low poly aesthetic: flatShading, no smooth. Material per tree. We can reuse materials arrays. For each tree use group with trunk mesh cylinder and 3 cone meshes, possibly deciduous? Forest trees ideally pines. Low-poly conifers. Could create tree variants:
- pine: trunk Cylinder 5 segments, foliage 3 ConeGeometry(radius, height, 7) with uneven.
- broadleaf maybe trunk + 3 icosahedron canopies. Variety. Requirement low-poly trees. 44 total, mix 34 pines + 10 broadleaf.
Need random around. Cone geometry apex; overlapping layers. Tree height 4-8. If camera at distance 18 and fire center, outer trees around radius 11-30; visible silhouette. Foreground trees at around camera could block. Place positions but skip if near camera line? We can define radius 11-32, but all. Camera at (12,7,15) is inside ring? distance 19 from center. Trees could be between camera and fire, obscure. We can specifically skip trees within angular sector around camera (angle atan2(z,x) around 0.9 radians) and radius 14-26? But then visible forest in back and sides. At least 30. Could have some foreground at edges but not block. We can check projected positions? Generate tree positions, skip if distance to camera < 4 and line. Any tree between camera and fire could still be side. Better clear a 70-degree front cone toward camera: tree angle around atan2(z,x) = 0.9 ±0.55 at radius 10-32 skip. Then backdrop dense elsewhere. Camera initial view toward center, behind fire is angle opposite camera around -2.24 rad, dense trees. Side edges too. Great. Could also include some foreground side at angle ±0.7 maybe edges. Fine.

Ground clearing radius ~8.5. Trees min 10.5. Rocks logs around fire.

Fog: `scene.fog = new THREE.FogExp2(0x071019, 0.027)` maybe distant at 30 fades 55%, at 60 96%. Distant tree silhouettes. Background same color. Maybe 0.022. Need dark night but visible. `FogExp2(0x07121c, .025)`.

Stars fog false. Moon fog false.

Ground: We need avoid weird large plane with fog and color. Use PlaneGeometry 100,100,40,40 rotated. Need modify z before rotation? Geometry positions x,y plane, z zero. Set z random height using deterministic pseudo-random. After rotate x, local z becomes world? Plane XY, rotate -pi/2 -> local z to world y. Set each vertex z = noise. But center clearing should flat within radius 10. Could set `height = r > 9 ? (sin...)*.18 : 0`, and slight random. Then ground.computeVertexNormals. For flatShading? If shared vertices and normals smooth. Low-poly look wants faceted. Can convert geometry to non-indexed and computeVertexNormals, material flatShading true. PlaneGeometry indexed then toNonIndexed. 40*40*6=9600 verts okay. Assign vertex colors per face? For faceted geometry, non-indexed and assign same color per triangle. We can loop triangles 3 vertices and assign based on random and height. But color attributes can make facets. Need deterministic. Use seeded RNG to make stable.

Base terrain color #13251e, variation. Clearing disk polygon radius 9.2 with perhaps ring geometry outer 12? The terrain under should remain. Create custom irregular polygon disk at y=0.04. Since terrain heights maybe zero within radius 10. Need shape with 48 segments, center + triangles. Use CircleGeometry(9.2,48), modify outer vertices positions radial random .85-1.08, but CircleGeometry position includes center and ring. Since triangles are fan, one color only. Flat material brown. Add inner patch CircleGeometry 5.4,32 maybe at y .065, more ash/dirt dark. Use irregular shape. Colors: outer clearing #2e261c (dark brown), inner #1b1712 scorched. Need visible under warm light. At night point light makes orange.

Could add subtle grass tufts low-poly around clearing, 80 small cone/triangles. Maybe richness. But task doesn't require. Add 70 grass blades as InstancedMesh ConeGeometry? small green triangular. Use low-poly. Could clutter. Add 55 tiny angular grass clumps around radius 6-10. InstancedMesh with ConeGeometry(.08,.45,3), dark green, positions. Each random, rotation, scale. This is visible. Add 45 little stones.

Rocks around fire: 12 Icosahedron detail 0, random scale .3-.9, y partly embedded. Use mesh material gray #3b4443, roughness. Cast receive. Perhaps use Dodeca? Icosahedron nice. Different geometry same. `IcosahedronGeometry(1,0)`. Modify scale with rotations. Add at radius 2.5-7.5. Need not cover logs.

Logs: custom log cylinder. `CylinderGeometry(radius, radius*1.05, length, 6,1,false)`, rotate z pi/2 to align X, then group rotate y. Mesh. Add end discs? Cylinder material array can specify side and caps if material array [side, top,bottom]; CylinderGeometry groups: side index0, top index1, bottom index2. Use dark bark material and cut wood material. Mesh with [bark,cut,cut]. Add smaller branch stub cylinders. Logs placed at radius 3.4, around; fallen logs not on center. Campfire logs: 4 charred logs crossing near center but don't fill. Create at y=.18, length 2.4, radius .18, around center positions offset. Point light shadows.

Maybe stones ring around fire? Add ring of 10 small stones radius 1.4? But campfires often ring. Yet requirement logs around. Could add ring stones dark at radius 1.25, but would obscure flame base. Use small scale .15-.25, y .1.

Flame position starts above logs. Need charred logs at center.

Campfire shadow: PointLight position 0,1.4,0. Shadow camera near .1 far 22. Point light distance 24. intensity maybe 130. physically inverse square; at ground 1.4 gives 66 lux. Mesh ground color gets visible. At tree 10m gives 1.3 lux still. fine. PointLight cast shadow. `normalBias 0.04`; bias -.0002.

Other lighting: Hemisphere 0.45 and Directional 0.65 cool. If too bright, point less dominant. Hemisphere color blue, ground dark. `HemisphereLight(0x6f89bf, 0x10160e, .55)`. Directional at (-10,20,-10) intensity .75 color #8ba7d8. Maybe cast shadow? directional cast false to reduce.

Fire pit details. Add ash circle `CircleGeometry(1.35,32)` at y .085 color #16130f. Maybe emissive slight around edges? no.

Sparks update. Need particles visible with point size 1.8 px maybe.

Could add firelight glow sprite at center, radial gradient canvas, SpriteMaterial additive opacity .35, scale 6, position y1.2. This helps bloom effect without postprocessing, but could wash. Use subtle. Also embers.

Moon glow sprite.

CSS UI: body black. canvas block. `#hint` bottom. top label maybe "EMBERWOOD" tiny uppercase, subtitle. Need not distract. Requirements no external fonts. Use system `font-family: Inter, ui-sans-serif`. A small top-left title in white with accent line. But screenshot likely scene. Could add "NIGHT CLEARING" and "A small fire beneath a wide sky". Yet user didn't ask. Maybe understated. Keep.

Need code ready.

Potential issue Three.js import map exact. Place:
<script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
...
</script>
No other URL. Correct.

Need if network no? allowed. use only exact.

Renderer set pixel ratio min(devicePixelRatio,1.8 maybe 2). performance. `renderer.setSize(innerWidth, innerHeight)`. `renderer.setAnimationLoop(animate)` or request. Use setAnimationLoop. `renderer.shadowMap.enabled=true; type=PCFSoftShadowMap`. `renderer.shadowMap.autoUpdate=false; renderer.shadowMap.needsUpdate=true;` Potential static shadow map issue: If autoUpdate false and needsUpdate true before first render, WebGLShadowMap render checks needsUpdate and then false. Fine. But directional? no cast maybe. Point shadows static. Yet if pointLight added before first render yes.

Controls: enableDamping true dampingFactor .06, minDistance 7, maxDistance 30, maxPolarAngle Math.PI*0.48? Need camera stays above ground. OrbitControls polar angle measured from y axis, 0 top, pi/2 horizon. maxPolarAngle = Math.PI/2 - .07 (86°) ensures camera always above target? Target at (0,1.3,0); camera y = target.y + distance cos(polar), at polar 86°, y=1.3+30*.07=3.4 > ground. Actually maxDistance 30. minPolar .18. `controls.maxPolarAngle=Math.PI/2 - 0.08; controls.minPolarAngle=.12`. target y 1.2. Camera min height target+7*.07=1.7. good. `enablePan=false` maybe keeps clearing center, nice. `autoRotate` maybe no, user can look. Could set autoRotate true subtle? Requirement OrbitControls look around. Better no auto rotate? AI not relevant. Scene static enough with fire. Could autoRotate very slowly? Might cause camera movement and screenshot stable. Maybe controls.autoRotate = true; autoRotateSpeed=.18. But user likely expects manual and it can distract. Not needed.

Need ensure camera stays above actual ground and not clip. maxPolar. minDistance 7.2.

Geometry functions.

Let's map code.

Seed RNG:
let seed=87421;
function rand(){ seed=(seed*1664525+1013904223)>>>0; return seed/4294967296; }
function randRange(a,b)...
This ensures.

Materials:
const bark = new MeshStandardMaterial({color:0x3b2c21, roughness:1, flatShading:true});
const barkDark maybe.
pineMats array colors 0x183b2e,0x1f4936,0x24513a,0x16382f. Need moon blue/dim. Maybe brighter 0x214a36.
leaf broad 0x244637 etc.
stoneMats 0x41494a,0x4a5150,0x343d3c.
ground vertexColors.

Trees. Need create geometry reused to reduce memory and draw same. But each mesh geometry reused. We can create geometries:
trunkGeo = CylinderGeometry(.18,.28,2.5,5,1); translate y=1.25. But tree height varied via group scale. foliage cone geometries unit? Could reuse ConeGeometry(1,1,7), scale per mesh. easier.
For pine group:
height = randRange(4.2,7.2)
trunk height height*.6? trunkGeo unit CylinderGeometry(1,1,1,5) scaled x .18 etc. Use `CylinderGeometry(0.18,0.28,1,5)` translate .5; scale y trunkH.
Need foliage tiers:
bottom y trunkH*.45? Tree total height. Let's define group origin ground. trunkHeight = h*0.72; trunk geometry height1 scaled y trunkHeight.
canopy layers 3:
layer radii [h*.25,h*.20,h*.14], heights [h*.48,h*.42,h*.35], positions bottom etc.
Cone geometry unit radius1 height1 centered y0; translate y .5 so base at0. mesh scale radius, height, position y = trunkH*.35 + i*h*.16 maybe.
Let's derive: trunk from 0 to .65h. bottom foliage base y=.28h, height .50h (tip .78h). second base .46h height .44h (tip .9h), third .62h height .37h (tip .99h). radii .27h,.22h,.165h. Good.
tree total h. Add trunk tip maybe hidden.
Pine foliage positions.
Group scale? We already h dimensions; can group scale 1. Use random.
Broadleaf trunk .62h, canopy 3 Icosahedron radius. Maybe add 3 ico lumps, positions around top. But branch. Use IcosahedronGeometry(1,0). Total canopy spread .28h. This makes variety.

Placement count maybe 48. angle. We want dense ring. Use radius = 11 + pow(rand,0.7)*23. If 48 around circumference average. At radius 11 circumference 69 /48 spacing1.4 dense. Outer too. But skip camera sector 18 trees means 30 remain. Need 36 remaining. Use 56 trees, skip maybe 15 =>41. At least 30. Better generate 52, skip camera wedge ±0.5 at radius>?? about 9 trees =>43. Good.
Angles random but even spacing+jitter ensures natural but dense. `angle = i/52*2pi + randRange(-.18,.18)`. But camera wedge skip ~9. 43. This positions around entire ring. radius random 11-30. There may be gaps. Additional distant silhouettes maybe 26 at radius 34-46 to fog. Requirement at least 30; if count metadata no. Could create 54 trees plus 24 far trees, total 78 but draw calls huge (300). Initial shadow only point. fine but scene render 300 draw calls. Fine but scene render 300 draw calls. Could use InstancedMesh for far silhouettes. Simpler.

Maybe create 44 trees in ring, skip wedge ~6 =>38. Enough. Add 22 far trees at radius 32-44 with lower detail one cone? Could use same group. total60. Good.

Need make 30 easily visible. Some are behind camera and not in initial view but around.

Set tree positions random ring. To avoid uniform, angle random for all 58. camera wedge only skip if r<27? Actually foreground wedge. If far r>27, may still cover upper? Keep some side. Skip angle diff <.42 and r<29. roughly area 0.84/6.28=13%, ~7.5. 58->50. Great.

Potential camera is at angle atan2(15,12)=0.896. skip ±0.5. 18% =>10. 48 left. good.

Could ensure count displayed? no.

Tree cast shadows. Shadow render 6 maps each with ~240 meshes, initial heavy but one time. okay. renderer shadow map point 1024 = six faces 6M pixels. Fine.

Could use `castShadow = radius < 22` for near trees, far false. still casts many. Point light shadow camera far 20 anyway so far trees irrelevant. Set all cast only tree radius<18 maybe. Requirement campfire casts shadows visible. Nearby rocks/logs/tree maybe. A tree at radius 11 casts. 20 trees x4.

Fire light shadows: PointLight shadow camera near0.5 far18, map512 maybe enough; 1024 better. Since static one time.

Need maybe add a warm translucent "light pool" disc on ground? Point light does actual.

Flame geometry. Write function makeFlameGeometry(seed offset):
radial=6, rings positions [0, .35, .8,1.25,1.6?], tip. For each ring create radial vertices. Could use custom BufferGeometry with triangles manually. Need shape taper, twist. Use rings array of {y,r,x}. at final tip ring radius .02. rings count 5. For each radial angle add x=cos*r + drift, z=sin*r*0.8, y. Ring around local. Build triangles between rings, base cap maybe. Use indexed? Flat shading via computeVertexNormals. If indexed flatShading causes material flat shading with derivatives? MeshStandardMaterial flatShading works shader flat normals even indexed likely. fine.
Need each flame geometry unique? Create one and clone? Shape can same with scales. To give leaning, group or mesh position. We can create geometry base one, clone maybe. Unique not essential. Outer flame 5 meshes, scale. Materials shared. All same shape but rotation/scale. animation rotation provides.
Maybe geometry has lean in x. We'll make 3 geometries variants.

Flame material outer `MeshBasicMaterial({color:0xff4d0a, transparent:true, opacity:.85, blending:THREE.AdditiveBlending, depthWrite:false, side:THREE.DoubleSide, fog:false, toneMapped:false})`. Additive can saturate yellow. Outer red edges, inner gold. `toneMapped:false` colors exact bright. Place outer meshes with scales .55 etc.
Maybe 4 outer orange, 3 inner yellow, 1 core pale. At base y=.24. But inner should overlap.
Core shape height 1.1 and radius .35 at base. `MeshBasicMaterial(0xffd35a)`, opacity .95.
A flame mesh scale x=.65,z=.65, y=1.0. Outer geometry base width .5 height1.6. At scale .8 => height1.3. Main should height2.1. Let's geometry height 1.8, base radius .38. Main scale (1.1,1.35,1) =2.43 high. position center? base at local0. Great. inner scale .6,.8. Multiple. Group flame at xz around center ±.25.
A single flame mesh around x .15, z .1. Others around.
`flameRoot` position y=.25.
Need depthWrite false and renderOrder perhaps 3.

Animate flame with `mesh.scale.set(base.x*(1+...), base.y*(1+...), base.z...)`; rotation.y; rotation.z lean; position slight. Since geometry already curved. Use phase.
Could switch flame opacity slightly.
Also light flicker via combined sin: `light.intensity = 90 + 18*sin(t*13)+10*sin(t*21.7)+randomish`. Ensure nonnegative. `light.position.y = 1.25 + .08*sin`; changing position would invalidate shadow update static; shadow map from position previous. Tiny change acceptable but map autoUpdate false would be slightly off. Keep position y1.35 fixed. Flicker intensity only.
Maybe set point light `shadow.autoUpdate`? `renderer.shadowMap.autoUpdate=false` static. The light intensity changes but shadows no. okay.

Sparks update with dt clamp .05. positions color per life? Color fixed orange/yellow. Need BufferAttribute DynamicDrawUsage. Each particle has pos and vel; life max. For deterministic, initialize.
Use `PointsMaterial({size:.09, sizeAttenuation:true, map: sparkTexture?})`. Without map points are squares. Could create circular radial gradient canvas texture for soft spark. But "simple particle system", okay. Use CanvasTexture glow to make round. Internal. PointsMaterial alphaMap maybe same texture, transparent, blending Additive. We can create radial texture once and use moon? moon glow separate.
`makeGlowTexture` radial gradient white center transparent. For points material map texture. color vertex. opacity .9 depthWrite false. size .12 maybe at 15m appears 1-2 px. use .13.
Particle positions from base around .3, y update 1.5-3.0/s, x drift .15. life 1-3. At y>5 reset. Some sparks rise. Gravity? buoyancy. vel y 1.5-3, add .4 dt. x sin.
Need initial positions all along trails. Set particle age random; update position based on age? We can initialize y=rand*4, x offset etc. velocities. Then update. Good.

Additional fire embers perhaps 80 points.

Stars. `BufferGeometry`, positions on sphere radius 70. Need ensure sky not affected by fog: PointsMaterial fog false. Colors. size 1.4 maybe with sizeAttenuation false (pixel). In PointsMaterial, `size` in pixels if attenuation false? According docs size in pixels. If true world units. Set size 1.6, sizeAttenuation false. There are 700. On 1280x800 visible. color #b8d0ee and some white. opacity .85. Could twinkle by material opacity sinus but global. Individual twinkle would require shader; can animate opacity global subtle. Could set stars.rotation.y=t*.002.

Moon: sphere material Basic. Position. Need moon texture maybe procedural canvas with craters? Create canvas 256x128 equirect? Simpler sphere plus crater spheres? At distance craters not obvious. Could use generated canvas texture on sphere with random dark circles. That's internal and good. Create canvas 512x256, fill #dce4df, random craters low opacity, add noise. But random canvas 2D. Texture wrap, colorSpace SRGB. MeshBasicMaterial map. SphereGeometry(2.4,24,16). Since moon at distance 40, enough. Add glow sprite.
Could show full moon. `moon.rotation.y=.7`.
Glow sprite: SpriteMaterial map radial, color 0x9fb9d8, transparent opacity .22, depthWrite false, blending Additive. scale (9,9,1). But sprite at same pos may obscure moon with center bright, okay additive.

Cloud wisps? no.

Fire glow sprite position (0,1.15,0), scale (5,4,1), orange, opacity .18. The sprite camera-facing could cover scene but nice. Since depthTest true? glow in front but center. depthWrite false, blending Additive. `depthTest:true`. Might overlay logs. Fine. Set opacity .12.

Potential order of transparency for flames and glow. renderOrder.

Low poly rocks: create materials. Place around clearing. Scale. Use mesh rotation.
Grass instanced. Need one geometry and one material. `InstancedMesh(grassGeo,grassMat,90)`. Each dummy. Color maybe per instance `setColorAt`. Color palette. At radius 5-10, exclude center. cast false. cone 3 sides.
Ground decorative twigs? no.

Maybe a stone fire ring: 11 rocks radius .85 around center? Campfire base. But logs around at radius 2.7 and center logs. Stone ring can make polished. Use small stones around radius1.0, scale .16-.28. However flame base and logs inside. okay.

Fire pit logs at center. Make charred logs via `makeLog` function with bark material and cut ends. Need use same cylinder geometry unit. Function:
const logGeo = new CylinderGeometry(.17,.23,1,7,1,false);
function addLog(x,z,length,angle,y=... ,mats):
group; mesh geometry scaled? geometry has height1 along y. rotate z pi/2. Then length along x. `mesh.scale.y=length` if rotated? Scale before rotation local y scales length then rotation. yes. geometry radius fixed .18. For different radius, group scale? Use `mesh.scale.set(radScale,length,radScale)` then rotate z. But geometry already radii. easier group.
mesh.position.y=y; rotation.z=Math.PI/2; rotation.y=angle (Euler order XYZ means after z? Order impacts axis; rotate around y after? Could group outer: group.rotation.y=angle; mesh.rotation.z=PI/2. so length along group's local x. good.
end caps material index.
Branches use cylinder geometry etc.
Fallen logs outer: length 3-5, radius .22, y=.25, group at x,z.
Center campfire logs: 5 logs length 2.3 around origin, groups positions offset .1, angle i*... y .18-.35; use charred material dark #211913 and cut dark #312119. This is "teepee".
Add perhaps embers glow small spheres at base? 8 ico orange emissive.

Shadow. Renderer autoUpdate false: Need ensure renderer shadowMap update after objects all added. Call `renderer.shadowMap.needsUpdate = true;` just before animation. Since autoUpdate false, first frame will render. There is `renderer.shadowMap.autoUpdate = false` and `needsUpdate=true`; okay.

But if point light shadow map static and light intensity varying, shadow opacity remains but ground actual direct lighting flickers. The shadow darkness from occlusion static. okay.

Maybe animated sparks cast no.

Need no console errors. `OrbitControls` import path valid. `renderer.outputColorSpace=THREE.SRGBColorSpace` valid. `THREE.ACESFilmicToneMapping`. `THREE.ColorManagement.enabled` default.

Need handle resize listener.

Need UI details:
HTML body:
&lt;div id="scene"&gt;&lt;/div&gt; maybe renderer append.
&lt;div class="vignette"&gt;&lt;/div&gt;
&lt;header class="masthead"&gt;&lt;div class=eyebrow&gt;&lt;span class=dot&gt;&lt;/span&gt; NIGHT STUDY / 01&lt;/div&gt;&lt;h1&gt;Emberlight&lt;/h1&gt;&lt;p&gt;Forest clearing · after midnight&lt;/p&gt;&lt;/header&gt;
&lt;div class=hint&gt;&lt;span&gt;Drag&lt;/span&gt; to orbit &lt;i&gt;•&lt;/i&gt; &lt;span&gt;Scroll&lt;/span&gt; to zoom&lt;/div&gt;
Maybe overlays could obscure. Top-left title small. The scene itself should be focus. Could include a top right mini status "LIVE • 21°C"? no.
Let's use simple:
&lt;div class="caption"&gt;&lt;div class="kicker"&gt;&lt;span&gt;&lt;/span&gt; NIGHTFALL&lt;/div&gt;&lt;div class="title"&gt;Emberlight&lt;/div&gt;&lt;div class="sub"&gt;forest clearing&lt;/div&gt;&lt;/div&gt;
bottom-left maybe coordinates? no.
Hint bottom right.
CSS with text-shadow, pointer-events none. `#caption` position top 28 left30. At mobile scale down.
Vignette radial gradient transparent center to rgba black .45 edges plus linear bottom. CSS pointer-events none.
Noise overlay? CSS SVG data URI forbidden? No external but data image? inline allowed, but not needed.
No click-to-start required because not a game. Keyboard not used.

Maybe OrbitControls listens pointer only, iframe receives keyboard after click irrelevant.

Need camera initial and maybe responsive. On narrow viewport, camera aspect adjusts and same FOV vertical means narrow crops scene horizontally but okay. Could adjust camera position distance based on aspect on resize? Requirement adapt gracefully down to375. At mobile, vertical FOV 55 and aspect .47 shows narrower horizontal. Fire centered and trees partial; okay. But UI. Could adjust camera fov maybe 62 on aspect&lt;0.8. `camera.fov = innerWidth &lt; 600 ? 62 : 52; camera.updateProjectionMatrix()`. This shows more vertically, not more horizontal because aspect fixed; horizontal FOV ~32°, still narrow. Could move camera back on mobile to show clearing. But OrbitControls distance. Set initial distance maybe 19. Mobile can zoom. Could adjust camera position radial to 24 for aspect&lt;.7. In resize, maybe don't change user camera unexpectedly. On initial set if aspect&lt;.8 camera position (15,9,19) target. Do before controls. Fine. 375x? screenshot maybe mobile. Make fire center.

Need perhaps count trees? Could add console log no.

Let's ensure 3D scale and screenshot composition. Camera (12,7,15), target (0,1.25,0), distance19.2, FOV52. Fire at screen center slightly lower. Ground clearing radius9 appears around. At camera height7, perspective. Trees at radius11 and h5-7. Dense back. Moon top-left.

Moon location: (-18,11,-30). Camera to moon vector (-30,4,-45). camera forward to target (-12,-5.75,-15) normalized. Dot angle maybe angle between vector lengths:
forward y -5.75 /19.2=-.30.
moon vec len54.4, normalized (-.552,.074,-.827). Dot forward normalized (-.624,-.299,-.781): .344 - .022 +.646=.968 angle14.5°. Great! Wait forward horizontal but downward, moon above horizontal. Dot angle 14.5, not 25 due 3D. So moon appears within. Vertical component depends camera up, likely 13°, okay. So nice.
Directional light from moon direction maybe `(-18,18,-30)`, intensity .65.

Terrain extent 100. Fog at 30 significant. Horizon due plane 100. Sky background fog color.

Need maybe add distant mountain silhouettes? Not required; trees fade. Could add polygon ridge in far background but orbit 360 maybe. Not needed.

At least 30 low-poly trees randomly around clearing. Randomness could be deterministic. use 52. Let's count placements and ensure at least 30 even with skip. We can set count 64 and skip wedge =&gt;~52. Draw calls 64*4=256. okay. Use 58 total. Maybe enough. Use 56.
For each tree group:
const isPine=rand()&lt;.78.
Ground y from terrainHeight(x,z) maybe terrain 0 inside r10 and height outside. tree base set y=terrainHeight. Need terrain heights low. For radius 11, h maybe .1. Set group.position.y=h.
Rocks also y.
Logs on clearing flat.

Ground height function in code should match actual random? Use analytic deterministic `Math.sin(x*.17)*Math.cos(z*.13)*.12` outside radius, but the mesh vertex random won't match. Trees may float/sink .1 only. Better ground base plane flat entirely with vertex color facets, no displacement except very subtle. Then groundHeight=0. Could set terrain geometry z heights using deterministic function and tree y same function. Implement `terrainHeight(x,z){ const r=Math.hypot(x,z); if(r&lt;10.5)return 0; return (Math.sin(x*.23)*.7+Math.cos(z*.19)*.5+Math.sin((x+z)*.11)*.6)*.12; }`. Geometry positions grid spacing2.5; trees sample function not exact, but close. Ground variation max .2. okay. Use same grid? no matter.

Create terrain:
const geom = new PlaneGeometry(100,100,50,50); for each vertex local x,y set z=terrainHeight(localX,localY) (plane local y maps world z after rotate -pi/2 but rotation causes world z=-localY? Plane local y -> world z=-localY after rotate -pi/2: x'=x, y'=z, z'=-y. So terrainHeight(x, world z=-localY) if matching. But tree world positions. We can rotate geometry first then modify position y. `geom.rotateX(-Math.PI/2);` then positions x,y,z, set y=terrainHeight(x,z). compute normals. toNonIndexed then face colors.
`geom = geom.toNonIndexed()` after rotate and y. compute normals. Loop i+=3, choose color based on random and center? vertexColors face. Base color terrainMat white and vertex color. Use Color clone and offsetHSL. For each triangle set same color. Need random face variation. We can set rgb. `const c=new THREE.Color().setHSL(.34+..., .25, .07+...)` dark. Night lit. colors perhaps visible. A Color HSL .35 .3 .09. Good.
Then clearing overlay. Circle geometries horizontal. Need CircleGeometry lies XY, rotateX(-pi/2). `CircleGeometry(9.5,48)`, modify outer vertices after rotate? position after rotation x,z. radial jitter. But center and ring. Outer radius. To avoid a perfect fan, okay. Could make custom disk with concentric rings to low-poly facets. Let's implement custom `makeClearing(radius, rings, segments, colorA, colorB)`:
Geometry positions triangles per cell with center? Grid in polar rings. Need no seams. Could use RingGeometry(inner,outer,seg,1) + center Circle; but circular not irregular.
Simpler custom triangulated irregular:
Create vertices for radial rings r=[0, .33R, .68R,R], segments 48. For each ring each angle, radius factor = 1 + sin(angle*3+phase)*.04 + rand? Vertex deterministic function. positions. Center one vertex. Triangles between rings. Build indexed BufferGeometry. But with indexed and flatShading? Material flatShading true. Compute normals; shared smooth maybe flatShading forces face normals? In modern three flatShading define derivatives independent of normals, so faceted. Vertex colors per triangle requires nonindexed. Make triangles manually:
function createPatch(radius, y, color1,color2):
rings 4, seg 48.
grid[r][s], angle jitter maybe. For each ring radius base r, edge perturb. Create positions array and colors per triangle by push 3 vertices and same color. For ring 0? Create center then ring 1. Easier create each quad as two triangles with duplicated vertices; for each cell r0,r1 and s,s+1, but center cells have degenerate if r0=0 repeated? Make center fan separately then rings 1..3.
- center vertex (0,0).
- ring vertices for r=1..4 each 48.
- center fan triangles center, ring1[s],ring1[next].
- for each adjacent rings triangles.
Set y plus small per-vertex? Could vary .01. color each face.
Boundary radial jitter based on angle and ring; radius factor `1 + .04*Math.sin(a*5+phase)+.02*Math.sin(a*9)`, mostly outer. At inner rings maybe lower.
Colors: outer brown #2c251c, inner #211a13. Different per face.
This patch overlays terrain and has faceted warm brown.
Need perhaps circle edges too geometric but low poly desired.

Make patch with radius 9.4 rings4 segments40. Inner ash patch radius4.5? maybe use same with color #17130f, #1c1511 and y .1. But central logs. Do.

Grass clumps ring radius 6-10 but patch outer 9.4. use 75.

Rocks: positions radius 2.7 to 8.5, avoid log areas maybe random. scale .25-.75. Put 16. color. group y scale*.45? Icosahedron centered origin radius1. Set position.y=scaleY*.62, so lower half buried.
Rock cast receive. Big rocks near edges.

Logs outer positions radius 3.5-6.5, angle. lengths 3-4.5. Could conflict. Use 5 angles. Each group position. orientation tangential maybe angle+π/2? nice.
Center logs. Need logs around campfire maybe outer logs.

Maybe place two leaning branches near fire.

Fire char logs group. use `createLog` with radius param. Cylinder geometry radii fixed .? Could create per function geometry? Reuse unit CylinderGeometry(1,1,1,7), scale x radius, y length, z radius. But cap material groups and scaling. Geometry unit radius. Great. mesh.scale.set(radius,length,radius); rotate z. At cap, same group materials array [bark,cut,cut]. If different radii ends not important.
`const logGeo = new THREE.CylinderGeometry(.82,1,1,7,1,false);` Then scale radius. Add branch using same geometry with branch material, etc.
Cylinder local y length, rotation z => y axis maps x negative maybe. good.
Log mesh position y=radius+? If horizontal, center y radius .2. `group.position.y=.2`.
For center charred logs crossing at y .22 + i*.04. length2.4, radius.17.
For outer fallen logs group y=.24 radius .25.
End grain maybe `cut` material pale #6a4a2b, can see.

Campfire flame root y .35. Logs in center.

Point light at y1.5. `distance=20`, decay2, intensity perhaps 150. Let's assess physically: MeshStandard with light intensity candela. Irradiance at 1m 150 lux? `intensity/distance^2`. at ground1.5 = 67. At 10m=1.5. Ground albedo .12 appears .1 maybe visible. Camera exposure 1.1. Fine. 90 at2=22.5. Use 110.
Hemisphere .5.

Renderer toneMappingExposure 1.1.

Stars and moon unaffected by fog. Scene background 0x050b14. Fog same 0x07111b.

Sky maybe gradient impossible. Background solid. Add huge sphere with ShaderMaterial gradient? Could implement to enhance: sky dome radius 80, BackSide, ShaderMaterial vertex world y and fragment mix bottom/top. But fog? custom no fog. This is no external. Great. A sky sphere with gradient dark navy at horizon to near black overhead. But stars rendered on top. Use `SphereGeometry(75,32,16)` and ShaderMaterial side BackSide, depthWrite false. Vertex shader pass normalized position; fragment mix. Uniform colors. But stars at radius60 inside sky sphere radius75. Fog doesn't affect sky. This looks nice. `scene.background` dark for maybe. Shader code:
varying vec3 vWorldPosition; worldPosition=modelMatrix*vec4(position,1); fragment h=normalize(vWorldPosition+vec3(0,20,0)).y; color mix(bottom #0d1b28, top #02050c, smoothstep(-.15,.8,h)); 
Since sphere centered origin, world y. Camera inside. Use `fog:false` no.
However if sphere depthWrite false but depthTest true and rendered maybe after opaque? Shader transparent? Set side BackSide, depthWrite false; it might render after stars due sorting opaque order? Stars Points transparent, sky opaque render first. Good. `renderOrder=-10`.
Scene.background 0x050a12. Fog color 0x08131d.

Moon at radius40 inside sky.

Add distant ground around. Good.

Fire glow canvas texture creation. `createRadialTexture` with 128x128, gradient center rgba(255,255,255,1), .2 rgba(255,255,255,.55), .55 .12,1 0. For colored material color. `CanvasTexture`, colorSpace SRGB. Map used both glow and points maybe map. For sparks map and alphaTest? PointsMaterial transparent. Use map with alpha.
Moon texture generated.

Need perhaps shadow receiving point light; all.

Ambient dust/mist? Add low-opacity large horizontal planes? no.

Animation:
const clock=new THREE.Clock();
let elapsed=0;
function animate(){
 const dt=Math.min(clock.getDelta(),.05);
 elapsed += dt;
 controls.update();
 flames.forEach...
 light.intensity=110 + ...
 fireGlow.material.opacity=.11+.03...
 stars.material.opacity=.75+.08 sin
 updateSparks(dt)
 renderer.render(scene,camera)
}
`renderer.setAnimationLoop(animate)`.

Flame base properties. For each `mesh.userData={baseScale: mesh.scale.clone(),phase,baseX,baseZ}`.
When updating if position changed. `mesh.rotation.z = Math.sin(t*2.6+phase)*.11; mesh.rotation.y=t*.35+phase;` `scale.y = base.y*(.88+.16*sin... + .07*sin...)`; `scale.x=base.x*(.94+.08...)`.
For outer material shared opacity cannot vary individually. fine.
Could `mesh.position.y=.26 + .04 sin`.

Light flicker deterministic:
`const flicker = 100 + 14*sin(elapsed*11.3)+9*sin(elapsed*19.7)+6*sin(elapsed*31.1+1.3); light.intensity=flicker;`
Could go 71-129. Fine.
Light color maybe vary `light.color.setHSL(.065 + .008*sin, .95, .58)` but base orange. Set once.

Sparks: each data {pos Vector3, vel Vector3, age, life}. Since positions buffer direct. We can arrays of objects with x,y,z,vx,vy,vz. update.
At reset x=randRange(-.35,.35), z, y=.35; vx rand etc, vy1.6-2.8, life1.4-3.2 age0.
Initial distribution: for each, set age rand*life; then approximate y = .3+vy*age - .15 age^2; x = x0+vx age + sin(...)*.1. update.
Each frame age += dt; if age>life or y>6 reset. x += (vx + sin(elapsed*4+phase)*.18)*dt; y += vy*dt; vy += .18*dt; z similar. `positionAttr.needsUpdate=true`.
At 3 sec visible.

Maybe particle sizes all same. fine.

Could animate moon? no.

Need make tree placement random and count. We could add `scene.userData.treeCount`. no.

Potential issue moon SphereGeometry receives directional? MeshBasic no.

Potential issue use `THREE.MathUtils.seededRandom` maybe available, but use own.

Potential issue `PointsMaterial` has `fog` property? Material has fog true but PointsMaterial constructor recognizes? yes `fog` property inherited but setValues checks property exists? Material has fog? Material doesn't, but PointsMaterial has fog property likely yes.

Potential issue `CanvasTexture.colorSpace=THREE.SRGBColorSpace`.

Potential issue SpriteMaterial map plus color. okay.

Potential issue shadows with `renderer.shadowMap.autoUpdate=false` and `needsUpdate` true. Since `needsUpdate` setter on shadowMap? WebGLShadowMap has needsUpdate property. yes.

Need maybe `renderer.shadowMap.needsUpdate = true` after all. There may be context issue initial frame. Fine.

Need animation while page hidden no.

Need user can orbit. controls target. `controls.screenSpacePanning=false`. `controls.enablePan=false`.
Need camera above ground: maxPolar. Also `controls.minDistance=8; maxDistance=28`.
Maybe controls target fixed at (0,1.2,0).

Responsive initial camera:
const isNarrow=innerWidth<600; camera.position.set(isNarrow?15:12,isNarrow?8.5:7,isNarrow?19:15);
But if 375x800, aspect .47. Fire circle width maybe crop; vertical huge. Camera distance22.5 and FOV58 gives horizontal FOV 29°, clearing radius9 requires 43°, still cropped. Could increase distance 32 but maxDistance28. For narrow, position radius30, height13 maybe. Distance31.9 > max28 controls clamps on update to28. Set maxDistance=36. At distance30 horizontal view width 2*30*tan(14.5)=15.5, still circle diameter18 slightly cropped. good. FOV60, horizontal=30.7 width16.4. Put distance32 gives17.5. Fire with logs fills. Set camera (18,12,22) distance31; target distance30.6. maxDistance36. For mobile initial. Desktop (12,7,15) distance19.
At 375, vertical FOV60, huge sky/ground; target center, fire visible. UI.
`controls.maxDistance=38`. desktop user can zoom out.
Min polar max. Camera y mobile 12 with target1.2, polar angle arccos(10.8/30)=69°, above.
At desktop polar arccos(5.75/19.2)=72.5°.

Maybe scene central fire appears small desktop due wide clearing. Camera distance19, FOV52, vertical height visible 18.6 at target; clearing diameter19 exactly, good. Fire height2 ~11% screen, nice. Trees visible.

Need maybe enlarge fire flame 2.4. yes.

Add a subtle bench? no.

Need forest clearing. Outer ground trees.

Could create fog bank planes? no.

Let's formulate complete HTML. Ensure exactly fenced code no extra.

Let's further reason on Three colors and night. MeshStandard ground dark under light. Hemisphere .55 may produce blue-green. Tree materials dark. Moon direction intensity 1.0 maybe enough. At night should be dark but visible. Set `renderer.toneMappingExposure=1.15`.

Lighting:
hemi 0x779bc9 /0x11170f intensity .65.
directional 0x9db8e8 intensity .8 position (-15,22,-18).
point 0xff7a24 intensity 120 distance 22 decay2.
Ambient perhaps .12.
Tree leaves dark green. Need screenshot not black. Hemi can illuminate.

Sky shader colors #091522 horizon and #02040a zenith. Fog color 0x08131c. Scene background.

Terrain colors: HSL .34, .25, .10 = RGB maybe dark green. Under hemi .65 visible.

Moon at (-18,11,-30). Full moon radius2.4. glow.

Stars size 1.25 pixels, 800. They might be too sparse but sky visible. Use 950. On 1280x800 around half sky visible ~150. good. Colors. No need fog.

Cloud? no.

Maybe low poly mountains at horizon to add depth. Requirement fog distant trees. Trees enough.

Tree far at radius 35-45; due fog, silhouette. Height maybe 5-8. Camera view horizon. Great.

Need create 56 trees but far positions. Use i count 62. Camera wedge skip. Set radius:
if i<42? ring near radius 11-22. else radius 26-42. Random angle. But random leaves perhaps uneven. Use angle i golden angle + jitter. `angle=i*2.39996+randRange(-.28,.28)` distributes. skip camera wedge if r<30. ~16%. 52 remain. Good. For far 22 trees, height 4.5-7.5. Near 42 h4.5-7.0. radius near10.8-23. The camera line clear.
Actually angle golden distribution.

Tree broadleaf geometry lumps; place 18%. Need low poly.

Ground patch: clearing radius9.2, trees min10.7.

Trees should cast shadows only near <20. Point light shadow far20. `mesh.castShadow = treeRadius < 18.5`; but trunk and foliage. Could set group traverse. Near trees around 10-18 ~16, 64 meshes. Fine.
Far tree receives directional? no shadows.

Point light shadow map 1024 six faces. static. Fine.

Need ensure objects added before `shadowMap.needsUpdate`; yes.

Sky material shader no compile errors. `varying vec3 vLocal; void main(){vLocal=position; gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);}` fragment normalized position. Since sphere centered, enough. `float h=normalize(vLocal).y; float horizon=smoothstep(-0.12,0.55,h); vec3 col=mix(bottom,top,horizon); gl_FragColor=vec4(col,1.0);` output color space/tone mapping? ShaderMaterial colors are linear; chosen hex-like values need linear? We use vec3 values direct interpreted linear, will appear brighter after output transform. Use small values .006 etc. For desired #0d1b28 sRGB linear around .004,.011,.021. Set bottom vec3(.006,.014,.025), top(.001,.002,.006). After tone mapping maybe dark. Fine.
Could add stars as PointsMaterial with toneMapped=false to stay bright. `toneMapped:false`.

Moon Basic toneMapped false. Glow sprite toneMapped false.

Fire Basic toneMapped false.

Ground standard under ACES.

Maybe warm ground too saturated due point light color. good.

Need create sky before stars.

Need moon texture canvas. Drawing random circles:
ctx.fillStyle '#d6ddd5'; fillRect. Add 80 craters with rgba(80,100,105,.12), random radius 2-18, positions. Use seeded rand but note global sequence. Since tree placements depend, doesn't matter. Could use separate local RNG to avoid. Use Math.random for texture? deterministic preferable. Use local `texRand`.
Canvas 512x256, sphere UV. Crater edges. Add subtle stripes? no.
Moon material map and color white, fog false.

Glow radial texture reused spark and moon? If spark material map color vertex colors; CanvasTexture white. For moon glow use same. Color.

Fire glow `SpriteMaterial({map:glowTexture,color:0xff5a10,...})`, opacity .25. Scale 4.6.

Need sprite may be affected by fog? SpriteMaterial has fog? property maybe true; set fog false.

Potential rendering transparency sorting: fire glow sprite transparent, flames transparent; sprite could render after and overlay orange center. Additive subtle.

Decorative ash circle. Could create patch radius4.3 y.11. Need logs visible.

Grass InstancedMesh. Cone geometry height1 base radius1? `ConeGeometry(.11,.55,3)`, translate y .275. For each place around radius6.2-10.4, y .05. scale random. setColorAt with palette. Instance matrix. `grass.instanceMatrix.needsUpdate=true`. `grass.receiveShadow=true` but instanced mesh supports. cast false.
Rocks at patch. Stone materials. Use 18. each geometry unit shared. random orientation. Colors perhaps set per mesh material array.

Could add "moss patches" as flat low-poly discs dark green around outer clearing. maybe unnecessary.

Fire base ring stones. 10 stones radius .95? logs in center around .4. Add small at radius1.15. Scale .16-.28. But point flame base xz maybe. Good.

Campfire logs around center maybe logs length2.1 crossing. Stone ring might overlap logs. Real camp. Keep logs crossed and small stones just beyond.

Outer logs around at radius4.0. Need logs around fire.

Maybe seat logs should be recognizable. Use 4 logs:
angle .3 radius4.2 length3.6 orientation angle+1.2
2.2 radius5.3
4.1 radius4.5
5.6 radius6.0.
At nighttime lit.

Let's code.

Need ensure camera can see moon after tree skip. Trees skip camera wedge; moon direction from camera to (-18,11,-30), angle in xz vector(-30,-45), world polar atan2(-45,-30)=-2.16 rad. Camera wedge tree skip around 0.896, so no trees on moon. Great.

Fog stars: fog doesn't affect sky. Point.

One concern `scene.fog = FogExp2`; MeshBasic fire set fog false. moon false. Stars false.
One concern point light shadow camera includes sky sphere? sky `castShadow=false`.
Point light shadows of grass no.
Directional moon no shadow.
Renderer clear color scene background.

Maybe point light can't cast shadow if renderer shadow autoUpdate false? initial render yes.

Use `renderer.shadowMap.needsUpdate=true` after renderer first? Let's verify WebGLShadowMap.render:
if (scope.enabled === false) return;
if (scope.autoUpdate === false && scope.needsUpdate === false) return;
...
scope.needsUpdate=false;
So first render with needsUpdate true works. Fine.

Resize updates pixel ratio maybe. `renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.75))`. On resize set.

Need fallback message if WebGL unsupported? Could add catch around renderer creation and display. But code after if failure. Not needed, but no console errors. Could handle:
let renderer;
try { renderer=...} catch(e){ document...; throw e;} would console error. skip.

Need maybe `window.addEventListener('resize', onResize);`.

OrbitControls handles touch. CSS `touch-action:none`.

Need HTML accessible.

Let's think of requirement exact import map only URL. The code has no URL other than import map. CSS no external. Good.

No localStorage etc.

Now write final code. Need perhaps include title meta. Great.

Let's formulate.

Detailed code order:
1 imports
2 DOM refs
3 renderer, scene, camera
4 controls
5 sky
6 lights
7 materials/geometries
8 ground patches
9 trees
10 rocks logs
11 fire
12 sparks
13 animation resize

Potential `THREE.Color().setHSL` for face colors and multiply? fine.

Ground geometry color faces:
const non = new PlaneGeometry(100,100,44,44).toNonIndexed after modify? We need modify indexed heights then toNonIndexed. `terrainGeo.rotateX`; positions each. setY. `computeVertexNormals`; then `const terrainFacets=terrainGeo.toNonIndexed(); terrainFacets.computeVertexNormals();` then color attr.
Loop face i +=3:
const tone=.085+rand*.025; const c=new Color().setHSL(.33+rand*.04,.22+rand*.08,tone); setXYZ.
FlatShading true material. Because nonindexed compute normals each triangle. good.

Patch custom function uses `positions` and `colors` per triangle. Need geometry `BufferGeometry` with position and color attributes, computeVertexNormals. Since nonindexed duplicated each face, normals per face. Function pushTri with color. To avoid exact same color per face. We can create vertices.
Detailed:
function makeGroundPatch(radius,y,innerColor,outerColor,rings=4,seg=40) {
 const positions=[],colors=[];
 const vertex=(r,a,ring)=> { const jitter = 1 + .035*Math.sin(a*3+.7)+.02*Math.cos(a*7); const rr=r*jitter; return new Vector3(cos(a)*rr, y + (ring? randRange(-.012,.012):0), sin(a)*rr);}
 center at 0.
 for s: push center, v(ring1 s), v(ring1 next), color maybe inner
 for ring1..rings-1:
   a0=r/ring, a1...
   triangles v(r0,s),v(r1,s),v(r1,next); v(r0,s),v(r1,next),v(r0,next)
 color mix based ring ratio and face random.
Use `pushTri(v1,v2,v3,color)` with same color. Need avoid random per vertex.
Colors use THREE.Color lerp.
At y offset.
Maybe ring1 center fan points all same radius ~2.5. Great.
No need circular inner patch? create outer patch radius9.4 y.035 inner #282319 outer #1d251b? Clearing should brown; outer edge dark green blends. Inner patch radius4.4 y.075 #241a12 outer #15110d. Ground triangle appears under.
First patch colors inner 0x33291d outer0x24251a maybe. Inner brown. Let's set outer patch inner `0x33291d`, outer `0x25251b`, radius9.5. This creates transition. Ash patch inner0x21170f outer0x17120e radius4.2.

Patch boundary at ring jitter could overlap.

Grass color 0x294334 maybe.

Tree materials:
`new MeshStandardMaterial({color, roughness:.95, metalness:0, flatShading:true})`.
ConeGeometry 1,1,7.
Tree group y=terrainHeight.
Trunk cylinder unit `CylinderGeometry(.13,.21,1,5)` but scaled y. Mesh position y=trunkH/2.
For pine foliage `ConeGeometry(1,1,7)` centered, translate y=.5? `geo.translate(0,.5,0)`, then mesh scale y=height and position base. Radius scale x/z. Cone base at y0.
`mesh.scale.set(radius,height,radius)`.
Group foliage materials random.
Broadleaf ico geo radius1. group lumps at y=.65h, x offsets .12h etc, scales.
Broadleaf trunk branch maybe `CylinderGeometry` branch geometry and mesh rotate z. Add.
Could simplify.

Trees random positions; group rotate y random; foliage material per tree from array.

Count. `let treeCount=0; for i<62... if skip continue; treeCount++`. At least 30 mathematically. Could maybe assert? `if(treeCount<30) ...` no.
Use `scene.userData.treeCount=treeCount`.

Rocks. For each angle random, radius random 2.8-8.6. Avoid central? yes. y=0.08 because patch. scale x etc. `rock.scale.set(sx,sy,sz)`, Icosa radius1. position y=sy*.55. cast. receive. Add maybe 20.
Some rocks near fire ring separately.

Logs. Materials arrays. `createLog` returns group. Need material arrays accepted. `mesh.material` array works.
Log geometry unit radius with top .85 bottom1; y length. scale. Group position x,z; group.rotation.y=angle.
For branch stub, if included within group, local rotation? Add mesh with same geometry but only bark? geometry groups material array mismatch if one material. use `[bark,bark,bark]`. branch length .8, radius .08; position(.2, .05,0), rotation z pi/2 and rotation x? no. Could skip branches.
Outer logs use bark and end wood.
Center logs use char bark/cut char.
Maybe add ring of rocks with small scale.

Fire root:
const flameRoot=new Group();
scene.add.
variants 6.
Outer geom createFlameGeometry variant. Need geometry function.

`makeFlameGeometry(seed)`:
rings array. use seeded local random. segments=6. positions arrays and indices.
for each ring:
ring data `[{y:0,r:.32,x:0},{y:.28,r:.46,x:.02},{y:.75,r:.34,x:.07},{y:1.25,r:.17,x:.13},{y:1.7,r:0,x:.2}]`.
At final r=.015. angle offset = ring*.17 + variant random. x = cos*a*r + x. z=sin*a*r*.82. base maybe local.
Indices between. Compute normals. Set uv? no maps. fine.
Each flame mesh use one of geometries. `new Mesh(geo, mat)`. scale.
Position xz random around .25. `baseScale` clone.
Outer 4 material variants orange/red. inner 3 gold. Core 1 yellow. Core at y .25 and scale .45,.75,.45.
Maybe outer flames should include transparent; Additive blending. Color. Fire appears too smooth? geometry low poly 6 sides. good.
Set `frustumCulled=false` no issue.
Outer flame root position y=.3.
Materials:
outerOrange 0xff4a08 opacity .72; outerGold0xff8a12 .8; core0xffe27a .95.
MeshBasic additive all, bright. In dark screenshot looks flame. Good.

Could add actual emissive MeshStandard outer to be shaded? Basic no 3D shading but low poly silhouette flat. Add inner MeshBasic. Maybe use MeshStandard for outer with emissive. But with Additive and Basic more luminous. Low poly geometry edges not visible due uniform color. Could create darker side material? Additive uniform still shape. We can use `MeshBasicMaterial({color, transparent, opacity:.78, side:DoubleSide, depthWrite:false})` normal blending. It will be solid bright, layering. Then inner. No shading, but silhouette. Add `emissive` Standard:
`new MeshStandardMaterial({color:0xff3b00, emissive:0xff3300, emissiveIntensity:2.2, roughness:.6, transparent:true, opacity:.82, depthWrite:false, side:DoubleSide, flatShading:true})`. Tone mapping makes. This can be nice. But scene lighting point already. Basic safe.
Maybe `MeshBasicMaterial` with vertex colors? Could set face colors different to show facets. We can assign color attribute to flame geometry per face with base color variations, and material vertexColors true. But same material color multiplies. Create each geometry colors around orange. For outer variants, color attr. Then Basic `vertexColors:true, color:0xffffff`. Yet inner etc different geometry. Could just uniform.

We can create `MeshLambertMaterial` emissive? Lambert supports emissive. transparent. It gets lit and emissive. But transparency sorting. Use MeshStandard with `emissive`.
Outer material `color:0xff3a00, emissive:0xff2a00, emissiveIntensity:2.0, roughness:.7, transparent:true, opacity:.78, depthWrite:false, flatShading:true, side:DoubleSide`.
Inner `color:0xffb21a, emissive:0xff7a00, emissiveIntensity:2.5, opacity:.9`.
Core `color:0xfff3ad, emissive:0xffd15a, emissiveIntensity:2.8`.
Point light makes. Since materials are transparent, depthWrite false.
Set renderOrder 4.
Could use additive? Standard blending normal. Fine.

Fire glow sprite adds.

Sparks material color vertex with texture. PointsMaterial map glow, color 0xffffff, vertexColors true. Points vertex colors are linear? set color.
`size: .12`, attenuation true. At 15m ~? 0.12 world unit projected 0.12/15* focal ~ (height/(2tan26)=820); 6.5 px? formula point size = size * (height/2) maybe 49 px world. Actually size .12 world => ~8 px at 15. too large. PointsMaterial size is diameter world scaled; .12 at 15 with viewport800 f=820 gives6.5px. Need .045 gives2.5px. Set .075 maybe4px. Sparks should small 2-4. set .07.
Stars size 1.4 px false.

`sparkGeometry.setAttribute('position', new BufferAttribute(...,3).setUsage(DynamicDrawUsage));` color.

Point lights and shadows. Point shadow map resolution 1024 and far20. `light.shadow.camera.near=.2; far=22; mapSize 1024; bias=-.0005; normalBias=.04`.

Directional no shadow.

Tree group cast shadow traverse:
`group.traverse(o=>{if(o.isMesh){o.castShadow = treeRadius<19.5; o.receiveShadow=true;}})`.
Trunk and foliage.

Rocks cast. Logs cast.

Ground receive.

Need maybe grass receives.

Can set `renderer.domElement.setAttribute('aria-label',...)`.

UI uses `mix-blend` no.

Let's verify tree geometry code broad:
trunk geometry Cylinder(.13,.2,1,5), trunkMesh.scale.y=trunkH; position y=trunkH/2. foliage cone base at pos.
For pine h = randRange(4.4,7.2), trunkH=.65h. layers:
radii [.28h,.23h,.17h], heights [.52h,.43h,.36h], base [.25h,.43h,.59h]. Tips .77,.86,.95. trunk to .65, top layer starts .59.
For broad h4-6.5, trunk .62h, canopy center around .72h. Three ico lumps radii .28h, positions offsets. top height ~1h. maybe 5 lumps. Could be 4. Colors.
Add two branch meshes perhaps.

Tree `group.scale` not. Scale h embedded.

Angle camera skip:
const camAngle=Math.atan2(camera.position.z,camera.position.x) but initial camera defined before placement. angle diff `Math.atan2(Math.sin(a-camAngle), Math.cos(...))`.
If abs diff<.5 && radius<30 skip. For narrow camera angle atan2(19,18)=.812 similar.
This ensures 50.

Position radius:
`radius = i<45 ? randRange(10.8,23) : randRange(26,42);`
Golden angle. Some near ring at same radius. Nice.

Need random seed consumed many by textures etc. fine.

Maybe trees near clearing block side view. okay.

Ground fog far.

Need create moon after sky and before stars perhaps render order.

Sky shader does not cast. `sky.frustumCulled=false`.

Moon glow scale. Sprite in world at position; SpriteMaterial size attenuation default true? Sprite size in world scale. fine.

Stars geometry radius 65. Need not behind moon? z buffer. Sky sphere depthWrite false. Stars Points at radius60. Moon at radius40, renders front.

Moon texture uses 2D canvas. `ctx.globalAlpha`. reset.

Radial glow texture canvas. Set `texture.minFilter=THREE.LinearFilter`.

Potential cross origin none.

Now animation flame. Need clone baseScale before modifications. `const base = new THREE.Vector3`.
Store `basePosition`.
`flame.rotation.y` maybe time.
Light intensity. `fireGlow.scale.set(4.8+...,...)` but scale x and y. Store base. If setting scale z? sprite scale z irrelevant.
Sparks.

Should star rotation affect positions. `stars.rotation.y=elapsed*.001` negligible.

Need maybe moon glow pulse.

No pointer custom.

Let's consider title overlay on screenshot. Top-left could cover moon if top-left. Moon likely upper center-left around x maybe? camera orientation moon left-ish but not extreme. Title at top-left could overlap. Could put title bottom-left, hint bottom-right. Scene sky top clean. Yet bottom logs. Minimal top-left label. Moon maybe upper 35% x30%, title top 30px x30, no overlap. Fine.

Let's estimate moon screen x. Camera right vector = forward cross up? forward=(-.624,-.299,-.781); right = normalize(cross(forward,up)) = (0.781,0,-.624) maybe. moon direction normalized (-.552,.074,-.827). dot right = -.431+.516=.085 -> slightly right of center! So moon near center top, good. Camera up vector = right cross forward maybe (-.187,.954,-.237)? dot moon = .103+.071+.196=.370 angle? Up component normalized .37, horizontal norm .93, vertical angle21.7°. FOV half26, near top ~8%. Moon screen x 9% right of center. Title top-left safe.
Default composition: fire center. Moon upper center slightly right. Nice.

Tree density behind fire. Clearing.

Maybe camera forward passes through no tree due wedge.

Need add some light fog maybe scene. `FogExp2 .024`; at distance40 factor exp(-(density*distance)^2)=exp(-.92)=.4 visible 60%; at50 .3. Distant trees visible silhouettes. Great.

Let's inspect fog formula FogExp2 factor exp(-density^2 depth^2). .024^2*1600=.922 => .4. okay.

Point light shadows with PointLight at y1.3. nearby objects shadows radially. Fire logs cast upward? Point inside logs maybe weird. But fine.

Could add a second low warm point light no shadow inside flames to light logs? Main point at1.2. It will.

Potential star field at y positive but camera looking slightly down, sky visible upper half. Stars at radius65 with hemisphere y>=2? If stars below horizon too, terrain occludes due depth. Fine. Generate spherical:
u=rand, v=rand; theta=2piu; y=randRange(.03,.95); horizontal=sqrt(1-y²); x=cos theta*horizontal*65, z=sin theta*horizontal*65, yy=y*65. At low y near3, visible near horizon. 900.

Could use color slight warm for some.

Now code final. Ensure no accidental `</script>` inside strings no.

One thought: renderer shadowMap autoUpdate false means `renderer.shadowMap.needsUpdate=true` property set before first render. If initial animation loop starts, yes. But controls update may not matter. Fine. If shadows don't appear due no update? likely.

One thought: canvas ground uses fog built-in, patch fog yes.

One thought: MeshBasic flame with transparent Standard? transparent Standard still rendered in transparent pass, point lighting. okay.

Let's create flame geometry with BufferGeometry index. FlatShading material and indexed geometry: flatShading uses derivatives in fragment, should show facets. Good.
At top ring r .02, no actual tip, can create final vertex at (drift,1.78,0) and triangles. Better rings last r0 and repeated vertices creates degenerate. Use rings first 4 and tip. Data:
rings [
{y:0,r:.30,x:0},
{y:.32,r:.46,x:.01},
{y:.82,r:.32,x:.07},
{y:1.35,r:.14,x:.15}
]
tip y1.75 x.22. segments6.
Index each ring; then tip triangles. Base no cap (hidden).
Geometry compute normals. Fine.

Flame x position root. Some lean inherent.

Create 7 flames:
outer 4 with scale width .75-.95 and height1.05-1.35; positions x around.
gold 3 smaller.
core 1.
If too much overlap, okay.

Maybe animate opacity using material shared affects all same. no.

Fire light flicker and warm pools. Great.

Let's think responsive CSS:
html,body {width:100%;height:100%;margin:0;overflow:hidden;background:#05080d;}
canvas display block.
#label fixed top 22 left 26; color.
.title font-size clamp(18px,2.2vw,30px); letter spacing .08em; font-weight 500.
.kicker 10px.
#hint fixed right24 bottom22 etc.
@media max600: top16 left16; hint bottom16 center; font-size10.
Vignette.

No buttons. all pointer-events none.

Potential iframe blocks scroll? body hidden.

Let's write final with one code block.