{
  "streamed": true,
  "usage": {
    "prompt_tokens": 162,
    "completion_tokens": 53631,
    "total_tokens": 53793,
    "cost": 2.68317,
    "is_byok": false,
    "prompt_tokens_details": {
      "cached_tokens": 0,
      "cache_write_tokens": 0,
      "audio_tokens": 0,
      "video_tokens": 0
    },
    "cost_details": {
      "upstream_inference_cost": 2.68317,
      "upstream_inference_prompt_cost": 0.00162,
      "upstream_inference_completions_cost": 2.68155
    },
    "completion_tokens_details": {
      "reasoning_tokens": 38782,
      "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<title>Big Mac — three.js</title>\n<style>\n  html,body{margin:0;height:100%;overflow:hidden;background:#0c0a09}\n  canvas{display:block}\n  #info{position:fixed;left:14px;bottom:12px;font:12px/1.4 system-ui,-apple-system,Segoe UI,sans-serif;\n        color:rgba(255,255,255,.45);pointer-events:none;letter-spacing:.02em;user-select:none}\n</style>\n<script type=\"importmap\">\n{\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}\n</script>\n</head>\n<body>\n<div id=\"info\">building burger…</div>\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';\n\n/* ------------------------------------------------------------------ utils */\nconst lerp=(a,b,t)=>a+(b-a)*t;\nconst clamp=(x,a,b)=>x<a?a:x>b?b:x;\nconst smooth=(a,b,x)=>{const t=clamp((x-a)/(b-a),0,1);return t*t*(3-2*t);};\nconst hex=h=>[(h>>16&255)/255,(h>>8&255)/255,(h&255)/255];\nlet _s=20240613;\nconst rand=()=>{_s|=0;_s=_s+0x6D2B79F5|0;let t=Math.imul(_s^_s>>>15,1|_s);t=t+Math.imul(t^t>>>7,61|t)^t;return((t^t>>>14)>>>0)/4294967296;};\nconst rr=(a,b)=>a+rand()*(b-a);\n\nfunction hash3(x,y,z){let n=(x*1619+y*31337+z*6971)|0;n=(n<<13)^n;n=(Math.imul(n,Math.imul(Math.imul(n,n),15731)+789221)+1376312589)&0x7fffffff;return n/2147483647;}\nconst sm=t=>t*t*(3-2*t);\nfunction noise3(x,y,z){\n  const xi=Math.floor(x),yi=Math.floor(y),zi=Math.floor(z);\n  const u=sm(x-xi),v=sm(y-yi),w=sm(z-zi);\n  const a=lerp(lerp(hash3(xi,yi,zi),hash3(xi+1,yi,zi),u),lerp(hash3(xi,yi+1,zi),hash3(xi+1,yi+1,zi),u),v);\n  const b=lerp(lerp(hash3(xi,yi,zi+1),hash3(xi+1,yi,zi+1),u),lerp(hash3(xi,yi+1,zi+1),hash3(xi+1,yi+1,zi+1),u),v);\n  return lerp(a,b,w);\n}\nfunction fbm3(x,y,z,o=4){let s=0,a=0.5,n=0,f=1;for(let i=0;i<o;i++){s+=a*noise3(x*f,y*f,z*f);n+=a;a*=0.5;f*=2.03;}return s/n;}\nfunction noise2p(x,y,px,py){\n  const xi=Math.floor(x),yi=Math.floor(y);const u=sm(x-xi),v=sm(y-yi);\n  const h=(a,b)=>hash3(((a%px)+px)%px,((b%py)+py)%py,7);\n  return lerp(lerp(h(xi,yi),h(xi+1,yi),u),lerp(h(xi,yi+1),h(xi+1,yi+1),u),v);\n}\nfunction fbm2p(x,y,px,py,o=4){let s=0,a=0.5,n=0,f=1;for(let i=0;i<o;i++){s+=a*noise2p(x*f,y*f,px*f,py*f);n+=a;a*=0.5;f*=2;}return s/n;}\n\nconst to8=x=>clamp(Math.round(x*255),0,255);\nlet maxAniso=1;\nfunction finishTex(t,srgb){t.wrapS=t.wrapT=THREE.RepeatWrapping;t.colorSpace=srgb?THREE.SRGBColorSpace:THREE.NoColorSpace;t.anisotropy=maxAniso;return t;}\nfunction genTex(size,fn,srgb=true){\n  const c=document.createElement('canvas');c.width=c.height=size;const ctx=c.getContext('2d');\n  const img=ctx.createImageData(size,size);const d=img.data;const col=[0,0,0];\n  for(let y=0;y<size;y++)for(let x=0;x<size;x++){fn(x/size,1-y/size,col);const i=(y*size+x)*4;d[i]=to8(col[0]);d[i+1]=to8(col[1]);d[i+2]=to8(col[2]);d[i+3]=255;}\n  ctx.putImageData(img,0,0);return finishTex(new THREE.CanvasTexture(c),srgb);\n}\n\n/* --------------------------------------------------------- lathe helpers */\nfunction profile(ctrl,n){\n  const pts=ctrl.map(p=>new THREE.Vector3(p[0],p[1],0));\n  const curve=new THREE.CatmullRomCurve3(pts,false,'centripetal');\n  return curve.getSpacedPoints(n).map(p=>new THREE.Vector2(Math.max(0,p.x),p.y));\n}\nfunction displace(geo,seg,npts,fn){\n  const pos=geo.attributes.position,nor=geo.attributes.normal;\n  const p=new THREE.Vector3(),n=new THREE.Vector3(),out=new THREE.Vector3();\n  for(let i=0;i<pos.count;i++){\n    p.fromBufferAttribute(pos,i);n.fromBufferAttribute(nor,i);out.set(0,0,0);\n    fn(p,n,out);pos.setXYZ(i,p.x+out.x,p.y+out.y,p.z+out.z);\n  }\n  pos.needsUpdate=true;geo.computeVertexNormals();\n  // stitch the seam normals\n  const na=geo.attributes.normal;const t=new THREE.Vector3();\n  for(let j=0;j<npts;j++){\n    const a=j,b=seg*npts+j;\n    t.set(na.getX(a)+na.getX(b),na.getY(a)+na.getY(b),na.getZ(a)+na.getZ(b)).normalize();\n    na.setXYZ(a,t.x,t.y,t.z);na.setXYZ(b,t.x,t.y,t.z);\n  }\n  na.needsUpdate=true;\n}\nfunction latheGeo(ctrl,n,seg,disp){\n  const pts=profile(ctrl,n);const geo=new THREE.LatheGeometry(pts,seg);\n  if(disp)displace(geo,seg,pts.length,disp);return{geo,pts};\n}\n// bake a 3D procedural shader onto the lathe's (angle, profile) parameterization -> no pole pinching\nfunction bakeLathe(pts,w,h,shade){\n  const mk=()=>{const c=document.createElement('canvas');c.width=w;c.height=h;const ctx=c.getContext('2d');const img=ctx.createImageData(w,h);return{c,ctx,img,d:img.data};};\n  const M=mk(),B=mk(),R=mk();const p=new THREE.Vector3();const o={r:0,g:0,b:0,bump:0.5,rough:0.5};\n  const n=pts.length-1;\n  for(let j=0;j<h;j++){\n    const v=1-j/(h-1);const f=v*n;const i0=Math.min(n-1,Math.floor(f));const t=f-i0;\n    const px=pts[i0].x+(pts[i0+1].x-pts[i0].x)*t,py=pts[i0].y+(pts[i0+1].y-pts[i0].y)*t;\n    for(let i=0;i<w;i++){\n      const a=(i/w)*Math.PI*2;p.set(px*Math.sin(a),py,px*Math.cos(a));shade(p,o);\n      const k=(j*w+i)*4;\n      M.d[k]=to8(o.r);M.d[k+1]=to8(o.g);M.d[k+2]=to8(o.b);M.d[k+3]=255;\n      const bb=to8(o.bump);B.d[k]=B.d[k+1]=B.d[k+2]=bb;B.d[k+3]=255;\n      const rg=to8(o.rough);R.d[k]=R.d[k+1]=R.d[k+2]=rg;R.d[k+3]=255;\n    }\n  }\n  for(const q of[M,B,R])q.ctx.putImageData(q.img,0,0);\n  return{map:finishTex(new THREE.CanvasTexture(M.c),true),bump:finishTex(new THREE.CanvasTexture(B.c),false),rough:finishTex(new THREE.CanvasTexture(R.c),false)};\n}\n\n/* ---------------------------------------------------------------- colors */\nconst C={\n  crownTop:hex(0xc2742d),crownSide:hex(0xdea562),dark:hex(0x86451a),crumb:hex(0xf3e2bd),\n  heelBot:hex(0xe3bd86),heelSide:hex(0xcf8b3f),club:hex(0xd5964a),\n  pattyA:hex(0x4a2a17),pattyB:hex(0x704326),pattyChar:hex(0x281609),pattyGrease:hex(0x80502e)\n};\nconst tmp=[0,0,0];\nfunction mixArr(a,b,t,o){o[0]=lerp(a[0],b[0],t);o[1]=lerp(a[1],b[1],t);o[2]=lerp(a[2],b[2],t);return o;}\n\nfunction bunShade(p,o,crust,cc){\n  const n=fbm3(p.x*3.5+1,p.y*3.5,p.z*3.5,4);\n  const sh=0.82+0.32*n;\n  let r=cc[0]*sh,g=cc[1]*sh,b=cc[2]*sh;\n  const spot=smooth(0.56,0.7,fbm3(p.x*2.3+7,p.y*2.3+2,p.z*2.3+5,3));\n  r=lerp(r,C.dark[0],spot*0.75);g=lerp(g,C.dark[1],spot*0.75);b=lerp(b,C.dark[2],spot*0.75);\n  const pore=noise3(p.x*48,p.y*48,p.z*48);const pk=pore>0.7?0.88:1;\n  const fine=(0.94+0.12*noise3(p.x*90,p.y*90,p.z*90))*pk;r*=fine;g*=fine;b*=fine;\n  const cn=noise3(p.x*26+3,p.y*26,p.z*26);const hole=noise3(p.x*20+9,p.y*20+4,p.z*20)>0.73;\n  const cs=(0.9+0.2*cn)*(hole?0.78:1);\n  o.r=lerp(C.crumb[0]*cs,r,crust);o.g=lerp(C.crumb[1]*cs,g,crust);o.b=lerp(C.crumb[2]*cs,b,crust);\n  const bCrust=0.45+0.35*fbm3(p.x*14,p.y*14,p.z*14,3)+(pore>0.7?0.3:0);\n  const bCrumb=0.35+0.45*cn+(hole?-0.3:0)+0.2*noise3(p.x*60,p.y*60,p.z*60);\n  o.bump=lerp(bCrumb,bCrust,crust);\n  o.rough=lerp(0.95,0.62+0.3*n-0.15*spot,crust);\n}\nconst crownShade=(p,o)=>{const y=p.y;bunShade(p,o,smooth(0,0.05,y),mixArr(C.crownSide,C.crownTop,smooth(0.14,0.74,y),tmp));};\nconst heelShade=(p,o)=>{const y=p.y,r=Math.hypot(p.x,p.z);const crust=Math.max(1-smooth(0.405,0.455,y),smooth(1.0,1.035,r));bunShade(p,o,crust,mixArr(C.heelBot,C.heelSide,smooth(0.0,0.22,y),tmp));};\nconst clubShade=(p,o)=>{const r=Math.hypot(p.x,p.z);bunShade(p,o,smooth(0.92,1.0,r),C.club);};\nfunction pattyShade(p,o){\n  const n=fbm3(p.x*6+4,p.y*6,p.z*6,4);\n  mixArr(C.pattyA,C.pattyB,n,tmp);\n  const ch=smooth(0.58,0.72,fbm3(p.x*3.2+17,p.y*3.2,p.z*3.2+9,3));\n  mixArr(tmp,C.pattyChar,ch*0.85,tmp);\n  const gr=smooth(0.55,0.8,fbm3(p.x*4.5+31,p.y*4.5,p.z*4.5,3));\n  mixArr(tmp,C.pattyGrease,gr*0.45,tmp);\n  const f=noise3(p.x*70,p.y*70,p.z*70);const fine=0.88+0.24*f;\n  o.r=tmp[0]*fine;o.g=tmp[1]*fine;o.b=tmp[2]*fine;\n  o.bump=0.5*fbm3(p.x*22,p.y*22,p.z*22,3)+0.3*f+0.2*noise3(p.x*140,p.y*140,p.z*140);\n  o.rough=0.82-0.42*gr-0.1*n;\n}\n\n/* --------------------------------------------------------- renderer/scene */\nconst renderer=new THREE.WebGLRenderer({antialias:true,powerPreference:'high-performance'});\nrenderer.setPixelRatio(Math.min(window.devicePixelRatio,2));\nrenderer.setSize(innerWidth,innerHeight);\nrenderer.toneMapping=THREE.ACESFilmicToneMapping;\nrenderer.toneMappingExposure=1.0;\nrenderer.shadowMap.enabled=true;\nrenderer.shadowMap.type=THREE.PCFSoftShadowMap;\ndocument.body.appendChild(renderer.domElement);\nmaxAniso=renderer.capabilities.getMaxAnisotropy();\n\nconst scene=new THREE.Scene();\n{\n  const pmrem=new THREE.PMREMGenerator(renderer);\n  const env=new RoomEnvironment();\n  scene.environment=pmrem.fromScene(env,0.04).texture;\n  scene.environmentIntensity=0.5;\n  pmrem.dispose();\n}\n{\n  const c=document.createElement('canvas');c.width=4;c.height=512;const g=c.getContext('2d');\n  const gr=g.createLinearGradient(0,0,0,512);\n  gr.addColorStop(0,'#3d322b');gr.addColorStop(0.55,'#1a1411');gr.addColorStop(1,'#0a0807');\n  g.fillStyle=gr;g.fillRect(0,0,4,512);\n  const t=new THREE.CanvasTexture(c);t.colorSpace=THREE.SRGBColorSpace;scene.background=t;\n}\nscene.fog=new THREE.Fog(0x110d0a,9,30);\n\nconst camera=new THREE.PerspectiveCamera(32,innerWidth/innerHeight,0.05,100);\ncamera.position.set(2.9,2.3,4.8);\nconst controls=new OrbitControls(camera,renderer.domElement);\ncontrols.target.set(0,1.2,0);\ncontrols.enableDamping=true;controls.dampingFactor=0.06;controls.enablePan=false;\ncontrols.minDistance=2.2;controls.maxDistance=11;controls.maxPolarAngle=Math.PI*0.495;\ncontrols.update();\n\n/* ---------------------------------------------------------------- lights */\nconst key=new THREE.DirectionalLight(0xfff0dc,3.4);\nkey.position.set(4,6.5,3.5);key.castShadow=true;\nkey.shadow.mapSize.set(2048,2048);\nObject.assign(key.shadow.camera,{near:1,far:20,left:-3,right:3,top:3,bottom:-3});\nkey.shadow.bias=-0.0003;key.shadow.normalBias=0.02;\nscene.add(key);\nconst fill=new THREE.DirectionalLight(0xd8e4ff,0.65);fill.position.set(-5,3,2);scene.add(fill);\nconst rim=new THREE.DirectionalLight(0xffd6a8,1.5);rim.position.set(-2,4,-6);scene.add(rim);\n\n/* ----------------------------------------------------------------- table */\n{\n  const dark=hex(0x2a170f),light=hex(0x6d3f1d);\n  const woodMap=genTex(512,(u,v,c)=>{\n    const plank=Math.floor(v*4);const off=Math.floor(hash3(plank,3,5)*4);\n    const g=fbm2p(u*4+off,v*72,4,72,5);const g2=fbm2p(u*12+off,v*160+off,12,160,3);\n    const t=Math.pow(g,1.4)*0.72+g2*0.28;\n    const fv=v*4-plank;const edge=smooth(0,0.02,fv)*smooth(1,0.985,fv);\n    const e=0.65+0.35*edge;\n    c[0]=lerp(dark[0],light[0],t)*e;c[1]=lerp(dark[1],light[1],t)*e;c[2]=lerp(dark[2],light[2],t)*e;\n  });\n  const woodRough=genTex(256,(u,v,c)=>{const g=0.3+0.4*fbm2p(u*4,v*40,4,40,4);c[0]=c[1]=c[2]=g;},false);\n  woodMap.repeat.set(5,5);woodRough.repeat.set(5,5);\n  const mat=new THREE.MeshPhysicalMaterial({map:woodMap,roughnessMap:woodRough,roughness:1,clearcoat:0.35,clearcoatRoughness:0.3});\n  const ground=new THREE.Mesh(new THREE.PlaneGeometry(60,60),mat);\n  ground.rotation.x=-Math.PI/2;ground.receiveShadow=true;scene.add(ground);\n}\n\n/* ----------------------------------------------------------------- burger */\nconst burger=new THREE.Group();scene.add(burger);\nconst SEG=128;\n\nfunction bunDisp(k){\n  return(p,n,out)=>{\n    const b=fbm3(p.x*3+k,p.y*3,p.z*3+k,3)-0.5;\n    const f=noise3(p.x*18+k,p.y*18,p.z*18)-0.5;\n    out.copy(n).multiplyScalar(b*0.04+f*0.008);\n    const r=Math.hypot(p.x,p.z);\n    if(r>1e-4){const lo=fbm3(p.x*1.2+k*3,0.3,p.z*1.2,2)-0.5;const w=Math.min(1,r)*0.06*lo;out.x+=p.x/r*w;out.z+=p.z/r*w;}\n  };\n}\nfunction pattyDisp(k){\n  return(p,n,out)=>{\n    const b=fbm3(p.x*5+k,p.y*5,p.z*5+k,3)-0.5,f=noise3(p.x*30,p.y*30+k,p.z*30)-0.5;\n    out.copy(n).multiplyScalar(b*0.05+f*0.012);\n    const r=Math.hypot(p.x,p.z);\n    if(r>1e-4){const a=Math.atan2(p.z,p.x);const e=fbm3(Math.cos(a)*2+11,Math.sin(a)*2+11,k,3)-0.5;const w=Math.pow(r/0.97,3)*0.14*e;out.x+=p.x/r*w;out.z+=p.z/r*w;}\n  };\n}\nfunction bakedPart(ctrl,n,disp,shade,tw,th,extra={}){\n  const {geo,pts}=latheGeo(ctrl,n,SEG,disp);\n  const tex=bakeLathe(pts,tw,th,shade);\n  const mat=new THREE.MeshPhysicalMaterial(Object.assign({map:tex.map,bumpMap:tex.bump,bumpScale:0.014,roughnessMap:tex.rough,roughness:1},extra));\n  return new THREE.Mesh(geo,mat);\n}\n\nconst CROWN=[[0,0],[0.35,0],[0.7,0],[0.95,0],[1.03,0.05],[1.06,0.15],[1.05,0.3],[0.99,0.48],[0.88,0.63],[0.7,0.75],[0.47,0.83],[0.22,0.87],[0,0.88]];\nconst HEEL=[[0,0],[0.3,0],[0.6,0],[0.88,0],[1.0,0.03],[1.04,0.12],[1.04,0.3],[0.99,0.41],[0.9,0.455],[0.6,0.46],[0.3,0.46],[0,0.46]];\nconst CLUB=[[0,0],[0.3,0],[0.6,0],[0.9,0],[1.0,0.04],[1.03,0.14],[1.03,0.26],[1.0,0.36],[0.9,0.4],[0.6,0.4],[0.3,0.4],[0,0.4]];\nconst PATTY=[[0,0],[0.3,0],[0.6,0],[0.85,0],[0.93,0.02],[0.97,0.08],[0.97,0.14],[0.93,0.2],[0.85,0.22],[0.6,0.22],[0.3,0.22],[0,0.22]];\n\n// layer heights\nconst Y_HEEL=0,Y_SAUCE1=0.44,Y_LET1=0.50,Y_CHEESE=0.645,Y_PATTY1=0.66,Y_CLUB=0.86,Y_SAUCE2=1.24,Y_LET2=1.30,Y_PICKLE=1.42,Y_PATTY2=1.44,Y_CROWN=1.66;\n\nconst heel=bakedPart(HEEL,60,bunDisp(0.7),heelShade,640,320,{sheen:0.15,sheenRoughness:0.8,sheenColor:new THREE.Color(0xffe0b0)});heel.position.y=Y_HEEL;burger.add(heel);\nconst club=bakedPart(CLUB,60,bunDisp(2.3),clubShade,640,320);club.position.y=Y_CLUB;burger.add(club);\nconst crown=bakedPart(CROWN,64,bunDisp(4.1),crownShade,768,384,{sheen:0.2,sheenRoughness:0.7,sheenColor:new THREE.Color(0xffe0b0)});crown.position.y=Y_CROWN;burger.add(crown);\nconst patty1=bakedPart(PATTY,50,pattyDisp(1.5),pattyShade,512,256,{bumpScale:0.02});patty1.position.y=Y_PATTY1;burger.add(patty1);\nconst patty2=bakedPart(PATTY,50,pattyDisp(6.2),pattyShade,512,256,{bumpScale:0.02});patty2.position.y=Y_PATTY2;patty2.rotation.y=2.1;burger.add(patty2);\n\n/* ---- sesame seeds */\nburger.updateMatrixWorld(true);\n{\n  const geo=new THREE.SphereGeometry(1,14,10);\n  const mat=new THREE.MeshStandardMaterial({color:0xffffff,roughness:0.42,metalness:0});\n  const N=380;const seeds=new THREE.InstancedMesh(geo,mat,N);\n  const ray=new THREE.Raycaster();const origin=new THREE.Vector3(),dir=new THREE.Vector3(0,-1,0);\n  const up=new THREE.Vector3(0,1,0),q=new THREE.Quaternion(),qy=new THREE.Quaternion(),qt=new THREE.Quaternion();\n  const m=new THREE.Matrix4(),pos=new THREE.Vector3(),scl=new THREE.Vector3(),nrm=new THREE.Vector3(),col=new THREE.Color();\n  const placed=[];let count=0,tries=0;\n  while(count<N&&tries<4000){\n    tries++;\n    const a=rand()*Math.PI*2,r=Math.sqrt(rand())*0.985;\n    const x=Math.cos(a)*r,z=Math.sin(a)*r;\n    let ok=true;for(const s of placed){if((s[0]-x)*(s[0]-x)+(s[1]-z)*(s[1]-z)<0.0052){ok=false;break;}}\n    if(!ok)continue;\n    origin.set(x,5,z);ray.set(origin,dir);\n    const hit=ray.intersectObject(crown,false)[0];if(!hit)continue;\n    nrm.copy(hit.face.normal).transformDirection(crown.matrixWorld).normalize();\n    if(nrm.y<0.05)continue;\n    placed.push([x,z]);\n    pos.copy(hit.point).addScaledVector(nrm,-0.006);\n    q.setFromUnitVectors(up,nrm);\n    qy.setFromAxisAngle(up,rand()*Math.PI*2);\n    qt.setFromAxisAngle(new THREE.Vector3(1,0,0),(rand()-0.5)*0.5);\n    q.multiply(qy).multiply(qt);\n    const s=rr(0.85,1.15);scl.set(0.034*s,0.013*s,0.021*s);\n    m.compose(pos,q,scl);seeds.setMatrixAt(count,m);\n    const t=rand();col.setRGB(lerp(0.97,0.86,t),lerp(0.92,0.74,t),lerp(0.82,0.5,t));\n    if(rand()<0.12)col.multiplyScalar(0.8);\n    seeds.setColorAt(count,col);count++;\n  }\n  seeds.count=count;seeds.instanceMatrix.needsUpdate=true;seeds.instanceColor.needsUpdate=true;\n  seeds.castShadow=true;seeds.receiveShadow=true;burger.add(seeds);\n}\n\n/* ---- lettuce */\nconst letBase=hex(0xa8cd52),letLight=hex(0xe6f0b4);\nconst lettuceMap=genTex(256,(u,v,c)=>{\n  const n=fbm2p(u*6,v*6,6,6,4);\n  const veins=smooth(0.5,0.74,fbm2p(u*3+2,v*9,3,9,3));\n  const cell=noise2p(u*40,v*40,40,40);\n  const t=clamp(veins*0.7+(cell>0.76?0.25:0)+0.18*n-0.08,0,1);\n  c[0]=lerp(letBase[0],letLight[0],t);c[1]=lerp(letBase[1],letLight[1],t);c[2]=lerp(letBase[2],letLight[2],t);\n});\nconst lettuceRough=genTex(128,(u,v,c)=>{const g=0.3+0.35*fbm2p(u*8,v*8,8,8,3);c[0]=c[1]=c[2]=g;},false);\nconst lettuceBump=genTex(256,(u,v,c)=>{const g=0.5*fbm2p(u*10,v*10,10,10,4)+0.5*smooth(0.5,0.74,fbm2p(u*3+2,v*9,3,9,3));c[0]=c[1]=c[2]=g;},false);\nconst lettuceMats=[0xffffff,0xe4f5c8,0xd2e8a6,0xf2f7d8].map(t=>new THREE.MeshPhysicalMaterial({\n  color:t,map:lettuceMap,roughnessMap:lettuceRough,roughness:1,bumpMap:lettuceBump,bumpScale:0.004,\n  side:THREE.DoubleSide,sheen:0.7,sheenRoughness:0.5,sheenColor:new THREE.Color(0xa6e07a)\n}));\nfunction lettucePiece(){\n  const w=rr(0.32,0.5),h=rr(0.22,0.34);\n  const g=new THREE.PlaneGeometry(w,h,14,10);const pos=g.attributes.position;\n  const ph=rand()*10,ph2=rand()*10,f1=rr(8,14),f2=rr(6,11),curl=(rand()-0.5)*0.5,curl2=(rand()-0.5)*0.4;\n  for(let i=0;i<pos.count;i++){\n    const x=pos.getX(i),y=pos.getY(i);const ex=Math.abs(x)/(w*0.5),ey=Math.abs(y)/(h*0.5);\n    let z=0.05*Math.sin(x*f1+ph)*Math.cos(y*f2+ph2)*(0.5+ex)+0.035*(noise3(x*9+ph,y*9,ph2)-0.5);\n    z+=curl*ex*ex+curl2*ey*ey;\n    pos.setZ(i,z);\n  }\n  g.computeVertexNormals();return g;\n}\nconst pieceVariants=[];for(let i=0;i<8;i++)pieceVariants.push(lettucePiece());\nconst BED=[[0,0],[0.4,0],[0.8,0],[0.96,0],[1.0,0.03],[0.99,0.08],[0.9,0.1],[0.5,0.1],[0,0.1]];\nconst yAxis=new THREE.Vector3(0,1,0);\nfunction lettuceLayer(y0,k){\n  const {geo}=latheGeo(BED,30,96,(p,n,out)=>{\n    const r=Math.hypot(p.x,p.z);out.copy(n).multiplyScalar((fbm3(p.x*6+k,p.y*6,p.z*6,3)-0.5)*0.03);\n    if(r>1e-4){const a=Math.atan2(p.z,p.x);const e=fbm3(Math.cos(a)*2.5+k,Math.sin(a)*2.5,3,3)-0.5;const w=Math.pow(r,3)*0.16*e;out.x+=p.x/r*w;out.z+=p.z/r*w;}\n  });\n  const bed=new THREE.Mesh(geo,lettuceMats[2]);bed.position.y=y0;burger.add(bed);\n  const N=72;\n  for(let i=0;i<N;i++){\n    const a=(i/N)*Math.PI*2+rr(-0.1,0.1),r=rr(0.5,1.06);\n    const m=new THREE.Mesh(pieceVariants[Math.floor(rand()*pieceVariants.length)],lettuceMats[Math.floor(rand()*lettuceMats.length)]);\n    const s=rr(0.7,1.25);m.scale.setScalar(s);\n    m.position.set(Math.cos(a)*r,y0+0.05+rand()*0.06,Math.sin(a)*r);\n    m.rotation.set(-Math.PI/2+(rand()-0.5)*0.9,0,(rand()-0.5)*0.6);\n    m.rotateOnWorldAxis(yAxis,rand()*Math.PI*2);\n    const droop=clamp((r-0.78)*3,0,1)*rr(0.35,1.0);\n    const tangent=new THREE.Vector3(-Math.sin(a),0,Math.cos(a));\n    m.rotateOnWorldAxis(tangent,-droop);\n    burger.add(m);\n  }\n}\nlettuceLayer(Y_LET1,1.3);\nlettuceLayer(Y_LET2,7.7);\n\n/* ---- sauce + onions */\nconst sauceBase=hex(0xeaa562),sauceGreen=hex(0x53702a),sauceRed=hex(0x9a3a22);\nconst sauceMap=genTex(256,(u,v,c)=>{\n  const n=fbm2p(u*10,v*10,10,10,3);let r=sauceBase[0]*(0.95+0.1*n),g=sauceBase[1]*(0.95+0.1*n),b=sauceBase[2]*(0.95+0.1*n);\n  const s1=noise2p(u*48+3,v*48,48,48),s2=noise2p(u*56+9,v*56+4,56,56);\n  if(s1>0.8){const t=smooth(0.8,0.86,s1);r=lerp(r,sauceGreen[0],t);g=lerp(g,sauceGreen[1],t);b=lerp(b,sauceGreen[2],t);}\n  if(s2>0.84){const t=smooth(0.84,0.88,s2);r=lerp(r,sauceRed[0],t);g=lerp(g,sauceRed[1],t);b=lerp(b,sauceRed[2],t);}\n  c[0]=r;c[1]=g;c[2]=b;\n});\nconst sauceMat=new THREE.MeshPhysicalMaterial({map:sauceMap,roughness:0.28,clearcoat:0.75,clearcoatRoughness:0.15,sheen:0.2,sheenColor:new THREE.Color(0xffd0a0)});\nconst onionMat=new THREE.MeshPhysicalMaterial({color:0xf7f2e6,roughness:0.28,transmission:0.4,thickness:0.05,ior:1.35,clearcoat:0.3});\nconst onionGeo=new THREE.BoxGeometry(1,1,1);\nconst SAUCE=[[0,0],[0.5,0],[0.9,0],[1.0,0],[1.05,0.03],[1.02,0.07],[0.9,0.08],[0.5,0.08],[0,0.08]];\nconst blobGeo=new THREE.SphereGeometry(1,20,14);\nfunction sauceLayer(y0,k){\n  const {geo}=latheGeo(SAUCE,28,96,(p,n,out)=>{\n    const r=Math.hypot(p.x,p.z);\n    if(r>1e-4){const a=Math.atan2(p.z,p.x);const e=fbm3(Math.cos(a)*3+k,Math.sin(a)*3,k,3)-0.5;const w=Math.pow(r,4)*0.14*e;out.x+=p.x/r*w;out.z+=p.z/r*w;}\n  });\n  const disk=new THREE.Mesh(geo,sauceMat);disk.position.y=y0;burger.add(disk);\n  for(let i=0;i<7;i++){\n    const a=rand()*Math.PI*2,r=rr(1.0,1.06);\n    const b=new THREE.Mesh(blobGeo,sauceMat);\n    b.scale.set(rr(0.08,0.15),rr(0.035,0.06),rr(0.07,0.11));\n    b.position.set(Math.cos(a)*r,y0+0.04,Math.sin(a)*r);b.rotation.y=-a;burger.add(b);\n    if(rand()<0.5){\n      const d=new THREE.Mesh(blobGeo,sauceMat);\n      d.scale.set(rr(0.035,0.055),rr(0.08,0.15),rr(0.03,0.045));\n      d.position.set(Math.cos(a)*1.045,y0-0.05-rand()*0.05,Math.sin(a)*1.045);burger.add(d);\n    }\n  }\n  for(let i=0;i<24;i++){\n    const a=rand()*Math.PI*2,r=rr(0.7,1.04);\n    const o=new THREE.Mesh(onionGeo,onionMat);\n    o.scale.set(rr(0.05,0.09),rr(0.02,0.03),rr(0.04,0.07));\n    o.position.set(Math.cos(a)*r,y0+0.06,Math.sin(a)*r);\n    o.rotation.set((rand()-0.5)*0.6,rand()*Math.PI*2,(rand()-0.5)*0.6);burger.add(o);\n  }\n}\nsauceLayer(Y_SAUCE1,2.2);\nsauceLayer(Y_SAUCE2,9.4);\n\n/* ---- cheese */\n{\n  const cb=hex(0xf4a42c);\n  const cheeseMap=genTex(256,(u,v,c)=>{const n=fbm2p(u*8,v*8,8,8,3);const s=0.94+0.1*n;c[0]=cb[0]*s;c[1]=cb[1]*s;c[2]=cb[2]*s;});\n  const cheeseRough=genTex(128,(u,v,c)=>{const g=0.32+0.25*fbm2p(u*6,v*6,6,6,3);c[0]=c[1]=c[2]=g;},false);\n  const cheeseBump=genTex(256,(u,v,c)=>{const g=fbm2p(u*12,v*12,12,12,4);c[0]=c[1]=c[2]=g;},false);\n  const mat=new THREE.MeshPhysicalMaterial({map:cheeseMap,roughnessMap:cheeseRough,roughness:1,bumpMap:cheeseBump,bumpScale:0.003,clearcoat:0.25,clearcoatRoughness:0.45,sheen:0.3,sheenColor:new THREE.Color(0xffc060)});\n  const g=new THREE.BoxGeometry(1.72,0.035,1.72,26,1,26);const pos=g.attributes.position;\n  for(let i=0;i<pos.count;i++){\n    const x=pos.getX(i),z=pos.getZ(i),y=pos.getY(i);\n    const d=Math.hypot(x,z);const ex=Math.max(0,d-0.86);\n    pos.setY(i,y-ex*ex*1.5+(noise3(x*6+2,z*6,1)-0.5)*0.015);\n  }\n  g.computeVertexNormals();\n  const cheese=new THREE.Mesh(g,mat);cheese.position.y=Y_CHEESE;cheese.rotation.y=0.35;burger.add(cheese);\n}\n\n/* ---- pickles */\n{\n  const s=256,c=document.createElement('canvas');c.width=c.height=s;const g=c.getContext('2d');\n  const rg=g.createRadialGradient(s/2,s/2,4,s/2,s/2,s/2);\n  rg.addColorStop(0,'#c6d05e');rg.addColorStop(0.45,'#a9b942');rg.addColorStop(0.8,'#7d9531');rg.addColorStop(0.95,'#4f6a22');rg.addColorStop(1,'#33471a');\n  g.fillStyle=rg;g.fillRect(0,0,s,s);\n  for(let k=0;k<3;k++){const a=k*Math.PI*2/3+0.4;g.save();g.translate(s/2+Math.cos(a)*s*0.16,s/2+Math.sin(a)*s*0.16);g.rotate(a);g.fillStyle='rgba(218,226,125,0.55)';g.beginPath();g.ellipse(0,0,s*0.16,s*0.09,0,0,Math.PI*2);g.fill();g.restore();}\n  for(let k=0;k<14;k++){const a=rand()*Math.PI*2,r=s*(0.05+rand()*0.22);g.save();g.translate(s/2+Math.cos(a)*r,s/2+Math.sin(a)*r);g.rotate(a+rand()*0.6);g.fillStyle='#ebe5a0';g.beginPath();g.ellipse(0,0,s*0.035,s*0.017,0,0,Math.PI*2);g.fill();g.restore();}\n  for(let k=0;k<320;k++){g.fillStyle=`rgba(40,60,20,${0.1+rand()*0.25})`;g.fillRect(rand()*s,rand()*s,1.5,1.5);}\n  const top=new THREE.CanvasTexture(c);top.colorSpace=THREE.SRGBColorSpace;top.anisotropy=maxAniso;\n  const bump=genTex(128,(u,v,cc)=>{const n=fbm2p(u*10,v*10,10,10,3);cc[0]=cc[1]=cc[2]=n;},false);\n  const topMat=new THREE.MeshPhysicalMaterial({map:top,roughness:0.32,clearcoat:0.6,clearcoatRoughness:0.3,bumpMap:bump,bumpScale:0.004});\n  const sideMat=new THREE.MeshPhysicalMaterial({color:0x4f6b23,roughness:0.45,clearcoat:0.4,clearcoatRoughness:0.4,bumpMap:bump,bumpScale:0.004});\n  const pg=new THREE.CylinderGeometry(0.3,0.3,0.04,48,1);\n  [0.7,2.8,4.9].forEach((a,i)=>{\n    const p=new THREE.Mesh(pg,[sideMat,topMat,topMat]);\n    p.position.set(Math.cos(a)*0.74,Y_PICKLE+0.02,Math.sin(a)*0.74);\n    p.rotation.set((rand()-0.5)*0.15,rand()*Math.PI*2,(rand()-0.5)*0.15);\n    burger.add(p);\n  });\n}\n\nburger.traverse(o=>{if(o.isMesh){o.castShadow=true;o.receiveShadow=true;}});\n\n/* ------------------------------------------------------------------ loop */\naddEventListener('resize',()=>{\n  camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();\n  renderer.setSize(innerWidth,innerHeight);\n});\ndocument.getElementById('info').textContent='drag to orbit · scroll to zoom';\nconst clock=new THREE.Clock();\nrenderer.setAnimationLoop(()=>{\n  const dt=Math.min(clock.getDelta(),0.1);\n  burger.rotation.y+=dt*0.25;\n  controls.update();\n  renderer.render(scene,camera);\n});\n</script>\n</body>\n</html>\n```"
}