1(()=>{ 2'use strict'; 3const root=document.getElementById('farm-game-root'); 4const canvas=document.getElementById('farm-game-canvas'); 5const liveUi=document.getElementById('farm-live-ui'); 6const toggle=document.getElementById('farm-toggle'); 7const exitButton=document.getElementById('farm-exit'); 8if(!root||!canvas||!liveUi||!toggle||!exitButton){console.warn('[Farm] Elemen game belum lengkap.');return} 9 10/* Fail-safe: jangan tampilkan canvas/UI sebagai elemen biasa bila file CSS salah lokasi atau gagal dimuat. */ 11const farmCssReady=getComputedStyle(root).position==='fixed'; 12if(!farmCssReady){ 13 console.error('[Farm] farm-background-fullmode.css belum termuat. Pastikan file berada di /farm-background-fullmode.css'); 14 return; 15} 16root.hidden=false; 17liveUi.hidden=false; 18 19const ctx=canvas.getContext('2d',{alpha:false,desynchronized:true}); 20if(!ctx)return; 21const hudDay=document.getElementById('farm-hud-day'); 22const hudTime=document.getElementById('farm-hud-time'); 23const hudCoins=document.getElementById('farm-hud-coins'); 24const hudAction=document.getElementById('farm-hud-action'); 25const toast=document.getElementById('farm-toast'); 26const TILE=48,COLS=44,ROWS=30,WW=COLS*TILE,WH=ROWS*TILE,DAY_SECONDS=150,SAVE_KEY='farm-background-save-v1'; 27const FIELD={minX:14,maxX:22,minY:8,maxY:15}; 28const HOUSE={x:4*TILE,y:5*TILE,w:6*TILE,h:5*TILE}; 29const keys=new Set(),plots=new Map(),camera={x:0,y:0}; 30const player={x:11.5*TILE,y:13.7*TILE,r:11,speed:175,dir:'down',walk:0,moving:false}; 31const state={coins:0,seconds:0,lastSave:0}; 32let active=false,previousFocus=null,vw=innerWidth,vh=innerHeight,dpr=1,last=performance.now(),hidden=document.hidden,toTimer=0,hudTick=0,growTick=0,saveTick=0,frameAccum=0; 33const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)); 34const lerp=(a,b,t)=>a+(b-a)*t; 35const keyOf=(x,y)=>`${x},${y}`; 36function hash(x,y){let n=Math.imul(x+17,374761393)^Math.imul(y+29,668265263);n=(n^(n>>>13))>>>0;return(Math.imul(n,1274126177)>>>0)/4294967295} 37function rng(seed){let a=seed>>>0;return()=>{a+=0x6d2b79f5;let t=a;t=Math.imul(t^(t>>>15),t|1);t^=t+Math.imul(t^(t>>>7),t|61);return((t^(t>>>14))>>>0)/4294967296}} 38function inField(x,y){return x>=FIELD.minX&&x<=FIELD.maxX&&y>=FIELD.minY&&y<=FIELD.maxY} 39function inPond(x,y){const dx=(x-33.5)/5.5,dy=(y-8.5)/4.2;return dx*dx+dy*dy<=1} 40function inHouseTile(x,y){return x>=4&&x<=9&&y>=5&&y<=9} 41function tile(x,y){if(x<0||y<0||x>=COLS||y>=ROWS)return'void';if(inPond(x,y))return'water';if(inField(x,y))return'field';if((y>=12&&y<=14)||(x>=10&&x<=12))return'path';return'grass'} 42for(let y=FIELD.minY;y<=FIELD.maxY;y++)for(let x=FIELD.minX;x<=FIELD.maxX;x++)plots.set(keyOf(x,y),{x,y,stage:0,readyAt:0}); 43const trees=[],flowers=[],random=rng(9122026); 44for(let y=1;y<ROWS-1;y++)for(let x=1;x<COLS-1;x++){ 45 if(tile(x,y)!=='grass'||inHouseTile(x,y))continue; 46 if(x>=8&&x<=25&&y>=6&&y<=18)continue; 47 if(Math.hypot(x-33.5,y-8.5)<7.2)continue; 48 const edge=x<4||x>COLS-5||y<4||y>ROWS-5,chance=edge?.34:.09; 49 if(random()<chance)trees.push({x:(x+.5+(random()-.5)*.18)*TILE,y:(y+.75+(random()-.5)*.12)*TILE,v:random()>.72?1:0,s:random()*Math.PI*2}); 50 else if(random()<.08)flowers.push({x:(x+.25+random()*.5)*TILE,y:(y+.3+random()*.45)*TILE,v:Math.floor(random()*3)}); 51} 52const chickens=[ 53 {x:24.4*TILE,y:17.2*TILE,d:1,t:.4,s:24,p:0}, 54 {x:27.2*TILE,y:15.8*TILE,d:-1,t:1.8,s:19,p:1.2}, 55 {x:30.1*TILE,y:18.4*TILE,d:1,t:2.7,s:22,p:2.4} 56]; 57function load(){try{const raw=localStorage.getItem(SAVE_KEY);if(!raw)return;const s=JSON.parse(raw);if(Number.isFinite(s.coins))state.coins=Math.max(0,s.coins);if(Number.isFinite(s.seconds))state.seconds=Math.max(0,s.seconds);if(Number.isFinite(s.px))player.x=clamp(s.px,TILE,WW-TILE);if(Number.isFinite(s.py))player.y=clamp(s.py,TILE,WH-TILE);if(s.plots)for(const[k,v]of Object.entries(s.plots)){const p=plots.get(k);if(p&&v){p.stage=clamp(Number(v.stage)||0,0,4);p.readyAt=Number(v.readyAt)||0}}}catch(e){console.warn('[Farm] Save tidak dapat dibaca.',e)}} 58function save(force=false){const now=Date.now();if(!force&&now-state.lastSave<1200)return;const out={};for(const[k,p]of plots)if(p.stage>0)out[k]={stage:p.stage,readyAt:p.readyAt};try{localStorage.setItem(SAVE_KEY,JSON.stringify({coins:state.coins,seconds:state.seconds,px:+player.x.toFixed(2),py:+player.y.toFixed(2),plots:out}));state.lastSave=now}catch(e){console.warn('[Farm] Progress tidak dapat disimpan.',e)}} 59function resize(){vw=Math.max(320,innerWidth);vh=Math.max(320,innerHeight);dpr=Math.min(2,Math.max(1,devicePixelRatio||1));canvas.width=Math.floor(vw*dpr);canvas.height=Math.floor(vh*dpr);canvas.style.width=vw+'px';canvas.style.height=vh+'px';ctx.setTransform(dpr,0,0,dpr,0,0);ctx.imageSmoothingEnabled=false} 60function message(text,duration=1800){if(!toast)return;toast.textContent=text;toast.classList.add('is-visible');clearTimeout(toTimer);toTimer=setTimeout(()=>toast.classList.remove('is-visible'),duration)} 61function setActive(next=true,shouldFocus=true){ 62 const enable=Boolean(next),wasActive=active; 63 active=enable; 64 document.body.classList.toggle('farm-game-fullscreen',enable); 65 root.setAttribute('aria-hidden',enable?'false':'true'); 66 toggle.setAttribute('aria-expanded',String(enable)); 67 toggle.setAttribute('aria-pressed',String(enable));
68 exitButton.setAttribute('aria-hidden',String(!enable)); 69 keys.clear(); 70 resize(); 71 last=performance.now(); 72 if(enable){ 73 if(!wasActive)previousFocus=document.activeElement; 74 if(shouldFocus)requestAnimationFrame(()=>{try{canvas.focus({preventScroll:true})}catch(_){canvas.focus()}}); 75 message('Mode permainan penuh aktif · gunakan WASD atau tombol arah, lalu E / Spasi untuk aksi.',2600); 76 }else{ 77 if(toast)toast.classList.remove('is-visible'); 78 save(true); 79 if(shouldFocus&&wasActive)requestAnimationFrame(()=>{const target=previousFocus&&typeof previousFocus.focus==='function'?previousFocus:toggle;try{target.focus({preventScroll:true})}catch(_){target.focus()}}); 80 } 81} 82function facingVector(){if(player.dir==='up')return{x:0,y:-1};if(player.dir==='left')return{x:-1,y:0};if(player.dir==='right')return{x:1,y:0};return{x:0,y:1}} 83function facingTile(){const d=facingVector(),r=TILE*.76;return{x:Math.floor((player.x+d.x*r)/TILE),y:Math.floor((player.y+d.y*r)/TILE)}} 84function actionLabel(){const f=facingTile(),p=plots.get(keyOf(f.x,f.y));if(!p)return'E: INTERAKSI';return['E: CANGKUL','E: TANAM','E: SIRAM','TANAMAN TUMBUH','E: PANEN'][p.stage]} 85function interact(){if(!active)return;const f=facingTile(),p=plots.get(keyOf(f.x,f.y));if(p){if(p.stage===0){p.stage=1;message('Tanah selesai dicangkul. Tekan aksi lagi untuk menanam.')}else if(p.stage===1){p.stage=2;message('Benih ditanam. Tekan aksi lagi untuk menyiram.')}else if(p.stage===2){p.stage=3;p.readyAt=Date.now()+4200;message('Tanaman disiram. Tunggu beberapa detik sampai matang.')}else if(p.stage===3){message(`Tanaman sedang tumbuh · sekitar ${Math.max(1,Math.ceil((p.readyAt-Date.now())/1000))} detik lagi.`)}else{p.stage=1;p.readyAt=0;
85state.coins+=25;message('Panen berhasil! +25 koin.')}save(true);updateHud();return}const kind=tile(f.x,f.y);if(kind==='water'){message('Kolam jernih. Air untuk menyiram tidak terbatas.');return}const wx=(f.x+.5)*TILE,wy=(f.y+.5)*TILE;if(wx>=HOUSE.x&&wx<=HOUSE.x+HOUSE.w&&wy>=HOUSE.y&&wy<=HOUSE.y+HOUSE.h){message('Rumah petani. Saat ini fokus dulu mengurus kebun.');return}message('Hadapkan karakter ke petak kebun, lalu tekan E atau Spasi.')} 86function blocked(x,y){const r=player.r,pts=[[x-r,y-3],[x+r,y-3],[x-r,y+7],[x+r,y+7]];for(const[a,b]of pts){if(a<TILE*.5||b<TILE*.5||a>WW-TILE*.5||b>WH-TILE*.5)return true;const tx=Math.floor(a/TILE),ty=Math.floor(b/TILE);if(tile(tx,ty)==='water')return true;const house=a>HOUSE.x+6&&a<HOUSE.x+HOUSE.w-6&&b>HOUSE.y+TILE*.75&&b<HOUSE.y+HOUSE.h-5;if(house)return true}for(const t of trees){if(Math.abs(t.x-x)>30||Math.abs(t.y-y)>24)continue;const dx=t.x-x,dy=t.y-y;if(dx*dx+dy*dy<19*19)return true}return false} 87function updatePlayer(dt){if(!active){player.moving=false;return}let x=0,y=0;if(keys.has('left')||keys.has('KeyA')||keys.has('ArrowLeft'))x--;if(keys.has('right')||keys.has('KeyD')||keys.has('ArrowRight'))x++;if(keys.has('up')||keys.has('KeyW')||keys.has('ArrowUp'))y--;if(keys.has('down')||keys.has('KeyS')||keys.has('ArrowDown'))y++;if(!x&&!y){player.moving=false;return}player.moving=true;const l=Math.hypot(x,y)||1;x/=l;y/=l;if(Math.abs(x)>Math.abs(y))player.dir=x<0?'left':'right';else player.dir=y<0?'up':'down';const d=player.speed*dt,nx=player.x+x*d,ny=player.y+y*d;if(!blocked(nx,player.y))player.x=nx;if(!blocked(player.x,ny))player.y=ny;player.walk+=dt*11} 88function updateChickens(dt){for(const c of chickens){c.t-=dt;c.p+=dt*7;if(c.t<=0){const q=hash(Math.floor(state.seconds*2+c.x),Math.floor(c.y));c.d=q>.52?1:-1;c.t=1.2+q*2.8}const nx=c.x+c.d*c.s*dt,tx=Math.floor(nx/TILE),ty=Math.floor(c.y/TILE),bad=tile(tx,ty)==='water'||inField(tx,ty)||inHouseTile(tx,ty);if(nx<23*TILE||nx>31.5*TILE||bad)c.d*=-1;else c.x=nx}} 89function grow(){const now=Date.now();let changed=false;for(const p of plots.values())if(p.stage===3&&p.readyAt&&now>=p.readyAt){p.stage=4;p.readyAt=0;changed=true}if(changed)save(true)} 90function updateCamera(dt){const dx=player.x-vw*.5,dy=player.y-vh*.56;const tx=WW<=vw?(WW-vw)*.5:clamp(dx,0,WW-vw),ty=WH<=vh?(WH-vh)*.5:clamp(dy,0,WH-vh),s=Math.min(1,dt*7.5);camera.x=lerp(camera.x,tx,s);camera.y=lerp(camera.y,ty,s)} 91function clock(){const day=Math.floor(state.seconds/DAY_SECONDS)+1,phase=(state.seconds%DAY_SECONDS)/DAY_SECONDS,total=Math.floor((360+phase*1440)%1440);return{day,hour:Math.floor(total/60),minute:total%60}} 92function updateHud(){const c=clock();if(hudDay)hudDay.textContent=c.day;if(hudTime)hudTime.textContent=String(c.hour).padStart(2,'0')+':'+String(c.minute).padStart(2,'0');if(hudCoins)hudCoins.textContent=state.coins.toLocaleString('id-ID');if(hudAction)hudAction.textContent=actionLabel()} 93function grass(sx,sy,x,y){const v=hash(x,y);ctx.fillStyle=v>.5?'#79b84a':'#72b143';ctx.fillRect(sx,sy,TILE+1,TILE+1);ctx.fillStyle='rgba(31,105,45,.24)';const px=Math.floor(hash(x+19,y+5)*34)+6,py=Math.floor(hash(x+3,y+21)*30)+8;ctx.fillRect(sx+px,sy+py,3,7);ctx.fillRect(sx+px-3,sy+py+3,3,3);if(v>.79){ctx.fillStyle='rgba(236,255,145,.48)';ctx.fillRect(sx+11,sy+13,2,5);ctx.fillRect(sx+8,sy+16,3,2)}} 94function pathTile(sx,sy,x,y){const v=hash(x+7,y+11);ctx.fillStyle=v>.5?'#c9a05c':'#c39a56';ctx.fillRect(sx,sy,TILE+1,TILE+1);ctx.fillStyle='rgba(108,73,35,.24)';ctx.fillRect(sx+7+Math.floor(v*11),sy+9,5,3);ctx.fillRect(sx+30,sy+31-Math.floor(v*8),7,3);ctx.fillStyle='rgba(255,232,164,.22)';ctx.fillRect(sx+17,sy+23,4,2)} 95function water(sx,sy,x,y,t){ctx.fillStyle=hash(x,y)>.5?'#2f9fd1':'#2996ca';ctx.fillRect(sx,sy,TILE+1,TILE+1);const w=Math.floor((t*18+x*7+y*11)%28);ctx.fillStyle='rgba(193,244,255,.48)';ctx.fillRect(sx+w-8,sy+14,14,3);ctx.fillRect(sx+((w+19)%38),sy+33,10,2);ctx.fillStyle='rgba(18,101,158,.32)';ctx.fillRect(sx+8,sy+24,17,3)} 96function fieldTile(sx,sy,x,y,t){const p=plots.get(keyOf(x,y));ctx.fillStyle='#8b5c39';ctx.fillRect(sx,sy,TILE+1,TILE+1);ctx.fillStyle='#704326';for(const yy of[8,22,36])ctx.fillRect(sx+4,sy+yy,TILE-8,4);if(!p||p.stage===0){ctx.fillStyle='rgba(210,152,91,.25)';ctx.fillRect(sx+8,sy+6,3,3);ctx.fillRect(sx+34,sy+29,4,3);return}ctx.fillStyle='#4e2d1d';for(const yy of[10,24,38])ctx.fillRect(sx+5,sy+yy,TILE-10,3);if(p.stage===1)return;if(p.stage===2){ctx.fillStyle='#e6d7a2';ctx.fillRect(sx+14,sy+18,3,3);ctx.fillRect(sx+31,sy+31,3,3);return}const sway=Math.round(Math.sin(t*4+x*.7+y)*1.5);for(const [px,py]of[[14,20],[31,33]]){const h=p.stage===4?16:10;ctx.fillStyle='#286d32';ctx.fillRect(sx+px+sway,sy+py-h,3,h);ctx.fillStyle=p.stage===4?'#f4cf35':'#4ea83f';ctx.fillRect(sx+px-5+sway,sy+py-h+2,6,5);ctx.fillRect(sx+px+3+sway,sy+py-h+5,6,5);if(p.stage===4){ctx.fillStyle='#ffef72';ctx.fillRect(sx+px-2+sway,sy+py-h-3,7,7)}}if(p.stage===3){ctx.fillStyle='rgba(156,232,255,.74)';const d=Math.floor((t*22+x*5+y*3)%20);ctx.fillRect(sx+9,sy+d,2,4);ctx.fillRect(sx+37,sy+((d+8)%20),2,4)}} 97function drawTiles(t){const sx=Math.max(0,Math.floor(camera.x/TILE)-1),sy=Math.max(0,Math.floor(camera.y/TILE)-1),ex=Math.min(COLS-1,Math.ceil((camera.x+v
97w)/TILE)+1),ey=Math.min(ROWS-1,Math.ceil((camera.y+vh)/TILE)+1);for(let y=sy;y<=ey;y++)for(let x=sx;x<=ex;x++){const px=Math.floor(x*TILE-camera.x),py=Math.floor(y*TILE-camera.y),k=tile(x,y);if(k==='path')pathTile(px,py,x,y);else if(k==='water')water(px,py,x,y,t);else if(k==='field')fieldTile(px,py,x,y,t);else grass(px,py,x,y)}} 98function pondBorder(){ctx.save();ctx.translate(-camera.x,-camera.y);ctx.strokeStyle='rgba(210,229,123,.72)';ctx.lineWidth=7;ctx.beginPath();ctx.ellipse(33.5*TILE,8.5*TILE,5.65*TILE,4.35*TILE,0,0,Math.PI*2);ctx.stroke();ctx.strokeStyle='rgba(50,112,46,.55)';ctx.lineWidth=3;ctx.beginPath();ctx.ellipse(33.5*TILE,8.5*TILE,5.75*TILE,4.45*TILE,0,0,Math.PI*2);ctx.stroke();ctx.restore()} 99function house(){const x=Math.floor(HOUSE.x-camera.x),y=Math.floor(HOUSE.y-camera.y);ctx.fillStyle='rgba(0,0,0,.24)';ctx.fillRect(x+18,y+HOUSE.h-12,HOUSE.w-18,16);ctx.fillStyle='#ead7a4';ctx.fillRect(x+18,y+70,HOUSE.w-36,HOUSE.h-76);ctx.fillStyle='#c89b5f';ctx.fillRect(x+26,y+82,HOUSE.w-52,5);ctx.fillRect(x+26,y+119,HOUSE.w-52,5);ctx.fillStyle='#9b3428';ctx.fillRect(x+3,y+43,HOUSE.w-6,32);ctx.fillStyle='#c94c31';ctx.fillRect(x+25,y+19,HOUSE.w-50,40);ctx.fillStyle='#e2673e';ctx.fillRect(x+48,y+4,HOUSE.w-96,28);ctx.fillStyle='#713027';for(let rx=20;rx<HOUSE.w-20;rx+=24)ctx.fillRect(x+rx,y+55,16,4);ctx.fillStyle='#704224';ctx.fillRect(x+HOUSE.w*.5-24,y+HOUSE.h-82,48,82);ctx.fillStyle='#2b1c13';ctx.fillRect(x+HOUSE.w*.5-14,y+HOUSE.h-65,28,47);ctx.fillStyle='#f1c644';ctx.fillRect(x+HOUSE.w*.5+8,y+HOUSE.h-43,5,5);for(const wx of[x+54,x+HOUSE.w-92]){ctx.fillStyle='#5c3b26';ctx.fillRect(wx-5,y+97,43,42);ctx.fillStyle='#77c8dd';ctx.fillRect(wx,y+102,33,32);ctx.fillStyle='rgba(255,255,255,.55)';ctx.fillRect(wx+5,y+107,9,18);ctx.fillStyle='#5c3b26';ctx.fillRect(wx+15,y+102,3,32);ctx.fillRect(wx,y+117,33,3)}ctx.fillStyle='#633826';ctx.fillRect(x+HOUSE.w-78,y+8,27,53);ctx.fillStyle='#44261b';ctx.fillRect(x+HOUSE.w-83,y+2,37,11)} 100function fence(){const l=FIELD.minX*TILE-12-camera.x,r=(FIELD.maxX+1)*TILE+12-camera.x,t=FIELD.minY*TILE-13-camera.y,b=(FIELD.maxY+1)*TILE+12-camera.y;ctx.fillStyle='#e1c183';for(let x=l;x<=r;x+=35){ctx.fillRect(Math.floor(x),Math.floor(t-4),7,18);ctx.fillRect(Math.floor(x),Math.floor(b-12),7,18)}for(let y=t;y<=b;y+=35){ctx.fillRect(Math.floor(l-3),Math.floor(y),18,7);ctx.fillRect(Math.floor(r-11),Math.floor(y),18,7)}ctx.fillStyle='#bb9056';ctx.fillRect(Math.floor(l),Math.floor(t+3),Math.floor(r-l),5);ctx.fillRect(Math.floor(l),Math.floor(b-5),Math.floor(r-l),5);ctx.fillRect(Math.floor(l+3),Math.floor(t),5,Math.floor(b-t));ctx.fillRect(Math.floor(r-5),Math.floor(t),5,Math.floor(b-t))} 101function flower(f){const x=Math.floor(f.x-camera.x),y=Math.floor(f.y-camera.y);if(x<-10||y<-10||x>vw+10||y>vh+10)return;const colors=['#fff36d','#f47ba8','#d9b1ff'];ctx.fillStyle='#2d7d3a';ctx.fillRect(x,y,2,8);ctx.fillStyle=colors[f.v]||colors[0];ctx.fillRect(x-3,y-2,4,4);ctx.fillRect(x+1,y-3,4,4);ctx.fillStyle='#fff4a0';ctx.fillRect(x,y-1,2,2)} 102function treeDraw(o,t){const x=Math.floor(o.x-camera.x+Math.sin(t*1.2+o.s)*1.3),y=Math.floor(o.y-camera.y);if(x<-70||y<-100||x>vw+70||y>vh+50)return;ctx.fillStyle='rgba(0,0,0,.2)';ctx.fillRect(x-26,y-5,52,13);ctx.fillStyle='#704225';ctx.fillRect(x-8,y-48,16,52);ctx.fillStyle='#9a5c2c';ctx.fillRect(x-3,y-45,5,43);const dark=o.v?'#245e32':'#1f6b37',mid=o.v?'#3f8b45':'#399547',light=o.v?'#62aa51':'#55b353';ctx.fillStyle=dark;ctx.fillRect(x-34,y-82,68,43);ctx.fillRect(x-25,y-101,50,66);ctx.fillStyle=mid;ctx.fillRect(x-26,y-88,52,40);ctx.fillRect(x-14,y-105,34,32);ctx.fillStyle=light;ctx.fillRect(x-18,y-95,25,17);ctx.fillRect(x+7,y-78,18,15);ctx.fillStyle='rgba(219,255,150,.35)';ctx.fillRect(x-9,y-91,11,7)} 103function chickenDraw(c){const x=Math.floor(c.x-camera.x),y=Math.floor(c.y-camera.y+Math.sin(c.p)*1.2);if(x<-30||y<-30||x>vw+30||y>vh+30)return;ctx.save();ctx.translate(x,y);ctx.scale(c.d<0?-1:1,1);ctx.fillStyle='rgba(0,0,0,.2)';ctx.fillRect(-14,8,29,6);ctx.fillStyle='#f8f1d8';ctx.fillRect(-12,-8,24,18);ctx.fillRect(4,-17,13,14);ctx.fillStyle='#d8d0b9';ctx.fillRect(-10,1,14,7);ctx.fillStyle='#e65538';ctx.fillRect(7,-21,3,5);ctx.fillRect(11,-22,3,6);ctx.fillStyle='#1a1a1a';ctx.fillRect(12,-13,2,2);ctx.fillStyle='#f0b62d';ctx.fillRect(17,-10,6,4);ctx.fillRect(-5,10,3,6);ctx.fillRect(7,10,3,6);
103ctx.restore()} 104function playerDraw(){const x=Math.floor(player.x-camera.x),y=Math.floor(player.y-camera.y),leg=player.moving?Math.round(Math.sin(player.walk)*3):0,bob=player.moving?Math.abs(Math.sin(player.walk))*1.5:0;ctx.save();ctx.translate(x,Math.floor(y-bob));ctx.fillStyle='rgba(0,0,0,.28)';ctx.fillRect(-15,8,31,8);ctx.fillStyle='#30251f';ctx.fillRect(-10,2+leg,8,13);ctx.fillRect(2,2-leg,8,13);ctx.fillStyle='#6a3d27';ctx.fillRect(-11,12+leg,10,4);ctx.fillRect(1,12-leg,10,4);ctx.fillStyle='#2674a8';ctx.fillRect(-12,-24,24,28);ctx.fillStyle='#f4c34e';ctx.fillRect(-15,-22,5,22);ctx.fillRect(10,-22,5,22);ctx.fillStyle='#f1bd8d';ctx.fillRect(-16,-4,5,8);ctx.fillRect(11,-4,5,8);ctx.fillStyle='#f2bf8e';ctx.fillRect(-10,-39,20,17);ctx.fillStyle='#6f3b26';ctx.fillRect(-10,-41,20,5);ctx.fillStyle='#d6422f';ctx.fillRect(-15,-48,30,8);ctx.fillRect(-10,-54,20,8);ctx.fillStyle='#f1d050';ctx.fillRect(-15,-43,30,3);if(player.dir==='up'){ctx.fillStyle='#6f3b26';ctx.fillRect(-8,-37,16,8)}else if(player.dir==='left'){ctx.fillStyle='#1b1b1b';ctx.fillRect(-7,-33,2,3);ctx.fillStyle='#d98968';ctx.fillRect(-11,-31,3,3)}else if(player.dir==='right'){ctx.fillStyle='#1b1b1b';ctx.fillRect(5,-33,2,3);ctx.fillStyle='#d98968';ctx.fillRect(8,-31,3,3)}else{ctx.fillStyle='#1b1b1b';ctx.fillRect(-6,-33,2,3);ctx.fillRect(4,-33,2,3);ctx.fillStyle='#d98968';ctx.fillRect(-2,-29,4,2)}ctx.restore()} 105function marker(t){if(!active)return;const f=facingTile(),p=plots.get(keyOf(f.x,f.y));if(!p)return;const x=Math.floor(f.x*TILE-camera.x),y=Math.floor(f.y*TILE-camera.y),a=.55+Math.sin(t*6)*.2;ctx.strokeStyle=`rgba(255,238,80,${a})`;ctx.lineWidth=3;ctx.strokeRect(x+3,y+3,TILE-6,TILE-6)} 106function entities(t){const list=[];for(const o of trees)list.push({y:o.y,draw:()=>treeDraw(o,t)});for(const c of chickens)list.push({y:c.y,draw:()=>chickenDraw(c)});list.push({y:player.y,draw:playerDraw});list.sort((a,b)=>a.y-b.y);for(const e of list)e.draw()} 107function lighting(){const c=clock(),h=c.hour+c.minute/60;let dark=0;if(h<5)dark=.48;else if(h<7)dark=lerp(.42,0,(h-5)/2);else if(h>21)dark=.48;else if(h>18)dark=lerp(0,.48,(h-18)/3);if(dark>.01){ctx.fillStyle=`rgba(16,27,73,${dark})`;ctx.fillRect(0,0,vw,vh)}if(h>=17.2&&h<=19.4){const q=1-Math.abs(18.3-h)/1.2;ctx.fillStyle=`rgba(255,135,59,${Math.max(0,q)*.12})`;ctx.fillRect(0,0,vw,vh)}} 108function render(t){ctx.setTransform(dpr,0,0,dpr,0,0);ctx.fillStyle='#214f2a';ctx.fillRect(0,0,vw,vh);drawTiles(t);pondBorder();house();fence();for(const f of flowers)flower(f);marker(t);entities(t);lighting()} 109function update(dt){state.seconds+=dt;updatePlayer(dt);updateChickens(dt);updateCamera(dt);growTick+=dt;if(growTick>=.35){growTick=0;grow()}hudTick+=dt;if(hudTick>=.12){hudTick=0;updateHud()}saveTick+=dt;if(saveTick>=4){saveTick=0;save()}} 110function frame(now){const raw=(now-last)/1000;last=now;const dt=hidden?0:Math.min(.05,Math.max(0,raw));if(!hidden){frameAccum+=dt;const step=active?1/30:1/20;if(frameAccum>=step){update(frameAccum);render(now/1000);frameAccum=0}}requestAnimationFrame(frame)} 111const code=e=>e.code||e.key||''; 112const gameKey=c=>['KeyW','KeyA','KeyS','KeyD','ArrowUp','ArrowDown','ArrowLeft','ArrowRight','KeyE','Space','Enter'].includes(c); 113addEventListener('keydown',e=>{ 114 const c=code(e); 115 if(c==='Escape'&&active){e.preventDefault();setActive(false);return} 116 if(!active)return; 117 const el=e.target; 118 if(el&&el!==canvas&&(el.isContentEditable||/^(INPUT|TEXTAREA|SELECT|BUTTON|A)$/.test(el.tagName)))return; 119 if(!gameKey(c))return; 120 e.preventDefault(); 121 if((c==='KeyE'||c==='Space'||c==='Enter')&&!e.repeat){interact();return} 122 keys.add(c); 123},{passive:false});
124addEventListener('keyup',e=>keys.delete(code(e))); 125addEventListener('blur',()=>keys.clear()); 126addEventListener('resize',resize,{passive:true}); 127addEventListener('beforeunload',()=>save(true)); 128addEventListener('pagehide',()=>save(true)); 129document.addEventListener('visibilitychange',()=>{hidden=document.hidden;if(!hidden)last=performance.now();else save(true)}); 130toggle.addEventListener('click',()=>setActive(true)); 131exitButton.addEventListener('click',()=>setActive(false)); 132root.addEventListener('pointerdown',()=>{if(active){try{canvas.focus({preventScroll:true})}catch(_){canvas.focus()}}}); 133for(const b of document.querySelectorAll('#farm-mobile-controls .farm-touch-btn[data-key]')){const k=b.dataset.key;if(!k)continue;const up=e=>{e.preventDefault();keys.delete(k);b.classList.remove('is-down')};b.addEventListener('pointerdown',e=>{e.preventDefault();if(!active)setActive(true);keys.add(k);b.classList.add('is-down');try{b.setPointerCapture(e.pointerId)}catch(_){}});b.addEventListener('pointerup',up);b.addEventListener('pointercancel',up);b.addEventListener('lostpointercapture',up)} 134const action=document.querySelector('#farm-mobile-controls .farm-action-btn');if(action){action.addEventListener('pointerdown',e=>{e.preventDefault();action.classList.add('is-down');interact()});const up=e=>{e.preventDefault();action.classList.remove('is-down')};action.addEventListener('pointerup',up);action.addEventListener('pointercancel',up);action.addEventListener('pointerleave',up)} 135load();resize();grow();updateHud();setActive(false,false);render(performance.now()/1000);requestAnimationFrame(frame); 136})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.