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
1 min 21 s
Cost
$0.0047
Output tokens
11,569
Spent thinking
7,680 tokens
Speed
148.5 tok/s
Console errors
0
First token after
3.4 s
First visible text after
58.4 s
Prompt tokens
555
Code size
13.4 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-5-nano
Reasoning sent
{"effort":"medium"}
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 · 410 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>Low-Poly Campfire in a Forest Clearing (Three.js, single file)</title>
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <!-- Import map for Three.js modules -->
  <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>

  <style>
    html, body {
      height: 100%;
      margin: 0;
      background: #04070a;
      overflow: hidden;
      font-family: system-ui, -apple-system, "Segoe UI", Roboto;
    }
    canvas { display: block; width: 100%; height: 100%; }
    /* Overlay message to start the demo */
    #overlay {
      position: fixed;
      top: 0; left: 0; right: 0; bottom: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      text-align: center;
      background: rgba(4,6,12,0.82);
      color: #e7f0ff;
      font-size: 28px;
      padding: 20px;
      z-index: 10;
      user-select: none;
      line-height: 1.3;
    }
    @media (max-width: 700px) {
      #overlay { font-size: 22px; padding: 16px; }
    }
  </style>
</head>
<body>
  <!-- Start message (visible on load). The 3D scene starts behind this after first click/keypress. -->
  <div id="overlay" aria-label="Start the demo">Click or press any key to start</div>

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

    // Core scene
    let scene, camera, renderer, controls;
    let campfireGroup;
    let flame;
    let fireLight;
    let sparks;
    let started = false;
    let clock;

    // Lazy overlay element reference
    let overlayEl;

    init();
    animate();

    function init() {
      // Scene
      scene = new THREE.Scene();
      // Night sky fog that fades distant trees
      scene.fog = new THREE.FogExp2(0x0a0f1a, 0.015);

      // Camera
      const width = window.innerWidth;
      const height = window.innerHeight;
      camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
      camera.position.set(0, 6, 14);

      // Renderer
      renderer = new THREE.WebGLRenderer({ antialias: true, gammaOutput: false, alpha: false });
      renderer.setSize(width, height);
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      document.body.appendChild(renderer.domElement);

      // Gentle ambient and a night palette
      const ambientLight = new THREE.AmbientLight(0x404060, 0.6);
      scene.add(ambientLight);

      // Ground
      const groundGeo = new THREE.PlaneGeometry(60, 60);
      const groundMat = new THREE.MeshStandardMaterial({ color: 0x2a4b2b, roughness: 0.9 });
      const ground = new THREE.Mesh(groundGeo, groundMat);
      ground.rotation.x = -Math.PI / 2;
      ground.receiveShadow = true;
      scene.add(ground);

      // Rocks
      function makeRock(x, z, scale) {
        const rockGeo = new THREE.DodecahedronGeometry(1 * scale, 0);
        const rockMat = new THREE.MeshStandardMaterial({ color: 0x8a8a8a, roughness: 0.9, metalness: 0 });
        const rock = new THREE.Mesh(rockGeo, rockMat);
        rock.position.set(x, 0.25 * scale, z);
        rock.rotation.y = Math.random() * Math.PI;
        rock.scale.setScalar(scale);
        rock.castShadow = true;
        rock.receiveShadow = true;
        return rock;
      }

      // Logs
      function makeLog(x, z, rot, scale) {
        const logGeo = new THREE.CylinderGeometry(0.24 * scale, 0.24 * scale, 2 * scale, 6);
        const logMat = new THREE.MeshStandardMaterial({ color: 0x8b5a2b });
        const log = new THREE.Mesh(logGeo, logMat);
        log.position.set(x, 0.15 * scale, z);
        log.rotation.y = rot;
        log.castShadow = true;
        log.receiveShadow = true;
        return log;
      }

      // Random rocks/logs around the clearing
      const rocks = [
        makeRock(6, 4, 1.2),
        makeRock(-5, -3, 0.9),
        makeRock(-4, 6, 0.7),
        makeRock(3, -6, 0.8)
      ];
      rocks.forEach(r => scene.add(r));

      const logs = [
        makeLog(1.5, 1.5, Math.PI / 6, 1.0),
        makeLog(-1.8, 1.3, -Math.PI / 4, 1.0)
      ];
      logs.forEach(l => scene.add(l));

      // Campfire assembly: logs + base + flames
      campfireGroup = new THREE.Group();

      // Base fire logs around center
      const baseLogGeom = new THREE.CylinderGeometry(0.24, 0.24, 1.6, 6);
      const baseLogMat = new THREE.MeshStandardMaterial({ color: 0x6b3a15 });
      for (let i = 0; i < 6; i++) {
        const lg = new THREE.Mesh(baseLogGeom, baseLogMat);
        lg.scale.set(1.2, 1.0, 1.0);
        lg.position.set(Math.cos(i * Math.PI / 3) * 1.5, 0.62, Math.sin(i * Math.PI / 3) * 1.5);
        lg.rotation.y = i * Math.PI / 3;
        lg.castShadow = true;
        lg.receiveShadow = true;
        campfireGroup.add(lg);
      }

      // Central glow
      const centerGeom = new THREE.SphereGeometry(0.22, 8, 6);
      const centerMat = new THREE.MeshStandardMaterial({ color: 0xffa000, emissive: 0xffa000, emissiveIntensity: 0.6, roughness: 0.8 });
      const center = new THREE.Mesh(centerGeom, centerMat);
      center.position.set(0, 0.25, 0);
      center.castShadow = true;
      campfireGroup.add(center);

      // Flame: a soft cone that will flicker
      flame = new THREE.Mesh(
        new THREE.ConeGeometry(0.45, 1.2, 6),
        new THREE.MeshStandardMaterial({
          color: 0xff8844,
          emissive: 0xffa34d,
          emissiveIntensity: 0.8,
          transparent: true,
          opacity: 0.95,
          side: THREE.DoubleSide
        })
      );
      flame.rotation.x = Math.PI;
      flame.position.set(0, 1.0, 0);
      flame.castShadow = true;
      flame.receiveShadow = true;
      campfireGroup.add(flame);

      // Fire light
      fireLight = new THREE.PointLight(0xffaa66, 2, 30, 2);
      fireLight.position.set(0, 0.9, 0);
      fireLight.castShadow = true;
      fireLight.shadow.mapSize.width = 256;
      fireLight.shadow.mapSize.height = 256;
      campfireGroup.add(fireLight);

      scene.add(campfireGroup);

      // Simple sparks particle system
      const sparkCount = 250;
      const sparkGeometry = new THREE.BufferGeometry();
      const positions = new Float32Array(sparkCount * 3);
      const lifetimes = new Float32Array(sparkCount);
      const speeds = new Float32Array(sparkCount);
      for (let i = 0; i < sparkCount; i++) {
        positions[i * 3 + 0] = 0;
        positions[i * 3 + 1] = 0.2;
        positions[i * 3 + 2] = 0;
        lifetimes[i] = Math.random() * 4 + 1;
        speeds[i] = Math.random() * 0.9 + 0.5;
      }
      sparkGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
      sparkGeometry.setAttribute('life', new THREE.BufferAttribute(lifetimes, 1));
      sparkGeometry.setAttribute('speed', new THREE.BufferAttribute(speeds, 1));

      const sparkMaterial = new THREE.PointsMaterial({
        color: 0xffee88,
        size: 0.08,
        transparent: true,
        blending: THREE.AdditiveBlending,
        depthWrite: false
      });
      sparks = new THREE.Points(sparkGeometry, sparkMaterial);
      sparks.frustumCulled = false;
      scene.add(sparks);

      // Trees around clearing (low-poly)
      const treeCount = 40;
      const treeGroup = new THREE.Group();
      for (let i = 0; i < treeCount; i++) {
        const tree = createTree();
        const r = 15 + Math.random() * 20;
        const a = Math.random() * Math.PI * 2;
        const x = Math.cos(a) * r;
        const z = Math.sin(a) * r;
        // Keep away from the center
        if (Math.hypot(x, z) < 6) { i--; continue; }
        tree.position.set(x, 0, z);
        treeGroup.add(tree);
      }
      scene.add(treeGroup);

      // Moon
      const moon = new THREE.Mesh(
        new THREE.SphereGeometry(2, 32, 32),
        new THREE.MeshStandardMaterial({ color: 0xf0f7ff, roughness: 0.8, emissive: 0xfffff0, emissiveIntensity: 0.9 })
      );
      moon.position.set(-20, 18, -40);
      moon.castShadow = false;
      moon.receiveShadow = false;
      scene.add(moon);

      // Stars (random points in upper sky)
      const starGeo = new THREE.BufferGeometry();
      const starCount = 500;
      const starPositions = new Float32Array(starCount * 3);
      for (let i = 0; i < starCount; i++) {
        const radius = 40 + Math.random() * 40;
        const theta = Math.random() * Math.PI * 2;
        const phi = 0.6 + Math.random() * 0.5;
        starPositions[i * 3 + 0] = Math.cos(theta) * radius;
        starPositions[i * 3 + 1] = 20 + Math.sin(phi) * radius * 0.5;
        starPositions[i * 3 + 2] = Math.sin(theta) * radius;
      }
      starGeo.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
      const starMaterial = new THREE.PointsMaterial({
        color: 0xffffff,
        size: 0.8,
        sizeAttenuation: true,
        transparent: true,
        opacity: 0.9
      });
      const stars = new THREE.Points(starGeo, starMaterial);
      stars.frustumCulled = false;
      scene.add(stars);

      // Orbit controls
      controls = new OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.05;
      controls.minDistance = 6;
      controls.maxDistance = 60;
      controls.maxPolarAngle = Math.PI * 0.5; // stay above ground
      controls.enabled = false; // disabled until start

      // Overlay handling (lazy fetch to avoid order issues)
      overlayEl = null;
      function start() {
        if (started) return;
        started = true;
        if (!overlayEl) overlayEl = document.getElementById('overlay');
        if (overlayEl) overlayEl.style.display = 'none';
        controls.enabled = true;
      }

      // Start on first user interaction (click on canvas) or key press
      window.addEventListener('mousedown', (e) => {
        if (!started && e.target === renderer.domElement) {
          start();
        }
      });
      window.addEventListener('keydown', () => {
        if (!started) start();
      });

      // Resize handling
      window.addEventListener('resize', onWindowResize, false);
      onWindowResize();

      clock = new THREE.Clock();
    }

    function createTree() {
      const trunkGeo = new THREE.CylinderGeometry(0.25, 0.3, 3, 6);
      const trunkMat = new THREE.MeshStandardMaterial({ color: 0x5a3d1b });
      const trunk = new THREE.Mesh(trunkGeo, trunkMat);
      trunk.position.y = 1.5;

      const canopyGeo = new THREE.ConeGeometry(1.6, 4, 6);
      const canopyMat = new THREE.MeshStandardMaterial({ color: 0x2e6b3a });
      const canopy = new THREE.Mesh(canopyGeo, canopyMat);
      canopy.position.y = 4.4;

      const tree = new THREE.Group();
      tree.add(trunk);
      tree.add(canopy);

      const s = 0.8 + Math.random() * 0.6;
      tree.scale.set(s, s, s);
      tree.rotation.y = Math.random() * Math.PI * 2;

      // Simple shadow-casting enabled
      trunk.castShadow = true;
      canopy.castShadow = true;

      return tree;
    }

    function onWindowResize() {
      const w = window.innerWidth;
      const h = window.innerHeight;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    }

    // Flame and spark animation plus subtle ambient motion
    function updateSparks() {
      if (!sparks) return;
      const positions = sparks.geometry.attributes.position.array;
      const lifeAttr = sparks.geometry.attributes.life;
      const life = lifeAttr.array;
      const speed = sparks.geometry.attributes.speed.array;

      for (let i = 0; i < life.length; i++) {
        life[i] -= 0.016;
        if (life[i] <= 0) {
          // respawn near the fire
          positions[i * 3 + 0] = 0;
          positions[i * 3 + 1] = 0.2;
          positions[i * 3 + 2] = 0;
          life[i] = Math.random() * 4 + 1;
          speed[i] = Math.random() * 0.9 + 0.5;
        } else {
          // simple upward motion with slight drift and gravity
          const vx = (Math.random() - 0.5) * 0.06;
          const vz = (Math.random() - 0.5) * 0.06;
          positions[i * 3 + 0] += vx;
          positions[i * 3 + 1] += speed[i] * 0.04;
          positions[i * 3 + 2] += vz;
          positions[i * 3 + 1] -= 0.01; // gravity
        }
      }

      sparks.geometry.attributes.position.needsUpdate = true;
      sparks.geometry.attributes.life.needsUpdate = true;
    }

    function animate() {
      requestAnimationFrame(animate);
      const dt = clock ? clock.getDelta() : 0;

      // Flame flicker and glow
      if (flame) {
        const t = performance.now() * 0.001;
        const scaleY = 0.9 + Math.sin(t * 6) * 0.15;
        flame.scale.set(1, scaleY, 1);
        flameMaterialColorPulse(flame, t);
        fireLight.intensity = 1.8 + Math.sin(t * 12) * 0.6;
      }

      updateSparks();

      // Gentle idle rotation for distant ambience (subtle)
      if (campfireGroup) {
        campfireGroup.rotation.y += 0.0005;
      }

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

    // helper to subtly pulse flame color
    function flameMaterialColorPulse(mesh, t) {
      if (!mesh || !mesh.material) return;
      const hue = 0.07; // orange
      const sat = 1.0;
      const light = 0.5 + Math.sin(t * 8) * 0.1;
      // Convert-ish to RGB hue shift approximation
      mesh.material.color.setHSL(hue, sat, light);
      mesh.material.emissive.setHex(0xffa34d);
    }
  </script>
</body>
</html>
Reasoning trace (7,680 tokens)
**Planning HTML output for demo**

I need to create a single HTML file with an import map that includes Three.js version 0.170.0. It should showcase a self-contained demo featuring elements like a ground plane, rocks, logs, and a campfire with animated flames and flickering light. I must include 30 low-poly trees, a night sky with stars and a moon, as well as fog and distance fade. I’ll implement OrbitControls, ensure the renderer fills the window, and add minimal UI for user interaction.**Setting up imports for Three.js**

I need to include a script that loads Three.js using import statements, specifically importing { OrbitControls } from 'three/addons/controls/OrbitControls.js'. I'll use an import map to alias "three" to three.module.js and "three/addons/" to point to the CDN path. The actual URL for the OrbitControls module for Three.js version 0.170.0 will be linked properly, ensuring everything functions without errors. Additionally, I want to create a simple starfield with small points, set up the scene, camera, renderer, and adjust the ground with grass color and some low-poly rocks and logs.**Designing low-poly campfire scene**

I'm planning to create minimal low-poly shapes using BoxGeometry with a few segments. I'll establish helper functions to create rocks and logs, placing them around the scene. 

For the campfire, I'll set a small cylinder as the base, arranging logs in a triangle. The flames can be represented by two or three rotating conical shapes or a simple elongated tetrahedron. I'll group these as 'campfire' and add a warm point light. 

For the sparks particle system, I'll use Points with a PointsMaterial, generating random velocities and lifetimes for the particles that rise above the fire, with gravity updates. Plus, I’ll include 30 low-poly trees using cones for canopies and cylinders for trunks.**Arranging trees and fog effects**

I’ll randomly place the trees around the clearing, making sure they're not too close to the center. To achieve this, I'll create a function called createTree() that returns an Object3D.

For the fog effect, I have a couple of options: I could use either FogExp2 or linear fog. I’m leaning towards using FogExp2 with a color of 0x0b0f1a and a density of 0.002 to create a nice distance fade effect in the scene. This should enhance the atmosphere nicely!**Designing the scene and UI elements**

I'm adding a moon as a large sphere with an emissive material in the top left corner, and creating a star field using small Points that are randomly positioned around the upper hemisphere, tinted white. Fog will help distant trees fade.

I'll enable shadows in the renderer and create a grass-colored ground plane using PlaneGeometry with slight color variations, ensuring it simulates texture without a network. The overlay will prompt users to click or press a key to start the animation. 

I’ll ensure at least 35 trees are generated for a low-poly effect and make provisions for responsiveness and zero errors in the code.**Implementing the HTML structure and Three.js setup**

I'm working on the HTML skeleton where I need a `<div>` for the overlay telling users to "Click or press a key to start." Instead of using a canvas directly, I’ll create a container to append the renderer's DOM element later.

In the initialization script, I’ll set up Three.js with a necessary import map. The scene will include fog and a perspective camera along with basic lighting, including an ambient light and a point light for the campfire. I’ll also create a ground plane with a specific material to receive shadows and provide gentle surface variations.**Creating rocks, logs, and campfire**

I'm designing rocks and logs around the scene by defining functions to create each. For rocks, I'm using a dodecahedron geometry with a grayish color and giving them a random rotation. Logs are created with cylinder geometry for that wood look, and I’ll position them similarly.

Then, I'll arrange several rocks and logs in the scene, forming the campfire base with logs positioned around it, constructing a glow effect at the center. Flames will be represented by cone shapes, and I'll add sparks as a particle system to enhance the dynamic ambience!**Validating the code structure**

I need to ensure that the code is valid. For rocks, I'm using Dodecahedron geometry with a radius of 1. The ground is a plane, while my tree creation utilizes cylinder geometry for the trunk and cone geometry for the canopy. I should correctly place 30 trees around the clearing.

I see I need to adjust the positioning of the import map in the HTML to load before the module script. The overlay div must also be properly declared to ensure it's accessible when needed within the init function. It's essential that the code structure is correct, so everything loads as intended!**Adjusting script structure**

To make sure everything works smoothly in my HTML, I need to reorganize how I handle the overlay element and the script tags. Since the overlay is referenced but possibly doesn’t exist when the script runs, it might lead to issues. I should declare the overlay variable in the outer scope and only assign it after the DOM is ready.

A good approach is to place the overlay div before the script tags. This way, I’ll ensure it exists when I need to reference it in the `start()` function. I’ll also make adjustments to manage the number of trees and ensure the overall structure is tidy. I need to verify that my code is syntactically correct too!**Structuring final HTML**

I need to ensure that the overlay div is properly placed in the HTML structure. For that, I’ll put it above the module script tag, ideally right after the overlay CSS style in the head section. The script will be at the end of the body to guarantee that all DOM elements are available.

Also, I want to make sure that the display properties apply correctly to the overlay. I’ll compile the final HTML code block to include everything necessary, like the import map, styles, and rendering parts while ensuring it’s all contained cleanly. Let's finalize that!