Gemini 3.8 Flash
by GoogleReasoningClean
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
- 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 • 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>