Models 44

Gemini 3.8 Flash

by GoogleReasoningClean
Open in new window

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

How it went

Total time
29.3 s
Cost
$0.021
Output tokens
5,376
Spent thinking
0 tokens
Speed
195.2 tok/s
Console errors
0
First token after
1.7 s
First visible text after
1.7 s
Prompt tokens
592
Code size
16.9 KB
Finish reason
stop
Served by
Google
Model id
google/gemini-3.8-flash
Reasoning sent
{"effort":"low"}
Max output
65,536 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 · 458 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 Campfire in Forest Clearing</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        body {
            overflow: hidden;
            background-color: #03050c;
            user-select: none;
            font-family: sans-serif;
        }
        #canvas-container {
            width: 100vw;
            height: 100vh;
            display: block;
        }
        #info {
            position: absolute;
            top: 20px;
            left: 20px;
            color: #d1dcff;
            font-size: 13px;
            letter-spacing: 1px;
            text-transform: uppercase;
            background: rgba(3, 8, 20, 0.6);
            padding: 10px 16px;
            border-radius: 8px;
            border: 1px solid rgba(130, 160, 255, 0.15);
            pointer-events: none;
            backdrop-filter: blur(4px);
        }
    </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 id="info">Campfire in the Pines &bull; Drag to Orbit</div>
    <div id="canvas-container"></div>

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

        // --- Core Setup ---
        const container = document.getElementById('canvas-container');
        const scene = new THREE.Scene();
        
        const NIGHT_COLOR = 0x050814;
        scene.background = new THREE.Color(NIGHT_COLOR);
        scene.fog = new THREE.FogExp2(NIGHT_COLOR, 0.028);

        const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200);
        camera.position.set(0, 7, 16);

        const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" });
        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.05;
        container.appendChild(renderer.domElement);

        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        controls.maxPolarAngle = Math.PI / 2 - 0.05; // Keep above ground
        controls.minDistance = 3;
        controls.maxDistance = 32;
        controls.target.set(0, 1.2, 0);

        // --- Materials ---
        const groundMat = new THREE.MeshStandardMaterial({
            color: 0x1a2e1d,
            flatShading: true,
            roughness: 0.95,
            metalness: 0.05
        });

        const trunkMat = new THREE.MeshStandardMaterial({
            color: 0x2b1d14,
            flatShading: true,
            roughness: 0.9
        });

        const leavesColors = [0x153823, 0x1e4a2c, 0x102d1d];

        const rockMat = new THREE.MeshStandardMaterial({
            color: 0x3d434a,
            flatShading: true,
            roughness: 0.85
        });

        const logMat = new THREE.MeshStandardMaterial({
            color: 0x3c2619,
            flatShading: true,
            roughness: 0.8
        });

        // --- Lighting ---
        // Subtle ambient for night visibility
        const ambientLight = new THREE.AmbientLight(0x0a1428, 0.8);
        scene.add(ambientLight);

        // Pale moonlight from high up
        const moonLight = new THREE.DirectionalLight(0x82a5d8, 0.45);
        moonLight.position.set(-25, 40, -30);
        moonLight.castShadow = true;
        moonLight.shadow.mapSize.width = 1024;
        moonLight.shadow.mapSize.height = 1024;
        moonLight.shadow.bias = -0.001;
        scene.add(moonLight);

        // Campfire warm point light
        const fireLight = new THREE.PointLight(0xff7722, 5.0, 24, 1.6);
        fireLight.position.set(0, 0.9, 0);
        fireLight.castShadow = true;
        fireLight.shadow.mapSize.width = 1024;
        fireLight.shadow.mapSize.height = 1024;
        fireLight.shadow.bias = -0.002;
        scene.add(fireLight);

        // Inner fire glow
        const innerGlow = new THREE.PointLight(0xff2200, 2.0, 8, 2);
        innerGlow.position.set(0, 0.4, 0);
        scene.add(innerGlow);

        // --- Moon & Sky ---
        const moonGroup = new THREE.Group();
        const moonGeo = new THREE.SphereGeometry(2.5, 7, 7);
        const moonMat = new THREE.MeshBasicMaterial({ color: 0xddf0ff });
        const moonMesh = new THREE.Mesh(moonGeo, moonMat);
        moonGroup.position.set(-35, 48, -45);
        moonGroup.add(moonMesh);
        scene.add(moonGroup);

        // Stars
        const starCount = 300;
        const starGeo = new THREE.BufferGeometry();
        const starPos = [];
        for (let i = 0; i < starCount; i++) {
            const x = (Math.random() - 0.5) * 140;
            const y = 15 + Math.random() * 55;
            const z = (Math.random() - 0.5) * 140;
            starPos.push(x, y, z);
        }
        starGeo.setAttribute('position', new THREE.Float32BufferAttribute(starPos, 3));
        const starMat = new THREE.PointsMaterial({
            color: 0xccddff,
            size: 0.35,
            transparent: true,
            opacity: 0.8
        });
        const stars = new THREE.Points(starGeo, starMat);
        scene.add(stars);

        // --- Low-Poly Ground ---
        const groundGeo = new THREE.PlaneGeometry(70, 70, 32, 32);
        groundGeo.rotateX(-Math.PI / 2);
        const posAttr = groundGeo.attributes.position;
        for (let i = 0; i < posAttr.count; i++) {
            const vx = posAttr.getX(i);
            const vz = posAttr.getZ(i);
            const distFromCenter = Math.sqrt(vx * vx + vz * vz);
            
            // Keep clearing level, displace edges upward slightly
            let elevation = 0;
            if (distFromCenter > 6) {
                const noise = Math.sin(vx * 0.3) * Math.cos(vz * 0.3) * 0.7;
                const edgeRamp = Math.min((distFromCenter - 6) * 0.12, 1.8);
                elevation = noise + edgeRamp;
            }
            posAttr.setY(i, elevation);
        }
        groundGeo.computeVertexNormals();
        const ground = new THREE.Mesh(groundGeo, groundMat);
        ground.receiveShadow = true;
        scene.add(ground);

        // --- Low-Poly Tree Generator ---
        function createTree(scale = 1) {
            const tree = new THREE.Group();

            // Trunk
            const trunkHeight = 1.4 * scale;
            const trunkGeo = new THREE.CylinderGeometry(0.18 * scale, 0.3 * scale, trunkHeight, 5);
            const trunk = new THREE.Mesh(trunkGeo, trunkMat);
            trunk.position.y = trunkHeight / 2;
            trunk.castShadow = true;
            trunk.receiveShadow = true;
            tree.add(trunk);

            // Foliage tiers
            const tiers = 3;
            const chosenCol = leavesColors[Math.floor(Math.random() * leavesColors.length)];
            const leavesMat = new THREE.MeshStandardMaterial({
                color: chosenCol,
                flatShading: true,
                roughness: 0.9
            });

            for (let i = 0; i < tiers; i++) {
                const radius = (1.5 - i * 0.32) * scale;
                const height = (1.8 - i * 0.25) * scale;
                const coneGeo = new THREE.ConeGeometry(radius, height, 5);
                const cone = new THREE.Mesh(coneGeo, leavesMat);
                cone.position.y = trunkHeight + (i * 0.9 * scale);
                cone.rotation.y = (i * 0.8) + Math.random() * 0.3;
                cone.castShadow = true;
                cone.receiveShadow = true;
                tree.add(cone);
            }

            return tree;
        }

        // Place Trees (At least 30 around clearing)
        const treeCount = 55;
        for (let i = 0; i < treeCount; i++) {
            const angle = Math.random() * Math.PI * 2;
            // Distribute outside clearing radius (r > 6.0)
            const dist = 6.2 + Math.sqrt(Math.random()) * 20;
            const x = Math.cos(angle) * dist;
            const z = Math.sin(angle) * dist;

            const scale = 0.8 + Math.random() * 0.6;
            const tree = createTree(scale);
            tree.position.set(x, 0, z);
            tree.rotation.y = Math.random() * Math.PI * 2;
            scene.add(tree);
        }

        // --- Low-Poly Rock Generator ---
        function createRock(scale = 1) {
            const rockGeo = new THREE.DodecahedronGeometry(0.5 * scale, 0);
            // Jitter vertices for low-poly variety
            const p = rockGeo.attributes.position;
            for (let i = 0; i < p.count; i++) {
                p.setXYZ(
                    i,
                    p.getX(i) + (Math.random() - 0.5) * 0.15 * scale,
                    p.getY(i) + (Math.random() - 0.5) * 0.15 * scale,
                    p.getZ(i) + (Math.random() - 0.5) * 0.15 * scale
                );
            }
            rockGeo.computeVertexNormals();
            const rock = new THREE.Mesh(rockGeo, rockMat);
            rock.castShadow = true;
            rock.receiveShadow = true;
            return rock;
        }

        // Fire Pit Stone Ring
        const ringRockCount = 10;
        const ringRadius = 1.05;
        for (let i = 0; i < ringRockCount; i++) {
            const angle = (i / ringRockCount) * Math.PI * 2 + (Math.random() * 0.1);
            const r = ringRadius + (Math.random() - 0.5) * 0.1;
            const rock = createRock(0.42 + Math.random() * 0.2);
            rock.position.set(Math.cos(angle) * r, 0.12, Math.sin(angle) * r);
            rock.rotation.set(Math.random(), Math.random(), Math.random());
            scene.add(rock);
        }

        // Additional rocks scattered in clearing
        for (let i = 0; i < 7; i++) {
            const angle = Math.random() * Math.PI * 2;
            const dist = 2.5 + Math.random() * 3.5;
            const rock = createRock(0.5 + Math.random() * 0.5);
            rock.position.set(Math.cos(angle) * dist, 0.2, Math.sin(angle) * dist);
            rock.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, 0);
            scene.add(rock);
        }

        // Firewood Logs in Campfire
        const fireWoodCount = 5;
        for (let i = 0; i < fireWoodCount; i++) {
            const logGeo = new THREE.CylinderGeometry(0.1, 0.12, 1.1, 5);
            const wood = new THREE.Mesh(logGeo, logMat);
            const angle = (i / fireWoodCount) * Math.PI * 2;
            wood.position.set(Math.cos(angle) * 0.25, 0.2, Math.sin(angle) * 0.25);
            wood.rotation.y = angle;
            wood.rotation.x = 0.55;
            wood.rotation.z = (Math.random() - 0.5) * 0.2;
            wood.castShadow = true;
            wood.receiveShadow = true;
            scene.add(wood);
        }

        // Sitting Log Bench near campfire
        const benchGeo = new THREE.CylinderGeometry(0.24, 0.26, 2.8, 6);
        benchGeo.rotateZ(Math.PI / 2);
        const bench = new THREE.Mesh(benchGeo, logMat);
        bench.position.set(0, 0.2, 2.2);
        bench.rotation.y = 0.15;
        bench.castShadow = true;
        bench.receiveShadow = true;
        scene.add(bench);

        // --- Low-Poly Animated Flames ---
        const flameGroup = new THREE.Group();
        const flameCount = 14;
        const flameMeshes = [];

        const flameGeo = new THREE.ConeGeometry(0.28, 0.9, 4);
        const flameMatOuter = new THREE.MeshBasicMaterial({
            color: 0xff5500,
            transparent: true,
            opacity: 0.82,
            blending: THREE.AdditiveBlending
        });
        const flameMatInner = new THREE.MeshBasicMaterial({
            color: 0xffcc22,
            transparent: true,
            opacity: 0.92,
            blending: THREE.AdditiveBlending
        });

        for (let i = 0; i < flameCount; i++) {
            const isInner = i < 5;
            const mesh = new THREE.Mesh(flameGeo, isInner ? flameMatInner : flameMatOuter);
            const radius = isInner ? 0.12 : 0.28;
            const angle = (i / flameCount) * Math.PI * 2;
            mesh.userData = {
                baseX: Math.cos(angle) * radius,
                baseZ: Math.sin(angle) * radius,
                speed: 4.5 + Math.random() * 5.0,
                phase: Math.random() * Math.PI * 2,
                baseScale: isInner ? 0.85 : 0.65
            };
            flameGroup.add(mesh);
            flameMeshes.push(mesh);
        }
        flameGroup.position.set(0, 0.25, 0);
        scene.add(flameGroup);

        // --- Rising Sparks Particle System ---
        const sparkCount = 80;
        const sparkGeo = new THREE.BufferGeometry();
        const sparkPositions = new Float32Array(sparkCount * 3);
        const sparkVelocities = [];

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

            sparkVelocities[index] = {
                vx: (Math.random() - 0.5) * 0.015,
                vy: 0.025 + Math.random() * 0.035,
                vz: (Math.random() - 0.5) * 0.015,
                life: Math.random() * 1.5,
                maxLife: 1.2 + Math.random() * 1.2
            };
        }

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

        sparkGeo.setAttribute('position', new THREE.BufferAttribute(sparkPositions, 3));
        const sparkMat = new THREE.PointsMaterial({
            color: 0xffaa22,
            size: 0.12,
            transparent: true,
            opacity: 0.85,
            blending: THREE.AdditiveBlending
        });
        const sparks = new THREE.Points(sparkGeo, sparkMat);
        scene.add(sparks);

        // --- Animation Loop ---
        const clock = new THREE.Clock();

        function animate() {
            requestAnimationFrame(animate);

            const elapsedTime = clock.getElapsedTime();

            // 1. Animate Fire Light Flickering
            const flicker = Math.sin(elapsedTime * 12) * 0.25 + 
                            Math.sin(elapsedTime * 23.4) * 0.18 + 
                            Math.sin(elapsedTime * 31.7) * 0.12;
            fireLight.intensity = 5.2 + flicker * 1.8;
            fireLight.position.x = Math.sin(elapsedTime * 7) * 0.08;
            fireLight.position.z = Math.cos(elapsedTime * 8.3) * 0.08;

            innerGlow.intensity = 2.2 + flicker * 0.8;

            // 2. Animate Flame Meshes
            for (let i = 0; i < flameMeshes.length; i++) {
                const mesh = flameMeshes[i];
                const u = mesh.userData;
                const wave = Math.sin(elapsedTime * u.speed + u.phase);
                
                // Pulsate height and width
                const sy = u.baseScale * (1.1 + wave * 0.35);
                const sxz = u.baseScale * (0.9 - wave * 0.2);
                mesh.scale.set(sxz, sy, sxz);

                // Sway outward and upward
                mesh.position.y = (sy * 0.45);
                mesh.position.x = u.baseX + Math.sin(elapsedTime * 5 + u.phase) * 0.04;
                mesh.position.z = u.baseZ + Math.cos(elapsedTime * 5 + u.phase) * 0.04;
                mesh.rotation.y = elapsedTime * 2 + u.phase;
                mesh.rotation.z = Math.sin(elapsedTime * 4 + u.phase) * 0.12;
            }

            // 3. Animate Rising Sparks
            const pos = sparks.geometry.attributes.position.array;
            for (let i = 0; i < sparkCount; i++) {
                const v = sparkVelocities[i];
                v.life += 0.016;

                pos[i * 3] += v.vx + Math.sin(elapsedTime * 4 + i) * 0.003;
                pos[i * 3 + 1] += v.vy;
                pos[i * 3 + 2] += v.vz + Math.cos(elapsedTime * 4 + i) * 0.003;

                // Drift slightly with wind
                pos[i * 3] += 0.002;

                if (v.life > v.maxLife || pos[i * 3 + 1] > 4.5) {
                    resetSpark(i);
                }
            }
            sparks.geometry.attributes.position.needsUpdate = true;

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

        animate();

        // --- Window Resize Handling ---
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        });
    </script>
</body>
</html>