/* ============================================================
   Wildlife Kingdom — js/sprites.js
   All original artwork, procedurally drawn on canvas.
   Consistent 45° iso look · soft warm shading · cute shapes.
   Global: WK.Sprites
   ============================================================ */
(function(){
'use strict';
const WK = window.WK;
const HW = WK.HW, HH = WK.HH, TAU = WK.TAU;

const SPR = (WK.Sprites = {});
const cache = new Map();

/* generic cached-sprite factory: drawFn draws with origin at anchor */
function get(key, w, h, ax, ay, drawFn){
  let s = cache.get(key);
  if(!s){
    const cv = WK.mkCanvas(w,h);
    const ctx = cv.getContext('2d');
    ctx.translate(ax,ay);
    drawFn(ctx);
    s = { cv, ax, ay };
    cache.set(key,s);
  }
  return s;
}
SPR.blit = function(ctx, s, sx, sy){
  ctx.drawImage(s.cv, Math.round(sx - s.ax), Math.round(sy - s.ay));
};

/* ============================================================
   GROUND TILES (painted directly into cached terrain layer)
   ============================================================ */
function diamondPath(ctx,cx,cy,inset){
  const hw=HW-(inset||0), hh=HH-(inset||0)*0.5;
  ctx.beginPath();
  ctx.moveTo(cx,cy-hh); ctx.lineTo(cx+hw,cy); ctx.lineTo(cx,cy+hh); ctx.lineTo(cx-hw,cy);
  ctx.closePath();
}
SPR.diamondPath = diamondPath;

SPR.paintGround = function(ctx,cx,cy,gid,h){
  const g = WK.Data.GROUND[gid];
  const c = h<0.5 ? g.colors[0] : g.colors[1];
  diamondPath(ctx,cx,cy,0);
  ctx.fillStyle=c; ctx.fill();
  // speckle texture
  ctx.save(); ctx.clip();
  ctx.globalAlpha=0.35;
  ctx.fillStyle = WK.shade(c, h>0.5?-0.09:0.14);
  const n=5;
  for(let i=0;i<n;i++){
    const hx=WK.hash2(i*7+Math.round(cx),i*13+Math.round(cy),i+3);
    const hy=WK.hash2(i*29,i*17+Math.round(cx),i+9);
    const px=cx+(hx-0.5)*HW*1.2, py=cy+(hy-0.5)*HH*1.2;
    ctx.beginPath();
    if(g.water){ ctx.ellipse(px,py,5,1.6,0,0,TAU); }
    else if(gid==='stone'){ ctx.fillRect(px-3,py-1.5,6,3); }
    else { ctx.ellipse(px,py,2.6,1.2,0,0,TAU); }
    ctx.fill();
  }
  ctx.restore();
};

/* path overlay inset on top of ground */
SPR.paintPath = function(ctx,cx,cy,pid,h){
  const p = WK.Data.PATH[pid];
  ctx.save();
  diamondPath(ctx,cx,cy,4);
  ctx.fillStyle=p.base; ctx.fill();
  ctx.strokeStyle='rgba(80,55,20,.18)'; ctx.lineWidth=1.5; ctx.stroke();
  ctx.clip();
  ctx.fillStyle=p.dark; ctx.globalAlpha=.75;
  for(let i=0;i<4;i++){
    const hx=WK.hash2(Math.round(cx)+i*31,i*11,5), hy=WK.hash2(i*7,Math.round(cy)+i*13,8);
    const px=cx+(hx-0.5)*(HW-14), py=cy+(hy-0.5)*(HH-8);
    if(pid==='wood'){ ctx.fillRect(px-7,py-1.4,14,2.4); }
    else if(pid==='stonep'){ ctx.beginPath(); ctx.ellipse(px,py,5,2.4,0,0,TAU); ctx.fill(); }
    else { ctx.beginPath(); ctx.arc(px,py,1.6,0,TAU); ctx.fill(); }
  }
  ctx.restore();
};

/* ============================================================
   NATURE OBJECTS
   ============================================================ */
const objDraw = {
  tree(ctx){
    WK.softShadow(ctx,10,'rgba(50,90,20,.3)',3);
    ctx.fillStyle='#8a5a34';
    WK.rr(ctx,-5,-38,10,38,4); ctx.fill(); WK.clearShadow(ctx);
    const leaf=(dx,dy,r,c)=>{WK.blob(ctx,dx,dy,r,r*.85,c);};
    leaf(-16,-46,22,'#57a83e'); leaf(16,-46,22,'#57a83e');
    leaf(0,-66,26,'#63b34c'); leaf(0,-44,24,'#6fc457');
    ctx.fillStyle='rgba(255,255,255,.25)';
    ctx.beginPath(); ctx.ellipse(-8,-72,9,5,-.5,0,TAU); ctx.fill();
  },
  bush(ctx){
    WK.blob(ctx,-8,-9,12,9,'#5aa73c'); WK.blob(ctx,9,-8,11,8,'#63b34c'); WK.blob(ctx,0,-14,13,10,'#6fc457');
    ctx.fillStyle='#e86a8a'; ctx.beginPath(); ctx.arc(-4,-14,2.2,0,TAU); ctx.arc(7,-10,2.2,0,TAU); ctx.fill();
  },
  flower(ctx){
    const cols=['#ff8aa8','#ffd23c','#ff9d47','#b78ae8'];
    for(let i=0;i<5;i++){
      const a=i/5*TAU, r=i%2?9:15;
      const x=Math.cos(a)*r, y=-4+Math.sin(a)*r*.45;
      ctx.strokeStyle='#4d9a37'; ctx.lineWidth=1.6;
      ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(x,y-7); ctx.stroke();
      ctx.fillStyle=cols[i%4];
      for(let p=0;p<5;p++){ const pa=p/5*TAU; ctx.beginPath(); ctx.arc(x+Math.cos(pa)*3,y-7+Math.sin(pa)*3,2.4,0,TAU); ctx.fill(); }
      ctx.fillStyle='#fff3c2'; ctx.beginPath(); ctx.arc(x,y-7,1.8,0,TAU); ctx.fill();
    }
  },
  rock(ctx){
    WK.softShadow(ctx,8,'rgba(50,40,20,.3)',3);
    WK.blob(ctx,-7,-8,14,10,'#9a958c');
    WK.clearShadow(ctx);
    WK.blob(ctx,7,-10,11,9,'#aaa49a');
    ctx.fillStyle='rgba(255,255,255,.3)';
    ctx.beginPath(); ctx.ellipse(-9,-14,5,2.5,-.4,0,TAU); ctx.fill();
  },
  palm(ctx){
    WK.softShadow(ctx,9,'rgba(50,90,20,.28)',3);
    ctx.strokeStyle='#a9743f'; ctx.lineWidth=7; ctx.lineCap='round';
    ctx.beginPath(); ctx.moveTo(0,0); ctx.quadraticCurveTo(6,-24,2,-44); ctx.stroke();
    WK.clearShadow(ctx);
    const frond=(a,len)=>{
      ctx.strokeStyle='#4da339'; ctx.lineWidth=6; ctx.lineCap='round';
      ctx.beginPath(); ctx.moveTo(2,-44);
      ctx.quadraticCurveTo(2+Math.cos(a)*len*.6,-44+Math.sin(a)*len*.6-4, 2+Math.cos(a)*len,-42+Math.sin(a)*len*.8);
      ctx.stroke();
    };
    frond(-Math.PI*.9,26); frond(-Math.PI*.5,28); frond(-Math.PI*.1,26);
    frond(-Math.PI*.7,22); frond(-Math.PI*.3,24);
    ctx.fillStyle='#8a5a34'; ctx.beginPath(); ctx.arc(6,-40,3,0,TAU); ctx.fill();
  },
  bamboo(ctx){
    for(let i=-1;i<=1;i++){
      const x=i*8, h=44-Math.abs(i)*8;
      ctx.strokeStyle=i===0?'#6db84a':'#5aa73c'; ctx.lineWidth=5; ctx.lineCap='round';
      ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x+i*2,-h); ctx.stroke();
      ctx.strokeStyle='rgba(60,110,40,.5)'; ctx.lineWidth=1.4;
      for(let k=1;k<4;k++){ const yy=-h*k/4; ctx.beginPath(); ctx.moveTo(x-2.6,yy); ctx.lineTo(x+2.6,yy); ctx.stroke(); }
      ctx.strokeStyle='#4d9a37'; ctx.lineWidth=2;
      ctx.beginPath(); ctx.moveTo(x,-h*.7); ctx.lineTo(x+9,-h*.82); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(x,-h*.55); ctx.lineTo(x-8,-h*.68); ctx.stroke();
    }
  },
  bench(ctx){
    WK.softShadow(ctx,7,'rgba(50,40,20,.28)',3);
    ctx.fillStyle='#a9743f'; WK.rr(ctx,-14,-12,28,5,2); ctx.fill();
    WK.rr(ctx,-14,-19,28,4,2); ctx.fill();
    ctx.fillStyle='#8a5a34';
    ctx.fillRect(-13,-8,3,8); ctx.fillRect(10,-8,3,8);
    ctx.fillRect(-13,-19,3,7); ctx.fillRect(10,-19,3,7);
    WK.clearShadow(ctx);
    ctx.strokeStyle='#c98d52'; ctx.lineWidth=1;
    ctx.beginPath(); ctx.moveTo(-10,-17.5); ctx.lineTo(10,-17.5); ctx.stroke();
  },
  bin(ctx){
    WK.softShadow(ctx,6,'rgba(50,40,20,.28)',3);
    WK.blob(ctx,0,-9,8,9,'#63b34c');
    WK.clearShadow(ctx);
    ctx.fillStyle='#3e8a33'; WK.rr(ctx,-9,-20,18,5,2); ctx.fill();
    ctx.fillStyle='#fff'; ctx.font='bold 9px sans-serif'; ctx.textAlign='center';
    ctx.fillText('♻',0,-12);
  },
  lamp(ctx){
    WK.softShadow(ctx,6,'rgba(50,40,20,.28)',3);
    ctx.fillStyle='#5f6b72'; ctx.fillRect(-2,-46,4,46);
    WK.clearShadow(ctx);
    WK.blob(ctx,0,-50,7,8,'#ffe066');
    ctx.fillStyle='#5f6b72'; ctx.beginPath(); ctx.arc(0,-56,4.5,Math.PI,0); ctx.fill();
  },
};
SPR.getObject = function(id, variant){
  variant = variant||0;
  return get('obj:'+id+':'+variant, 120,130, 60,122, ctx=>{
    ctx.save();
    if(variant===1) ctx.scale(.88,.88);
    if(variant===2){ ctx.scale(1.08,1.08); }
    if(variant===1) ctx.rotate(-.03);
    objDraw[id](ctx);
    ctx.restore();
  });
};
SPR.objectRealSize = { tree:1.15, palm:1.1, bamboo:.95 }; // visual scale multipliers

/* ============================================================
   FENCE EDGES (drawn dynamically)
   ============================================================ */
SPR.drawFenceEdge = function(ctx, x1,y1, x2,y2, kind, t){
  // posts at ends, rails between; hedge = green blobs
  if(kind===2){ // hedge
    ctx.strokeStyle='#4d9a37'; ctx.lineCap='round'; ctx.lineWidth=9;
    ctx.beginPath(); ctx.moveTo(x1,y1-6); ctx.lineTo(x2,y2-6); ctx.stroke();
    ctx.strokeStyle='#63b34c'; ctx.lineWidth=6;
    ctx.beginPath(); ctx.moveTo(x1,y1-9); ctx.lineTo(x2,y2-9); ctx.stroke();
    return;
  }
  const wood = kind===1;
  const postC = wood?'#a9743f':'#8f9aa2', railC = wood?'#c98d52':'#aab4bc';
  const nx=(y2-y1), ny=-(x2-x1); const L=Math.hypot(nx,ny)||1;
  const px=nx/L*2.6, py=ny/L*2.6; // perpendicular offset so rails show thickness
  ctx.strokeStyle=railC; ctx.lineWidth=3; ctx.lineCap='round';
  for(const dy of [-5,-11]){
    ctx.beginPath(); ctx.moveTo(x1,y1+dy); ctx.lineTo(x2,y2+dy); ctx.stroke();
  }
  if(kind===3){ // gate: crossbar + sign
    ctx.strokeStyle=postC; ctx.lineWidth=2.4;
    ctx.beginPath(); ctx.moveTo(x1,y1-3); ctx.lineTo(x2,y2-13); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(x1,y1-13); ctx.lineTo(x2,y2-3); ctx.stroke();
    ctx.fillStyle='#ffd23c';
    ctx.save(); ctx.translate((x1+x2)/2,(y1+y2)/2-9);
    WK.rr(ctx,-6,-5,12,10,3); ctx.fill();
    ctx.fillStyle='#7a5210'; ctx.fillRect(-1.2,-3,2.4,6); ctx.fillRect(-3,-1.2,6,2.4);
    ctx.restore();
  } else {
    ctx.strokeStyle=railC; ctx.lineWidth=2;
    ctx.beginPath(); ctx.moveTo(x1+px,y1-2+py); ctx.lineTo(x2+px,y2-2+py); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(x1-px,y1-2-py); ctx.lineTo(x2-px,y2-2-py); ctx.stroke();
  }
  for(const [ex,ey] of [[x1,y1],[x2,y2]]){
    ctx.fillStyle=postC;
    WK.rr(ctx,ex-2.4,ey-16,4.8,16,2); ctx.fill();
    ctx.fillStyle=WK.shade(postC,-.2);
    ctx.beginPath(); ctx.arc(ex,ey-16,2.6,Math.PI,0); ctx.fill();
  }
};

/* ============================================================
   BUILDINGS
   ============================================================ */
/* Footprint corner math: tile (i,j) center = ((i-j)*HW,(i+j)*HH).
   Returns the 4 base-diamond corners relative to footprint center. */
function fcorners(w,h){
  const P=(i,j)=>[(i-j)*HW,(i+j)*HH];
  const c=P(w/2,h/2);
  const rel=(p)=>[p[0]-c[0],p[1]-c[1]];
  return { N:rel(P(0,0)), E:rel(P(w,0)), S:rel(P(w,h)), W:rel(P(0,h)) };
}
function poly(ctx,pts,fill){
  ctx.beginPath(); ctx.moveTo(pts[0][0],pts[0][1]);
  for(let i=1;i<pts.length;i++) ctx.lineTo(pts[i][0],pts[i][1]);
  ctx.closePath(); ctx.fillStyle=fill; ctx.fill();
}
function isoPrism(ctx,C,w,h,z,colT,colL,colR){
  const k=fcorners(w,h);
  const N=[k.N[0]+C[0],k.N[1]+C[1]-z], E=[k.E[0]+C[0],k.E[1]+C[1]-z],
        S=[k.S[0]+C[0],k.S[1]+C[1]-z], W=[k.W[0]+C[0],k.W[1]+C[1]-z];
  // walls
  poly(ctx,[W,S,[k.S[0]+C[0],k.S[1]+C[1]],[k.W[0]+C[0],k.W[1]+C[1]]],colL);
  poly(ctx,[S,E,[k.E[0]+C[0],k.E[1]+C[1]],[k.S[0]+C[0],k.S[1]+C[1]]],colR);
  // subtle wall shading line at corner
  ctx.strokeStyle='rgba(60,40,15,.18)'; ctx.lineWidth=1.4;
  ctx.beginPath(); ctx.moveTo(S[0],S[1]); ctx.lineTo(k.S[0]+C[0],k.S[1]+C[1]); ctx.stroke();
  // roof/top
  poly(ctx,[N,E,S,W],colT);
  ctx.strokeStyle='rgba(60,40,15,.14)';
  ctx.beginPath(); ctx.moveTo(N[0],N[1]); ctx.lineTo(E[0],E[1]); ctx.lineTo(S[0],S[1]); ctx.lineTo(W[0],W[1]); ctx.closePath(); ctx.stroke();
  return {N,E,S,W};
}
function doorOnRightFace(ctx,C,w,h,z,colD){
  const k=fcorners(w,h);
  // right face runs S->E ; door near middle of that face
  const mx=C[0]+(k.S[0]+k.E[0])/2, my=C[1]-z+(k.S[1]+k.E[1])/2;
  const dw=Math.min(16,6+w*2), dh=Math.min(22,8+h*3);
  ctx.fillStyle=colD||'#6b4a2e';
  ctx.beginPath();
  ctx.moveTo(mx-dw/2,my); ctx.lineTo(mx-dw/2,my-dh+dw/2);
  ctx.arc(mx,my-dh+dw/2,dw/2,Math.PI,0);
  ctx.lineTo(mx+dw/2,my); ctx.closePath(); ctx.fill();
  ctx.fillStyle='rgba(255,236,180,.85)';
  ctx.beginPath(); ctx.arc(mx,my-dh+dw/2,dw/4,Math.PI,0); ctx.fill();
  return [mx,my];
}
function windowsOnLeftFace(ctx,C,w,h,z,n){
  const k=fcorners(w,h);
  for(let i=1;i<=n;i++){
    const tt=i/(n+1);
    const x=C[0]+k.W[0]+(k.S[0]-k.W[0])*tt, y=C[1]-z+(k.W[1]+(k.S[1]-k.W[1])*tt);
    ctx.fillStyle='rgba(255,255,255,.5)';
    WK.rr(ctx,x-5,y-14,10,11,3); ctx.fill();
    ctx.strokeStyle='rgba(90,60,30,.35)'; ctx.lineWidth=1.2;
    WK.rr(ctx,x-5,y-14,10,11,3); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(x-5,y-8.5); ctx.lineTo(x+5,y-8.5); ctx.stroke();
  }
}
function signPlaque(ctx,x,y,color){
  ctx.fillStyle=color; ctx.beginPath(); ctx.arc(x,y,7,0,TAU); ctx.fill();
  ctx.strokeStyle='rgba(255,255,255,.8)'; ctx.lineWidth=1.6;
  ctx.beginPath(); ctx.arc(x,y,4.4,0,TAU); ctx.stroke();
}

const roofDraw = {
  flat(ctx,C,w,h,z,col){
    const k=fcorners(w,h);
    const pts=[k.N,k.E,k.S,k.W].map(p=>[p[0]+C[0],p[1]+C[1]-z]);
    poly(ctx,pts.map(p=>[p[0]*0.92+C[0]*0.08, p[1]*0.92+(C[1]-z)*0.08-3]),WK.shade(col,-.12));
    ctx.strokeStyle=WK.shade(col,-.3); ctx.lineWidth=2;
    ctx.beginPath(); ctx.moveTo(pts[0][0],pts[0][1]);
    pts.slice(1).forEach(p=>ctx.lineTo(p[0],p[1])); ctx.closePath(); ctx.stroke();
    ctx.fillStyle=WK.shade(col,.15);
    ctx.fillRect(C[0]+k.E[0]*.4-6, C[1]-z+k.E[1]*.4-14, 12,7); // vent
  },
  gable(ctx,C,w,h,z,col){
    const k=fcorners(w,h);
    const ridgeY=z+16;
    const Rw=[k.W[0]+C[0], k.W[1]+C[1]-ridgeY], Re=[k.E[0]+C[0], k.E[1]+C[1]-ridgeY];
    // left slope
    poly(ctx,[[k.W[0]+C[0],k.W[1]+C[1]-z],[k.N[0]+C[0],k.N[1]+C[1]-z],Re,Rw],WK.shade(col,.18));
    // right slope
    poly(ctx,[[k.N[0]+C[0],k.N[1]+C[1]-z],[k.E[0]+C[0],k.E[1]+C[1]-z],Re],WK.shade(col,-.05));
    poly(ctx,[[k.E[0]+C[0],k.E[1]+C[1]-z],[k.S[0]+C[0],k.S[1]+C[1]-z],Rw,Re],col);
    ctx.strokeStyle=WK.shade(col,-.32); ctx.lineWidth=1.6;
    ctx.beginPath(); ctx.moveTo(Rw[0],Rw[1]); ctx.lineTo(Re[0],Re[1]); ctx.stroke();
  },
  slant(ctx,C,w,h,z,col){
    const k=fcorners(w,h);
    const high=z+20;
    poly(ctx,[[k.W[0]+C[0],k.W[1]+C[1]-high],[k.N[0]+C[0],k.N[1]+C[1]-high*.4],[k.E[0]+C[0],k.E[1]+C[1]-high*.4],[k.S[0]+C[0],k.S[1]+C[1]-z]],col);
    poly(ctx,[[k.N[0]+C[0],k.N[1]+C[1]-high*.4],[k.E[0]+C[0],k.E[1]+C[1]-high*.4],[k.S[0]+C[0],k.S[1]+C[1]-z],[k.S[0]+C[0],k.S[1]+C[1]-z]],WK.shade(col,-.15));
    ctx.strokeStyle=WK.shade(col,-.28); ctx.lineWidth=1.5;
    for(let i=1;i<w;i++){
      const tt=i/w;
      ctx.beginPath();
      ctx.moveTo(C[0]+k.W[0]*(1-tt), C[1]-high*(1-tt)+k.W[1]*0 );
      ctx.stroke();
    }
  },
  coneSwirl(ctx,C,w,h,z,col){ // ice cream stand: giant soft-serve
    const x=C[0], y=C[1]-z;
    ctx.fillStyle='#f7e2b8';
    ctx.beginPath(); ctx.moveTo(x-11,y+2); ctx.lineTo(x+11,y+2); ctx.lineTo(x,y-20); ctx.closePath(); ctx.fill();
    ctx.strokeStyle='#d9b97e'; ctx.lineWidth=1.2;
    for(let i=0;i<3;i++){ ctx.beginPath(); ctx.moveTo(x-9+i*3,y-2-i*4); ctx.lineTo(x+9-i*3,y-2-i*4); ctx.stroke(); }
    WK.blob(ctx,x,y-24,10,7,col);
    WK.blob(ctx,x,y-31,7.5,5.5,WK.shade(col,.12));
    WK.blob(ctx,x,y-37,5,4,WK.shade(col,.22));
    ctx.fillStyle='#e8624e'; ctx.beginPath(); ctx.arc(x+2,y-42,2.4,0,TAU); ctx.fill(); // cherry
  },
  dome(ctx,C,w,h,z,col){
    const x=C[0], y=C[1]-z;
    ctx.fillStyle=col;
    ctx.beginPath(); ctx.ellipse(x,y,(w+h)*HH*0.62,(w+h)*HH*0.52,0,Math.PI,0); ctx.closePath(); ctx.fill();
    ctx.fillStyle='rgba(255,255,255,.35)';
    ctx.beginPath(); ctx.ellipse(x-((w+h)*HH)*.18,y-((w+h)*HH)*.2,(w+h)*HH*.16,(w+h)*HH*.2,-.5,0,TAU); ctx.fill();
    ctx.strokeStyle='#eef6fb'; ctx.lineWidth=1.4;
    for(let i=1;i<3;i++){
      ctx.beginPath(); ctx.ellipse(x,y,(w+h)*HH*0.62*i/3,(w+h)*HH*0.52*i/3,0,Math.PI,0); ctx.stroke();
    }
  },
  mesh(ctx,C,w,h,z,col){ // aviary dome with struts + birds
    roofDraw.dome(ctx,C,w,h,z,col);
    const x=C[0], y=C[1]-z, R=(w+h)*HH*0.62;
    ctx.strokeStyle='rgba(70,90,100,.55)'; ctx.lineWidth=1;
    for(let i=-2;i<=2;i++){
      ctx.beginPath(); ctx.ellipse(x+i*R/2.6,y,R*.18,R*.92,0,Math.PI,0); ctx.stroke();
    }
    for(const dy of [.35,.65]){
      ctx.beginPath(); ctx.ellipse(x,y-R*dy,R*(1-dy*.5),R*.16,0,Math.PI,0); ctx.stroke();
    }
    ctx.fillStyle='#e86a8a';
    ctx.beginPath(); ctx.arc(x-R*.4,y-R*.5,2.6,0,TAU); ctx.fill();
    ctx.fillStyle='#69c3ef';
    ctx.beginPath(); ctx.arc(x+R*.3,y-R*.65,2.6,0,TAU); ctx.fill();
  },
  rock(ctx,C,w,h,z,col){ // reptile house: boulder roof
    const x=C[0], y=C[1]-z;
    WK.blob(ctx,x-((w)*HW)*.3,y-4,(w+h)*HH*.42,(w+h)*HH*.3,col);
    WK.blob(ctx,x+((w)*HW)*.25,y-8,(w+h)*HH*.36,(w+h)*HH*.27,WK.shade(col,.1));
    ctx.fillStyle='rgba(255,255,255,.22)';
    ctx.beginPath(); ctx.ellipse(x-((w)*HW)*.34,y-((w+h)*HH)*.16,(w+h)*HH*.14,(w+h)*HH*.07,-.4,0,TAU); ctx.fill();
  },
  wave(ctx,C,w,h,z,col){ // aquarium
    roofDraw.dome(ctx,C,w,h,z,col);
    const x=C[0], y=C[1]-z, R=(w+h)*HH*0.62;
    ctx.strokeStyle='rgba(255,255,255,.85)'; ctx.lineWidth=2.2; ctx.lineCap='round';
    for(let i=0;i<3;i++){
      ctx.beginPath();
      ctx.moveTo(x-R*.7+i*R*.3, y-R*.28-i*3);
      ctx.quadraticCurveTo(x-R*.45+i*R*.3, y-R*.5-i*3, x-R*.2+i*R*.3, y-R*.28-i*3);
      ctx.stroke();
    }
    ctx.fillStyle='#ffd23c';
    ctx.beginPath(); ctx.ellipse(x+R*.35,y-R*.4,4,2.6,-.4,0,TAU); ctx.fill();
    ctx.beginPath(); ctx.moveTo(x+R*.35+4,y-R*.4); ctx.lineTo(x+R*.35+7,y-R*.4-2.6); ctx.lineTo(x+R*.35+7,y-R*.4+2.6); ctx.closePath(); ctx.fill();
  },
  awning(ctx,C,w,h,z,col){
    roofDraw.flat(ctx,C,w,h,z,WK.shade(col,-.25));
    const k=fcorners(w,h);
    const fx=k.S[0]+C[0], fy=k.S[1]+C[1];
    const lx=k.W[0]+C[0], ly=k.W[1]+C[1];
    const rx=k.E[0]+C[0], ry=k.E[1]+C[1];
    // scalloped awning across the front corner
    const steps=6;
    for(let i=0;i<steps;i++){
      const t0=i/steps,t1=(i+1)/steps;
      const ax=lx+(rx-lx)*t0, ay=ly-z*.35+(ry-ly)*t0-14;
      const bx=lx+(rx-lx)*t1, by=ly-z*.35+(ry-ly)*t1-14;
      poly(ctx,[[ax,ay],[bx,by],[bx,by+6],[ax,ay+6]], i%2? col : '#fffdf4');
    }
  },
  archway(ctx,C,w,h,z,col,big){
    const k=fcorners(w,h);
    // two pillar prisms at front corners + banner between
    const pw=Math.max(1,w*.16), ph=h*.55;
    const pz=z+26;
    const p1=fcorners(pw,ph), p2=fcorners(pw,ph);
    const P1=[k.W[0]+C[0],k.W[1]+C[1]], P2=[k.E[0]+C[0],k.E[1]+C[1]];
    const drawPillar=(cx,cy)=>{
      const q=fcorners(pw,ph);
      const N=[q.N[0]+cx,q.N[1]+cy-pz],E=[q.E[0]+cx,q.E[1]+cy-pz],S=[q.S[0]+cx,q.S[1]+cy-pz],W2=[q.W[0]+cx,q.W[1]+cy-pz];
      poly(ctx,[W2,S,[q.S[0]+cx,q.S[1]+cy],[q.W[0]+cx,q.W[1]+cy]],'#caa763');
      poly(ctx,[S,E,[q.E[0]+cx,q.E[1]+cy],[q.S[0]+cx,q.S[1]+cy]],'#b8924e');
      poly(ctx,[N,E,S,W2],'#e8cf96');
    };
    drawPillar(P1[0],P1[1]); drawPillar(P2[0],P2[1]);
    // banner
    const bx1=P1[0], by1=P1[1]-pz+4, bx2=P2[0], by2=P2[1]-pz+4;
    poly(ctx,[[bx1,by1-14],[bx2,by2-14],[bx2,by2+8],[bx1,by1+8]],col);
    ctx.fillStyle='rgba(255,255,255,.9)';
    ctx.font='bold '+Math.min(13,6+w)+'px sans-serif'; ctx.textAlign='center';
    ctx.fillText('ZOO',(bx1+bx2)/2,(by1+by2)/2+5);
    // scallop flags under banner
    const n=big?7:5;
    for(let i=0;i<n;i++){
      const t=(i+.5)/n;
      const fx=bx1+(bx2-bx1)*t, fy=by1+(by2-by1)*t+8;
      ctx.fillStyle=['#e8624e','#ffd23c','#63b34c','#69c3ef'][i%4];
      ctx.beginPath(); ctx.moveTo(fx-4,fy); ctx.lineTo(fx+4,fy); ctx.lineTo(fx,fy+7); ctx.closePath(); ctx.fill();
    }
    if(big){
      // pennant pole
      ctx.strokeStyle='#8a5a34'; ctx.lineWidth=2.4;
      ctx.beginPath(); ctx.moveTo(C[0],C[1]-z-2); ctx.lineTo(C[0],C[1]-z-34); ctx.stroke();
      ctx.fillStyle='#ffd23c';
      ctx.beginPath(); ctx.moveTo(C[0],C[1]-z-34); ctx.lineTo(C[0]+14,C[1]-z-29); ctx.lineTo(C[0],C[1]-z-24); ctx.closePath(); ctx.fill();
    }
  },
};

const bldExtras = {
  hut(ctx,C,w,h,z){ windowsOnLeftFace(ctx,C,w,h,z,1); },
  restroom(ctx,C,w,h,z){ doorOnRightFace(ctx,C,w,h,z,'#4d7f96'); signPlaque(ctx,C[0]+fcorners(w,h).E[0]*.55,C[1]-z-6,'#4f9fd0'); },
  burger(ctx,C,w,h,z){
    const x=C[0],y=C[1]-z;
    // burger sign on roof
    WK.blob(ctx,x,y-10,11,7,'#e8a24e');
    ctx.fillStyle='#63b34c'; WK.rr(ctx,x-10,y-6,20,3,1.5); ctx.fill();
    ctx.fillStyle='#e8624e'; ctx.beginPath(); ctx.ellipse(x,y-13,10,3,0,Math.PI,true); ctx.fill();
    ctx.fillStyle='#fff3c2'; ctx.beginPath(); ctx.ellipse(x,y-15.5,9,3.4,0,Math.PI,true); ctx.fill();
    doorOnRightFace(ctx,C,w,h,z,'#8a5a34');
  },
  icecream(){}, // cone roof covers it
  gift(ctx,C,w,h,z){
    const x=C[0],y=C[1]-z-8;
    ctx.fillStyle='#e8624e'; WK.rr(ctx,x-8,y-8,16,14,2); ctx.fill();
    ctx.fillStyle='#ffd23c'; ctx.fillRect(x-2,y-8,4,14); ctx.fillRect(x-8,y-3,16,4);
    ctx.fillStyle='#ffb63c';
    ctx.beginPath(); ctx.arc(x-3,y-10,3,0,TAU); ctx.arc(x+3,y-10,3,0,TAU); ctx.fill();
    doorOnRightFace(ctx,C,w,h,z,'#8a5a34');
  },
  visitor(ctx,C,w,h,z){ windowsOnLeftFace(ctx,C,w,h,z,2); doorOnRightFace(ctx,C,w,h,z,'#8a5a34');
    // clock
    const x=C[0]+fcorners(w,h).N[0]*0.2, y=C[1]-z-20;
    ctx.fillStyle='#fffdf4'; ctx.beginPath(); ctx.arc(x,y,6,0,TAU); ctx.fill();
    ctx.strokeStyle='#43362a'; ctx.lineWidth=1.2;
    ctx.beginPath(); ctx.moveTo(x,y); ctx.lineTo(x,y-4); ctx.moveTo(x,y); ctx.lineTo(x+3,y); ctx.stroke();
  },
  staff(ctx,C,w,h,z){ doorOnRightFace(ctx,C,w,h,z,'#6b6257'); windowsOnLeftFace(ctx,C,w,h,z,1); },
  research(ctx,C,w,h,z){
    windowsOnLeftFace(ctx,C,w,h,z,2);
    const x=C[0]+fcorners(w,h).E[0]*.3, y=C[1]-z;
    ctx.strokeStyle='#7a8a94'; ctx.lineWidth=2;
    ctx.beginPath(); ctx.moveTo(x,y-6); ctx.lineTo(x,y-26); ctx.stroke();
    ctx.fillStyle='#e8624e'; ctx.beginPath(); ctx.arc(x,y-28,2.6,0,TAU); ctx.fill();
    signPlaque(ctx,C[0]+fcorners(w,h).E[0]*.6,C[1]-z+2,'#69c3ef');
  },
  vet(ctx,C,w,h,z){
    doorOnRightFace(ctx,C,w,h,z,'#b85454');
    const k=fcorners(w,h);
    const x=C[0]+(k.S[0]+k.E[0])/2, y=C[1]-z+(k.S[1]+k.E[1])/2-16;
    ctx.fillStyle='#e8624e';
    WK.rr(ctx,x-2.5,y-8,5,16,2); ctx.fill(); WK.rr(ctx,x-8,y-2.5,16,5,2); ctx.fill();
  },
  restaurant(ctx,C,w,h,z){
    windowsOnLeftFace(ctx,C,w,h,z,2); doorOnRightFace(ctx,C,w,h,z,'#7a4a8a');
    // umbrella table beside door
    const ux=C[0]+fcorners(w,h).E[0]*.72, uy=C[1]+fcorners(w,h).E[1]*.72;
    ctx.strokeStyle='#8a5a34'; ctx.lineWidth=2;
    ctx.beginPath(); ctx.moveTo(ux,uy-4); ctx.lineTo(ux,uy-20); ctx.stroke();
    ctx.fillStyle='#f2b3d0';
    ctx.beginPath(); ctx.arc(ux,uy-20,9,Math.PI,0); ctx.fill();
    ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(ux,uy-20,9,Math.PI,Math.PI*1.2); ctx.lineTo(ux,uy-20); ctx.closePath(); ctx.fill();
  },
  aviary(ctx,C,w,h,z){ doorOnRightFace(ctx,C,w,h,z,'#5f7d8a'); },
  reptile(ctx,C,w,h,z){ doorOnRightFace(ctx,C,w,h,z,'#5a6e46'); signPlaque(ctx,C[0]+fcorners(w,h).E[0]*.5,C[1]-z+4,'#7f9166'); },
  aquarium(ctx,C,w,h,z){
    doorOnRightFace(ctx,C,w,h,z,'#3d7fa8');
    windowsOnLeftFace(ctx,C,w,h,z,3);
  },
  entrance_s(ctx,C,w,h,z){},
  entrance_g(ctx,C,w,h,z){},
};

SPR.getBuilding = function(id){
  const b = WK.Data.BUILDING[id];
  const w=b.w, h=b.h;
  const zBase = 18 + Math.max(w,h)*4;
  const styleH = { archway: b.big?64:52, gable:26, slant:30, dome:34, mesh:40, rock:24, wave:40, awning:16, coneSwirl:48, flat:10 }[b.roof.style] || 16;
  const H = (w+h)*HH + zBase + styleH + 46;
  const Wd = (w+h)*HW + 60;
  return get('bld:'+id, Wd, H, Wd/2, H-26, ctx=>{
    const C=[0,-24]; // lift so shadow fits
    // ground shadow
    const k=fcorners(w,h);
    ctx.fillStyle='rgba(60,45,15,.22)';
    ctx.beginPath();
    ctx.moveTo(k.N[0]+C[0],k.N[1]+C[1]+3); ctx.lineTo(k.E[0]+C[0],k.E[1]+C[1]+3);
    ctx.lineTo(k.S[0]+C[0],k.S[1]+C[1]+3); ctx.lineTo(k.W[0]+C[0],k.W[1]+C[1]+3);
    ctx.closePath(); ctx.fill();
    // doormat
    ctx.fillStyle='rgba(120,90,50,.35)';
    ctx.beginPath(); ctx.ellipse(k.S[0]+C[0],k.S[1]+C[1]+2,14,5,0,0,TAU); ctx.fill();
    const z=zBase;
    const tops=isoPrism(ctx,C,w,h,z,b.wall[0],b.wall[1],b.wall[2]);
    const st=b.roof.style;
    if(st==='cone') roofDraw.coneSwirl(ctx,C,w,h,z,b.roof.color);
    else if(st==='archway') roofDraw.archway(ctx,C,w,h,z,b.roof.color,b.big);
    else if(st==='awning'){ roofDraw.awning(ctx,C,w,h,z,b.roof.color); }
    else roofDraw[st] && roofDraw[st](ctx,C,w,h,z,b.roof.color);
    bldExtras[id] && bldExtras[id](ctx,C,w,h,z);
  });
};

/* ============================================================
   PEOPLE (guests / staff) — drawn live
   ============================================================ */
SPR.drawPerson = function(ctx, o){
  // o: {phase, state:'walk'|'stand'|'sit'|'photo'|'feed', shirt, pants, skin, kid, balloon, tool}
  const s=o.kid?0.68:1;
  ctx.save();
  ctx.scale(o.face<0?-s:s, s);
  // shadow
  ctx.fillStyle='rgba(60,45,15,.25)';
  ctx.beginPath(); ctx.ellipse(0,1,7,3,0,0,TAU); ctx.fill();
  const ph=o.phase||0;
  const moving=o.state==='walk';
  const l1=moving?Math.sin(ph)*3.4:0, l2=moving?-Math.sin(ph)*3.4:0;
  const bob=moving?Math.abs(Math.sin(ph))*1.6:Math.sin(ph*0.4)*0.6;
  if(o.state==='sit'){
    // sitting on ground level offset handled by caller
    ctx.fillStyle=o.pants;
    WK.rr(ctx,-5,-8,10,7,3); ctx.fill();
  }else{
    ctx.strokeStyle=o.pants; ctx.lineWidth=3.4; ctx.lineCap='round';
    ctx.beginPath(); ctx.moveTo(-1.5,-8); ctx.lineTo(-1.5+l1*.6,-1); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(1.5,-8); ctx.lineTo(1.5+l2*.6,-1); ctx.stroke();
  }
  // body
  WK.blob(ctx,0,-13-bob,6,7,o.shirt);
  // arms
  ctx.strokeStyle=o.shirt; ctx.lineWidth=2.8;
  if(o.state==='photo'){
    ctx.beginPath(); ctx.moveTo(-4,-16-bob); ctx.lineTo(-7,-24-bob); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(4,-16-bob); ctx.lineTo(7,-24-bob); ctx.stroke();
    ctx.fillStyle='#43362a'; WK.rr(ctx,-4,-28-bob,8,5,1.5); ctx.fill();
    ctx.fillStyle='#9fd8f5'; ctx.fillRect(-3,-27.2-bob,6,3.4);
  }else if(o.state==='feed'||o.state==='point'){
    ctx.beginPath(); ctx.moveTo(-4,-15-bob); ctx.lineTo(-7,-11-bob); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(4,-15-bob); ctx.lineTo(9,-19-bob); ctx.stroke();
  }else{
    const a1=moving?Math.sin(ph)*3:1, a2=moving?-Math.sin(ph)*3:-1;
    ctx.beginPath(); ctx.moveTo(-4.6,-15-bob); ctx.lineTo(-4.6+a1*.8,-9-bob); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(4.6,-15-bob); ctx.lineTo(4.6+a2*.8,-9-bob); ctx.stroke();
  }
  // head
  WK.blob(ctx,0,-23-bob,5.4,5.2,o.skin);
  // hair cap
  ctx.fillStyle=o.hair||'#5a3c22';
  ctx.beginPath(); ctx.arc(0,-24.5-bob,5.2,Math.PI*1.02,Math.PI*1.98); ctx.fill();
  ctx.beginPath(); ctx.ellipse(0,-25.5-bob,5.2,2.6,0,Math.PI,0); ctx.fill();
  // face
  ctx.fillStyle='#33261a';
  ctx.beginPath(); ctx.arc(1.8,-22.6-bob,0.9,0,TAU); ctx.fill();
  if(o.smile){ WK.mouth(ctx,2.2,-20.8-bob,1.6,o.state==='photo'?0.4:0.15); }
  else { ctx.strokeStyle='#7c4a3a'; ctx.lineWidth=.9; ctx.beginPath(); ctx.moveTo(1.2,-20.6-bob); ctx.lineTo(3.2,-21.2-bob); ctx.stroke(); }
  // balloon
  if(o.balloon){
    ctx.strokeStyle='rgba(70,50,30,.6)'; ctx.lineWidth=1;
    ctx.beginPath(); ctx.moveTo(5,-18-bob);
    ctx.quadraticCurveTo(8,-26-bob, 6+Math.sin(ph*.7)*1.5,-33-bob); ctx.stroke();
    WK.blob(ctx,6+Math.sin(ph*.7)*1.5,-36-bob,4.6,5.4,o.balloon);
  }
  ctx.restore();
};

/* ============================================================
   ANIMALS — 15 species × poses, drawn live
   pose: {state:'idle'|'walk'|'eat'|'happy', ph, t}
   Facing handled by caller flipping ctx. Feet at (0,0).
   ============================================================ */
function legs4(ctx,color,ph,len,spread,moving){
  ctx.strokeStyle=color; ctx.lineCap='round';
  const lw=len*0.34;
  ctx.lineWidth=lw;
  const o=moving?Math.sin(ph)*len*0.28:0;
  // back pair then front pair
  for(const [x,off] of [[-spread,o],[-spread+lw*0.8,-o*0.7],[spread-lw*0.8,o*0.7],[spread,-o]]){
    ctx.beginPath(); ctx.moveTo(x,-len*0.4); ctx.lineTo(x+off*0.5,0); ctx.stroke();
  }
}
function happyFx(ctx,t,r){
  // little bounce sparkles
  for(let i=0;i<3;i++){
    const a=t*3+i*2.1;
    ctx.fillStyle=i%2?'rgba(255,210,60,.9)':'rgba(255,140,170,.9)';
    ctx.beginPath(); ctx.arc(Math.cos(a)*r*0.8,-r*1.15+Math.sin(a)*4,1.8,0,TAU); ctx.fill();
  }
}

const ANIM = {
  lion(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*7))*5:Math.sin(p.t*1.8)*0.8);
    legs4(ctx,'#d99c46',p.ph,10,9,mv);
    ctx.save(); ctx.translate(0,bob);
    WK.blob(ctx,0,-14,17,11,'#e8ac54');
    WK.blob(ctx,0,-10,12,6,'#f5cd8a'); // belly
    // mane
    WK.blob(ctx,13,-19,10.5,10,'#b3701e');
    WK.blob(ctx,13,-19,7.6,7.4,'#c98a2e');
    // tail
    ctx.strokeStyle='#b3701e'; ctx.lineWidth=3; ctx.lineCap='round';
    ctx.beginPath(); ctx.moveTo(-16,-16);
    ctx.quadraticCurveTo(-24,-18,-24,-24+Math.sin(p.t*2.4)*2); ctx.stroke();
    ctx.fillStyle='#7a4a10'; ctx.beginPath(); ctx.arc(-24,-25+Math.sin(p.t*2.4)*2,2.6,0,TAU); ctx.fill();
    // face
    WK.eye(ctx,11,-21,2.6,{lookY:p.state==='eat'?0.5:0});
    WK.eye(ctx,16.5,-21,2.6,{lookY:p.state==='eat'?0.5:0});
    WK.mouth(ctx,14,-16.5,3,p.state==='eat'?(0.3+0.3*Math.sin(p.t*9)):hp?0.6:0.12);
    WK.blush(ctx,9,-17.5,2.4,'#ff9d8a',.3);
    ctx.restore();
    if(hp) happyFx(ctx,p.t,14);
  },
  tiger(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*7))*5:Math.sin(p.t*1.8)*0.8);
    legs4(ctx,'#e8823c',p.ph,10,9,mv);
    ctx.save(); ctx.translate(0,bob);
    WK.blob(ctx,0,-14,17,11,'#f08c3e');
    WK.blob(ctx,0,-9.5,12,5.5,'#fbe3c2');
    // stripes
    ctx.strokeStyle='#43362a'; ctx.lineWidth=2.6; ctx.lineCap='round';
    for(let i=-2;i<=2;i++){
      ctx.beginPath(); ctx.moveTo(i*5.4,-23.5); ctx.quadraticCurveTo(i*5.4+2,-18,i*5.4,-13.5); ctx.stroke();
    }
    // ears
    ctx.fillStyle='#f08c3e';
    ctx.beginPath(); ctx.arc(8,-24,3.4,0,TAU); ctx.arc(18,-24,3.4,0,TAU); ctx.fill();
    ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(8,-24,1.6,0,TAU); ctx.arc(18,-24,1.6,0,TAU); ctx.fill();
    // tail
    ctx.strokeStyle='#f08c3e'; ctx.lineWidth=3.4;
    ctx.beginPath(); ctx.moveTo(-16,-15); ctx.quadraticCurveTo(-24,-16,-25,-23+Math.sin(p.t*2.6)*2); ctx.stroke();
    ctx.strokeStyle='#43362a'; ctx.lineWidth=3.4; ctx.setLineDash([3,4]);
    ctx.beginPath(); ctx.moveTo(-25,-23+Math.sin(p.t*2.6)*2); ctx.lineTo(-26,-27); ctx.stroke();
    ctx.setLineDash([]);
    WK.eye(ctx,10.5,-20,2.6,{}); WK.eye(ctx,16,-20,2.6,{});
    WK.mouth(ctx,13.5,-15.5,3,p.state==='eat'?(0.3+0.3*Math.sin(p.t*9)):hp?0.6:0.12);
    WK.blush(ctx,8,-16.8,2.4,'#ff9d8a',.28);
    ctx.restore();
    if(hp) happyFx(ctx,p.t,14);
  },
  elephant(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*6))*4:Math.sin(p.t*1.6)*0.8);
    legs4(ctx,'#9aa7b5',p.ph,11,10,mv);
    ctx.save(); ctx.translate(0,bob);
    WK.blob(ctx,0,-16,19,13,'#aab8c6');
    // ear
    const flap=Math.sin(p.t*2.2)*2;
    WK.blob(ctx,4+flap*.3,-18,8.5,10,'#93a2b2');
    // trunk
    ctx.strokeStyle='#aab8c6'; ctx.lineWidth=6.5; ctx.lineCap='round';
    const sw=p.state==='eat'?Math.sin(p.t*8)*3:Math.sin(p.t*1.9)*2.5;
    ctx.beginPath(); ctx.moveTo(17,-14);
    ctx.quadraticCurveTo(25+sw*.4,-12,25+sw,-4+(p.state==='eat'?4:0)); ctx.stroke();
    // tusk
    ctx.strokeStyle='#fdf6e7'; ctx.lineWidth=2.6;
    ctx.beginPath(); ctx.arc(20,-9,4,-.4,1.2); ctx.stroke();
    // tail
    ctx.strokeStyle='#93a2b2'; ctx.lineWidth=3;
    ctx.beginPath(); ctx.moveTo(-18,-16); ctx.quadraticCurveTo(-23,-13,-22,-8); ctx.stroke();
    // eye
    WK.eye(ctx,14,-19,2.6,{});
    WK.blush(ctx,11,-13,2.6,'#ff9d8a',.25);
    ctx.restore();
    if(hp){ happyFx(ctx,p.t,16); }
  },
  giraffe(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*6))*4:Math.sin(p.t*1.7)*0.8);
    legs4(ctx,'#e8b64e',p.ph,14,7,mv);
    ctx.save(); ctx.translate(0,bob);
    WK.blob(ctx,0,-19,14,9.5,'#f0c05c');
    // neck+head (dips when eating)
    const dip=p.state==='eat'?14:0;
    ctx.strokeStyle='#f0c05c'; ctx.lineWidth=8; ctx.lineCap='round';
    ctx.beginPath(); ctx.moveTo(8,-24);
    ctx.quadraticCurveTo(17,-34-dip*.4, 20+dip*.6,-42-dip); ctx.stroke();
    WK.blob(ctx,21+dip*.7,-44-dip,7,5.4,'#f0c05c');
    // ossicones
    ctx.strokeStyle='#c98a2e'; ctx.lineWidth=2.2;
    ctx.beginPath(); ctx.moveTo(19+dip*.7,-49-dip); ctx.lineTo(18+dip*.7,-53-dip); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(24+dip*.7,-49-dip); ctx.lineTo(24.5+dip*.7,-53-dip); ctx.stroke();
    ctx.fillStyle='#8a5a34';
    ctx.beginPath(); ctx.arc(18+dip*.7,-53.5-dip,1.7,0,TAU); ctx.arc(24.5+dip*.7,-53.5-dip,1.7,0,TAU); ctx.fill();
    // patches
    ctx.fillStyle='#b3701e';
    const patches=[[ -6,-22],[2,-16],[-2,-25],[6,-19],[-9,-15]];
    for(const [px,py] of patches){ ctx.beginPath(); ctx.ellipse(px,py,2.8,2.2,.4,0,TAU); ctx.fill(); }
    ctx.fillStyle='#b3701e';
    ctx.beginPath(); ctx.ellipse(22+dip*.7,-43-dip,2.2,1.8,.3,0,TAU); ctx.fill();
    WK.eye(ctx,24+dip*.7,-45.5-dip,2.2,{});
    // tail
    ctx.strokeStyle='#e8b64e'; ctx.lineWidth=2.4;
    ctx.beginPath(); ctx.moveTo(-13,-20); ctx.quadraticCurveTo(-18,-18,-19,-13); ctx.stroke();
    ctx.restore();
    if(hp) happyFx(ctx,p.t,16);
  },
  zebra(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*7))*4:Math.sin(p.t*1.8)*0.8);
    legs4(ctx,'#eef2f5',p.ph,10,9,mv);
    ctx.save(); ctx.translate(0,bob);
    WK.blob(ctx,0,-14,16.5,10.5,'#f2f5f8');
    // bold stripes
    ctx.strokeStyle='#2e2a26'; ctx.lineWidth=2.8; ctx.lineCap='round';
    for(let i=-2;i<=2;i++){
      ctx.beginPath(); ctx.moveTo(i*5.2,-23); ctx.quadraticCurveTo(i*5.2+2.5,-17.5,i*5.2+1,-11.5); ctx.stroke();
    }
    // mane
    ctx.fillStyle='#2e2a26';
    ctx.beginPath(); ctx.ellipse(9,-24,4,3,-.4,0,TAU); ctx.fill();
    // muzzle
    WK.blob(ctx,16,-16.5,4.4,3.6,'#cfd6dc');
    ctx.fillStyle='#2e2a26';
    ctx.beginPath(); ctx.arc(17.5,-16,1.2,0,TAU); ctx.fill();
    WK.eye(ctx,11,-20,2.5,{});
    // tail swish
    ctx.strokeStyle='#2e2a26'; ctx.lineWidth=2.4;
    ctx.beginPath(); ctx.moveTo(-15.5,-16); ctx.quadraticCurveTo(-21,-14+Math.sin(p.t*3)*2,-22,-9); ctx.stroke();
    WK.mouth(ctx,16,-13.8,2.4,hp?0.5:(p.state==='eat'?0.35:0.1));
    ctx.restore();
    if(hp) happyFx(ctx,p.t,13);
  },
  gorilla(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2.4:(hp?-Math.abs(Math.sin(p.t*6))*5:Math.sin(p.t*1.5)*0.9);
    ctx.save(); ctx.translate(0,bob);
    // legs
    ctx.strokeStyle='#4a4448'; ctx.lineWidth=6; ctx.lineCap='round';
    const o=mv?Math.sin(p.ph)*3:0;
    ctx.beginPath(); ctx.moveTo(-5,-9); ctx.lineTo(-6+o*.5,0); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(6,-9); ctx.lineTo(7-o*.5,0); ctx.stroke();
    // body
    WK.blob(ctx,0,-17,15,13,'#5a5358');
    WK.blob(ctx,2,-15,9.5,8.5,'#7d7378'); // chest
    // arms (long)
    ctx.strokeStyle='#4a4448'; ctx.lineWidth=6.5;
    if(hp){
      const beat=Math.sin(p.t*14)>0?1:0;
      ctx.beginPath(); ctx.moveTo(-10,-22); ctx.lineTo(-14-beat*4,-34-beat*6); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(11,-22); ctx.lineTo(15+beat*4,-34-beat*6); ctx.stroke();
    }else{
      const a=mv?Math.sin(p.ph)*3:0;
      ctx.beginPath(); ctx.moveTo(-11,-20); ctx.lineTo(-13+a*.5,-2); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(12,-20); ctx.lineTo(14-a*.5,-2); ctx.stroke();
    }
    // head
    WK.blob(ctx,4,-32,7.5,6.8,'#5a5358');
    WK.blob(ctx,4,-29.5,4.6,3.6,'#8a7470'); // face plate
    WK.eye(ctx,2,-31,1.9,{}); WK.eye(ctx,7,-31,1.9,{});
    WK.mouth(ctx,4.5,-27,2.6,hp?0.6:0.1);
    ctx.restore();
    if(hp) happyFx(ctx,p.t,16);
  },
  panda(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const eating=p.state==='eat';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*6))*4:Math.sin(p.t*1.4)*1);
    ctx.save(); ctx.translate(0,bob);
    if(eating){
      // sitting panda munching bamboo
      WK.blob(ctx,0,-12,14,11,'#f5f8fa');
      // bamboo stalk
      ctx.strokeStyle='#6db84a'; ctx.lineWidth=3.4; ctx.lineCap='round';
      ctx.beginPath(); ctx.moveTo(12,-2); ctx.lineTo(15,-26); ctx.stroke();
      ctx.strokeStyle='#4d9a37'; ctx.lineWidth=2;
      ctx.beginPath(); ctx.moveTo(14,-14); ctx.lineTo(22,-18); ctx.stroke();
      // paws holding
      WK.blob(ctx,10,-10,4,3.6,'#3a3438');
      // head
      WK.blob(ctx,4,-22,9.5,8.5,'#f5f8fa');
      ctx.fillStyle='#3a3438';
      ctx.beginPath(); ctx.arc(-1,-23,3,0,TAU); ctx.fill(); // ear
      ctx.beginPath(); ctx.arc(9,-23,3,0,TAU); ctx.fill();
      ctx.beginPath(); ctx.ellipse(1.4,-21.5,2.8,3.6,.3,0,TAU); ctx.fill();
      ctx.beginPath(); ctx.ellipse(7.4,-21.5,2.8,3.6,-.3,0,TAU); ctx.fill();
      WK.eye(ctx,1.4,-21.5,1.5,{}); WK.eye(ctx,7.4,-21.5,1.5,{});
      WK.mouth(ctx,4.5,-17.5,2.6,0.25+0.25*Math.sin(p.t*9));
    }else{
      legs4(ctx,'#3a3438',p.ph,9,8,mv);
      WK.blob(ctx,0,-13,15,11,'#f5f8fa');
      ctx.strokeStyle='#3a3438'; ctx.lineWidth=5.5; ctx.lineCap='round';
      ctx.beginPath(); ctx.moveTo(10,-18); ctx.lineTo(15,-8); ctx.stroke();
      // head
      WK.blob(ctx,8,-24,9,8.2,'#f5f8fa');
      ctx.fillStyle='#3a3438';
      ctx.beginPath(); ctx.arc(3,-26,3,0,TAU); ctx.fill();
      ctx.beginPath(); ctx.arc(13,-26,3,0,TAU); ctx.fill();
      ctx.beginPath(); ctx.ellipse(6,-24,2.7,3.4,.3,0,TAU); ctx.fill();
      ctx.beginPath(); ctx.ellipse(11.4,-24,2.7,3.4,-.3,0,TAU); ctx.fill();
      WK.eye(ctx,6,-24,1.5,{}); WK.eye(ctx,11.4,-24,1.5,{});
      WK.mouth(ctx,8.6,-20,2.4,hp?0.5:0.1);
      if(hp) happyFx(ctx,p.t,14);
    }
    ctx.restore();
  },
  penguin(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const waddle=mv?Math.sin(p.ph)*0.16:0;
    ctx.save(); ctx.rotate(waddle);
    const bob=mv?Math.abs(Math.cos(p.ph))*1.6:(hp?-Math.abs(Math.sin(p.t*7))*5:Math.sin(p.t*1.8)*0.7);
    ctx.translate(0,bob);
    // feet
    ctx.fillStyle='#f0a24a';
    ctx.beginPath(); ctx.ellipse(-3,0,3.4,1.8,0,0,TAU); ctx.ellipse(3,0,3.4,1.8,0,0,TAU); ctx.fill();
    // body egg
    WK.blob(ctx,0,-13,9.5,13,'#3a4650');
    WK.blob(ctx,0,-10.5,6.4,9,'#f5f8fa'); // belly
    // flippers
    const fl=hp?-14:(mv?Math.sin(p.ph)*3:Math.sin(p.t*2)*1.5);
    ctx.strokeStyle='#3a4650'; ctx.lineWidth=4.4; ctx.lineCap='round';
    ctx.beginPath(); ctx.moveTo(-8,-15); ctx.quadraticCurveTo(-13,-10+fl*.4,-11,-4+fl*.6); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(8,-15); ctx.quadraticCurveTo(13,-10-fl*.4,11,-4-fl*.6); ctx.stroke();
    // face
    WK.eye(ctx,-3,-20,2.2,{}); WK.eye(ctx,4,-20,2.2,{});
    WK.blush(ctx,-6,-16.5,2,'#ffb0c0',.4); WK.blush(ctx,7,-16.5,2,'#ffb0c0',.4);
    // beak
    ctx.fillStyle='#f0a24a';
    ctx.beginPath(); ctx.moveTo(-2,-17); ctx.lineTo(3.6,-17); ctx.lineTo(0.8,-13.6); ctx.closePath(); ctx.fill();
    ctx.restore();
    if(hp) happyFx(ctx,p.t,11);
  },
  polarbear(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2.2:(hp?-Math.abs(Math.sin(p.t*5.5))*4:Math.sin(p.t*1.5)*0.9);
    legs4(ctx,'#f2f6f9',p.ph,11,10,mv);
    ctx.save(); ctx.translate(0,bob);
    WK.blob(ctx,0,-16,19,12.5,'#f7fafc');
    // head
    WK.blob(ctx,14,-20,8.5,7.4,'#f7fafc');
    ctx.fillStyle='#f7fafc';
    ctx.beginPath(); ctx.arc(9,-26,3,0,TAU); ctx.arc(18,-26,3,0,TAU); ctx.fill(); // ears
    ctx.fillStyle='#c8d4dc';
    ctx.beginPath(); ctx.arc(9,-26,1.4,0,TAU); ctx.arc(18,-26,1.4,0,TAU); ctx.fill();
    ctx.fillStyle='#3a4650';
    ctx.beginPath(); ctx.ellipse(20,-19.5,2,1.5,0,0,TAU); ctx.fill(); // nose
    WK.eye(ctx,12.5,-21,2.2,{}); WK.eye(ctx,17,-21,2.2,{});
    WK.mouth(ctx,18,-16.5,2.6,hp?0.5:(p.state==='eat'?0.35:0.1));
    ctx.restore();
    if(hp) happyFx(ctx,p.t,15);
  },
  monkey(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*8))*6:Math.sin(p.t*2.2)*1);
    ctx.save(); ctx.translate(0,bob);
    // curly tail
    ctx.strokeStyle='#8a5a34'; ctx.lineWidth=2.6; ctx.lineCap='round';
    const tx=-10, ty=-14;
    ctx.beginPath(); ctx.moveTo(tx,ty);
    ctx.quadraticCurveTo(tx-9,ty-2,tx-9,ty-8+Math.sin(p.t*3)*2);
    ctx.quadraticCurveTo(tx-9,ty-13,tx-5,ty-12); ctx.stroke();
    // legs
    ctx.strokeStyle='#8a5a34'; ctx.lineWidth=3.6;
    const o=mv?Math.sin(p.ph)*3:0;
    ctx.beginPath(); ctx.moveTo(-4,-8); ctx.lineTo(-5+o*.5,0); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(4,-8); ctx.lineTo(5-o*.5,0); ctx.stroke();
    // body
    WK.blob(ctx,0,-13,8.5,8,'#a9743f');
    WK.blob(ctx,1,-12,4.6,4.6,'#e8c49a');
    // arms
    ctx.strokeStyle='#8a5a34'; ctx.lineWidth=3.4;
    if(hp){
      const wav=Math.sin(p.t*12)*3;
      ctx.beginPath(); ctx.moveTo(-6,-17); ctx.lineTo(-11,-25-wav); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(7,-17); ctx.lineTo(12,-25+wav); ctx.stroke();
    }else{
      ctx.beginPath(); ctx.moveTo(-7,-15); ctx.lineTo(-9,-6); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(7,-15); ctx.lineTo(9,-6); ctx.stroke();
    }
    // head
    WK.blob(ctx,2,-25,7.4,6.8,'#a9743f');
    WK.blob(ctx,2.6,-24,4.6,4.2,'#e8c49a');
    // ears
    ctx.fillStyle='#a9743f';
    ctx.beginPath(); ctx.arc(-5,-25,2.6,0,TAU); ctx.arc(9,-25,2.6,0,TAU); ctx.fill();
    ctx.fillStyle='#e8c49a';
    ctx.beginPath(); ctx.arc(-5,-25,1.2,0,TAU); ctx.arc(9,-25,1.2,0,TAU); ctx.fill();
    WK.eye(ctx,0.6,-25,1.8,{}); WK.eye(ctx,5.2,-25,1.8,{});
    WK.mouth(ctx,3,-21.5,2.2,hp?0.6:(p.state==='eat'?0.4:0.15));
    ctx.restore();
    if(hp) happyFx(ctx,p.t,12);
  },
  deer(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*7))*5:Math.sin(p.t*1.8)*0.8);
    legs4(ctx,'#c99552',p.ph,11,7,mv);
    ctx.save(); ctx.translate(0,bob);
    WK.blob(ctx,0,-15,12.5,8.5,'#d4a260');
    ctx.fillStyle='#faf3e4';
    ctx.beginPath(); ctx.ellipse(-8,-13,4.5,5,0,0,TAU); ctx.fill(); // rump patch
    // neck & head
    const dip=p.state==='eat'?12:0;
    ctx.strokeStyle='#d4a260'; ctx.lineWidth=6; ctx.lineCap='round';
    ctx.beginPath(); ctx.moveTo(7,-20); ctx.quadraticCurveTo(12,-26-dip*.5,13+dip*.5,-31-dip); ctx.stroke();
    WK.blob(ctx,14+dip*.5,-32-dip,6,4.6,'#d4a260');
    // antlers
    ctx.strokeStyle='#8a5a34'; ctx.lineWidth=2;
    ctx.beginPath(); ctx.moveTo(12+dip*.5,-36-dip); ctx.lineTo(10+dip*.5,-42-dip); ctx.moveTo(10.6+dip*.5,-39.5-dip); ctx.lineTo(8+dip*.5,-41-dip); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(16+dip*.5,-36-dip); ctx.lineTo(17.5+dip*.5,-42-dip); ctx.moveTo(16.9+dip*.5,-39.5-dip); ctx.lineTo(19+dip*.5,-41-dip); ctx.stroke();
    // ear
    ctx.fillStyle='#d4a260';
    ctx.beginPath(); ctx.ellipse(9+dip*.5,-34-dip,3,1.6,-.5,0,TAU); ctx.fill();
    ctx.fillStyle='#3a2c20';
    ctx.beginPath(); ctx.arc(17+dip*.5,-32.5-dip,1.2,0,TAU); ctx.fill();
    WK.eye(ctx,14.6+dip*.5,-32.5-dip,1.9,{});
    ctx.fillStyle='#faf3e4';
    ctx.beginPath(); ctx.arc(-4,-19,1.6,0,TAU); ctx.arc(1,-18,1.6,0,TAU); ctx.arc(6,-19.5,1.6,0,TAU); ctx.fill();
    ctx.restore();
    if(hp) happyFx(ctx,p.t,14);
  },
  rhino(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*5))*3.5:Math.sin(p.t*1.5)*0.8);
    legs4(ctx,'#a8b2ba',p.ph,10,10,mv);
    ctx.save(); ctx.translate(0,bob);
    WK.blob(ctx,0,-15,18,12,'#b4bec6');
    // armor plates hint
    ctx.strokeStyle='rgba(90,105,115,.4)'; ctx.lineWidth=1.6;
    for(let i=-1;i<=1;i++){ ctx.beginPath(); ctx.arc(i*7-2,-15,9,-.7,.7); ctx.stroke(); }
    // horn
    ctx.fillStyle='#e8e2d4';
    ctx.beginPath(); ctx.moveTo(16,-22); ctx.quadraticCurveTo(21,-30,22,-31); ctx.quadraticCurveTo(21,-26,19,-21); ctx.closePath(); ctx.fill();
    // head mass
    WK.blob(ctx,14,-17,8,7,'#b4bec6');
    // ears
    ctx.fillStyle='#b4bec6';
    ctx.beginPath(); ctx.ellipse(9,-24,2.6,3.4,-.3,0,TAU); ctx.ellipse(17,-24,2.6,3.4,.3,0,TAU); ctx.fill();
    WK.eye(ctx,12,-19,2.1,{});
    WK.mouth(ctx,17,-12.5,3,hp?0.5:0.1);
    ctx.restore();
    if(hp) happyFx(ctx,p.t,15);
  },
  hippo(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*2:(hp?-Math.abs(Math.sin(p.t*5.5))*3.5:Math.sin(p.t*1.6)*0.9);
    legs4(ctx,'#9a8bab',p.ph,9,11,mv);
    ctx.save(); ctx.translate(0,bob);
    WK.blob(ctx,0,-13,19,11.5,'#a89ab8');
    // huge mouth
    const open=p.state==='eat'?0.9:(hp?0.7:0.08);
    WK.blob(ctx,14,-13,9,8,'#a89ab8');
    WK.mouth(ctx,15,-9.5,5.5,open,'#7c5a74');
    // nostrils
    ctx.fillStyle='#7c5a74';
    ctx.beginPath(); ctx.arc(17,-19,1.3,0,TAU); ctx.arc(20.5,-18,1.3,0,TAU); ctx.fill();
    // tiny ears
    ctx.fillStyle='#a89ab8';
    ctx.beginPath(); ctx.arc(9,-22,2,0,TAU); ctx.arc(15,-23,2,0,TAU); ctx.fill();
    WK.eye(ctx,11,-18,2,{}); WK.eye(ctx,16,-18,2,{});
    WK.blush(ctx,8,-12,2.4,'#e8a0b8',.35);
    ctx.restore();
    if(hp) happyFx(ctx,p.t,15);
  },
  flamingo(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=mv?Math.abs(Math.sin(p.ph))*1.8:(hp?-Math.abs(Math.sin(p.t*7))*5:Math.sin(p.t*2)*1);
    ctx.save(); ctx.translate(0,bob);
    // standing leg (one lifted when idle!)
    ctx.strokeStyle='#f0a24a'; ctx.lineWidth=2.2; ctx.lineCap='round';
    const lifted=!mv&&!hp;
    ctx.beginPath(); ctx.moveTo(0,-12); 
    if(lifted) ctx.quadraticCurveTo(-3,-6,-5,-3); else ctx.lineTo(mv?Math.sin(p.ph)*2:0,0);
    ctx.stroke();
    if(!lifted){ ctx.fillStyle='#f0a24a'; ctx.beginPath(); ctx.ellipse(mv?Math.sin(p.ph)*2:0,0,3,1.4,0,0,TAU); ctx.fill(); }
    if(mv){ ctx.beginPath(); ctx.moveTo(0,-12); ctx.lineTo(-Math.sin(p.ph)*3,-3); ctx.stroke(); }
    // body
    WK.blob(ctx,0,-17,10.5,7.5,'#f48fb1');
    // wing
    WK.blob(ctx,-2,-17,6.5,4.4,'#ec7ba2');
    // tail feathers
    ctx.strokeStyle='#ec7ba2'; ctx.lineWidth=3;
    ctx.beginPath(); ctx.moveTo(-9,-18); ctx.quadraticCurveTo(-14,-19,-15,-15+Math.sin(p.t*3)); ctx.stroke();
    // S-neck
    const dip=p.state==='eat'?10:0;
    ctx.strokeStyle='#f48fb1'; ctx.lineWidth=3.4; ctx.lineCap='round';
    ctx.beginPath(); ctx.moveTo(6,-20);
    ctx.bezierCurveTo(12,-26,12,-30-dip,8,-36-dip);
    ctx.stroke();
    // head
    WK.blob(ctx,7.4,-38-dip,4,3.4,'#f48fb1');
    // beak
    ctx.fillStyle='#3a3438';
    ctx.beginPath(); ctx.moveTo(10.4,-38-dip); ctx.quadraticCurveTo(15,-37.4-dip,14,-34.6-dip); ctx.quadraticCurveTo(12,-36-dip,9.6,-36.4-dip); ctx.closePath(); ctx.fill();
    ctx.fillStyle='#f0a24a'; ctx.fillRect(9.6,-37.4-dip,2.4,1.6);
    WK.eye(ctx,8,-39-dip,1.7,{});
    ctx.restore();
    if(hp) happyFx(ctx,p.t,11);
  },
  crocodile(ctx,p){
    const mv=p.state==='walk', hp=p.state==='happy';
    const bob=Math.sin(p.t*1.6)*(mv?1.2:0.5);
    ctx.save(); ctx.translate(0,bob);
    // tail
    ctx.strokeStyle='#5a8a46'; ctx.lineWidth=5; ctx.lineCap='round';
    const wag=mv?Math.sin(p.ph)*4:Math.sin(p.t*2)*2;
    ctx.beginPath(); ctx.moveTo(-14,-7); ctx.quadraticCurveTo(-24,-7+wag*.4,-30,-3+wag); ctx.stroke();
    // body low & long
    WK.blob(ctx,0,-7.5,16,6,'#63a04c');
    // scutes
    ctx.fillStyle='#4d8a3a';
    for(let i=-2;i<=1;i++){ ctx.beginPath(); ctx.moveTo(i*6-2,-12); ctx.lineTo(i*6+2,-12); ctx.lineTo(i*6,-15.5); ctx.closePath(); ctx.fill(); }
    // stubby legs
    ctx.strokeStyle='#4d8a3a'; ctx.lineWidth=3.4;
    const o=mv?Math.sin(p.ph)*2.5:0;
    ctx.beginPath(); ctx.moveTo(-9,-5); ctx.lineTo(-9+o,0); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(8,-5); ctx.lineTo(8-o,0); ctx.stroke();
    // snout
    const open=p.state==='eat'?0.7:(hp?0.45:0);
    WK.blob(ctx,17,-8,9,4.4,'#63a04c');
    if(open>0.1){
      ctx.save(); ctx.translate(20,-8.5); ctx.rotate(-open*.5);
      WK.blob(ctx,4,0,7,2.6,'#63a04c'); ctx.restore();
      ctx.save(); ctx.translate(20,-7); ctx.rotate(open*.4);
      WK.blob(ctx,4,1,7,2,'#578f42'); ctx.restore();
    }else{
      ctx.strokeStyle='#3a5c2c'; ctx.lineWidth=1;
      for(let i=0;i<3;i++){ ctx.beginPath(); ctx.moveTo(13+i*4,-5.4); ctx.lineTo(15+i*4,-5.4); ctx.stroke(); }
    }
    ctx.fillStyle='#ffd23c';
    ctx.beginPath(); ctx.arc(14,-10.5,1.5,0,TAU); ctx.fill();
    ctx.fillStyle='#3a3438'; ctx.beginPath(); ctx.arc(14,-10.5,0.7,0,TAU); ctx.fill();
    ctx.restore();
    if(hp) happyFx(ctx,p.t,14);
  },
};

/* swimmer clipping: caller may clip to waterline before calling */
SPR.drawAnimal = function(ctx, speciesId, pose){
  const fn = ANIM[speciesId==='hippo'?'hippo':speciesId] || ANIM.deer;
  fn(ctx,pose);
};
SPR.hasSpecies = id=> !!ANIM[id];

/* ============================================================
   UI ICONS
   ============================================================ */
const iconBg = { ground:'#dcedc2', path:'#efe0bd', nature:'#dff0d0', animal:'#ffe3d0', facility:'#dbe9f5', res:'#fff3c2' };

SPR.getIcon = function(kind, size){
  size=size||52;
  return get('icon:'+kind+':'+size, size, size, size/2, size/2, ctx=>{
    const s=size/52;
    ctx.scale(s,s);
    const [cat,id]=kind.split(':');
    if(cat==='res'){
      ctx.fillStyle='#fff3c2'; ctx.beginPath(); ctx.arc(0,0,24,0,TAU); ctx.fill();
      if(id==='coin'){
        WK.blob(ctx,0,0,13,13,'#ffc93c');
        ctx.strokeStyle='#d99a1e'; ctx.lineWidth=2.4; ctx.beginPath(); ctx.arc(0,0,9.4,0,TAU); ctx.stroke();
        // paw print
        ctx.fillStyle='#d99a1e';
        ctx.beginPath(); ctx.ellipse(0,3,3.4,2.8,0,0,TAU); ctx.fill();
        for(const [px,py] of [[-4,-1],[-1.6,-3.6],[1.6,-3.6],[4,-1]]){ ctx.beginPath(); ctx.arc(px,py,1.7,0,TAU); ctx.fill(); }
      }else if(id==='guest'){
        WK.blob(ctx,0,-4,8,8,'#f0c8a0');
        ctx.fillStyle='#7a4a2a'; ctx.beginPath(); ctx.arc(0,-6.5,8,Math.PI,0); ctx.fill();
        WK.blob(ctx,0,10,11,7,'#63b34c');
      }else if(id==='heart'){
        ctx.fillStyle='#ff6b81';
        ctx.beginPath();
        ctx.moveTo(0,9); ctx.bezierCurveTo(-16,-2,-9,-15,0,-6); ctx.bezierCurveTo(9,-15,16,-2,0,9);
        ctx.fill();
      }else if(id==='flask'){
        ctx.strokeStyle='#7a8a94'; ctx.lineWidth=3;
        ctx.beginPath(); ctx.moveTo(-3,-13); ctx.lineTo(-3,-4); ctx.lineTo(-10,10); ctx.quadraticCurveTo(-12,14,-7,14); ctx.lineTo(7,14); ctx.quadraticCurveTo(12,14,10,10); ctx.lineTo(3,-4); ctx.lineTo(3,-13); ctx.stroke();
        ctx.fillStyle='#69c3ef'; ctx.beginPath(); ctx.moveTo(-8.4,6); ctx.lineTo(8.4,6); ctx.lineTo(6.6,12.4); ctx.lineTo(-6.6,12.4); ctx.closePath(); ctx.fill();
        ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(2,9,1.6,0,TAU); ctx.fill();
      }else if(id==='star'){
        star(ctx,0,0,13,5.6,'#ffc93c');
      }else if(id==='dozer'){
        ctx.fillStyle='#e8624e'; WK.rr(ctx,-12,-12,24,24,6); ctx.fill();
        ctx.strokeStyle='#fff'; ctx.lineWidth=4; ctx.lineCap='round';
        ctx.beginPath(); ctx.moveTo(-6,-6); ctx.lineTo(6,6); ctx.moveTo(6,-6); ctx.lineTo(-6,6); ctx.stroke();
      }
      return;
    }
    // category background
    ctx.fillStyle=iconBg[cat]||'#eee';
    ctx.beginPath(); ctx.arc(0,0,24,0,TAU); ctx.fill();
    ctx.strokeStyle='rgba(122,94,50,.25)'; ctx.lineWidth=2; ctx.stroke();
    try{
      if(cat==='ground'){
        SPR.diamondPath(ctx,0,2,0); ctx.scale(1.15,1.15);
        SPR.paintGround(ctx,0,1.7,id,0.4);
      }else if(cat==='path'){
        ctx.scale(1.15,1.15);
        SPR.paintGround(ctx,0,1.7,'grass',0.4);
        SPR.paintPath(ctx,0,1.7,id,0.4);
      }else if(cat==='nature'){
        const s=SPR.getObject(id,0);
        ctx.save(); ctx.scale(0.42,0.42); ctx.translate(0,52); ctx.imageSmoothingEnabled=true;
        ctx.drawImage(s.cv,-s.ax,-s.ay); ctx.restore();
      }else if(cat==='animal'){
        const sp=WK.Data.SPECIES[id];
        ctx.save(); ctx.translate(0,16); ctx.scale(0.62,0.62);
        SPR.drawAnimal(ctx,id,{state:'idle',ph:0,t:1.2});
        ctx.restore();
      }else if(cat==='facility'){
        const b=SPR.getBuilding(id);
        const sc=Math.min(40/b.cv.width,40/b.cv.height)*1.6;
        ctx.save(); ctx.scale(sc,sc); ctx.drawImage(b.cv,-b.cv.width/2,-b.cv.height/2+b.cv.height*0.36); ctx.restore();
      }else if(cat==='fence'){
        SPR.drawFenceEdge(ctx,-16,6,14,-4,id==='gate'?3:(id==='hedge'?2:1),0);
      }
    }catch(e){}
  });
};
function star(ctx,x,y,R,r,color){
  ctx.fillStyle=color; ctx.beginPath();
  for(let i=0;i<10;i++){
    const rad=i%2?r:R, a=-Math.PI/2 + i*Math.PI/5;
    const px=x+Math.cos(a)*rad, py=y+Math.sin(a)*rad;
    i?ctx.lineTo(px,py):ctx.moveTo(px,py);
  }
  ctx.closePath(); ctx.fill();
  ctx.strokeStyle='rgba(160,100,10,.4)'; ctx.lineWidth=1.4; ctx.stroke();
}
SPR.star = star;

/* data-URL <img> markup for DOM panels */
SPR.iconImg = function(kind,size){
  size=size||44;
  return '<img src="'+SPR.getIcon(kind,size).cv.toDataURL()+'" width="'+size+'" height="'+size+'" alt="">';
};

})();
