Models 44

Muse Ember

Open in new window

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

How it went

Total time
16.6 s
Cost
$0.0041
Output tokens
8,023
Spent thinking
2,138 tokens
Speed
598.7 tok/s
Console errors
0
First token after
3.2 s
First visible text after
8.8 s
Prompt tokens
574
Code size
17.0 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"medium"}
Max output
943,718 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 · 369 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Midnight Campfire — Low-Poly Forest Clearing</title>
<style>
  * { margin:0; padding:0; box-sizing:border-box; }
  html,body { width:100%; height:100%; overflow:hidden; background:#070d1a; font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; }
  #app { position:fixed; inset:0; }
  canvas { display:block; }
  .vignette { position:fixed; inset:0; pointer-events:none; background:radial-gradient(ellipse at center, transparent 55%, rgba(0,0,0,.42) 100%); }
  .panel { position:fixed; top:18px; left:18px; pointer-events:none; color:#eaf2ff; text-shadow:0 2px 12px rgba(0,0,0,.9); max-width:min(420px, calc(100vw - 36px)); }
  .panel h1 { font-size:clamp(22px,3vw,34px); letter-spacing:.02em; line-height:1.05; font-weight:800; }
  .panel h1 span { color:#ffb15e; }
  .panel p { margin-top:8px; font-size:13px; line-height:1.45; color:rgba(226,238,255,.82); }
  .badge { display:inline-block; margin-top:10px; padding:5px 10px; border-radius:999px; border:1px solid rgba(255,255,255,.22); background:rgba(8,15,30,.45); backdrop-filter:blur(6px); font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:#cfe2ff; }
  .hint { position:fixed; left:50%; bottom:16px; transform:translateX(-50%); pointer-events:none; white-space:nowrap; padding:9px 14px; border-radius:999px; background:rgba(5,10,20,.55); border:1px solid rgba(255,255,255,.14); color:rgba(235,244,255,.92); font-size:12px; backdrop-filter:blur(8px); box-shadow:0 10px 30px rgba(0,0,0,.35); }
  .hint b { color:#ffc06e; font-weight:700; }
  .hint i { font-style:normal; opacity:.55; margin:0 7px; }
  @media (max-width:520px){ .panel{ top:12px; left:12px; } .panel p{ font-size:12px; } .hint{ font-size:11px; bottom:10px; max-width:calc(100vw - 20px); white-space:normal; text-align:center; } }
</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="app"></div>
<div class="vignette"></div>
<div class="panel">
  <h1>🌲 Midnight <span>Campfire</span></h1>
  <p>A low-poly forest clearing at night — flickering firelight, rising sparks, pine trees in the fog, stars and a full moon.</p>
  <span class="badge">Three.js &nbsp;•&nbsp; 45 trees &nbsp;•&nbsp; live shadows</span>
</div>
<div class="hint"><b>Drag</b> to orbit <i>•</i> <b>Scroll / pinch</b> to zoom <i>•</i> Camera stays above ground</div>

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

/* ---------- deterministic random so composition is always good ---------- */
let _seed = 1234567;
function rand(){ _seed |= 0; _seed = _seed + 0x6D2B79F5 | 0; let t = Math.imul(_seed ^ _seed >>> 15, 1 | _seed); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }
function rr(min,max){ return min + rand()*(max-min); }
function pick(arr){ return arr[Math.floor(rand()*arr.length)]; }

/* ---------- renderer / scene / camera ---------- */
const container = document.getElementById('app');
const renderer = new THREE.WebGLRenderer({ antialias:true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.15;
renderer.outputColorSpace = THREE.SRGBColorSpace;
container.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x070d1a);
scene.fog = new THREE.Fog(0x0a1526, 16, 58);

const camera = new THREE.PerspectiveCamera(55, window.innerWidth/window.innerHeight, 0.1, 220);
camera.position.set(9.5, 5.6, 11.5);

const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1.0, 0);
controls.enableDamping = true;
controls.dampingFactor = 0.06;
controls.enablePan = false;
controls.minDistance = 4.5;
controls.maxDistance = 24;
controls.minPolarAngle = 0.18;
controls.maxPolarAngle = Math.PI * 0.49; // strictly above ground plane
controls.autoRotate = true;
controls.autoRotateSpeed = 0.55;
controls.update();

/* ---------- lights ---------- */
scene.add(new THREE.AmbientLight(0x334466, 0.55));
const hemi = new THREE.HemisphereLight(0x28406e, 0x0c1a12, 0.65);
scene.add(hemi);

const moonLight = new THREE.DirectionalLight(0x9db8ff, 0.45);
moonLight.position.set(-18, 24, -20);
scene.add(moonLight);

const fireLight = new THREE.PointLight(0xff8a2a, 34, 26, 1.6);
fireLight.position.set(0, 1.35, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(1024, 1024);
fireLight.shadow.camera.near = 0.25;
fireLight.shadow.camera.far = 26;
fireLight.shadow.bias = -0.004;
scene.add(fireLight);

const fireLight2 = new THREE.PointLight(0xffc266, 9, 9, 1.8);
fireLight2.position.set(0, 0.6, 0);
scene.add(fireLight2);

/* ---------- helpers ---------- */
function glowTexture(){
  const c = document.createElement('canvas'); c.width = c.height = 128;
  const g = c.getContext('2d');
  const grad = g.createRadialGradient(64,64,0,64,64,64);
  grad.addColorStop(0,'rgba(255,255,255,1)');
  grad.addColorStop(0.25,'rgba(255,220,150,0.55)');
  grad.addColorStop(1,'rgba(255,160,60,0)');
  g.fillStyle = grad; g.fillRect(0,0,128,128);
  const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace; return t;
}
const sharedGlow = glowTexture();

/* ---------- ground ---------- */
const groundMat = new THREE.MeshStandardMaterial({ color:0x182f21, roughness:0.95, flatShading:true });
const ground = new THREE.Mesh(new THREE.CircleGeometry(55, 64), groundMat);
ground.rotation.x = -Math.PI/2; ground.receiveShadow = true; scene.add(ground);

/* dirt clearing under the fire */
const dirt = new THREE.Mesh(new THREE.CircleGeometry(3.4, 24), new THREE.MeshStandardMaterial({ color:0x3b2a18, roughness:1, flatShading:true }));
dirt.rotation.x = -Math.PI/2; dirt.position.y = 0.015; dirt.receiveShadow = true; scene.add(dirt);

/* dark ground patches */
for (let i=0;i<14;i++){
  const p = new THREE.Mesh(new THREE.CircleGeometry(rr(1.2,3.2), 8), new THREE.MeshStandardMaterial({ color: pick([0x122418,0x1e3a28,0x142a1e]), roughness:1, flatShading:true }));
  p.rotation.x = -Math.PI/2; p.rotation.z = rand()*Math.PI;
  const a = rand()*Math.PI*2, d = rr(4,26);
  p.position.set(Math.cos(a)*d, 0.01+i*0.0005, Math.sin(a)*d);
  p.receiveShadow = true; scene.add(p);
}

/* grass tufts */
const tuftGeo = new THREE.ConeGeometry(0.16, 0.5, 5);
const tuftMat = new THREE.MeshStandardMaterial({ color:0x2c5a36, roughness:1, flatShading:true });
for (let i=0;i<70;i++){
  const a = rand()*Math.PI*2, d = rr(3.6, 30);
  const t = new THREE.Mesh(tuftGeo, tuftMat);
  t.position.set(Math.cos(a)*d, 0.2, Math.sin(a)*d);
  t.scale.setScalar(rr(0.7,1.6)); t.rotation.y = rand()*Math.PI;
  t.castShadow = true; scene.add(t);
}

/* ---------- rocks ---------- */
const rockMat = new THREE.MeshStandardMaterial({ color:0x6b7280, roughness:0.95, flatShading:true });
const darkRockMat = new THREE.MeshStandardMaterial({ color:0x4b5563, roughness:0.95, flatShading:true });
for (let i=0;i<18;i++){
  const a = rand()*Math.PI*2, d = rr(4.2, 30);
  const r = new THREE.Mesh(new THREE.DodecahedronGeometry(rr(0.35,0.95),0), rand()>0.5?rockMat:darkRockMat);
  r.position.set(Math.cos(a)*d, rr(0.05,0.2), Math.sin(a)*d);
  r.scale.y = rr(0.5,0.8); r.rotation.set(rand()*3, rand()*3, rand()*3);
  r.castShadow = true; r.receiveShadow = true; scene.add(r);
}

/* ---------- trees (45+) ---------- */
const trunkMat = new THREE.MeshStandardMaterial({ color:0x5a3d24, roughness:1, flatShading:true });
const leafMats = [0x1e4d2f,0x265c36,0x17402a,0x2a6b3c,0x1a5a3a].map(c=>new THREE.MeshStandardMaterial({ color:c, roughness:1, flatShading:true }));
const treePositions = [];
function makeTree(x,z){
  const g = new THREE.Group();
  const h = rr(0.85, 1.55);
  const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.13,0.2,1.1,6), trunkMat);
  trunk.position.y = 0.55; trunk.castShadow = true; g.add(trunk);
  const mat = pick(leafMats);
  const layers = 3;
  for (let i=0;i<layers;i++){
    const cone = new THREE.Mesh(new THREE.ConeGeometry(1.25 - i*0.32, 1.7, 6), mat);
    cone.position.y = 1.35 + i*0.85;
    cone.rotation.y = rand()*Math.PI;
    cone.castShadow = true; cone.receiveShadow = true;
    g.add(cone);
  }
  g.position.set(x,0,z); g.scale.setScalar(h); g.rotation.y = rand()*Math.PI*2;
  scene.add(g); treePositions.push(new THREE.Vector2(x,z));
}
let attempts = 0;
while (treePositions.length < 48 && attempts < 600){
  attempts++;
  const a = rand()*Math.PI*2;
  const d = 6.5 + Math.sqrt(rand()) * 30; // clearing radius 6.5, forest to ~36
  const x = Math.cos(a)*d, z = Math.sin(a)*d;
  if (treePositions.some(p => p.distanceTo(new THREE.Vector2(x,z)) < 1.9)) continue;
  makeTree(x,z);
}

/* ---------- campfire ---------- */
const fireGroup = new THREE.Group(); scene.add(fireGroup);

/* stone ring */
const ringGeo = new THREE.DodecahedronGeometry(0.28,0);
for (let i=0;i<11;i++){
  const a = (i/11)*Math.PI*2 + rr(-0.1,0.1);
  const s = new THREE.Mesh(ringGeo, pick([rockMat,darkRockMat]));
  s.position.set(Math.cos(a)*1.15, 0.14, Math.sin(a)*1.15);
  s.scale.set(rr(0.8,1.3), rr(0.55,0.85), rr(0.8,1.3));
  s.rotation.set(rand()*3, rand()*3, rand()*3);
  s.castShadow = true; s.receiveShadow = true; fireGroup.add(s);
}

/* burning logs */
const logMat = new THREE.MeshStandardMaterial({ color:0x6b4423, roughness:1, flatShading:true });
const logEndMat = new THREE.MeshStandardMaterial({ color:0xb98a52, roughness:1, flatShading:true });
const logGeo = new THREE.CylinderGeometry(0.09,0.11,1.25,7);
for (let i=0;i<5;i++){
  const a = (i/5)*Math.PI*2;
  const log = new THREE.Mesh(logGeo, [logEndMat, logMat, logEndMat]);
  log.position.set(Math.cos(a)*0.32, 0.22, Math.sin(a)*0.32);
  log.rotation.set(Math.sin(a)*0.7, 0, Math.cos(a)*0.7);
  log.castShadow = true; fireGroup.add(log);
}
for (let i=0;i<3;i++){
  const log = new THREE.Mesh(logGeo, [logEndMat, logMat, logEndMat]);
  log.position.set(rr(-0.2,0.2), 0.12, rr(-0.2,0.2));
  log.rotation.set(Math.PI/2, rand()*Math.PI, 0.15);
  log.castShadow = true; fireGroup.add(log);
}

/* glowing embers */
const emberGeo = new THREE.DodecahedronGeometry(0.09,0);
const emberMat = new THREE.MeshStandardMaterial({ color:0x2a1206, emissive:0xff5a00, emissiveIntensity:2.6, roughness:1, flatShading:true });
for (let i=0;i<9;i++){
  const e = new THREE.Mesh(emberGeo, emberMat);
  const a = rand()*Math.PI*2, d = rand()*0.5;
  e.position.set(Math.cos(a)*d, 0.1, Math.sin(a)*d);
  e.scale.setScalar(rr(0.6,1.4)); fireGroup.add(e);
}

/* flames — layered low-poly cones, animated */
function flameCone(radius,height,color,opacity){
  const geo = new THREE.ConeGeometry(radius,height,7);
  geo.translate(0,height/2,0);
  const mat = new THREE.MeshStandardMaterial({ color:color, emissive:color, emissiveIntensity:1.6, transparent:true, opacity:opacity, roughness:0.4, flatShading:true, depthWrite:false });
  const m = new THREE.Mesh(geo,mat); m.position.y = 0.18; return m;
}
const flameOuter = flameCone(0.58,1.55,0xff6a00,0.88);
const flameMid   = flameCone(0.40,1.15,0xffa318,0.92);
const flameInner = flameCone(0.23,0.72,0xffe07a,0.97);
fireGroup.add(flameOuter, flameMid, flameInner);

/* fire glow sprite */
const glow = new THREE.Sprite(new THREE.SpriteMaterial({ map:sharedGlow, color:0xff9a33, transparent:true, opacity:0.55, blending:THREE.AdditiveBlending, depthWrite:false }));
glow.scale.setScalar(4.2); glow.position.y = 0.85;火花GroupFix: fireGroup.add(glow);

/* ---------- sitting logs & stumps ---------- */
const seatGeo = new THREE.CylinderGeometry(0.24,0.24,2.3,8);
[[2.6,0.6],[ -1.8,2.4],[0.4,-2.8]].forEach(([x,z],i)=>{
  const s = new THREE.Mesh(seatGeo, [logEndMat, logMat, logEndMat]);
  s.position.set(x,0.24,z);
  s.rotation.set(0, Math.atan2(z,x)+Math.PI/2, Math.PI/2);
  s.castShadow = true; s.receiveShadow = true; scene.add(s);
});
for (let i=0;i<3;i++){
  const a = rr(0,Math.PI*2), d = rr(4,9);
  const st = new THREE.Mesh(new THREE.CylinderGeometry(0.42,0.48,0.55,8), [logEndMat, logMat, logEndMat]);
  st.position.set(Math.cos(a)*d, 0.27, Math.sin(a)*d);
  st.castShadow = true; st.receiveShadow = true; scene.add(st);
}

/* ---------- sparks particle system ---------- */
const SPARKS = 90;
const sparkPos = new Float32Array(SPARKS*3);
const sparkVel = new Float32Array(SPARKS*3);
const sparkLife = new Float32Array(SPARKS);
function resetSpark(i, randomY){
  sparkPos[i*3]   = rr(-0.25,0.25);
  sparkPos[i*3+1] = randomY ? rr(0.3,3.2) : rr(0.35,0.6);
  sparkPos[i*3+2] = rr(-0.25,0.25);
  sparkVel[i*3]   = rr(-0.15,0.15);
  sparkVel[i*3+1] = rr(0.55,1.35);
  sparkVel[i*3+2] = rr(-0.15,0.15);
  sparkLife[i] = rr(0.6,1);
}
for (let i=0;i<SPARKS;i++) resetSpark(i,true);
const sparkGeo = new THREE.BufferGeometry();
sparkGeo.setAttribute('position', new THREE.BufferAttribute(sparkPos,3));
const sparks = new THREE.Points(sparkGeo, new THREE.PointsMaterial({ color:0xffc266, size:0.09, transparent:true, opacity:0.95, blending:THREE.AdditiveBlending, depthWrite:false, sizeAttenuation:true }));
scene.add(sparks);

/* ---------- fireflies ---------- */
const FLIES = 22;
const flyPos = new Float32Array(FLIES*3);
const flySeed = new Float32Array(FLIES*3);
for (let i=0;i<FLIES;i++){
  const a = rand()*Math.PI*2, d = rr(3.5,18);
  flyPos[i*3]=Math.cos(a)*d; flyPos[i*3+1]=rr(0.5,2.2); flyPos[i*3+2]=Math.sin(a)*d;
  flySeed[i*3]=rand()*10; flySeed[i*3+1]=rand()*10; flySeed[i*3+2]=rand()*10;
}
const flyGeo = new THREE.BufferGeometry();
flyGeo.setAttribute('position', new THREE.BufferAttribute(flyPos,3));
const flies = new THREE.Points(flyGeo, new THREE.PointsMaterial({ color:0xbfff9a, size:0.07, transparent:true, opacity:0.8, blending:THREE.AdditiveBlending, depthWrite:false }));
scene.add(flies);

/* ---------- stars & moon ---------- */
const STAR_COUNT = 900;
const starPos = new Float32Array(STAR_COUNT*3);
for (let i=0;i<STAR_COUNT;i++){
  const a = rand()*Math.PI*2;
  const y = rr(0.06,1);
  const horiz = Math.sqrt(1-y*y);
  const r = 85;
  starPos[i*3]   = Math.cos(a)*horiz*r;
  starPos[i*3+1] = y*r;
  starPos[i*3+2] = Math.sin(a)*horiz*r;
}
const starGeo = new THREE.BufferGeometry();
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos,3));
const stars = new THREE.Points(starGeo, new THREE.PointsMaterial({ color:0xdbeafe, size:0.35, sizeAttenuation:false, fog:false, transparent:true, opacity:0.95, depthWrite:false }));
scene.add(stars);

const moon = new THREE.Mesh(new THREE.SphereGeometry(3.2, 20, 20), new THREE.MeshBasicMaterial({ color:0xf2f5ff, fog:false }));
moon.position.set(-30, 26, -36); scene.add(moon);
const moonGlow = new THREE.Sprite(new THREE.SpriteMaterial({ map:sharedGlow, color:0xbfd4ff, transparent:true, opacity:0.5, blending:THREE.AdditiveBlending, depthWrite:false, fog:false }));
moonGlow.scale.setScalar(16); moonGlow.position.copy(moon.position); scene.add(moonGlow);

/* ---------- animation ---------- */
const clock = new THREE.Clock();
let t = 0;
function animate(){
  requestAnimationFrame(animate);
  const dt = Math.min(clock.getDelta(), 0.05);
  t += dt;

  /* flicker — layered sines + small random jitter */
  const flick = Math.sin(t*11)*0.5 + Math.sin(t*23+1.3)*0.3 + Math.sin(t*5.1)*0.2;
  const jitter = (Math.random()-0.5)*0.12;
  const level = 1 + flick*0.22 + jitter;
  fireLight.intensity = 34 * level;
  fireLight2.intensity = 9 * level;
  fireLight.position.x = Math.sin(t*7)*0.08;
  fireLight.position.z = Math.cos(t*6)*0.08;

  /* flame dance */
  flameOuter.scale.set(1 + Math.sin(t*9)*0.08, 1 + Math.sin(t*12.3)*0.18, 1 + Math.cos(t*8)*0.08);
  flameMid.scale.set(1 + Math.cos(t*10.5)*0.1, 1 + Math.sin(t*14.1+2)*0.22, 1 + Math.sin(t*9.4)*0.1);
  flameInner.scale.set(1 + Math.sin(t*13.7)*0.12, 1 + Math.cos(t*16.2)*0.2, 1);
  flameOuter.rotation.y = t*1.4; flameMid.rotation.y = -t*1.9; flameInner.rotation.y = t*2.6;
  glow.material.opacity = 0.42 + flick*0.1;
  const gs = 4.1 + flick*0.35; glow.scale.set(gs,gs,1);

  /* sparks rise & sway */
  for (let i=0;i<SPARKS;i++){
    sparkPos[i*3]   += (sparkVel[i*3] + Math.sin(t*3+i)*0.12)*dt;
    sparkPos[i*3+1] += sparkVel[i*3+1]*dt;
    sparkPos[i*3+2] += (sparkVel[i*3+2] + Math.cos(t*2.6+i)*0.12)*dt;
    sparkLife[i] -= dt*0.45;
    if (sparkPos[i*3+1] > 3.6 || sparkLife[i] <= 0) resetSpark(i,false);
  }
  sparkGeo.attributes.position.needsUpdate = true;

  /* fireflies wander */
  for (let i=0;i<FLIES;i++){
    flyPos[i*3]   += Math.sin(t*0.7+flySeed[i*3])*0.35*dt;
    flyPos[i*3+1] += Math.sin(t*1.1+flySeed[i*3+1])*0.22*dt;
    flyPos[i*3+2] += Math.cos(t*0.6+flySeed[i*3+2])*0.35*dt;
    flyPos[i*3+1] = THREE.MathUtils.clamp(flyPos[i*3+1], 0.35, 3.2);
  }
  flyGeo.attributes.position.needsUpdate = true;

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

/* ---------- resize ---------- */
window.addEventListener('resize', ()=>{
  camera.aspect = window.innerWidth/window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
Reasoning trace (2,138 tokens)
Building a self-contained Threejs low-poly night campfire scene with lighting, particles, fog, and camera controls.Adding camera constraints, low-poly ground, stone ring, logs, flickering flame geometry, and point light.Configuring shadow settings, instanced trees, particles, lights, and camera controls for performance and visuals.