Models 44

GPT-5 Nano

by OpenAIReasoningClean
Open in new window

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>