GPT-5 Nano
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 31.0 s
- Cost
- $0.0017
- Output tokens
- 4,218
- Spent thinking
- 384 tokens
- Speed
- 153.9 tok/s
- Console errors
- 0
- First token after
- 3.6 s
- First visible text after
- 3.6 s
- Prompt tokens
- 555
- Code size
- 12.4 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-5-nano- Reasoning sent
{"effort":"low"}- Max output
- 128,000 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 · 361 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Low-Poly Campfire in a Forest Clearing</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root { color-scheme: dark; }
html, body { margin: 0; padding: 0; height: 100%; background: #0b1020; }
canvas { display: block; width: 100%; height: 100%; }
/* Start overlay */
#startOverlay {
position: fixed; inset: 0;
display: flex; align-items: center; justify-content: center;
background: rgba(6, 10, 20, 0.85);
color: #e8f0ff; font-family: system-ui, -apple-system, "Segoe UI", Roboto;
z-index: 2;
text-align: center;
padding: 20px;
}
#startOverlay h1 { margin: 0 0 8px; font-size: 28px; }
#startOverlay p { margin: 0; font-size: 14px; opacity: 0.95; }
@media (max-width: 600px) {
#startOverlay h1 { font-size: 20px; }
}
/* Subtle vignette for mood */
.vignette {
position: fixed; left:0; top:0; width:100%; height:100%;
pointer-events:none; mix-blend-mode: multiply;
background: radial-gradient(circle at center, rgba(0,0,0,0) 25%, rgba(0,0,0,0.35) 60%, rgba(0,0,0,0.65) 100%);
}
</style>
<!-- Import map with Three.js 0.170.0 as requested -->
<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="startOverlay" aria-label="Start message" role="dialog" tabindex="0">
<div>
<h1>Click or press any key to start</h1>
<p>A low-poly campfire in a forest clearing. Orbit around with the mouse to explore.</p>
</div>
</div>
<div class="vignette" aria-hidden="true"></div>
<script type="module">
// Import Three.js modules
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Scene, camera, renderer
const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x0a0b1a, 40, 160);
// Camera setup
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(0, 8, 22);
// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// Orbit controls with limits
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.minDistance = 6;
controls.maxDistance = 60;
controls.minPolarAngle = THREE.MathUtils.degToRad(8); // prevent looking too low
controls.maxPolarAngle = THREE.MathUtils.degToRad(85); // stay above ground
controls.enablePan = false;
// Lighting
const ambient = new THREE.AmbientLight(0x333355);
scene.add(ambient);
// Moon and stars (sky)
const skyColor = new THREE.Color(0x0a0b1a);
scene.background = skyColor;
// Moon
const moonGeo = new THREE.SphereGeometry(2.4, 32, 16);
const moonMat = new THREE.MeshStandardMaterial({
color: 0xeeeeff,
emissive: 0x8888ff,
emissiveIntensity: 0.7,
roughness: 0.6,
});
const moon = new THREE.Mesh(moonGeo, moonMat);
moon.position.set(-40, 50, -60);
moon.scale.set(0.9, 0.9, 0.9);
moon.castShadow = false;
moon.receiveShadow = false;
scene.add(moon);
// Stars
const starCount = 300;
const starsGeo = new THREE.BufferGeometry();
const starsPos = [];
for (let i = 0; i < starCount; i++) {
const radial = 60 + Math.random() * 80;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos((Math.random() * 2) - 1);
const x = radial * Math.sin(phi) * Math.cos(theta);
const y = radial * Math.sin(phi) * Math.sin(theta) + 20;
const z = radial * Math.cos(phi);
starsPos.push(x, y, z);
}
starsGeo.setAttribute('position', new THREE.Float32BufferAttribute(starsPos, 3));
const starsMat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.4, sizeAttenuation: true });
const stars = new THREE.Points(starsGeo, starsMat);
scene.add(stars);
// Ground
const groundGeo = new THREE.PlaneGeometry(120, 120);
const groundMat = new THREE.MeshStandardMaterial({ color: 0x2a5a2b, roughness: 0.9 });
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
ground.position.y = 0;
scene.add(ground);
// Slight elevation for clearing
const clearingRadius = 18;
// Fog-distant trees around clearing
const treeGroup = new THREE.Group();
scene.add(treeGroup);
// Helper: low-poly tree prototype
function createLowPolyTree() {
const trunk = new THREE.Mesh(
new THREE.CylinderGeometry(0.3, 0.4, 2.5, 6),
new THREE.MeshStandardMaterial({ color: 0x6b3e16, roughness: 0.9 })
);
trunk.castShadow = true;
trunk.receiveShadow = true;
// foliage as stacked cones
const foliage = new THREE.Group();
const coneMat = new THREE.MeshStandardMaterial({ color: 0x2e8b57, roughness: 0.9 });
const layers = 3;
for (let i = 0; i < layers; i++) {
const r = 1.8 - i * 0.5;
const h = 1.0;
const cone = new THREE.Mesh(new THREE.ConeGeometry(r, h, 6), coneMat);
cone.position.y = 2.2 + i * 0.9;
cone.rotation.y = (Math.random() * 0.5);
cone.castShadow = true;
cone.receiveShadow = true;
foliage.add(cone);
}
const group = new THREE.Group();
group.add(trunk);
group.add(foliage);
// Slight random tilt
group.rotation.y = Math.random() * Math.PI * 2;
return group;
}
// Place ~30 trees randomly around clearing but not on fire area
const treeCount = 30;
for (let i = 0; i < treeCount; i++) {
const t = createLowPolyTree();
// random polar around center with radius [clear radius, ~40]
const angle = Math.random() * Math.PI * 2;
const dist = clearingRadius + Math.random() * 25;
t.position.set(Math.cos(angle) * dist, 0, Math.sin(angle) * dist);
t.rotation.y = Math.random() * Math.PI * 2;
t.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
treeGroup.add(t);
}
// Campfire base
const fireGroup = new THREE.Group();
fireGroup.position.set(0, 0.15, 0);
scene.add(fireGroup);
// Fire pit ring - low poly stones
const rockMat = new THREE.MeshStandardMaterial({ color: 0x8a8a8a, roughness: 0.9, metalness: 0 });
const ringRadius = 2.0;
const ring = new THREE.Mesh(new THREE.TorusGeometry(ringRadius, 0.15, 6, 12), rockMat);
ring.rotation.x = Math.PI / 2;
ring.receiveShadow = true;
ring.castShadow = true;
fireGroup.add(ring);
// Logs around the fire (low-poly look)
const logMat = new THREE.MeshStandardMaterial({ color: 0x6b4a1e, roughness: 0.9 });
const logGeo = new THREE.BoxGeometry(0.8, 0.25, 2.2);
for (let i = 0; i < 4; i++) {
const log = new THREE.Mesh(logGeo, logMat);
log.rotation.y = i * (Math.PI / 2) + (Math.random() * 0.15 - 0.075);
log.position.set(Math.cos(i * Math.PI / 2) * 1.2, 0.15, Math.sin(i * Math.PI / 2) * 1.2);
log.castShadow = true;
log.receiveShadow = true;
fireGroup.add(log);
}
// Campfire flames - animated
const flameBase = new THREE.Mesh(new THREE.ConeGeometry(0.9, 1.8, 6), new THREE.MeshStandardMaterial({
color: 0xffa500, emissive: 0xffa500, emissiveIntensity: 1, transparent: true, opacity: 0.95
}));
flameBase.position.set(0, 0.9, 0);
flameBase.rotation.x = Math.PI;
flameBase.castShadow = true;
flameBase.receiveShadow = true;
fireGroup.add(flameBase);
// Inner glow
const glow = new THREE.Mesh(new THREE.SphereGeometry(1.1, 8, 8),
new THREE.MeshBasicMaterial({ color: 0xffd27a, transparent: true, opacity: 0.25 }));
glow.position.set(0, 0.75, 0);
fireGroup.add(glow);
// Spark particles rising from fire
const sparkCount = 80;
const sparkGeo = new THREE.BufferGeometry();
const sparksPos = new Float32Array(sparkCount * 3);
for (let i = 0; i < sparkCount; i++) {
sparksPos[i * 3 + 0] = (Math.random() - 0.5) * 0.8;
sparksPos[i * 3 + 1] = Math.random() * 0.6;
sparksPos[i * 3 + 2] = (Math.random() - 0.5) * 0.8;
}
sparkGeo.setAttribute('position', new THREE.BufferAttribute(sparksPos, 3));
const sparkMat = new THREE.PointsMaterial({ color: 0xffffaa, size: 0.08, additive: true, transparent: true, opacity: 0.9 });
const sparks = new THREE.Points(sparkGeo, sparkMat);
sparks.frustumCulled = false;
sparks.userData = { life: 0, speed: 0.01 };
fireGroup.add(sparks);
// Simple point light for warm glow with flicker
const fireLight = new THREE.PointLight(0xffd0a0, 1.4, 25, 2);
fireLight.position.set(0, 0.9, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(512, 512);
fireGroup.add(fireLight);
// Ground-level shadows for nearby rocks/logs
// Rocks around campfire (low-poly)
function addRock(x, z, scale = 1) {
const rock = new THREE.Mesh(new THREE.IcosahedronGeometry(0.4 * scale, 0), new THREE.MeshStandardMaterial({ color: 0x7a6a5c, roughness: 0.9 }));
rock.position.set(x, 0.15, z);
rock.scale.set(scale, scale, scale);
rock.rotation.set(Math.random() * 0.4, Math.random() * Math.PI, Math.random() * 0.4);
rock.castShadow = true;
rock.receiveShadow = true;
scene.add(rock);
}
addRock(2.6, 4.0, 0.9);
addRock(-3.5, 5.4, 1.0);
addRock(-4.2, -3.1, 0.8);
addRock(3.8, -4.6, 0.7);
// Logs nearby - extra detail
function addLog(x, z, yaw) {
const log = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.25, 2.5), logMat);
log.position.set(x, 0.15, z);
log.rotation.y = yaw;
log.castShadow = true;
log.receiveShadow = true;
scene.add(log);
}
addLog(-1.2, 2.8, Math.PI * 0.25);
addLog(1.6, -2.2, -Math.PI * 0.1);
// Resize handling
window.addEventListener('resize', onResize, { passive: true });
function onResize() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
}
// Start / interaction overlay
const overlay = document.getElementById('startOverlay');
let started = false;
function startDemo() {
if (started) return;
started = true;
overlay.style.opacity = '0';
overlay.style.pointerEvents = 'none';
// kick off small animation pace
}
// Keyboard / click to start
window.addEventListener('pointerdown', startDemo, { once: true });
window.addEventListener('keydown', startDemo, { once: true });
// Animate
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
// Flames sway and scale
const flameScale = 0.9 + Math.sin(t * 6) * 0.15;
flameBase.scale.set(flameScale, flameScale, flameScale);
flameBase.material.color.setHSL(0.07, 1.0, 0.5 + Math.sin(t * 4) * 0.05);
// Flicker fire light
fireLight.intensity = 1.2 + Math.sin(t * 8) * 0.4;
fireLight.color.setHSL(0.08 + Math.sin(t * 2) * 0.02, 1.0, 0.6);
// Sparks move upward and fade
const pos = sparks.geometry.attributes.position;
const count = pos.count;
for (let i = 0; i < count; i++) {
let x = pos.getX(i);
let y = pos.getY(i);
let z = pos.getZ(i);
y += (0.005 + Math.abs(Math.sin(t * 3 + i) * 0.003)) * 2;
x += Math.sin(t * 2 + i) * 0.0008;
z += Math.cos(t * 2 + i) * 0.0008;
if (y > 2.0) {
// reset near base
y = Math.random() * 0.6;
x = (Math.random() - 0.5) * 0.6;
z = (Math.random() - 0.5) * 0.6;
}
pos.setXYZ(i, x, y, z);
}
pos.needsUpdate = true;
// update controls if started
if (started) controls.update();
renderer.render(scene, camera);
}
// Start loop but keep overlay until user starts
animate();
</script>
</body>
</html>