Models 44

DeepSeek V4 Pro 0813

by DeepSeekreasoning defaultClean
Open in new window

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

How it went

Total time
4 min 41 s
Cost
$0.071
Output tokens
15,974
Spent thinking
9,267 tokens
Speed
56.9 tok/s
Console errors
0
First token after
419 ms
First visible text after
2 min 19 s
Prompt tokens
572
Code size
23.0 KB
Finish reason
stop
Served by
Wafer
Model id
deepseek/deepseek-v4-pro-0813
Reasoning sent
model default
Max output
943,718 tokens
Generated
Nov 12, 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 · 551 lines

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Low-Poly Forest Campfire - Night Scene</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            overflow: hidden;
            background: #0a0a1a;
            font-family: system-ui, sans-serif;
            cursor: grab;
        }
        body:active {
            cursor: grabbing;
        }
        canvas {
            display: block;
        }
        #info {
            position: fixed;
            bottom: 16px;
            left: 50%;
            transform: translateX(-50%);
            color: rgba(255, 220, 180, 0.85);
            font-size: 13px;
            letter-spacing: 0.5px;
            pointer-events: none;
            text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
            transition: opacity 1.2s ease;
            z-index: 10;
        }
        @media (max-width: 600px) {
            #info {
                font-size: 11px;
                bottom: 10px;
            }
        }
    </style>
</head>
<body>
    <div id="info">Drag to orbit • Scroll to zoom • Low-poly night campfire</div>

    <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';

        // ===== Scene Setup =====
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x0a0a1a);
        scene.fog = new THREE.Fog(0x0a0a1a, 8, 42);

        const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 120);
        camera.position.set(10, 6, 12);

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        renderer.shadowMap.enabled = true;
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        renderer.toneMapping = THREE.ACESFilmicToneMapping;
        renderer.toneMappingExposure = 1.0;
        document.body.appendChild(renderer.domElement);

        // ===== Controls =====
        const controls = new OrbitControls(camera, renderer.domElement);
        controls.target.set(0, 1.1, 0);
        controls.enableDamping = true;
        controls.dampingFactor = 0.06;
        controls.minDistance = 4;
        controls.maxDistance = 28;
        controls.maxPolarAngle = Math.PI / 2 - 0.04;
        controls.minPolarAngle = 0.1;
        controls.autoRotate = true;
        controls.autoRotateSpeed = 0.35;
        controls.update();

        // ===== Lighting =====
        // Subtle night ambient from hemisphere
        const hemiLight = new THREE.HemisphereLight(0x1a1a3a, 0x1a1510, 0.4);
        scene.add(hemiLight);

        // Cool moonlight direction
        const moonLight = new THREE.DirectionalLight(0x8899bb, 0.25);
        moonLight.position.set(20, 30, -15);
        scene.add(moonLight);

        // Warm fire point light (flickers, casts shadows)
        const fireLight = new THREE.PointLight(0xff8833, 5.0, 22, 1.8);
        fireLight.position.set(0, 1.8, 0);
        fireLight.castShadow = true;
        fireLight.shadow.mapSize.width = 512;
        fireLight.shadow.mapSize.height = 512;
        fireLight.shadow.camera.near = 0.1;
        fireLight.shadow.camera.far = 20;
        fireLight.shadow.bias = -0.001;
        fireLight.shadow.radius = 3;
        scene.add(fireLight);

        // Secondary warm fill without shadows (for nearby glow)
        const fireGlowLight = new THREE.PointLight(0xff5511, 1.8, 8, 2.0);
        fireGlowLight.position.set(0, 0.7, 0);
        scene.add(fireGlowLight);

        // ===== Materials =====
        const groundMat = new THREE.MeshStandardMaterial({
            color: 0x2a2018,
            roughness: 0.9,
            metalness: 0.0,
            flatShading: true
        });
        const rockMat = new THREE.MeshStandardMaterial({
            color: 0x666a70,
            roughness: 0.8,
            metalness: 0.05,
            flatShading: true
        });
        const rockMatDark = new THREE.MeshStandardMaterial({
            color: 0x4a4e54,
            roughness: 0.85,
            metalness: 0.05,
            flatShading: true
        });
        const logMat = new THREE.MeshStandardMaterial({
            color: 0x5a3820,
            roughness: 0.85,
            metalness: 0.0,
            flatShading: true
        });
        const burntLogMat = new THREE.MeshStandardMaterial({
            color: 0x1a1008,
            roughness: 0.95,
            metalness: 0.0,
            flatShading: true
        });
        const trunkMat1 = new THREE.MeshStandardMaterial({ color: 0x4a3728, roughness: 0.9, flatShading: true });
        const trunkMat2 = new THREE.MeshStandardMaterial({ color: 0x3e2d20, roughness: 0.9, flatShading: true });
        const foliageMat1 = new THREE.MeshStandardMaterial({ color: 0x1a3d1a, roughness: 0.8, flatShading: true });
        const foliageMat2 = new THREE.MeshStandardMaterial({ color: 0x234d23, roughness: 0.8, flatShading: true });
        const foliageMat3 = new THREE.MeshStandardMaterial({ color: 0x2d5d2d, roughness: 0.8, flatShading: true });
        const foliageMat4 = new THREE.MeshStandardMaterial({ color: 0x164016, roughness: 0.8, flatShading: true });
        const foliageMats = [foliageMat1, foliageMat2, foliageMat3, foliageMat4];
        const trunkMats = [trunkMat1, trunkMat2];

        const flameMat1 = new THREE.MeshBasicMaterial({ color: 0xff4400, transparent: true, opacity: 0.9 });
        const flameMat2 = new THREE.MeshBasicMaterial({ color: 0xff8800, transparent: true, opacity: 0.92 });
        const flameMat3 = new THREE.MeshBasicMaterial({ color: 0xffcc33, transparent: true, opacity: 0.95 });
        const flameMat4 = new THREE.MeshBasicMaterial({ color: 0xfff0a0, transparent: true, opacity: 0.98 });

        const moonMat = new THREE.MeshBasicMaterial({ color: 0xddd8cc, fog: false });

        // ===== Ground =====
        const groundGeom = new THREE.PlaneGeometry(60, 60, 48, 48);
        const posAttr = groundGeom.attributes.position;
        for (let i = 0; i < posAttr.count; i++) {
            const x = posAttr.getX(i);
            const y = posAttr.getY(i);
            const dist = Math.sqrt(x * x + y * y);
            let displacement = 0;
            if (dist > 2.5) {
                displacement = (Math.random() * 0.6 - 0.3) * (1 - Math.min(dist / 35, 1));
            } else {
                displacement = (Math.random() * 0.12 - 0.06);
            }
            posAttr.setZ(i, displacement);
        }
        groundGeom.computeVertexNormals();
        const ground = new THREE.Mesh(groundGeom, groundMat);
        ground.rotation.x = -Math.PI / 2;
        ground.receiveShadow = true;
        ground.position.y = -0.05;
        scene.add(ground);

        // Dark fire pit disc
        const pitGeom = new THREE.CircleGeometry(1.6, 18);
        const pitMat = new THREE.MeshStandardMaterial({ color: 0x0d0804, roughness: 1, flatShading: true });
        const pitDisc = new THREE.Mesh(pitGeom, pitMat);
        pitDisc.rotation.x = -Math.PI / 2;
        pitDisc.position.y = 0.02;
        pitDisc.receiveShadow = true;
        scene.add(pitDisc);

        // ===== Rocks around fire =====
        const rockGeom = new THREE.IcosahedronGeometry(0.35, 0);
        const rockCount = 11;
        for (let i = 0; i < rockCount; i++) {
            const angle = (i / rockCount) * Math.PI * 2 + Math.random() * 0.2;
            const radius = 1.35 + Math.random() * 0.2;
            const rock = new THREE.Mesh(rockGeom, Math.random() > 0.3 ? rockMat : rockMatDark);
            rock.position.set(Math.cos(angle) * radius, 0.12 + Math.random() * 0.1, Math.sin(angle) * radius);
            rock.scale.setScalar(0.7 + Math.random() * 0.6);
            rock.rotation.set(Math.random() * 0.8, Math.random() * Math.PI, Math.random() * 0.8);
            rock.castShadow = true;
            rock.receiveShadow = true;
            scene.add(rock);
        }

        // Secondary scattered small rocks
        for (let i = 0; i < 6; i++) {
            const angle = Math.random() * Math.PI * 2;
            const radius = 2.5 + Math.random() * 3.5;
            const rock = new THREE.Mesh(rockGeom, rockMatDark);
            rock.position.set(Math.cos(angle) * radius, 0.08 + Math.random() * 0.06, Math.sin(angle) * radius);
            rock.scale.setScalar(0.35 + Math.random() * 0.4);
            rock.rotation.set(Math.random() * 1.2, Math.random() * Math.PI, Math.random() * 1.2);
            rock.castShadow = true;
            rock.receiveShadow = true;
            scene.add(rock);
        }

        // ===== Logs over fire =====
        const logGeom = new THREE.CylinderGeometry(0.14, 0.18, 1.8, 6);
        for (let i = 0; i < 4; i++) {
            const angle = (i / 4) * Math.PI + Math.random() * 0.4;
            const log = new THREE.Mesh(logGeom, i < 2 ? logMat : burntLogMat);
            const height = 0.28 + Math.random() * 0.1;
            log.position.set(Math.cos(angle) * 0.55, height, Math.sin(angle) * 0.55);
            log.rotation.z = Math.cos(angle) * 0.5;
            log.rotation.x = -Math.sin(angle) * 0.5;
            log.rotation.y = angle + Math.PI / 2;
            log.castShadow = true;
            log.receiveShadow = true;
            scene.add(log);
        }
        // A couple of extra logs higher up
        for (let i = 0; i < 2; i++) {
            const angle = Math.random() * Math.PI * 2;
            const log = new THREE.Mesh(logGeom, burntLogMat);
            log.position.set(Math.cos(angle) * 0.4, 0.42, Math.sin(angle) * 0.4);
            log.rotation.z = Math.cos(angle) * 0.7;
            log.rotation.x = -Math.sin(angle) * 0.7;
            log.rotation.y = angle + Math.PI / 2;
            log.scale.setScalar(0.85);
            log.castShadow = true;
            log.receiveShadow = true;
            scene.add(log);
        }

        // ===== Campfire Flames =====
        const fireGroup = new THREE.Group();
        fireGroup.position.set(0, 0.3, 0);

        const flameCones = [];
        const flameConfigs = [
            { geom: new THREE.ConeGeometry(0.62, 2.0, 7), mat: flameMat1, y: 0.7, scaleBase: 1.0, freq: 2.3, phase: 0 },
            { geom: new THREE.ConeGeometry(0.48, 1.7, 7), mat: flameMat2, y: 0.65, scaleBase: 1.06, freq: 2.8, phase: 1.7 },
            { geom: new THREE.ConeGeometry(0.32, 1.35, 7), mat: flameMat3, y: 0.6, scaleBase: 1.1, freq: 3.4, phase: 3.2 },
            { geom: new THREE.ConeGeometry(0.18, 0.85, 6), mat: flameMat4, y: 0.55, scaleBase: 1.12, freq: 4.1, phase: 5.0 },
        ];

        flameConfigs.forEach(cfg => {
            const mesh = new THREE.Mesh(cfg.geom, cfg.mat);
            mesh.position.y = cfg.y;
            mesh.castShadow = false;
            mesh.receiveShadow = false;
            fireGroup.add(mesh);
            flameCones.push({ mesh, cfg });
        });

        // Small ember glow at base of fire
        const emberGeom = new THREE.SphereGeometry(0.5, 6, 5);
        const emberMat = new THREE.MeshBasicMaterial({ color: 0xff3300, transparent: true, opacity: 0.5 });
        const ember = new THREE.Mesh(emberGeom, emberMat);
        ember.position.y = 0.15;
        ember.scale.set(1.5, 0.4, 1.2);
        fireGroup.add(ember);
        flameCones.push({ mesh: ember, cfg: { freq: 3.0, phase: 1.0, scaleBase: 1.0 } });

        scene.add(fireGroup);

        // ===== Trees =====
        const treeGroup = new THREE.Group();
        const treeCount = 42;
        const clearingRadius = 7.5;
        const maxTreeRadius = 38;

        function createTree() {
            const tree = new THREE.Group();
            const trunkHeight = 1.3 + Math.random() * 0.7;
            const trunkGeom = new THREE.CylinderGeometry(0.14, 0.22, trunkHeight, 6);
            const trunk = new THREE.Mesh(trunkGeom, trunkMats[Math.floor(Math.random() * trunkMats.length)]);
            trunk.position.y = trunkHeight / 2;
            trunk.castShadow = true;
            trunk.receiveShadow = true;
            tree.add(trunk);

            const foliageCount = 2 + Math.floor(Math.random() * 2);
            for (let i = 0; i < foliageCount; i++) {
                const coneSize = 1.9 - i * 0.45 + Math.random() * 0.2;
                const coneGeom = new THREE.ConeGeometry(coneSize * 0.72, coneSize, 6 + Math.floor(Math.random() * 2));
                const coneMat = foliageMats[Math.floor(Math.random() * foliageMats.length)];
                const cone = new THREE.Mesh(coneGeom, coneMat);
                cone.position.y = trunkHeight + i * 0.55 + 0.15;
                cone.castShadow = true;
                cone.receiveShadow = true;
                tree.add(cone);
            }
            return tree;
        }

        for (let i = 0; i < treeCount; i++) {
            const tree = createTree();
            const angle = Math.random() * Math.PI * 2;
            const distance = clearingRadius + Math.random() * (maxTreeRadius - clearingRadius);
            const x = Math.cos(angle) * distance;
            const z = Math.sin(angle) * distance;

            tree.position.set(x, 0, z);
            const scale = 0.75 + Math.random() * 1.6;
            tree.scale.setScalar(scale);
            tree.rotation.y = Math.random() * Math.PI * 2;
            tree.rotation.z = (Math.random() - 0.5) * 0.04;
            tree.rotation.x = (Math.random() - 0.5) * 0.04;
            treeGroup.add(tree);
        }

        // A few extra trees scattered further for depth
        for (let i = 0; i < 12; i++) {
            const tree = createTree();
            const angle = Math.random() * Math.PI * 2;
            const distance = maxTreeRadius + Math.random() * 12;
            const x = Math.cos(angle) * distance;
            const z = Math.sin(angle) * distance;
            tree.position.set(x, 0, z);
            const scale = 1.2 + Math.random() * 1.4;
            tree.scale.setScalar(scale);
            tree.rotation.y = Math.random() * Math.PI * 2;
            treeGroup.add(tree);
        }

        scene.add(treeGroup);

        // ===== Stars =====
        const starCount = 600;
        const starGeom = new THREE.BufferGeometry();
        const starPositions = new Float32Array(starCount * 3);
        const starColors = new Float32Array(starCount * 3);
        for (let i = 0; i < starCount; i++) {
            const theta = Math.random() * Math.PI * 2;
            const phi = Math.acos(Math.random() * 0.9 + 0.05);
            const radius = 70 + Math.random() * 15;
            starPositions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
            starPositions[i * 3 + 1] = radius * Math.cos(phi);
            starPositions[i * 3 + 2] = radius * Math.sin(phi) * Math.sin(theta);

            const brightness = 0.5 + Math.random() * 0.5;
            const tint = Math.random();
            starColors[i * 3] = brightness * (0.9 + tint * 0.1);
            starColors[i * 3 + 1] = brightness * (0.9 + tint * 0.05);
            starColors[i * 3 + 2] = brightness * (1.0 - tint * 0.15);
        }
        starGeom.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
        starGeom.setAttribute('color', new THREE.BufferAttribute(starColors, 3));
        const starMat = new THREE.PointsMaterial({
            size: 0.3,
            vertexColors: true,
            transparent: true,
            opacity: 0.9,
            sizeAttenuation: true,
            fog: false,
            blending: THREE.AdditiveBlending,
            depthWrite: false
        });
        const stars = new THREE.Points(starGeom, starMat);
        scene.add(stars);

        // ===== Moon =====
        const moonGroup = new THREE.Group();
        const moonGeom = new THREE.SphereGeometry(1.6, 14, 12);
        const moon = new THREE.Mesh(moonGeom, moonMat);
        moon.position.set(0, 0, 0);
        moonGroup.add(moon);

        // Moon glow halo
        const haloGeom = new THREE.SphereGeometry(2.4, 12, 10);
        const haloMat = new THREE.MeshBasicMaterial({
            color: 0xddd8cc,
            transparent: true,
            opacity: 0.12,
            fog: false,
            blending: THREE.AdditiveBlending,
            depthWrite: false
        });
        const halo = new THREE.Mesh(haloGeom, haloMat);
        halo.position.set(0, 0, 0);
        moonGroup.add(halo);

        moonGroup.position.set(28, 32, -42);
        moonGroup.lookAt(0, 2, 0);
        scene.add(moonGroup);

        // ===== Sparks Particle System =====
        const sparkCount = 120;
        const sparkGeom = new THREE.BufferGeometry();
        const sparkPositions = new Float32Array(sparkCount * 3);
        const sparkVelocities = new Float32Array(sparkCount * 3);
        const sparkLifetimes = new Float32Array(sparkCount);
        const sparkMaxLifetimes = new Float32Array(sparkCount);
        const sparkSizes = new Float32Array(sparkCount);
        const sparkColorArray = new Float32Array(sparkCount * 3);

        for (let i = 0; i < sparkCount; i++) {
            resetSpark(i, true);
        }

        function resetSpark(index, initialSpawn = false) {
            const angle = Math.random() * Math.PI * 2;
            const radius = 0.25 + Math.random() * 0.4;
            sparkPositions[index * 3] = Math.cos(angle) * radius;
            sparkPositions[index * 3 + 1] = 0.35 + Math.random() * 0.3;
            sparkPositions[index * 3 + 2] = Math.sin(angle) * radius;

            sparkVelocities[index * 3] = (Math.random() - 0.5) * 0.4;
            sparkVelocities[index * 3 + 1] = 1.0 + Math.random() * 1.6;
            sparkVelocities[index * 3 + 2] = (Math.random() - 0.5) * 0.4;

            sparkMaxLifetimes[index] = 1.8 + Math.random() * 2.5;
            sparkLifetimes[index] = initialSpawn ? Math.random() * sparkMaxLifetimes[index] : 0;
            sparkSizes[index] = 0.04 + Math.random() * 0.07;

            const warmth = Math.random();
            sparkColorArray[index * 3] = 1.0;
            sparkColorArray[index * 3 + 1] = 0.5 + warmth * 0.45;
            sparkColorArray[index * 3 + 2] = 0.15 + warmth * 0.25;
        }

        sparkGeom.setAttribute('position', new THREE.BufferAttribute(sparkPositions, 3));
        sparkGeom.setAttribute('color', new THREE.BufferAttribute(sparkColorArray, 3));
        sparkGeom.setAttribute('size', new THREE.BufferAttribute(sparkSizes, 1));

        const sparkMat = new THREE.PointsMaterial({
            size: 0.06,
            vertexColors: true,
            transparent: true,
            opacity: 0.85,
            sizeAttenuation: true,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
            fog: true
        });

        const sparkPoints = new THREE.Points(sparkGeom, sparkMat);
        scene.add(sparkPoints);

        // ===== Animation Variables =====
        function lerp(a, b, t) { return a + (b - a) * t; }

        function smoothNoise(t) {
            return Math.sin(t * 1.3) * 0.5 + Math.sin(t * 2.7) * 0.3 + Math.sin(t * 5.1) * 0.2;
        }

        const clock = new THREE.Clock();

        // ===== Animation Loop =====
        function animate() {
            const delta = Math.min(clock.getDelta(), 0.05);
            const elapsed = clock.elapsedTime;

            // --- Flame animation ---
            flameCones.forEach((item, idx) => {
                if (item.cfg && item.cfg.freq) {
                    const noise1 = Math.sin(elapsed * item.cfg.freq + item.cfg.phase);
                    const noise2 = Math.sin(elapsed * item.cfg.freq * 1.7 + item.cfg.phase * 1.3);
                    const flicker = 0.75 + (noise1 * 0.18 + noise2 * 0.1);
                    const baseScale = item.cfg.scaleBase || 1.0;
                    const s = baseScale * flicker;
                    item.mesh.scale.set(s, s * (0.9 + noise1 * 0.12), s);
                    const baseY = item.cfg.y || 0.5;
                    item.mesh.position.y = baseY + noise1 * 0.08 + noise2 * 0.04;
                    if (item.mesh.material && item.mesh.material.opacity !== undefined) {
                        item.mesh.material.opacity = 0.82 + noise1 * 0.13;
                    }
                    item.mesh.rotation.y += delta * 2.5 * (idx + 1) * 0.3;
                }
            });

            // --- Fire light flickering ---
            const lightNoise1 = Math.sin(elapsed * 2.2) * 0.5;
            const lightNoise2 = Math.sin(elapsed * 4.5 + 0.7) * 0.3;
            const lightNoise3 = Math.sin(elapsed * 7.3 + 2.1) * 0.15;
            const lightIntensity = 4.0 + (lightNoise1 + lightNoise2 + lightNoise3) * 1.6;
            fireLight.intensity = Math.max(2.5, lightIntensity);

            const glowNoise = Math.sin(elapsed * 3.1 + 0.5) * 0.25 + Math.sin(elapsed * 6.7) * 0.15;
            fireGlowLight.intensity = 1.5 + glowNoise;

            // Slight light position variation
            fireLight.position.y = 1.8 + Math.sin(elapsed * 2.8) * 0.12;
            fireLight.position.x = Math.sin(elapsed * 1.9) * 0.06;
            fireLight.position.z = Math.cos(elapsed * 2.3) * 0.06;

            // --- Sparks update ---
            const sparkPos = sparkGeom.attributes.position;
            const sparkSize = sparkGeom.attributes.size;
            for (let i = 0; i < sparkCount; i++) {
                sparkLifetimes[i] += delta;
                if (sparkLifetimes[i] >= sparkMaxLifetimes[i]) {
                    resetSpark(i);
                    sparkSize.setX(i, sparkSizes[i]);
                } else {
                    sparkPositions[i * 3] += sparkVelocities[i * 3] * delta;
                    sparkPositions[i * 3 + 1] += sparkVelocities[i * 3 + 1] * delta;
                    sparkPositions[i * 3 + 2] += sparkVelocities[i * 3 + 2] * delta;
                    // Slight upward acceleration (heat)
                    sparkVelocities[i * 3 + 1] += 0.25 * delta;
                    // Horizontal drift
                    sparkVelocities[i * 3] += (Math.random() - 0.5) * 0.5 * delta;
                    sparkVelocities[i * 3 + 2] += (Math.random() - 0.5) * 0.5 * delta;
                    // Size fade with age
                    const lifeRatio = sparkLifetimes[i] / sparkMaxLifetimes[i];
                    sparkSize.setX(i, sparkSizes[i] * (1 - lifeRatio * 0.8));
                }
            }
            sparkPos.needsUpdate = true;
            sparkSize.needsUpdate = true;

            // --- Controls ---
            controls.update();

            // --- Render ---
            renderer.render(scene, camera);
            requestAnimationFrame(animate);
        }

        // ===== Resize Handling =====
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });

        // ===== Hide info after a while =====
        setTimeout(() => {
            const infoEl = document.getElementById('info');
            if (infoEl) infoEl.style.opacity = '0';
        }, 6000);

        // ===== Start =====
        animate();
    </script>
</body>
</html>
Reasoning trace (9,267 tokens)
The user wants a complete, single HTML file with a low-poly 3D scene built with Three.js. Let me plan this carefully:

1. **Setup**: Import map for Three.js, Three.js scene, camera, renderer, OrbitControls
2. **Scene elements**:
   - Ground plane (dark green/brown)
   - Low-poly rocks around the campfire
   - Logs around the campfire
   - Campfire with animated flames (cone geometry, animated scale/rotation)
   - Point light with flickering
   - Particle system for sparks
   - 30+ low-poly trees randomly placed
   - Dark night sky with stars and moon
   - Fog
3. **Controls**: OrbitControls with limits
4. **Responsive**: Handle resize

Let me build this out carefully.

Key considerations:
- Use `coneGeometry` or custom low-poly geometry for trees
- Use `IcosahedronGeometry` with low detail for rocks
- Flames: cone geometry with vertex animation or just scaling/color shifting
- Sparks: Points with upward velocity
- Stars: Points spread across a large sphere or dome
- Moon: A sphere with emissive material
- Fog: `scene.fog` with `THREE.FogExp2` or `THREE.Fog`
- Shadows: Need `renderer.shadowMap.enabled = true`, point light casting shadows
- Low-poly aesthetic: use `flatShading: true` on materials

Let me think about the trees. Low-poly trees can be made with:
- Cone geometry for the foliage (stacked cones)
- Cylinder geometry for the trunk

For the ground, I'll use a large plane with a dark color.

For rocks, use `IcosahedronGeometry` with low detail (like `IcosahedronGeometry(0.3, 0)` which gives 20 faces) and `flatShading: true`.

For logs, use `CylinderGeometry` positioned horizontally.

For the flames, I can use a cone with vertex animation, or I can create multiple cones that scale and change color. A common approach is to have a few overlapping cones with `MeshBasicMaterial` (since fire is emissive) that scale and rotate slightly over time.

For sparks, use a `BufferGeometry` with positions, animate them upward with some randomness, and reset when they go too high.

For stars, use `BufferGeometry` with random positions on a large sphere, using `Points` with a small size.

For the moon, a sphere with emissive material placed far away.

Let me write the code:

```javascript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
```

The scene setup:
- Background color: very dark blue/near black
- Fog: `THREE.FogExp2(0x0a0a1a, 0.025)` or similar
- Camera: perspective, positioned at a reasonable angle
- Renderer: with shadows enabled

Trees: I'll create a function that makes a low-poly tree. Each tree has a trunk (cylinder) and 2-3 cones for foliage. Randomize: position within a ring (excluding the center clearing), rotation, scale.

The clearing: the center should be relatively clear so the campfire is visible. Trees should be placed at various distances, with more density further out. I'll use a random distribution that avoids the center.

Let me structure this:

1. Ground plane
2. Rock ring around fire
3. Logs around fire
4. Campfire (fire pit, logs inside, flames)
5. Trees (30+)
6. Stars
7. Moon
8. Sparks particle system
9. Point light with flicker
10. OrbitControls
11. Animation loop
12. Resize handler

Let me also think about the fire light. I'll use a `PointLight` with warm color, positioned near the fire. It should cast shadows. I'll also add a subtle ambient light so the scene isn't completely dark.

For the flame animation, I'll use 2-3 cone geometries with `MeshBasicMaterial` in orange/yellow, and animate their scale and position slightly. I'll also add a subtle emissive glow.

The sparks: a particle system where each particle has a position and velocity. They start near the fire, go up, fade out, and respawn.

Let me also think about the clearing size. The ground could be about 100x100 units. Trees are placed in a ring from maybe 8 units to 50 units from center. The campfire is at the center.

For shadows: the point light casts shadows. The trees should cast shadows too. This might be expensive but should be fine for the demo.

Actually, for shadow casting with a PointLight, it renders the scene 6 times for cube shadow maps. This could be expensive with 30+ trees and all the geometry. Let me consider using a `SpotLight` instead that points at the fire area and casts shadows, or just use the point light with a limited shadow camera. Actually, a `PointLight` with shadows in Three.js uses cube shadow mapping which is quite expensive. Let me think...

With 30 trees, rocks, logs, etc., point light shadows could be slow. I might use a `SpotLight` instead, positioned above the fire pointing down, which would cast nice shadows on the ground but less dramatically. Or I could use the point light without shadows, and use a directional light with a very low intensity for shadow casting.

Actually, let me just use a PointLight with shadows. The key is to limit the shadow map size and the distance. I'll set `shadow.mapSize` to 512x512 and limit the shadow camera near/far. PointLight shadows use a cube map, so 512x512 means each face is 512x512, which is 6 faces = still manageable.

Actually, for better performance and visual quality, let me use a `SpotLight` positioned above the fire, pointing down at it, with a fairly wide angle, casting shadows. This simulates the firelight illuminating the area around it. The warm color will create the right mood. I'll pair it with the point light (without shadows) for the flickering light effect.

Hmm, actually let me reconsider. A PointLight with shadows is fine for this scene size. Let me set the shadow map to 256 or 512 and limit the distance. The key requirement says "a warm point light that flickers and casts shadows" - so I need a PointLight with shadows.

Let me set `light.shadow.mapSize.width = 512` and `light.shadow.mapSize.height = 512`, and `light.castShadow = true`. I'll also set `light.shadow.camera.near = 0.1` and `light.shadow.camera.far = 20`.

Ok, let me write the full code now.

For the ground, I'll create a large plane with a dark earthy color. I might add some subtle bump by using a slightly irregular position for vertices, but keeping it simple with a flat plane is fine for low-poly.

Actually, for a more interesting ground, I can create a `PlaneGeometry` with some segments and then slightly perturb the vertices to create subtle bumps. This adds to the low-poly feel.

Let me also add some grass or small details around. Maybe just keep it simple with the ground, rocks, logs, camping items.

For the fire pit, I'll create a ring of rocks (small Icosahedron geometry scaled) around the center.

For the logs crossing over the fire, I'll create 2-3 cylinder geometries positioned across the fire pit.

For the flames, I'll create 2-3 cone geometries stacked, with warm orange/yellow colors, and animate them.

For the sparks, a Points system with maybe 100-200 particles.

Stars: A Points system with maybe 500-1000 stars on a large sphere or dome.

Moon: A sphere with emissive material, maybe with a slight glow.

Let me think about the overall color palette:
- Ground: dark earthy brown/green (#2d2418 or similar)
- Trees: dark green foliage (#1a3a1a to #2d4a2d) with brown trunks
- Rocks: gray (#6a6a6a to #8a8a8a)
- Fire: orange (#ff6600), yellow (#ffcc00), warm
- Sky: very dark blue/black
- Stars: white/light blue
- Moon: pale yellow/white

Let me also think about the camera. Starting position should be a nice viewing angle, looking at the campfire from a slight elevation and distance.

Camera: position around (8, 5, 8) looking at (0, 1, 0). OrbitControls with min/max distance and min/max polar angle to keep the camera above ground.

For OrbitControls limits:
- `controls.minDistance = 3`
- `controls.maxDistance = 25`
- `controls.maxPolarAngle = Math.PI / 2 - 0.05` (keep above ground)
- `controls.target.set(0, 1, 0)` (look at the fire)
- `controls.enableDamping = true`

Now let me think about the spark particle system. I'll create a BufferGeometry with positions. Each spark has a velocity. The animation updates positions, and when a spark goes above a certain height or lives too long, it resets to the fire position.

For the flame animation, I'll create 3 cone meshes with slightly different sizes and colors, positioned at the fire center. Each frame, I'll:
- Slightly vary their scale (grow/shrink)
- Slightly vary their y position
- Maybe slightly rotate them

I can also animate the point light intensity to flicker.

Let me finalize the code structure and write it out.

Actually, I need to be careful about performance. With 30+ trees, each having a trunk and 2-3 cones, that's ~100+ meshes. Plus rocks, logs, flames, particles, etc. This should be fine for modern browsers.

One thing I need to handle: the trees should have merged geometry or be individual meshes. For simplicity and to be able to reposition them, I'll create them as groups with child meshes.

For optimization, I could use InstancedMesh for the trees, but for simplicity and since 30 trees is not that many, I'll use regular meshes grouped together.

Actually, to further optimize, I can merge the tree geometries using BufferGeometryUtils.mergeGeometries. But this adds complexity. With 30 trees and maybe 3-4 meshes each, that's ~120 draw calls, which is fine for modern browsers.

Let me keep it simple with individual meshes.

Wait, I should also consider: for the shadows, the trees need to cast shadows. Each mesh in the tree group should have `castShadow = true`. The ground should have `receiveShadow = true`.

Ok, I think I have a good plan. Let me write the complete code.

One more thing: the ground plane should be big enough so the fog fades the edges. With `FogExp2`, the fog density determines how fast it fades. I'll set a ground plane of about 80x80 or 100x100 units, and fog density that makes things fade out over 30-50 units.

Actually, let me reconsider the fog. `THREE.FogExp2` with density around 0.02-0.03 would be good. At distance 20-30 units, things would be significantly fogged. `THREE.Fog` with near=10, far=50 would also work.

I'll use `THREE.Fog` with near=15 and far=50, color matching the sky.

Let me now also think about the background. The background should be dark. I'll set `scene.background = new THREE.Color(0x0a0a1a)` and also have the fog use a similar color.

For the moon, I'll place a sphere at a far distance (like y=30, x=20, z=-40) with emissive material. I could add a slight glow effect by adding a larger transparent sphere around it.

For stars, I'll use a SphereGeometry or create points distributed on a hemisphere above the scene.

Actually, for stars, the easiest approach is to create a `BufferGeometry` with random positions on a sphere of large radius (like 80), then render as `Points`. I'll only include stars that are above the horizon by checking y > 0.

For the moon, I'll create a sphere with `MeshBasicMaterial` (since it's emissive and shouldn't be affected by lighting) or `MeshStandardMaterial` with high emissive.

Let me write the code now.

Actually, I realize I should also think about how the campfire logs are arranged. Typically, logs are placed in a teepee or criss-cross pattern over the fire. I'll place 3-4 logs crossing over a central point, resting on rocks.

For the fire pit rocks, I'll arrange about 8-12 small rocks in a circle.

Ok, let me write the complete HTML file. I need to be very careful about making it work correctly.

Here's my plan for the tree placement:
- The clearing center is at (0, 0, 0)
- Trees are placed in a ring starting from radius ~8 units up to radius ~40 units
- Trees should not be in the center where the fire is
- I'll use random polar coordinates (angle, distance) and convert to Cartesian
- I'll ensure a minimum distance from center (like 8 units) so the clearing is open

Tree creation:
- Trunk: CylinderGeometry(0.2, 0.3, 1.5, 6) with brown color
- Foliage: 2-3 cones stacked, with green colors
- Each tree is a Group positioned and scaled randomly

Let me also think about the tree size variation. Trees closer to the center should be larger (more visible), and trees further away should be smaller. Actually, random sizing from ~1 to ~3 in scale would give nice variety.

For the low-poly look, I'll use low segment counts on all geometry (cones with 5-8 segments, cylinders with 6-8 segments).

Alright, I'm ready to write the code. Let me also make sure the self-playing demo aspect is covered - since this is a scene viewer with OrbitControls, the demo mode could be a gentle camera orbit. But actually, since this is a static scene with interactive controls, the "demo" is the live animation of the fire, sparks, and flickering light. The user can interact with OrbitControls to look around. I think this satisfies the requirement as the scene is animated on first paint.

Actually, looking at the requirements again, I don't see a requirement for a "self-playing demo mode" or "click to start" message. That was part of the general instructions but this specific task is about building a scene with Three.js. The scene will be animated (flames, sparks, flickering) from the start, which is the "demo." OrbitControls allows interaction.

Wait, re-reading the general instructions: "Games must start in a self-playing demo mode..." - this is specifically about games. This task is not a game; it's a 3D scene viewer. So I don't need the "click to start" overlay. The scene should just load and be visible with the fire animated.

But the instruction also says "Everything that makes the demo interesting must be visible within the first seconds" - the fire animation, sparks, trees, stars, moon should all be visible immediately.

Ok, let me finalize and write the code.

One more consideration: for the Altitude of the camera and the OrbitControls. The camera should start at a nice position. Let's say (10, 6, 10) looking at the fire. With `controls.target = (0, 0.5, 0)`.

I also want to consider using `flatShading: true` on all materials to enhance the low-poly look. This is important for the aesthetic.

For the ground, I could use `MeshStandardMaterial` with a rough brown/dark color and `flatShading: true`. The ground will receive shadows.

For trees, the foliage cones can use `MeshStandardMaterial` with `flatShading: true` and dark green colors. The trunks use brown.

For rocks, gray with `flatShading: true`.

For the fire, the flames use `MeshBasicMaterial` (since they're emissive) with orange/yellow colors. `MeshBasicMaterial` doesn't need flatShading since it's not lit by lights.

For the moon, `MeshBasicMaterial` with warm white color.

For sparks, `PointsMaterial` with orange/yellow color.

Alright, I'll also add some ambient light so the scene isn't completely black. Maybe `AmbientLight` with intensity 0.15-0.2 and very dark blue color, or a warm subtle ambient.

Actually, for a night scene, I want it fairly dark with the fire being the main light source. I'll use:
- `AmbientLight` with very low intensity (0.1) and dark blue color for starlight/moonlight
- `DirectionalLight` with very low intensity simulating moonlight (maybe not needed if ambient is enough)
- `PointLight` at the fire with warm orange color, flickering, casting shadows

Let me use a `HemisphereLight` for subtle ambient night lighting (sky dark blue, ground dark brown) with low intensity. This gives a nice night feel.

Actually, a `HemisphereLight` with sky color dark blue and ground color dark brown, intensity around 0.2-0.3, would give a nice subtle night lighting. Combined with the warm point light from the fire, the contrast should look great.

Let me now write the complete code. I'll be thorough and careful.

Actually, I want to also create an AnimationLoop that handles:
1. Flame animation (scale changes, position changes)
2. Fire light flickering (intensity changes)
3. Spark particle updates
4. OrbitControls update
5. Render

For the flickering, I'll use a noise-like function or just random oscillations. I can use `Math.random()` for quick flickering, or combine sine waves for a more natural flicker. I'll use a combination of sine waves at different frequencies.

For the flame animation, I'll have multiple cone meshes and I'll scale them up and down. I'll use different phases for each to make it look organic.

Alright, let me also think about spark behavior:
- Sparks start at approximately (0, 0.8, 0) (just above the fire)
- They rise with some horizontal drift
- They fade out as they rise
- When they reach a certain height (like y > 6) or after a certain time, they reset
- They should be small, bright points

For the particle system, I'll use a `BufferGeometry` with positions and a `PointsMaterial`. I'll store velocity for each particle in a separate array (not in the geometry, since we need to update it each frame).

Spark count: maybe 80-120 particles.

The sparks should have warm colors (orange, yellow, maybe some red). Using `PointsMaterial` with a single color is simpler, but I could use vertex colors for variety.

Actually, for simplicity, I'll use `PointsMaterial` with a warm orange color and additive blending for a glow effect. The sparks might look better with additive blending.

Let me also consider: should I add `TRANSPARENT` blending mode for the sparks? Yes, with additive blending they'll glow nicely.

Now, let me also think about the atmosphere. The fog is important for the "night forest" feel. I'll use:
- `scene.fog = new THREE.Fog(0x0a0a1a, 10, 45)`

This means things within 10 units are fully visible, and things at 45 units are fully fogged out. The ground plane should be large enough that its edges fade into the fog.

Ground size: maybe 80x80 or 60x60. With fog far at 45, the edges will be fogged nicely.

Let me write the final code:

I realize I should also handle the shadow settings carefully. For a PointLight with shadows:
- `light.castShadow = true`
- `light.shadow.mapSize.width = 512`
- `light.shadow.mapSize.height = 512`
- `light.shadow.camera.near = 0.1`
- `light.shadow.camera.far = 20`
- `light.shadow.bias = -0.001` (to prevent shadow acne)

The point light should be positioned at the fire location, maybe slightly above it, like (0, 1.2, 0).

For the ground to receive shadows: `ground.receiveShadow = true`
For trees to cast shadows: each tree mesh `castShadow = true`
For rocks: `castShadow = true`
For logs: `castShadow = true`

Now let me code it all up.

Actually, I realize I need to double-check the OrbitControls import path. The import map includes `three/addons/` mapping to `https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/`. So the import would be:

```javascript
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
```

This should work with the import map.

One more thing: I need a container div or just append the renderer to the body. I'll use `document.body.appendChild(renderer.domElement)`.

Also, for the background color, I'll set `scene.background = new THREE.Color(0x0a0a1a)`.

Let me also decide on the exact tree placement. I'll place 35-40 trees. The clearing radius is about 7 units (so trees start from distance 7 from center). Trees extend to about 40 units from center. I'll also ensure they don't overlap too much by using a simple rejection sampling approach or just accepting some overlap (it's a forest, overlap is natural).

Actually, for a forest feel, I'll use a radius range of 7 to 40 units and place trees randomly. Some trees will be closer to the clearing (more visible), some far (fogged).

For tree count: The requirement says "at least 30". I'll go with 40 trees.

Let me also add some variation - some trees are bigger, some smaller. Scale from 0.7 to 2.5.

Alright, writing the code now. I'll structure it as:
1. HTML with importmap
2. Style (fullscreen, no margin)
3. Script with module import
4. Scene setup
5. Ground
6. Campfire with rocks, logs, flames
7. Trees
8. Stars
9. Moon
10. Particles (sparks)
11. Lights
12. Controls
13. Animation
14. Resize handling

Let me write it all out. I'll be very careful with the details.

For the ground, I'll create a `PlaneGeometry` with some segments and perturb the vertices slightly for a bumpy low-poly look. I'll use `flatShading: true` on the material.

Actually, for the ground, I want it to look natural but flat enough for the scene. I'll use a `PlaneGeometry(60, 60, 40, 40)` with some vertex perturbation, and dark earthy color.

For the fire rocks, I'll create about 10-12 small icosahedron geometries arranged in a circle with radius ~1.2 around the fire center.

For the logs, I'll create 3 cylinders positioned to cross over the fire center, slightly above the rocks.

For the fire pit, I could also create a shallow depression or just place rocks in a circle.

OK I think I've planned enough. Let me write the complete code.

Actually, one important thing: for the sparks to look good, they need to be visible against the dark background. I'll use `PointsMaterial` with a warm color and `size: 0.05` (which will be in world units), with `blending: THREE.AdditiveBlending` and `transparent: true`, and `depthWrite: false`.

For the moon, I'll use a sphere with `MeshBasicMaterial` with a warm pale color (0xffffcc or similar). I'll also possibly add a larger transparent glow sphere.

Wait, for the moon I should use `MeshStandardMaterial` with `emissive` set to the moon color so it's not affected by scene lighting. Or `MeshBasicMaterial` which is simpler. `MeshBasicMaterial` ignores lighting and just renders the color, which is perfect for a moon.

Actually, the moon should probably not be affected by the fog, or should it? In reality, the moon is very far away and not affected by atmospheric fog. In the scene, I could place it very far and it would be fogged out. To avoid this, I could use a different approach: put the moon on a separate layer that doesn't get fog, or just place it closer and make it smaller. 

For simplicity, I'll place the moon at a moderate distance (like 35 units away) where the fog will partially affect it, giving it a nice atmospheric look. Or I could position it outside the fog range. Actually, `THREE.Fog` doesn't affect `MeshBasicMaterial` in the same way? No, fog DOES affect all materials uniformly. The fog factor is applied based on distance regardless of material.

Hmm, to make the moon visible through the fog, I could use `material.fog = false` on the moon's material. This is a property of `MeshBasicMaterial` and other materials. Setting `fog: false` on the moon material will prevent it from being fogged.

For the stars, I'll also set `fog: false` on the `PointsMaterial`.

Wait, actually `PointsMaterial` and `MeshBasicMaterial` both have a `fog` property that can be set to `false` to disable fog for those objects. This is great for stars and the moon.

For the stars, I need them to be visible but they're at a large distance. If I place stars at distance 50+ and fog starts at 10 and is full at 45, the stars would be completely fogged. So I'll set `fog: false` on the star material.

Actually, for the stars, they should be more of a background element. I could:
1. Create a large sphere (radius 80) with stars as points on it, with fog disabled
2. Or create a separate scene for the sky

Option 1 with fog disabled on the star material is simpler.

Let me code it all now. I'll be comprehensive.

One more thing: the OrbitControls need to have `controls.target` set appropriately. I'll set it to (0, 0.5, 0) so the camera orbits around the fire area.

For the camera initial position: (12, 6, 12) gives a nice 45-degree viewing angle from a distance.

Alright, here's the complete code:

Actually, wait - I need to double-check: does the user's instruction say "self-playing demo mode" applies to games only? Let me re-read...

"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."

This is specifically about games. This 3D scene is not a game, so it doesn't need this. The scene should just load and be visible immediately.

Also: "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." - This applies to keyboard input. OrbitControls uses mouse/touch primarily, so no keyboard needed. The scene doesn't need keyboard input.

Alright, I think I have everything. Let me write the code.

Hmm, one more consideration: I want the flame animation to look good. I'll create 2-3 cone meshes:
- Main flame: cone geometry, orange color, positioned at fire center
- Inner flame: smaller cone, yellow color
- Maybe a third, even smaller, near-white flame

Each frame, I'll scale them up and down using sine waves at different frequencies, and also slightly move them up/down. This gives a flickering flame effect.

For the flame materials, I'll use `MeshBasicMaterial` since they're emissive:
- Outer flame: 0xff5500 (orange)
- Middle flame: 0xff9900 (warm orange)  
- Inner flame: 0xffdd00 (yellow)

I might also add a soft glow using a larger, semi-transparent sphere or cone around the fire.

Actually, for the glow effect, I can use a `PointLight` with a relatively large radius and warm color. The `PointLight` already provides the lighting for the scene. I could add a second point light that doesn't cast shadows and has a larger radius for a softer glow around the fire.

For the flame geometry, I want low-poly. I'll use `ConeGeometry` with 6-8 radial segments.

Let me finalize: 
- ConeGeometry(0.5, 1.5, 6) for the outer flame
- ConeGeometry(0.35, 1.2, 6) for the middle flame
- ConeGeometry(0.2, 0.8, 6) for the inner flame

These will be positioned at slightly different heights so the tips are at different levels, creating a layered flame effect.

OK I'm going to write the code now. I've been planning long enough.

Let me make sure I handle a few edge cases:
- The ground needs to be large enough
- The trees need to not intersect the fire
- The fog should create a nice atmospheric effect
- Shadows need to be configured correctly

For the ground vertex perturbation, I'll use `PlaneGeometry(60, 60, 40, 40)` and displace y-coordinates slightly. This gives a bumpy ground for the low-poly look.

Let me also make the campfire area slightly depressed (a small clearing in the center). I could make the ground displacement such that it's slightly lower near the center, creating a natural clearing.

Actually, that might be overcomplicating things. I'll just have a relatively flat ground with some minor bumps and place the fire on top.

Ok, here's my final plan for the tree creation function:

```javascript
function createTree() {
    const tree = new THREE.Group();
    
    // Trunk
    const trunkGeom = new THREE.CylinderGeometry(0.15, 0.25, 1.5, 6);
    const trunkMat = new THREE.MeshStandardMaterial({ color: 0x4a3728, flatShading: true });
    const trunk = new THREE.Mesh(trunkGeom, trunkMat);
    trunk.position.y = 0.75;
    trunk.castShadow = true;
    tree.add(trunk);
    
    // Foliage - stacked cones
    const foliageColors = [0x1a3d1a, 0x234d23, 0x2d5d2d];
    const foliageCount = 2 + Math.floor(Math.random() * 2); // 2-3 cones
    
    for (let i = 0; i < foliageCount; i++) {
        const size = 1.8 - i * 0.4;
        const foliageGeom = new THREE.ConeGeometry(size * 0.7, size, 6);
        const foliageMat = new THREE.MeshStandardMaterial({ 
            color: foliageColors[Math.floor(Math.random() * foliageColors.length)], 
            flatShading: true 
        });
        const foliage = new THREE.Mesh(foliageGeom, foliageMat);
        foliage.position.y = 1.5 + i * 0.6;
        foliage.castShadow = true;
        tree.add(foliage);
    }
    
    return tree;
}
```

This creates a nice low-poly tree with a trunk and 2-3 cone foliage layers.

For tree placement:
```javascript
for (let i = 0; i < 40; i++) {
    const tree = createTree();
    const angle = Math.random() * Math.PI * 2;
    const distance = 7 + Math.random() * 33; // 7 to 40 units from center
    const x = Math.cos(angle) * distance;
    const z = Math.sin(angle) * distance;
    tree.position.set(x, 0, z);
    const scale = 0.7 + Math.random() * 1.8;
    tree.scale.set(scale, scale, scale);
    tree.rotation.y = Math.random() * Math.PI * 2;
    scene.add(tree);
}
```

This will place trees in a ring around the clearing, from 7 to 40 units out.

Alright, writing the complete HTML file now. I'll be thorough.

Actually, I just realized I should also think about the overall code structure more carefully. Let me lay it out:

1. Import from Three.js module
2. Import OrbitControls
3. Create scene, camera, renderer
4. Set up fog and background
5. Add lights (hemisphere, point light for fire)
6. Create ground
7. Create rocks around fire
8. Create logs over fire
9. Create fire flames
10. Create trees
11. Create sparks particle system
12. Create stars
13. Create moon
14. Set up OrbitControls
15. Animation loop
16. Resize handler

Let me now write it. I'll aim for clean, well-commented code.

Regarding the ground: I want to make it a nice dark earthy color. Something like `0x2a2018` or `0x1e1810`. Let me use `0x2a2018` as it's a dark brown that will work well with the fire's warm light.

Actually, I want to be careful about the overall darkness. A pure night scene can be too dark. Let me use:
- Background: `0x0a0a1a` (very dark blue)
- Fog: same color as background
- Ground: `0x2a2018` (dark brown)
- Hemisphere light with sky `0x1a1a3a` and ground `0x1a1510`, intensity 0.3

This should give enough visibility while maintaining a night atmosphere.

For the hemisphere light, this is actually perfect for a night scene. The sky is dark blue (moonlight/starlight) and the ground is dark brown (reflected light). The fire's warm point light will contrast nicely.

Let me finalize the code now. I need to be precise.

I also want to consider adding some kind of ambient life - maybe fireflies? No, the task doesn't mention fireflies. I'll stick to what was requested.

One thing I want to add: the campfire should look like it has burning logs. I'll add some cylinder "logs" positioned as if they're in the fire, with a reddish-brown color. These will be visible beneath the flames.

OK, I'm writing the full code now. Let me also be specific about the spark particle system implementation.

Sparks:
- Count: 100
- Each spark has: position (x, y, z), velocity (vx, vy, vz), life
- They spawn at the fire position with some random offset
- They rise with some upward velocity, and drift slightly
- They die (reset) after reaching a certain height or age
- Their color can be warm orange/yellow, possibly with size variation

I'll use a `BufferGeometry` with `position` attribute and a `PointsMaterial`. I'll update the positions in the animation loop.

For the spark size, I'll use `PointsMaterial.size` set to maybe 0.06 (small bright points). With `AdditiveBlending` and `transparent: true`, they'll look like glowing sparks.

For spark velocity, most sparks should go up with some horizontal drift. A few might have more dramatic trajectories.

The sparks should also fade in and out. Since `PointsMaterial` doesn't support per-particle opacity, I could:
1. Use `vertexColors` with color attribute to vary colors
2. Just use a uniform color and rely on the additive blending for the glow effect
3. Use a custom shader material for more control

For simplicity, I'll use a uniform color with `AdditiveBlending`. The sparks will be bright at spawn and will naturally look like they fade as they rise and their size stays constant but they spread out... actually, with `AdditiveBlending`, overlapping sparks will look brighter. This might actually look good.

Actually, I can also vary the spark sizes using a `size` attribute and use `PointsMaterial` with `sizeAttenuation: true` so that closer sparks appear larger. But for this scene, simple uniform size should be fine.

I could also use `Points` with `alphaMap` for a soft glow, but since we can't use external textures, I'll create a canvas-generated texture for the spark sprite. Or I can just use simple points. For the low-poly aesthetic, simple points work well.

Alright, I think I've overthought this. Let me just write the code.

One more thing: the pixel ratio. I should set `renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))` to keep the renderer sharp without being too expensive.

OK, here's the complete code. I'll write it now.

Let me also add a subtle ground texture change near the fire. I can create a slightly darker circle of ground around the fire to represent the fire pit area. Or I could just rely on the rocks.

For the fire pit, I think rocks arranged in a circle plus some dark burn marks (a dark disc on the ground) would look nice. I'll create a slightly darker, flattened cylinder or disc on the ground at the center.

Actually, I'll use a flattened cylinder or just a circle geometry with a dark material for the fire pit. A `CircleGeometry` flat on the ground at y=0.01 with a dark color.

Ok here is my final structure. Writing the code now.

Let me also think about whether to add any post-processing. For a low-poly scene, post-processing might not be necessary and would add complexity. I'll skip it.

Half the code will be setting up the scene, the other half will be the tree generation and particle systems.

Alright, here's the code. I'm going to write it all out now completely.

I want to also handle the case where the user's `OrbitControls` target is set. The controls should orbit around the fire. I'll set `controls.target.set(0, 1, 0)`.

For the fire, the center is at (0, 0, 0), with flames extending up to about y=1.5-2. The controls target of y=1 should be good.

I also need to consider `controls.update()` - with `enableDamping: true`, I need to call `controls.update()` in the animation loop.

Alright, final code:

I'll also add `controls.autoRotate = true` initially with a slow auto-rotate speed so the scene has some gentle motion even without interaction. Wait, actually, that might not be desirable. Let me set `autoRotate` to false and just rely on the fire animation for motion. The flames and sparks will be noticeably animated.

Actually, a very slow auto-rotate could be nice for the demo aspect. Let me use a very slow auto-rotate: 0.3 degrees per second. The user can still override with manual rotation. OrbitControls supports `autoRotate` and `autoRotateSpeed`.

Hmm, but the instruction says "Everything that makes the demo interesting must be visible within the first seconds" - a rotating camera would show off the scene nicely. But it might also be distracting. I'll enable a very slow auto-rotate.

Actually, I'll leave auto-rotate off. The fire animation (flames, sparks, flickering light) is already quite dynamic. The user can rotate the camera to explore.

OK, final final code. Writing now.

I realize I should also consider the shadows more carefully. With a PointLight casting shadows, only objects within the shadow camera's frustum will cast shadows. Since it's a point light, the shadow camera is actually a cube (6 faces). The `shadow.camera.near` and `shadow.camera.far` determine the range.

I'll set:
- `pointLight.shadow.mapSize.width = 512`
- `pointLight.shadow.mapSize.height = 512`
- `pointLight.shadow.camera.near = 0.1`
- `pointLight.shadow.camera.far = 20`
- `pointLight.shadow.bias = -0.001`

The `PointLight` will be at (0, 1.5, 0), slightly above the fire.

This should cast nice shadows from the trees, rocks, and logs onto the ground.

OK, writing the code now. For real this time.

One important thing: in the import map, I need to make sure the JSON is correct. The user specified:
```
<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>
```

This is the exact format I should use.

Now, for the OrbitControls import, since it's from the `three/addons/` namespace, the path would be `three/addons/controls/OrbitControls.js`.

Let me also verify: Three.js r170 should have OrbitControls at `examples/jsm/controls/OrbitControls.js`. Yes, this is standard.

Now let me draft the complete HTML and make sure everything is right.

One more consideration - the total size and complexity. The scene has:
- 1 ground plane
- ~12 rocks
- ~3-4 logs over fire
- 3 cone flames
- ~40 trees (each with 1 trunk + 2-3 cones = ~3-4 meshes each, so ~120-160 meshes)
- 100 sparks
- ~500 stars
- 1 moon

Total: ~230-270 draw calls. This should be fine for modern browsers.

For the tree trunk and foliage, I'll use shared geometries and materials where possible to reduce memory. But for the low-poly flat-shaded look, I might need flat shading which works fine with shared materials.

Actually, for different tree colors, I'll create a few different foliage materials and reuse them. This reduces the number of material instances.

OK, I'll create material caches:
- 2-3 trunk materials (brown variations)
- 3-4 foliage materials (green variations)
- 1 rock material
- 1 ground material

This will be efficient.

OK, I'm going to write the complete code now. No more planning.