{
  "streamed": true,
  "usage": {
    "prompt_tokens": 149,
    "completion_tokens": 36826,
    "total_tokens": 36975,
    "cost": 0.221254,
    "is_byok": false,
    "prompt_tokens_details": {
      "cached_tokens": 0,
      "cache_write_tokens": 0,
      "audio_tokens": 0,
      "video_tokens": 0
    },
    "cost_details": {
      "upstream_inference_cost": 0.221254,
      "upstream_inference_prompt_cost": 0.000298,
      "upstream_inference_completions_cost": 0.220956
    },
    "completion_tokens_details": {
      "reasoning_tokens": 28399,
      "image_tokens": 0,
      "audio_tokens": 0
    }
  },
  "finishReason": "stop",
  "content": "```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\"/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>\n<meta name=\"theme-color\" content=\"#0d0906\"/>\n<title>Big Mac — three.js</title>\n<style>\n  html,body{\n    margin:0;height:100%;overflow:hidden;\n    background:radial-gradient(130% 100% at 50% 28%, #281c11 0%, #150f09 46%, #070504 100%);\n  }\n  canvas{display:block}\n  .ui{position:fixed;z-index:2;pointer-events:none;color:#f3e2c8;\n      font-family:\"Avenir Next\",\"Futura\",\"Century Gothic\",\"Segoe UI\",Verdana,sans-serif;}\n  #vignette{z-index:1;inset:0;\n      background:radial-gradient(120% 95% at 50% 40%, transparent 55%, rgba(0,0,0,.45) 100%);}\n  #brand{left:28px;bottom:26px;}\n  #brand h1{margin:0;font-size:clamp(20px,3.2vw,34px);font-weight:700;\n      letter-spacing:.38em;text-transform:uppercase;text-shadow:0 2px 18px rgba(0,0,0,.65);}\n  #brand p{margin:7px 0 0;font-size:10px;letter-spacing:.52em;text-transform:uppercase;\n      color:rgba(243,214,166,.55);}\n  #hint{right:26px;bottom:28px;font-size:10px;letter-spacing:.34em;text-transform:uppercase;\n      color:rgba(243,226,200,.5);transition:opacity 1.4s;}\n  #hint.gone{opacity:0}\n  #cover{position:fixed;inset:0;z-index:5;display:flex;align-items:center;justify-content:center;\n      background:radial-gradient(130% 100% at 50% 28%, #281c11 0%, #150f09 46%, #070504 100%);\n      color:rgba(243,226,200,.55);font-size:11px;letter-spacing:.5em;text-transform:uppercase;\n      font-family:\"Avenir Next\",\"Futura\",\"Segoe UI\",sans-serif;transition:opacity .9s;}\n  #cover.off{opacity:0;pointer-events:none}\n</style>\n</head>\n<body>\n<div id=\"cover\"><span>plating&nbsp;·&nbsp;please&nbsp;wait</span></div>\n<div class=\"ui\" id=\"vignette\"></div>\n<div class=\"ui\" id=\"brand\"><h1>Big&nbsp;Mac</h1><p>a&nbsp;procedural&nbsp;study&nbsp;·&nbsp;three.js</p></div>\n<div class=\"ui\" id=\"hint\">drag&nbsp;·&nbsp;orbit&nbsp;&nbsp;&nbsp;scroll&nbsp;·&nbsp;zoom</div>\n\n<script type=\"importmap\">\n{\"imports\":{\n  \"three\":\"https://cdn.jsdelivr.net/npm/three@0.165.0/build/three.module.js\",\n  \"three/addons/\":\"https://cdn.jsdelivr.net/npm/three@0.165.0/examples/jsm/\"\n}}\n</script>\n\n<script type=\"module\">\nimport * as THREE from 'three';\nimport {OrbitControls} from 'three/addons/controls/OrbitControls.js';\nimport {RoomEnvironment} from 'three/addons/environments/RoomEnvironment.js';\nimport {mergeVertices} from 'three/addons/utils/BufferGeometryUtils.js';\n\n/* ---------------------------------------------------------- renderer */\nconst renderer = new THREE.WebGLRenderer({antialias:true, alpha:true});\nrenderer.setPixelRatio(Math.min(devicePixelRatio,2));\nrenderer.setSize(innerWidth,innerHeight);\nrenderer.shadowMap.enabled = true;\nrenderer.shadowMap.type = THREE.PCFSoftShadowMap;\nrenderer.toneMapping = THREE.ACESFilmicToneMapping;\nrenderer.toneMappingExposure = 1.12;\nrenderer.outputColorSpace = THREE.SRGBColorSpace;\ndocument.body.appendChild(renderer.domElement);\n\nconst scene  = new THREE.Scene();\nconst camera = new THREE.PerspectiveCamera(38, innerWidth/innerHeight, .1, 60);\ncamera.position.set(8.2,4.8,9.2);\n\nconst pmrem = new THREE.PMREMGenerator(renderer);\nscene.environment = pmrem.fromScene(new RoomEnvironment(), .04).texture;\nif ('environmentIntensity' in scene) scene.environmentIntensity = .5;\n\nconst controls = new OrbitControls(camera, renderer.domElement);\ncontrols.target.set(0,1.22,0);\ncontrols.enableDamping = true; controls.dampingFactor = .06;\ncontrols.minDistance = 2.9;    controls.maxDistance = 11;\ncontrols.minPolarAngle = .12;  controls.maxPolarAngle = 1.50;\ncontrols.enablePan = false;\n\n/* --------------------------------------------- procedural textures */\nfunction canvasTex(size, draw, srgb = true){\n  const c = document.createElement('canvas'); c.width = c.height = size;\n  draw(c.getContext('2d'), size);\n  const t = new THREE.CanvasTexture(c);\n  if (srgb) t.colorSpace = THREE.SRGBColorSpace;\n  t.anisotropy = renderer.capabilities.getMaxAnisotropy();\n  return t;\n}\nfunction speckle(g,s,n,colors,rmin,rmax,aMin=.05,aMax=.3){\n  for(let i=0;i<n;i++){\n    const r = rmin + Math.random()*(rmax-rmin);\n    g.globalAlpha = aMin + Math.random()*(aMax-aMin);\n    g.fillStyle = colors[(Math.random()*colors.length)|0];\n    g.beginPath(); g.arc(Math.random()*s, Math.random()*s, r, 0, 6.283); g.fill();\n  }\n  g.globalAlpha = 1;\n}\nfunction bumpCanvas(amp){\n  return canvasTex(256,(g,s)=>{\n    g.fillStyle='#808080'; g.fillRect(0,0,s,s);\n    const id=g.getImageData(0,0,s,s), d=id.data;\n    for(let i=0;i<d.length;i+=4){\n      const v=(Math.random()*2-1)*amp;\n      d[i]=128+v; d[i+1]=128+v; d[i+2]=128+v;\n    }\n    g.putImageData(id,0,0);\n  },false);\n}\n\nconst bunMap = canvasTex(512,(g,s)=>{\n  const gr=g.createLinearGradient(0,s,0,0);\n  gr.addColorStop(0,'#c07f36'); gr.addColorStop(.45,'#d99a48');\n  gr.addColorStop(.8,'#e3a54f'); gr.addColorStop(1,'#cf8b3b');\n  g.fillStyle=gr; g.fillRect(0,0,s,s);\n  for(let i=0;i<26;i++){\n    const x=Math.random()*s,y=Math.random()*s,r=20+Math.random()*70;\n    const rg=g.createRadialGradient(x,y,0,x,y,r);\n    rg.addColorStop(0, Math.random()<.5 ? 'rgba(150,86,30,.20)' : 'rgba(244,196,120,.16)');\n    rg.addColorStop(1,'rgba(0,0,0,0)');\n    g.fillStyle=rg; g.beginPath(); g.arc(x,y,r,0,6.283); g.fill();\n  }\n  speckle(g,s,700,['#a86a24','#e8b268','#8f5a1e'],.6,2.2,.05,.22);\n});\nconst pattyMap = canvasTex(512,(g,s)=>{\n  g.fillStyle='#5c3319'; g.fillRect(0,0,s,s);\n  for(let i=0;i<40;i++){\n    const x=Math.random()*s,y=Math.random()*s,r=14+Math.random()*46;\n    const rg=g.createRadialGradient(x,y,0,x,y,r);\n    rg.addColorStop(0, Math.random()<.5 ? 'rgba(46,24,10,.35)' : 'rgba(140,82,44,.30)');\n    rg.addColorStop(1,'rgba(0,0,0,0)');\n    g.fillStyle=rg; g.beginPath(); g.arc(x,y,r,0,6.283); g.fill();\n  }\n  speckle(g,s,1600,['#38200f','#7d4a26','#241207','#8f5c33'],.5,2.6,.1,.5);\n  g.strokeStyle='rgba(25,12,5,.5)'; g.lineWidth=1.4;\n  for(let i=0;i<120;i++){\n    const x=Math.random()*s,y=Math.random()*s,a=Math.random()*6.283,l=3+Math.random()*9;\n    g.beginPath(); g.moveTo(x,y); g.lineTo(x+Math.cos(a)*l,y+Math.sin(a)*l); g.stroke();\n  }\n});\nconst cheeseMap = canvasTex(512,(g,s)=>{\n  g.fillStyle='#f6a61c'; g.fillRect(0,0,s,s);\n  for(let i=0;i<30;i++){\n    const x=Math.random()*s,y=Math.random()*s,r=10+Math.random()*40;\n    const rg=g.createRadialGradient(x,y,0,x,y,r);\n    rg.addColorStop(0, Math.random()<.6 ? 'rgba(255,195,90,.25)' : 'rgba(224,143,12,.22)');\n    rg.addColorStop(1,'rgba(0,0,0,0)');\n    g.fillStyle=rg; g.beginPath(); g.arc(x,y,r,0,6.283); g.fill();\n  }\n  speckle(g,s,260,['#ffc85e','#e08d0c'],1,5,.04,.12);\n});\nconst plateMap = canvasTex(512,(g,s)=>{\n  const rg=g.createRadialGradient(s/2,s/2,s*.08,s/2,s/2,s*.5);\n  rg.addColorStop(0,'#2b221a'); rg.addColorStop(.7,'#1c1510'); rg.addColorStop(1,'#120d09');\n  g.fillStyle=rg; g.fillRect(0,0,s,s);\n  g.globalAlpha=.05; g.strokeStyle='#000';\n  for(let i=0;i<40;i++){\n    g.beginPath(); g.arc(s/2,s/2,s*.06+Math.random()*s*.44,0,6.283);\n    g.lineWidth=.5+Math.random(); g.stroke();\n  }\n  g.globalAlpha=1;\n  speckle(g,s,300,['#3a2e22','#0d0906'],.5,1.6,.04,.14);\n});\nconst lettuceBump = canvasTex(256,(g,s)=>{\n  g.fillStyle='#808080'; g.fillRect(0,0,s,s); g.lineWidth=1.2;\n  for(let i=0;i<160;i++){\n    g.strokeStyle = Math.random()<.5 ? 'rgba(60,60,60,.30)' : 'rgba(200,200,200,.25)';\n    const x=Math.random()*s,y=Math.random()*s;\n    g.beginPath(); g.moveTo(x,y);\n    g.quadraticCurveTo(x+(Math.random()-.5)*40,y+(Math.random()-.5)*18,\n                       x+(Math.random()-.5)*70,y+(Math.random()-.5)*30);\n    g.stroke();\n  }\n},false);\nconst aoTex = canvasTex(256,(g,s)=>{\n  const rg=g.createRadialGradient(s/2,s/2,0,s/2,s/2,s/2);\n  rg.addColorStop(0,'rgba(0,0,0,.5)'); rg.addColorStop(.55,'rgba(0,0,0,.28)');\n  rg.addColorStop(1,'rgba(0,0,0,0)');\n  g.fillStyle=rg; g.fillRect(0,0,s,s);\n});\nconst steamTex = canvasTex(128,(g,s)=>{\n  g.clearRect(0,0,s,s);\n  const blob=(x,y,r,a)=>{\n    const rg=g.createRadialGradient(x,y,0,x,y,r);\n    rg.addColorStop(0,`rgba(255,250,240,${a})`); rg.addColorStop(1,'rgba(255,250,240,0)');\n    g.fillStyle=rg; g.fillRect(0,0,s,s);\n  };\n  blob(64,64,60,.55); blob(48,50,34,.35); blob(80,74,38,.3); blob(64,40,30,.25);\n});\nconst bunBump    = bumpCanvas(26);\nconst pattyBump  = bumpCanvas(70);\nconst cheeseBump = bumpCanvas(14);\n\n/* -------------------------------------------------------- materials */\nconst bunMat = new THREE.MeshPhysicalMaterial({\n  map:bunMap, bumpMap:bunBump, bumpScale:.035,\n  roughness:.52, clearcoat:.28, clearcoatRoughness:.5, envMapIntensity:.9\n});\nconst meatTop = new THREE.MeshStandardMaterial({\n  map:pattyMap, bumpMap:pattyBump, bumpScale:.09, roughness:.93, envMapIntensity:.35\n});\nconst meatSide = new THREE.MeshStandardMaterial({\n  map:pattyMap, color:0x8f6b52, bumpMap:pattyBump, bumpScale:.11,\n  roughness:.96, envMapIntensity:.3\n});\nconst cheeseMat = new THREE.MeshPhysicalMaterial({\n  map:cheeseMap, bumpMap:cheeseBump, bumpScale:.015,\n  roughness:.38, clearcoat:.12, clearcoatRoughness:.4, envMapIntensity:.7\n});\nconst lettuceMat = new THREE.MeshStandardMaterial({\n  vertexColors:true, side:THREE.DoubleSide, roughness:.5,\n  envMapIntensity:.55, bumpMap:lettuceBump, bumpScale:.03\n});\nconst onionMat = new THREE.MeshPhysicalMaterial({\n  color:0xf6f0dd, roughness:.28, transmission:.55, thickness:.04,\n  ior:1.4, envMapIntensity:.8\n});\nconst sauceMat = new THREE.MeshPhysicalMaterial({\n  color:0xe9a04b, roughness:.24, clearcoat:.55, clearcoatRoughness:.25, envMapIntensity:.9\n});\nconst seedMat = new THREE.MeshStandardMaterial({color:0xffffff, roughness:.45, envMapIntensity:.7});\nconst plateMat = new THREE.MeshPhysicalMaterial({\n  map:plateMap, roughness:.48, clearcoat:.3, clearcoatRoughness:.35, envMapIntensity:.6\n});\n\n/* --------------------------------------------------- bun profiles */\nfunction profile(ctrl,samples){\n  const c = new THREE.SplineCurve(ctrl.map(p=>new THREE.Vector2(p[0],p[1])));\n  const pts = c.getPoints(samples);\n  for(const p of pts) p.x = Math.max(0,p.x);\n  return pts;\n}\nconst crownPts = profile([\n  [0,0],[.55,.002],[1.05,.018],[1.38,.05],[1.545,.115],[1.60,.215],[1.585,.34],\n  [1.47,.52],[1.22,.68],[.86,.80],[.45,.875],[0,.90]\n],56);\nconst heelPts = profile([\n  [0,0],[.62,.002],[1.12,.02],[1.42,.09],[1.55,.20],[1.575,.315],[1.49,.405],[1.30,.448]\n],42);\nheelPts.push(new THREE.Vector2(.85,.458), new THREE.Vector2(.40,.462), new THREE.Vector2(0,.463));\nconst midPts = profile([\n  [0,0],[.6,.002],[1.15,.015],[1.42,.045],[1.55,.105],[1.575,.175],\n  [1.55,.245],[1.42,.305],[1.15,.335],[.6,.348],[0,.35]\n],44);\n\n/* ---------------------------------------------------- patty / cheese */\nfunction makePatty(r=1.47,h=.30,ph=0){\n  const g = new THREE.CylinderGeometry(r,r,h,110,6);\n  const pos = g.attributes.position, v = new THREE.Vector3();\n  for(let i=0;i<pos.count;i++){\n    v.fromBufferAttribute(pos,i);\n    const rr = Math.hypot(v.x,v.z), ang = Math.atan2(v.z,v.x);\n    if(rr>.001){\n      const n = .55*Math.sin(ang*7+ph) + .35*Math.sin(ang*13+ph*2.3)\n              + .25*Math.sin(ang*23+v.y*9+ph);\n      const isTop = v.y>h/2-.001, isBot = v.y<-h/2+.001;\n      let rr2 = rr*(1+.020*n+.02*Math.sin((v.y/h+.5)*Math.PI));\n      if(isTop) rr2*=.99;\n      let y = v.y;\n      if(isTop)      y += .014*Math.sin(ang*17+ph)+.01*Math.sin(v.x*5+v.z*6);\n      else if(!isBot)y += .006*Math.sin(ang*19+ph);\n      pos.setXYZ(i, Math.cos(ang)*rr2, y, Math.sin(ang)*rr2);\n    }else{\n      pos.setY(i, v.y + (v.y>0 ? .012*Math.sin(v.x*6+v.z*7+ph) : 0));\n    }\n  }\n  g.computeVertexNormals();\n  return g;\n}\nfunction roundedRect(s,r){\n  const sh = new THREE.Shape();\n  sh.moveTo(-s+r,-s);\n  sh.lineTo(s-r,-s);  sh.absarc(s-r,-s+r,r,-Math.PI/2,0,false);\n  sh.lineTo(s,s-r);   sh.absarc(s-r,s-r,r,0,Math.PI/2,false);\n  sh.lineTo(-s+r,s);  sh.absarc(-s+r,s-r,r,Math.PI/2,Math.PI,false);\n  sh.lineTo(-s,-s+r); sh.absarc(-s+r,-s+r,r,Math.PI,Math.PI*1.5,false);\n  return sh;\n}\nfunction makeCheese(){\n  const s=1.22, th=.042;\n  let g = new THREE.ExtrudeGeometry(roundedRect(s,.14),{\n    depth:th, bevelEnabled:true, bevelThickness:.012, bevelSize:.012,\n    bevelSegments:2, curveSegments:10\n  });\n  g = mergeVertices(g);\n  g.rotateX(-Math.PI/2);\n  const pos = g.attributes.position, v = new THREE.Vector3();\n  for(let i=0;i<pos.count;i++){\n    v.fromBufferAttribute(pos,i);\n    const m = Math.max(Math.abs(v.x),Math.abs(v.z));\n    const t = THREE.MathUtils.clamp((m-.92)/(s-.92),0,1);\n    const corner = Math.min(Math.abs(v.x),Math.abs(v.z))/(m+1e-4);\n    let droop = Math.pow(t,1.7)*(.24+.28*corner);\n    droop *= .88 + .24*(.5+.5*Math.sin(v.x*6.1+v.z*4.7));\n    pos.setY(i, v.y - droop);\n  }\n  g.computeVertexNormals();\n  return g;\n}\n\n/* --------------------------------------------------------- lettuce */\nfunction makeLettuce(rIn,rOut,waves,phase,seedN){\n  const seg=440, rows=9, pos=[], col=[], idx=[];\n  const cIn = new THREE.Color('#e9efc0'),\n        cMid = new THREE.Color('#a3cf63'),\n        cOut = new THREE.Color('#5f9c39'),\n        c = new THREE.Color();\n  const fract = x => x - Math.floor(x);\n  for(let i=0;i<=seg;i++){\n    const th = i/seg*Math.PI*2;\n    const w1 = Math.sin(th*waves+phase),\n          w2 = Math.sin(th*(waves+4)+phase*1.7),\n          w3 = Math.sin(th*23+phase*3);\n    const edge = rOut*(1+.055*w1+.03*w2+.016*w3);\n    for(let j=0;j<=rows;j++){\n      const t = j/rows, tt = t*t;\n      const r = rIn + (edge-rIn)*t;\n      const y = tt*(.115+.075*w1+.045*w2) + t*.018*w3\n              + .012*Math.sin(th*31+j*2.7+phase);\n      pos.push(r*Math.cos(th), y, r*Math.sin(th));\n      if(t<.5) c.copy(cIn).lerp(cMid,t*2); else c.copy(cMid).lerp(cOut,(t-.5)*2);\n      const vv = .92 + .16*fract(Math.sin(i*12.7+j*5.3+seedN)*43758.5);\n      col.push(c.r*vv,c.g*vv,c.b*vv);\n    }\n  }\n  for(let i=0;i<seg;i++)for(let j=0;j<rows;j++){\n    const a=i*(rows+1)+j, b=a+1, c2=(i+1)*(rows+1)+j, d=c2+1;\n    idx.push(a,c2,b, b,c2,d);\n  }\n  const g = new THREE.BufferGeometry();\n  g.setAttribute('position', new THREE.Float32BufferAttribute(pos,3));\n  g.setAttribute('color',    new THREE.Float32BufferAttribute(col,3));\n  g.setIndex(idx); g.computeVertexNormals();\n  return g;\n}\n\n/* -------------------------------------------- onions, sauce, seeds */\nfunction scatterOnions(y,count){\n  const grp = new THREE.Group();\n  for(let i=0;i<count;i++){\n    const g = new THREE.TorusGeometry(.12+Math.random()*.09,.020+Math.random()*.010,\n                                      10,40,(.55+Math.random()*.45)*Math.PI*2);\n    const m = new THREE.Mesh(g,onionMat);\n    const a = Math.random()*Math.PI*2, dist = .55+Math.random()*.85;\n    m.position.set(Math.cos(a)*dist, y+Math.random()*.02, Math.sin(a)*dist);\n    m.rotation.set(Math.PI/2+(Math.random()-.5)*.5, Math.random()*Math.PI*2,\n                   (Math.random()-.5)*.5);\n    grp.add(m);\n  }\n  return grp;\n}\nconst sauceGeo = new THREE.SphereGeometry(1,18,12);\nfunction sauceRing(y,rad,n,phase){\n  const grp = new THREE.Group();\n  for(let i=0;i<n;i++){\n    const a = phase + i/n*Math.PI*2 + Math.random()*.4;\n    const d = rad + Math.random()*.18, s = .09+Math.random()*.06;\n    const m = new THREE.Mesh(sauceGeo,sauceMat);\n    m.scale.set(s*(1+Math.random()*.6), s*.42, s);\n    m.position.set(Math.cos(a)*d, y, Math.sin(a)*d);\n    m.rotation.y = Math.random()*6.28;\n    grp.add(m);\n  }\n  return grp;\n}\nfunction addSeeds(parent, prof){\n  const geo = new THREE.SphereGeometry(1,12,10);\n  const seeds = new THREE.InstancedMesh(geo,seedMat,150);\n  const dummy = new THREE.Object3D(), nrm = new THREE.Vector3(),\n        Y = new THREE.Vector3(0,1,0),\n        q1 = new THREE.Quaternion(), q2 = new THREE.Quaternion(),\n        col = new THREE.Color();\n  const L = prof.length;\n  for(let i=0;i<150;i++){\n    const f = .28 + .72*Math.random();\n    const k = f*(L-1), i0 = k|0, i1 = Math.min(L-1,i0+1), ff = k-i0;\n    const px = THREE.MathUtils.lerp(prof[i0].x,prof[i1].x,ff);\n    const py = THREE.MathUtils.lerp(prof[i0].y,prof[i1].y,ff);\n    const a = prof[Math.max(0,i0-1)], b = prof[Math.min(L-1,i1+1)];\n    const tx = b.x-a.x, ty = b.y-a.y, tl = Math.hypot(tx,ty)||1;\n    const nx = ty/tl, ny = -tx/tl;\n    const th = Math.random()*Math.PI*2;\n    nrm.set(nx*Math.cos(th), ny, nx*Math.sin(th));\n    dummy.position.set(px*Math.cos(th)+nrm.x*.02, py+nrm.y*.02, px*Math.sin(th)+nrm.z*.02);\n    q1.setFromUnitVectors(Y,nrm);\n    q2.setFromAxisAngle(nrm,Math.random()*Math.PI*2);\n    dummy.quaternion.copy(q2).multiply(q1);\n    const s = .8+.45*Math.random();\n    dummy.scale.set(.052*s,.030*s,.082*s);\n    dummy.updateMatrix();\n    seeds.setMatrixAt(i,dummy.matrix);\n    col.setHSL(.10+.03*Math.random(),.45,.72+.10*Math.random());\n    seeds.setColorAt(i,col);\n  }\n  seeds.castShadow = true;\n  parent.add(seeds);\n}\n\n/* ---------------------------------------------------- scene assembly */\nconst plate = new THREE.Mesh(new THREE.CylinderGeometry(3.25,3.42,.16,96), plateMat);\nplate.position.y = -.08; plate.receiveShadow = true;\nscene.add(plate);\n\nconst ao = new THREE.Mesh(new THREE.CircleGeometry(2.35,64),\n          new THREE.MeshBasicMaterial({map:aoTex,transparent:true,depthWrite:false}));\nao.rotation.x = -Math.PI/2; ao.position.y = .008;\nscene.add(ao);\n\nconst burger = new THREE.Group();\nscene.add(burger);\nfunction add(mesh,y,rotY=0,cast=true,recv=true){\n  mesh.position.y = y; mesh.rotation.y = rotY;\n  mesh.castShadow = cast; mesh.receiveShadow = recv;\n  burger.add(mesh); return mesh;\n}\nadd(new THREE.Mesh(new THREE.LatheGeometry(heelPts,96), bunMat), .02, .3);\nadd(new THREE.Mesh(makePatty(1.47,.30,0),  [meatSide,meatTop,meatTop]), .63, 0);\nadd(new THREE.Mesh(makeCheese(), cheeseMat), .784, .55);\nadd(new THREE.Mesh(makeLettuce(.85,1.90,9,0,1), lettuceMat), .80, 0);\nadd(scatterOnions(.845,7), 0, 0, false, false);\nadd(sauceRing(.805,1.52,6,.4), 0, 0, false, false);\nadd(new THREE.Mesh(new THREE.LatheGeometry(midPts,96), bunMat), .93, 1.9);\nadd(new THREE.Mesh(makePatty(1.47,.30,3.7),[meatSide,meatTop,meatTop]), 1.42, 0);\nadd(sauceRing(1.59,1.5,5,2.0), 0, 0, false, false);\nadd(new THREE.Mesh(makeLettuce(.85,1.86,8,2.2,7), lettuceMat), 1.585, .8);\nadd(scatterOnions(1.625,6), 0, 0, false, false);\n\nconst crownGrp = new THREE.Group();\ncrownGrp.position.y = 1.72; crownGrp.rotation.y = .8;\nconst crown = new THREE.Mesh(new THREE.LatheGeometry(crownPts,96), bunMat);\ncrown.castShadow = crown.receiveShadow = true;\ncrownGrp.add(crown);\naddSeeds(crownGrp, crownPts);\nburger.add(crownGrp);\n\n/* fallen sesame seeds + crumbs on the plate (static, don't spin) */\n{\n  const geo = new THREE.SphereGeometry(1,10,8);\n  const fallen = new THREE.InstancedMesh(geo,seedMat,5);\n  const d = new THREE.Object3D(), col = new THREE.Color();\n  for(let i=0;i<5;i++){\n    const a = Math.random()*6.283, dist = 2.0+Math.random()*.9;\n    d.position.set(Math.cos(a)*dist,.022,Math.sin(a)*dist);\n    d.rotation.set(0,Math.random()*6.283,Math.PI/2*(.9+Math.random()*.2));\n    const s = .8+.3*Math.random();\n    d.scale.set(.052*s,.030*s,.082*s);\n    d.updateMatrix(); fallen.setMatrixAt(i,d.matrix);\n    col.setHSL(.10,.4,.74); fallen.setColorAt(i,col);\n  }\n  fallen.castShadow = true; scene.add(fallen);\n  const crumbMat = new THREE.MeshStandardMaterial({color:0xc98d45,roughness:.8});\n  for(let i=0;i<8;i++){\n    const m = new THREE.Mesh(new THREE.SphereGeometry(.015+Math.random()*.015,8,6),crumbMat);\n    const a = Math.random()*6.283, dist = 1.9+Math.random()*.9;\n    m.position.set(Math.cos(a)*dist,.014,Math.sin(a)*dist);\n    m.castShadow = true; scene.add(m);\n  }\n}\n\n/* -------------------------------------------------------- lighting */\nconst key = new THREE.DirectionalLight(0xffdcb0,3.4);\nkey.position.set(4.2,6.5,3.2);\nkey.castShadow = true;\nkey.shadow.mapSize.set(2048,2048);\nkey.shadow.camera.near = 2;  key.shadow.camera.far = 16;\nkey.shadow.camera.left = -4; key.shadow.camera.right = 4;\nkey.shadow.camera.top  =  4; key.shadow.camera.bottom = -3;\nkey.shadow.bias = -.0004;    key.shadow.normalBias = .03;\nscene.add(key);\nconst fill = new THREE.DirectionalLight(0xb9c8e6,.7);\nfill.position.set(-5,2.6,-1.5); scene.add(fill);\nconst rim  = new THREE.DirectionalLight(0xffc98c,1.6);\nrim.position.set(-2.5,4.5,-5);  scene.add(rim);\nscene.add(new THREE.HemisphereLight(0xffe6c4,0x241407,.32));\nconst glow = new THREE.PointLight(0xff9440,5,10,2);\nglow.position.set(1.6,1.4,3.1); scene.add(glow);\n\n/* ------------------------------------------------------------ steam */\nconst steams = [];\nfor(let i=0;i<12;i++){\n  const m = new THREE.SpriteMaterial({map:steamTex,transparent:true,opacity:0,\n                                      depthWrite:false,color:0xffe9d0});\n  const sp = new THREE.Sprite(m);\n  scene.add(sp);\n  steams.push({sp, off:Math.random(), spd:.05+Math.random()*.05,\n               x:(Math.random()-.5)*.9, z:(Math.random()-.5)*.9,\n               ph:Math.random()*6.283});\n}\n\n/* -------------------------------------------------------- animation */\nconst clock = new THREE.Clock();\nconst camStart = new THREE.Vector3(8.2,4.8,9.2),\n      camEnd   = new THREE.Vector3(4.5,2.5,5.1);\nlet intro = true, hold = false, spin = 0, revealed = false;\nconst hintEl = document.getElementById('hint');\ncontrols.addEventListener('start',()=>{intro=false; hold=true; hintEl.classList.add('gone');});\ncontrols.addEventListener('end',  ()=>{hold=false;});\nsetTimeout(()=>hintEl.classList.add('gone'), 8000);\n\nfunction tick(){\n  requestAnimationFrame(tick);\n  const dt = Math.min(clock.getDelta(),.05), t = clock.elapsedTime;\n\n  if(intro){\n    const k = Math.min(1,t/2.4), e = 1-Math.pow(1-k,3);\n    camera.position.lerpVectors(camStart,camEnd,e);\n    if(k>=1) intro = false;\n  }\n  spin += ((hold?0:.22)-spin)*Math.min(1,dt*2.5);\n  burger.rotation.y += spin*dt;\n\n  for(const s of steams){\n    const p = (t*s.spd+s.off)%1;\n    const sway = .25*p;\n    s.sp.position.set(s.x+Math.sin(t*.6+s.ph)*sway, 2.66+p*1.9,\n                      s.z+Math.cos(t*.5+s.ph)*sway*.7);\n    const sc = .5+p*1.5;\n    s.sp.scale.set(sc,sc,1);\n    s.sp.material.opacity = Math.sin(p*Math.PI)*.12;\n  }\n\n  controls.update();\n  renderer.render(scene,camera);\n  if(!revealed){\n    revealed = true;\n    setTimeout(()=>document.getElementById('cover').classList.add('off'),150);\n  }\n}\ntick();\n\naddEventListener('resize',()=>{\n  camera.aspect = innerWidth/innerHeight;\n  camera.updateProjectionMatrix();\n  renderer.setSize(innerWidth,innerHeight);\n});\n</script>\n</body>\n</html>\n```"
}