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
9.8 s
Cost
$0.0035
Output tokens
6,786
Spent thinking
638 tokens
Speed
818 tok/s
Console errors
0
First token after
1.5 s
First visible text after
2.1 s
Prompt tokens
574
Code size
18.6 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"low"}
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 · 405 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Campfire Clearing — Low-Poly Night Forest</title>
<style>
  *{margin:0;padding:0;box-sizing:border-box}
  html,body{width:100%;height:100%;overflow:hidden;background:#060b1a;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif}
  #app{position:fixed;inset:0}
  canvas{display:block}
  .ui{position:fixed;top:0;left:0;right:0;padding:18px 20px;display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none;z-index:10;color:#fff;gap:16px}
  .card{pointer-events:auto;background:rgba(8,12,28,.58);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.12);border-radius:14px;padding:14px 16px;box-shadow:0 10px 30px rgba(0,0,0,.35)}
  .title{font-size:15px;letter-spacing:.16em;text-transform:uppercase;font-weight:800}
  .title span{color:#ffb347}
  .sub{font-size:12.5px;line-height:1.5;color:#cbd5e1;margin-top:4px;max-width:360px}
  .pill{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:.08em;text-transform:uppercase;background:rgba(255,150,50,.16);color:#ffcf9a;border:1px solid rgba(255,170,80,.3);padding:5px 9px;border-radius:999px;margin-top:9px}
  .dot{width:7px;height:7px;border-radius:50%;background:#ff9d3c;box-shadow:0 0 10px #ff9d3c;animation:pulse 1.6s infinite}
  @keyframes pulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.5);opacity:.6}}
  .controls{text-align:right;font-size:12px;color:#dbeafe;line-height:1.7;min-width:170px}
  .controls b{color:#fff;font-weight:700}
  .kbd{display:inline-block;background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.18);border-bottom-width:2px;border-radius:6px;padding:1px 6px;font-size:11px;color:#fff}
  .badge{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);background:rgba(5,8,20,.62);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.12);color:#e2e8f0;padding:8px 14px;border-radius:999px;font-size:12px;white-space:nowrap;z-index:10;pointer-events:none}
  .badge i{color:#ffb347;font-style:normal}
  @media(max-width:640px){.controls{display:none}.sub{max-width:260px}.title{font-size:13px} .badge{font-size:11px;bottom:12px;max-width:92vw;white-space:normal;text-align:center}}
</style>
</head>
<body>
<div id="app"></div>
<div class="ui">
  <div class="card">
    <div class="title">🔥 Campfire <span>Clearing</span></div>
    <div class="sub">A low-poly forest at night — flickering flames, rising sparks, pine trees in the fog and a bright moon overhead.</div>
    <div class="pill"><span class="dot"></span> Live &nbsp;•&nbsp; 34 trees &nbsp;•&nbsp; Real-time shadows</div>
  </div>
  <div class="card controls">
    <div><b>Drag</b> to orbit &nbsp;•&nbsp; <b>Scroll</b> to zoom</div>
    <div><b>Right-drag</b> to pan</div>
    <div style="margin-top:6px;color:#94a3b8">Camera stays above the ground</div>
    <div style="margin-top:6px"><span class="kbd">R</span> reset view &nbsp; <span class="kbd">Space</span> pause flicker</div>
  </div>
</div>
<div class="badge">✦ &nbsp;Drag to explore the clearing — the fire lights the trees around it&nbsp; ✦</div>

<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>

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

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.1;
container.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x060b1a);
scene.fog = new THREE.FogExp2(0x0a1428, 0.028);

const camera = new THREE.PerspectiveCamera(52, window.innerWidth/window.innerHeight, 0.1, 300);
const START_POS = new THREE.Vector3(9.5, 5.2, 12.5);
const START_TARGET = new THREE.Vector3(0, 1.1, 0);
camera.position.copy(START_POS);

const controls = new OrbitControls(camera, renderer.domElement);
controls.target.copy(START_TARGET);
controls.enableDamping = true;
controls.dampingFactor = 0.06;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.35;
controls.minDistance = 4;
controls.maxDistance = 38;
controls.minPolarAngle = 0.15;
controls.maxPolarAngle = Math.PI * 0.495; // stays above ground
controls.enablePan = true;
controls.panSpeed = 0.6;
// pause auto-rotate on interaction, resume after idle
let idleTimer;
controls.addEventListener('start', ()=>{ controls.autoRotate=false; clearTimeout(idleTimer); });
controls.addEventListener('end', ()=>{ idleTimer=setTimeout(()=>controls.autoRotate=true, 4000); });

// ---------- Lights ----------
scene.add(new THREE.HemisphereLight(0x2a3a66, 0x0b1a0f, 0.55));
const moonLight = new THREE.DirectionalLight(0x8fb4ff, 0.55);
moonLight.position.set(-18, 28, -14);
moonLight.castShadow = true;
moonLight.shadow.mapSize.set(1024,1024);
moonLight.shadow.camera.left=-30; moonLight.shadow.camera.right=30;
moonLight.shadow.camera.top=30; moonLight.shadow.camera.bottom=-30;
moonLight.shadow.camera.far=80;
scene.add(moonLight);

const fireLight = new THREE.PointLight(0xff8c2f, 38, 28, 1.8);
fireLight.position.set(0, 1.6, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(1024,1024);
fireLight.shadow.bias = -0.004;
scene.add(fireLight);
const fireLight2 = new THREE.PointLight(0xff6a00, 10, 10, 2);
fireLight2.position.set(0,0.5,0);
scene.add(fireLight2);

// ---------- Materials (flat shading low-poly) ----------
const mat = {
  ground: new THREE.MeshStandardMaterial({ color:0x1e3d24, roughness:1, flatShading:true }),
  ground2: new THREE.MeshStandardMaterial({ color:0x2a4a28, roughness:1, flatShading:true }),
  dirt: new THREE.MeshStandardMaterial({ color:0x3b2b1e, roughness:1, flatShading:true }),
  trunk: new THREE.MeshStandardMaterial({ color:0x4a2f1d, roughness:1, flatShading:true }),
  pineDark: new THREE.MeshStandardMaterial({ color:0x14351e, roughness:1, flatShading:true }),
  pine: new THREE.MeshStandardMaterial({ color:0x1b5a2e, roughness:1, flatShading:true }),
  pineLight: new THREE.MeshStandardMaterial({ color:0x2a7a3e, roughness:1, flatShading:true }),
  rock: new THREE.MeshStandardMaterial({ color:0x6b7280, roughness:1, flatShading:true }),
  rockDark: new THREE.MeshStandardMaterial({ color:0x4b5563, roughness:1, flatShading:true }),
  log: new THREE.MeshStandardMaterial({ color:0x5c3a22, roughness:1, flatShading:true }),
  woodSeat: new THREE.MeshStandardMaterial({ color:0x6b4426, roughness:1, flatShading:true }),
  flameOuter: new THREE.MeshStandardMaterial({ color:0xff6a00, emissive:0xff5500, emissiveIntensity:2.2, transparent:true, opacity:0.92, flatShading:true }),
  flameMid: new THREE.MeshStandardMaterial({ color:0xffaa00, emissive:0xff9900, emissiveIntensity:2.6, transparent:true, opacity:0.95, flatShading:true }),
  flameInner: new THREE.MeshStandardMaterial({ color:0xffe27a, emissive:0xffdd55, emissiveIntensity:3, transparent:true, opacity:0.98, flatShading:true }),
};

// ---------- Ground ----------
const groundGeo = new THREE.CircleGeometry(55, 48);
const pos = groundGeo.attributes.position;
for(let i=0;i<pos.count;i++){
  const x=pos.getX(i), y=pos.getY(i);
  const d=Math.hypot(x,y);
  const h = d<6 ? (Math.random()*0.08) : (Math.sin(x*0.35)*0.35 + Math.cos(y*0.3)*0.3 + Math.random()*0.25) * Math.min(1,(d-6)/10);
  pos.setZ(i, h);
}
groundGeo.computeVertexNormals();
const ground = new THREE.Mesh(groundGeo, mat.ground);
ground.rotation.x = -Math.PI/2;
ground.receiveShadow = true;
scene.add(ground);

// dirt patch in clearing
const dirt = new THREE.Mesh(new THREE.CircleGeometry(4.2, 24), mat.dirt);
dirt.rotation.x=-Math.PI/2; dirt.position.y=0.02; dirt.receiveShadow=true;
scene.add(dirt);
const dirt2 = new THREE.Mesh(new THREE.CircleGeometry(2.6, 20), new THREE.MeshStandardMaterial({color:0x2b1f16, roughness:1, flatShading:true}));
dirt2.rotation.x=-Math.PI/2; dirt2.position.y=0.035; dirt2.receiveShadow=true;
scene.add(dirt2);

// path
for(let i=0;i<14;i++){
  const p=new THREE.Mesh(new THREE.CircleGeometry(0.55+Math.random()*0.4,7), mat.ground2);
  const t=i/13;
  p.position.set(3.5 + t*16 + Math.sin(t*6)*1.2, 0.015, 2.5 + Math.cos(t*4)*1.5);
  p.rotation.x=-Math.PI/2; p.receiveShadow=true; scene.add(p);
}

// ---------- Helpers ----------
function seededRand(seed){ let s=seed; return ()=>{ s=(s*16807)%2147483647; return (s-1)/2147483646; }; }
const rand = seededRand(42);
function makeTree(scale=1, variant=0){
  const g=new THREE.Group();
  const trunkH=1.2*scale;
  const trunk=new THREE.Mesh(new THREE.CylinderGeometry(0.22*scale,0.32*scale,trunkH,6), mat.trunk);
  trunk.position.y=trunkH/2; trunk.castShadow=true; g.add(trunk);
  const mats=[mat.pineDark, mat.pine, mat.pineLight];
  const pineMat=mats[variant % mats.length];
  const layers=3;
  for(let i=0;i<layers;i++){
    const r=(1.5 - i*0.38)*scale;
    const h=1.6*scale;
    const cone=new THREE.Mesh(new THREE.ConeGeometry(r,h,7), pineMat);
    cone.position.y=trunkH + 0.6*scale + i*0.75*scale;
    cone.castShadow=true; cone.receiveShadow=true;
    g.add(cone);
  }
  const top=new THREE.Mesh(new THREE.ConeGeometry(0.45*scale,0.9*scale,6), pineMat);
  top.position.y=trunkH+2.6*scale; top.castShadow=true; g.add(top);
  return g;
}

// ---------- Trees (>=30) ----------
const treePositions=[];
function placeTrees(){
  // ring + scattered clearing edge
  for(let i=0;i<34;i++){
    let x,z,d;
    let tries=0;
    do{
      const angle=rand()*Math.PI*2;
      d=11 + rand()*28; // 11..39
      x=Math.cos(angle)*d; z=Math.sin(angle)*d;
      tries++;
    }while(tries<20 && treePositions.some(p=>Math.hypot(p[0]-x,p[1]-z)<3.2));
    treePositions.push([x,z]);
    const s=0.8+rand()*1.4;
    const tree=makeTree(s, Math.floor(rand()*3));
    tree.position.set(x,0,z);
    tree.rotation.y=rand()*Math.PI*2;
    tree.rotation.z=(rand()-0.5)*0.08;
    scene.add(tree);
  }
  // a few nearer framing trees (not on fire)
  [[-8.5,-6.5,1.15],[9,-7,1.3],[-10,4,1.0],[7.5,8.5,0.9]].forEach(([x,z,s])=>{
    const t=makeTree(s,1); t.position.set(x,0,z); t.rotation.y=rand()*3; scene.add(t);
  });
}
placeTrees();

// bushes & grass tufts
for(let i=0;i<28;i++){
  const a=rand()*Math.PI*2, d=5.5+rand()*18;
  const x=Math.cos(a)*d, z=Math.sin(a)*d;
  if(Math.random()<0.5){
    const b=new THREE.Mesh(new THREE.IcosahedronGeometry(0.45+rand()*0.5,0), mat.pine);
    b.position.set(x,0.3,z); b.scale.y=0.7; b.castShadow=true; b.receiveShadow=true; scene.add(b);
  }else{
    for(let k=0;k<3;k++){
      const tuft=new THREE.Mesh(new THREE.ConeGeometry(0.14,0.55,5), mat.pineLight);
      tuft.position.set(x+(rand()-0.5)*0.5,0.25,z+(rand()-0.5)*0.5); tuft.castShadow=true; scene.add(tuft);
    }
  }
}

// ---------- Rocks ----------
for(let i=0;i<12;i++){
  const a=rand()*Math.PI*2, d=4.5+rand()*12;
  const rock=new THREE.Mesh(new THREE.DodecahedronGeometry(0.28+rand()*0.55,0), rand()>0.5?mat.rock:mat.rockDark);
  rock.position.set(Math.cos(a)*d, 0.18, Math.sin(a)*d);
  rock.rotation.set(rand()*3,rand()*3,rand()*3);
  rock.castShadow=true; rock.receiveShadow=true; scene.add(rock);
}
// fire ring stones
for(let i=0;i<10;i++){
  const a=(i/10)*Math.PI*2;
  const s=new THREE.Mesh(new THREE.DodecahedronGeometry(0.22+rand()*0.12,0), mat.rock);
  s.position.set(Math.cos(a)*1.05,0.18,Math.sin(a)*1.05);
  s.rotation.set(rand()*2,rand()*2,rand()*2); s.castShadow=true; s.receiveShadow=true; scene.add(s);
}

// ---------- Logs ----------
function makeLog(r=0.22,len=1.6){
  const log=new THREE.Mesh(new THREE.CylinderGeometry(r,r*0.9,len,7), mat.log);
  log.castShadow=true; log.receiveShadow=true;
  const capMat=new THREE.MeshStandardMaterial({color:0xc9a06a, roughness:1, flatShading:true});
  const cap=new THREE.Mesh(new THREE.CylinderGeometry(r*0.92,r*0.92,0.02,7), capMat);
  cap.position.y=len/2+0.01; log.add(cap);
  return log;
}
// teepee firewood
for(let i=0;i<7;i++){
  const a=(i/7)*Math.PI*2;
  const log=makeLog(0.14,1.1);
  log.position.set(Math.cos(a)*0.35,0.22,Math.sin(a)*0.35);
  log.rotation.z=Math.cos(a)*0.85;
  log.rotation.x=Math.sin(a)*0.85;
  scene.add(log);
}
// horizontal burning logs
for(let i=0;i<3;i++){
  const log=makeLog(0.16,1.0);
  log.rotation.z=Math.PI/2; log.rotation.y=i*1.1;
  log.position.set((rand()-0.5)*0.3,0.18,(rand()-0.5)*0.3);
  scene.add(log);
}
// seating logs around fire
for(let i=0;i<4;i++){
  const a=(i/4)*Math.PI*2 + 0.6;
  const seat=makeLog(0.28,1.9);
  seat.rotation.z=Math.PI/2;
  seat.rotation.y=-a+Math.PI/2;
  seat.position.set(Math.cos(a)*3.1,0.28,Math.sin(a)*3.1);
  scene.add(seat);
  // log end caps visible - already added
}
// extra fallen logs
[[5,-4,0.8],[-6,6,-0.6],[12,6,0.3]].forEach(([x,z,r])=>{
  const l=makeLog(0.24,2.2); l.rotation.z=Math.PI/2; l.rotation.y=r; l.position.set(x,0.24,z); scene.add(l);
});

// ---------- Flames ----------
const flameGroup=new THREE.Group(); scene.add(flameGroup);
function flameCone(r,h,matl,y){
  const m=new THREE.Mesh(new THREE.ConeGeometry(r,h,7), matl);
  m.position.y=y; m.castShadow=false; flameGroup.add(m); return m;
}
const flameOuter=flameCone(0.55,1.35,mat.flameOuter,0.95);
const flameMid=flameCone(0.38,1.05,mat.flameMid,0.9);
const flameInner=flameCone(0.22,0.7,mat.flameInner,0.8);
// embers glow base
const ember=new THREE.Mesh(new THREE.IcosahedronGeometry(0.42,0), new THREE.MeshStandardMaterial({color:0xff4400, emissive:0xff3300, emissiveIntensity:2.5, flatShading:true}));
ember.position.y=0.22; ember.scale.set(1,0.5,1); scene.add(ember);
// glow sprite (canvas)
function glowTexture(){
  const c=document.createElement('canvas'); c.width=c.height=128;
  const ctx=c.getContext('2d');
  const g=ctx.createRadialGradient(64,64,0,64,64,64);
  g.addColorStop(0,'rgba(255,180,80,0.9)'); g.addColorStop(0.4,'rgba(255,120,30,0.35)'); g.addColorStop(1,'rgba(255,80,0,0)');
  ctx.fillStyle=g; ctx.fillRect(0,0,128,128);
  return new THREE.CanvasTexture(c);
}
const glow=new THREE.Sprite(new THREE.SpriteMaterial({map:glowTexture(), transparent:true, depthWrite:false, blending:THREE.AdditiveBlending}));
glow.scale.set(3.2,3.2,1); glow.position.y=1.1; scene.add(glow);

// ---------- Sparks ----------
const SPARKS=140;
const sparkGeo=new THREE.BufferGeometry();
const sparkPos=new Float32Array(SPARKS*3);
const sparkVel=[];
const sparkLife=new Float32Array(SPARKS);
for(let i=0;i<SPARKS;i++){
  sparkPos[i*3]= (Math.random()-0.5)*0.6;
  sparkPos[i*3+1]= Math.random()*2.2;
  sparkPos[i*3+2]= (Math.random()-0.5)*0.6;
  sparkVel.push({x:(Math.random()-0.5)*0.45, y:0.9+Math.random()*1.6, z:(Math.random()-0.5)*0.45});
  sparkLife[i]=Math.random();
}
sparkGeo.setAttribute('position', new THREE.BufferAttribute(sparkPos,3));
const sparkMat=new THREE.PointsMaterial({color:0xffcc66, size:0.09, transparent:true, opacity:0.95, blending:THREE.AdditiveBlending, depthWrite:false, sizeAttenuation:true});
const sparks=new THREE.Points(sparkGeo, sparkMat); scene.add(sparks);

// ---------- Stars & Moon ----------
function makeStars(){
  const N=900; const g=new THREE.BufferGeometry(); const p=new Float32Array(N*3);
  for(let i=0;i<N;i++){
    const theta=Math.random()*Math.PI*2, phi=Math.random()*Math.PI*0.45;
    const r=110;
    p[i*3]=Math.cos(theta)*Math.cos(phi)*r;
    p[i*3+1]=Math.sin(phi)*r+4;
    p[i*3+2]=Math.sin(theta)*Math.cos(phi)*r;
  }
  g.setAttribute('position', new THREE.BufferAttribute(p,3));
  const m=new THREE.PointsMaterial({color:0xffffff, size:0.55, sizeAttenuation:false, transparent:true, opacity:0.9, fog:false});
  const stars=new THREE.Points(g,m); scene.add(stars); return stars;
}
const stars=makeStars();

// moon
const moon=new THREE.Mesh(new THREE.SphereGeometry(3.2,24,24), new THREE.MeshBasicMaterial({color:0xf5f3ce, fog:false}));
moon.position.set(-28,32,-38); scene.add(moon);
const moonGlow=new THREE.Sprite(new THREE.SpriteMaterial({map:glowTexture(), color:0xbdd7ff, transparent:true, opacity:0.5, depthWrite:false, blending:THREE.AdditiveBlending, fog:false}));
moonGlow.scale.set(16,16,1); moonGlow.position.copy(moon.position); scene.add(moonGlow);
// distant mountain silhouettes
for(let i=0;i<8;i++){
  const a=(i/8)*Math.PI*2 + 0.4;
  const m=new THREE.Mesh(new THREE.ConeGeometry(14+rand()*10, 10+rand()*8, 5), new THREE.MeshStandardMaterial({color:0x0d1f33, roughness:1, flatShading:true}));
  m.position.set(Math.cos(a)*52, 2, Math.sin(a)*52); scene.add(m);
}

// ---------- Interaction extras ----------
let paused=false;
window.addEventListener('keydown', (e)=>{
  if(e.code==='Space'){ paused=!paused; e.preventDefault(); }
  if(e.key==='r'||e.key==='R'){ camera.position.copy(START_POS); controls.target.copy(START_TARGET); }
});
window.addEventListener('resize', ()=>{
  camera.aspect=window.innerWidth/window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

// ---------- Animation ----------
const clock=new THREE.Clock();
function animate(){
  requestAnimationFrame(animate);
  const t=clock.getElapsedTime();
  const dt=Math.min(clock.getDelta(),0.05) || 0.016;
  const flick = paused ? 1 : 1 + Math.sin(t*11)*0.08 + Math.sin(t*23+1.3)*0.12 + Math.sin(t*5)*0.06 + (Math.random()-0.5)*0.14;
  fireLight.intensity = 38 * flick;
  fireLight.position.x = Math.sin(t*7)*0.08;
  fireLight.position.z = Math.cos(t*6)*0.08;
  fireLight2.intensity = 10 * flick;
  glow.material.opacity = 0.75 + Math.sin(t*9)*0.12;
  glow.scale.setScalar(3.1 + Math.sin(t*8)*0.25);
  ember.material.emissiveIntensity = 2.2 + Math.sin(t*6)*0.6;

  if(!paused){
    flameOuter.scale.set(1+Math.sin(t*12)*0.18, 1+Math.sin(t*15)*0.22, 1+Math.cos(t*10)*0.18);
    flameOuter.rotation.y=t*1.2;
    flameMid.scale.set(1+Math.cos(t*14)*0.2, 1+Math.sin(t*18)*0.25, 1+Math.sin(t*13)*0.2);
    flameMid.rotation.y=-t*1.7;
    flameInner.scale.set(1+Math.sin(t*20)*0.22, 1+Math.cos(t*17)*0.28, 1+Math.cos(t*19)*0.22);
    flameGroup.position.y=Math.sin(t*11)*0.04;
    flameGroup.rotation.y=Math.sin(t*2)*0.1;

    // sparks
    const arr=sparkGeo.attributes.position.array;
    for(let i=0;i<SPARKS;i++){
      sparkLife[i]+=dt*0.55;
      if(sparkLife[i]>1){ sparkLife[i]=0; arr[i*3]=(Math.random()-0.5)*0.5; arr[i*3+1]=0.4; arr[i*3+2]=(Math.random()-0.5)*0.5; }
      const v=sparkVel[i];
      arr[i*3]+= (v.x + Math.sin(t*3+i)*0.12)*dt;
      arr[i*3+1]+= v.y*dt*(1 - sparkLife[i]*0.4);
      arr[i*3+2]+= (v.z + Math.cos(t*2.5+i)*0.12)*dt;
    }
    sparkGeo.attributes.position.needsUpdate=true;
    sparkMat.opacity=0.9;
  }
  stars.rotation.y=t*0.005;
  controls.update();
  renderer.render(scene,camera);
}
animate();
</script>
</body>
</html>
Reasoning trace (638 tokens)
Designing a single-file ThreeJS 0.