(function(){ "use strict"; // ═══════════════════════════════════════════ // Stein der Erinnerung – Full Implementation // ═══════════════════════════════════════════ var CW=800,CH=600; var PATH_W=36; var WALK_SPEED=2.4, PUSH_SPEED=1.5, SPRINT_SPEED=4.5; var STONE_R=14; var LEVELS=[ {name:"Tal der Steine",stones:5,keimGrow:0.003,keimShrink:0.035,medsMax:100,title:"Zystinstein-Lehrling", skyA:"#3a5068",skyB:"#4a6741",ground:"#3a5230",hintAlpha:0.6,aloneTime:25}, {name:"Schlucht der Pr\u00FCfung",stones:7,keimGrow:0.005,keimShrink:0.028,medsMax:90,title:"Zystinstein-Kenner", skyA:"#40455a",skyB:"#5a5040",ground:"#3d4a2e",hintAlpha:0.35,aloneTime:20}, {name:"Gipfel der Erkenntnis",stones:10,keimGrow:0.008,keimShrink:0.022,medsMax:80,title:"Zystinstein-Guru", skyA:"#3a3050",skyB:"#604858",ground:"#35402c",hintAlpha:0.18,aloneTime:16} ]; var MEDS=[ {id:"water",name:"Wasser",color:"#4fc3f7",pow:1,icon:"\uD83D\uDCA7"}, {id:"bicarb",name:"Bikarbonat",color:"#81c784",pow:2.2,icon:"\uD83D\uDC8A"}, {id:"thiola",name:"Thiola\u00AE",color:"#ffb74d",pow:4,icon:"\uD83D\uDC89"} ]; var STONE_COLS=["#8d8d8d","#9e9e9e","#7a7a6e","#a09080","#887766","#7b8b7a","#8e7b6b","#998877","#8a8a7a","#6e7b6e"]; var CYSTINE_COL="#9a8a6a"; // ─── Util ────────────── function dist(a,b){return Math.sqrt((a.x-b.x)**2+(a.y-b.y)**2)} function clamp(v,a,b){return Math.max(a,Math.min(b,v))} function lerp(a,b,t){return a+(b-a)*t} function rng(a,b){return Math.random()*(b-a)+a} function lerpCol(a,b,t){ var pa=[parseInt(a.slice(1,3),16),parseInt(a.slice(3,5),16),parseInt(a.slice(5,7),16)]; var pb=[parseInt(b.slice(1,3),16),parseInt(b.slice(3,5),16),parseInt(b.slice(5,7),16)]; return "rgb("+Math.round(lerp(pa[0],pb[0],t))+","+Math.round(lerp(pa[1],pb[1],t))+","+Math.round(lerp(pa[2],pb[2],t))+")"; } // ─── Audio ───────────── var actx=null; function ea(){if(!actx)actx=new(window.AudioContext||window.webkitAudioContext)()} function tn(f,d,v){try{ea();var o=actx.createOscillator(),g=actx.createGain();o.type="sine";o.frequency.value=f;g.gain.value=v||0.06;g.gain.exponentialRampToValueAtTime(0.001,actx.currentTime+d);o.connect(g);g.connect(actx.destination);o.start();o.stop(actx.currentTime+d)}catch(e){}} function sfxPick(){tn(520,0.12);tn(680,0.1)} function sfxNeg(){tn(280,0.25)} function sfxPos(){tn(440,0.15,0.08);setTimeout(function(){tn(660,0.15,0.08)},80);setTimeout(function(){tn(880,0.2,0.08)},160)} function sfxRefill(){tn(600,0.1);tn(750,0.1)} function sfxSpray(){tn(900+Math.random()*200,0.06,0.04)} function sfxWin(){tn(523,0.15,0.1);setTimeout(function(){tn(659,0.15,0.1)},120);setTimeout(function(){tn(784,0.2,0.1)},240);setTimeout(function(){tn(1047,0.3,0.1)},360)} function sfxLose(){tn(300,0.3,0.08);setTimeout(function(){tn(200,0.4,0.08)},200)} // ─── Path Network (orthogonal) ───────── function genNet(li){ // Grid-based but irregular: not a checkerboard var cfg=LEVELS[li]; var cols=6+li*2, rows=8+li*3; var gapX=100+rng(-10,10), gapY=80+rng(-10,10); var offX=80, offY=80; var mapW=offX*2+cols*gapX, mapH=offY*2+rows*gapY; // Create grid nodes with some randomly removed var grid=[]; // grid[r][c] = node or null var nodes=[]; var nid=0; for(var r=0;r<=rows;r++){ grid[r]=[]; for(var c=0;c<=cols;c++){ // Always keep edges and some key positions var keep = r===0||r===rows||c===0||c===cols|| (r===rows&&c===Math.floor(cols/2))|| // start (r===0&&c===Math.floor(cols/2))|| // peak (r===rows-1&&c<=1)|| // lab area (r===Math.floor(rows*0.4)&&c>=cols-1); // apo area if(!keep && Math.random()<0.35){ grid[r][c]=null; continue; } // Offset positions slightly for organic feel var jx=(r>0&&r0&&c0&&r0&&c40&&nx40&&ny=2)show("victory");else{curLvl=l+1;show("game");}}; show("transition"); } function showGO(reason){ sfxLose(); $("go-r").textContent=reason; $("go-retry").onclick=function(){show("game")}; $("go-rst").onclick=function(){curLvl=0;show("game")}; $("go-quit").onclick=function(){show("title")}; show("gameover"); } // ─── Start Game ──────── function startGame(){ initGame(curLvl); frame=0; showTut=true; var c=LEVELS[curLvl]; $("tut-h").textContent="Level "+(curLvl+1)+": "+c.name; $("tut-sc").textContent=c.stones; $("tut-ov").style.display="flex"; $("tut-ov").onclick=function(){$("tut-ov").style.display="none";showTut=false;ea()}; updateHUD(); gameLoop(); } // ─── Input ───────────── document.addEventListener("keydown",function(e){ keys[e.key.toLowerCase()]=true; if(e.key==="Shift")keys.shift=true; if(e.key===" "||e.key==="Enter"){actionQ=true;e.preventDefault()} if(S){if(e.key==="1")S.selMed=0;if(e.key==="2")S.selMed=1;if(e.key==="3")S.selMed=2;} }); document.addEventListener("keyup",function(e){keys[e.key.toLowerCase()]=false;if(e.key==="Shift")keys.shift=false;}); var gw=$("screen-game"); gw.addEventListener("touchstart",function(e){var t=e.touches[0];touch={active:true,sx:t.clientX,sy:t.clientY,dx:0,dy:0}},{passive:false}); gw.addEventListener("touchmove",function(e){if(!touch.active)return;var t=e.touches[0];touch.dx=t.clientX-touch.sx;touch.dy=t.clientY-touch.sy;e.preventDefault()},{passive:false}); gw.addEventListener("touchend",function(){if(Math.abs(touch.dx)<15&&Math.abs(touch.dy)<15)actionQ=true;touch.active=false;touch.dx=0;touch.dy=0}); $("act-btn").addEventListener("click",function(){actionQ=true}); document.querySelectorAll(".med-b").forEach(function(b,i){b.addEventListener("click",function(){if(S)S.selMed=i})}); $("btn-start").addEventListener("click",function(){curLvl=0;show("game")}); // ─── Game Loop ───────── function gameLoop(){ if(curScreen!=="game"||!S)return; if(showTut){animId=requestAnimationFrame(gameLoop);return} if(S.won||S.lost)return; var dt=1/60; S.time+=dt;frame++; // Input direction var mx=0,my=0; if(keys["w"]||keys["arrowup"])my=-1; if(keys["s"]||keys["arrowdown"])my=1; if(keys["a"]||keys["arrowleft"])mx=-1; if(keys["d"]||keys["arrowright"])mx=1; if(touch.active&&(Math.abs(touch.dx)>15||Math.abs(touch.dy)>15)){ if(Math.abs(touch.dx)>Math.abs(touch.dy))mx=touch.dx>0?1:-1; else my=touch.dy>0?1:-1; } // Prefer cardinal: zero out smaller axis for cleaner movement on orthogonal paths if(mx&&my){if(Math.abs(mx)>=Math.abs(my))my=0;else mx=0;} S.player.sprinting=keys.shift&&!S.rollingStone; var speed=S.rollingStone?PUSH_SPEED:(S.player.sprinting?SPRINT_SPEED:WALK_SPEED); if(mx||my){ var len=Math.sqrt(mx*mx+my*my); var nx=S.player.x+(mx/len)*speed; var ny=S.player.y+(my/len)*speed; // Constrain to paths var cp=closestOnPaths(nx,ny,S.net.edges,S.net.nodes); if(cp.d>PATH_W*0.55){nx=cp.x;ny=cp.y;} nx=clamp(nx,10,S.net.mapW-10); ny=clamp(ny,10,S.net.mapH-10); S.player.x=nx;S.player.y=ny; S.player.dir=Math.atan2(my,mx); S.player.frame+=S.player.sprinting?0.25:0.15; } // Roll stone alongside player if(S.rollingStone){ var rs=S.rollingStone; // Stone follows player offset in movement direction var offDist=22; var sdx=Math.cos(S.player.dir)*offDist; var sdy=Math.sin(S.player.dir)*offDist; var tsx=S.player.x+sdx, tsy=S.player.y+sdy; // Snap stone to path var scp=closestOnPaths(tsx,tsy,S.net.edges,S.net.nodes); if(scp.d>PATH_W*0.5){tsx=scp.x;tsy=scp.y;} // Smooth follow rs.x=lerp(rs.x,tsx,0.15); rs.y=lerp(rs.y,tsy,0.15); // Roll animation if(mx||my){ var rollDist=Math.sqrt((rs.x-tsx)**2+(rs.y-tsy)**2); rs.rollAngle+=(mx||my)*0.08; } } // Action if(actionQ){actionQ=false;handleAction()} // Check pharmacy proximity S.atPharmacy=dist(S.player,S.net.apo)<65; // Phase 2 logic if(S.phase===2){ S.phase2Timer+=dt; // Grow keims for(var ki=0;ki100&&dist(kp,S.net.apo)>70&&dist(kp,S.net.lab)>70){ S.keims.push({x:kp.x,y:kp.y,size:0.06,id:Date.now()+Math.random(),pulse:0}); } } // Kidney damage var totK=0; for(var ki=0;ki=100){ S.lost=true; showGO("Die Kristallkeime haben die Niere kritisch gesch\u00E4digt. Der Stein kann nicht mehr bewegt werden."); return; } if(S.stoneAloneTimer>=S.maxAloneTime){ S.lost=true; showGO("Der Weg zum Gipfel ist von Kristallen dauerhaft blockiert. Der Zystinstein sitzt fest!"); return; } // Win check if(S.rollingStone&&S.rollingStone.isCystine&&dist(S.rollingStone,S.net.peak)<50){ S.won=true; S.score+=Math.max(0,Math.floor(1000-S.phase2Timer*5+(100-S.kidneyDmg)*5)); sfxWin(); setTimeout(function(){showTrans(curLvl)},700); return; } // Passive med drain (slow) ["water","bicarb","thiola"].forEach(function(m){ S.meds[m]=Math.max(0,S.meds[m]-dt*(0.3+curLvl*0.15)); }); } // Spray particles update for(var pi=sprayParticles.length-1;pi>=0;pi--){ var p=sprayParticles[pi]; p.life-=dt; p.x+=p.vx*dt;p.y+=p.vy*dt; p.vx*=0.95;p.vy*=0.95; if(p.life<=0)sprayParticles.splice(pi,1); } if(S.msgTimer>0){S.msgTimer-=dt;if(S.msgTimer<=0)S.msg="";} // Camera var tcx=S.player.x-CW/2,tcy=S.player.y-CH/2; S.camera.x=lerp(S.camera.x,clamp(tcx,0,S.net.mapW-CW),0.1); S.camera.y=lerp(S.camera.y,clamp(tcy,0,S.net.mapH-CH),0.1); render(); if(frame%5===0)updateHUD(); animId=requestAnimationFrame(gameLoop); } // ─── Action ──────────── function handleAction(){ if(!S)return; var P=S.player; // ── Phase 1 ── if(S.phase===1){ if(!S.rollingStone){ // Pick up nearest unscanned stone var best=null,bd=Infinity; S.stones.forEach(function(st){ if(st.scanned||st.rolling)return; var d=dist(P,st); if(d<55&&d0){ var hit=false; for(var ki=0;ki0.02}); } } } function setMsg(m,d,al){S.msg=m;S.msgTimer=d;S.msgAl=!!al;} // ─── HUD ─────────────── function updateHUD(){ if(!S)return; $("hud-lv").textContent="Lvl "+(curLvl+1); $("hud-sc").textContent="\u2B50 "+S.score; $("hud-ph").textContent=S.phase===1?("\uD83D\uDD0D "+S.stonesScanned+"/"+S.stones.length):"\u26A1 Phase 2"; var kw=$("kid-w"),kf=$("kid-f"); if(S.phase===2){ kw.style.display="flex"; kf.style.width=S.kidneyDmg+"%"; kf.className="bar-f"+(S.kidneyDmg>70?" bar-dn":S.kidneyDmg>40?" bar-wn":" bar-ok"); }else kw.style.display="none"; var te=$("st-tm"); if(S.phase===2&&S.parkedStone&&!S.rollingStone&&S.stoneAloneTimer>0){ var rem=Math.max(0,S.maxAloneTime-S.stoneAloneTimer); te.textContent="\u23F0 "+rem.toFixed(0)+"s"; te.className="st-tm"+(S.stoneAloneTimer>S.maxAloneTime*0.6?" st-d":" st-w"); te.style.display="inline"; }else te.style.display="none"; var bot=$("hud-bot"); if(S.phase===2)bot.classList.add("vis");else bot.classList.remove("vis"); MEDS.forEach(function(m,i){ var b=$("med-"+i),fl=$("mfl-"+i),st=$("mst-"+i); var pct=(S.meds[m.id]/S.medsMax)*100; b.className="med-b"+(S.selMed===i?" sel":""); b.style.borderColor=S.selMed===i?m.color:"#333"; fl.style.width=pct+"%"; fl.style.background=pct<20?"#e53935":m.color; st.textContent=pct<20?"NIEDRIG!":Math.round(pct)+"%"; st.className="med-st"+(pct<20?" med-lo":" med-ok"); }); var mp=$("msg-pop"); if(S.msg){mp.style.display="block";mp.textContent=S.msg;mp.className="msg-pop"+(S.msgAl?" msg-al":"");} else mp.style.display="none"; } // ─── Render ──────────── function render(){ var canvas=$("gc"),ctx=canvas.getContext("2d"); var cam=S.camera,P=S.player,net=S.net,cfg=S.cfg; ctx.save();ctx.clearRect(0,0,CW,CH); // Sky var sg=ctx.createLinearGradient(0,0,0,CH); if(S.phase===1){sg.addColorStop(0,cfg.skyA);sg.addColorStop(1,cfg.skyB);} else{var u=Math.min(1,S.kidneyDmg/80);sg.addColorStop(0,lerpCol(cfg.skyA,"#5a2020",u));sg.addColorStop(1,lerpCol(cfg.skyB,"#3a1515",u));} ctx.fillStyle=sg;ctx.fillRect(0,0,CW,CH); ctx.translate(-cam.x,-cam.y); // Ground ctx.fillStyle=cfg.ground;ctx.fillRect(0,0,net.mapW,net.mapH); // Ground specks ctx.fillStyle="#00000014"; for(var gi=0;gi<250;gi++)ctx.fillRect((gi*137.5)%net.mapW,(gi*97.3)%net.mapH,2,2); // ── Paths ── ctx.lineCap="round";ctx.lineJoin="round"; // Shadow ctx.strokeStyle="#1a150f";ctx.lineWidth=PATH_W+8; net.edges.forEach(function(e){var a=net.nodes[e[0]],b=net.nodes[e[1]];ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.stroke()}); // Main path ctx.strokeStyle="#8a7a5e";ctx.lineWidth=PATH_W; net.edges.forEach(function(e){var a=net.nodes[e[0]],b=net.nodes[e[1]];ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.stroke()}); // Center highlight ctx.strokeStyle="#a0906840";ctx.lineWidth=PATH_W*0.5; net.edges.forEach(function(e){var a=net.nodes[e[0]],b=net.nodes[e[1]];ctx.beginPath();ctx.moveTo(a.x,a.y);ctx.lineTo(b.x,b.y);ctx.stroke()}); // ── Bushes (behind trees) ── var bCols=["#2d6b1e","#3a7a2a","#1e5a14"]; S.veg.bushes.forEach(function(b){ ctx.fillStyle=bCols[b.shade%3]; ctx.beginPath();ctx.arc(b.x,b.y,b.sz,0,Math.PI*2);ctx.fill(); ctx.fillStyle="#22551540"; ctx.beginPath();ctx.arc(b.x-b.sz*0.3,b.y-b.sz*0.3,b.sz*0.5,0,Math.PI*2);ctx.fill(); }); // ── Trees ── var tCols=["#1e6b1e","#2a7a25","#22651a","#2b7030"]; S.veg.trees.forEach(function(t){ ctx.fillStyle="#5a3a1a";ctx.fillRect(t.x-3,t.y,6,t.trunk); ctx.fillStyle="#00000020";ctx.beginPath();ctx.ellipse(t.x,t.y+t.trunk+2,t.sz*0.6,t.sz*0.25,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle=tCols[t.shade%4]; ctx.beginPath();ctx.arc(t.x,t.y-t.sz*0.2,t.sz,0,Math.PI*2);ctx.fill(); ctx.fillStyle="#3a9a3018";ctx.beginPath();ctx.arc(t.x-t.sz*0.25,t.y-t.sz*0.5,t.sz*0.45,0,Math.PI*2);ctx.fill(); }); // ── Keims (yellow crystals with red aura) ── for(var ki=0;ki0.35){ ctx.strokeStyle="rgba(255,50,50,"+(k.size*0.6)+")"; ctx.lineWidth=1.5;ctx.beginPath();ctx.arc(k.x,k.y,r2*2,0,Math.PI*2);ctx.stroke(); } } // ── Spray particles ── sprayParticles.forEach(function(p){ var alpha=p.life*1.5; ctx.fillStyle=p.color.replace(")",","+alpha+")").replace("rgb","rgba"); ctx.beginPath();ctx.arc(p.x,p.y,2+p.life*3,0,Math.PI*2);ctx.fill(); }); // ── Buildings ── drawBldg(ctx,net.lab.x,net.lab.y,"\uD83D\uDD2C","Labor","#304050"); drawBldg(ctx,net.apo.x,net.apo.y,"\uD83D\uDC8A","Apotheke","#2a4a2a"); // Peak ctx.fillStyle="#9a9a8a"; ctx.beginPath();ctx.moveTo(net.peak.x-50,net.peak.y+30);ctx.lineTo(net.peak.x-12,net.peak.y-40);ctx.lineTo(net.peak.x+8,net.peak.y-35);ctx.lineTo(net.peak.x+50,net.peak.y+30);ctx.closePath();ctx.fill(); ctx.fillStyle="#e0e0e0";ctx.beginPath();ctx.moveTo(net.peak.x-18,net.peak.y-22);ctx.lineTo(net.peak.x-12,net.peak.y-40);ctx.lineTo(net.peak.x+8,net.peak.y-35);ctx.lineTo(net.peak.x+14,net.peak.y-20);ctx.closePath();ctx.fill(); ctx.fillStyle="#fff8";ctx.font="11px sans-serif";ctx.textAlign="center";ctx.fillText("\u26F0\uFE0F Gipfel",net.peak.x,net.peak.y+48); // ── Hints (phase 1) ── if(S.phase===1){ var hAlpha=S.cfg.hintAlpha; S.hints.forEach(function(h){ var sp=Math.sin(frame*0.06+h.phase)*0.5+0.5; ctx.fillStyle="rgba(255,240,180,"+(sp*hAlpha)+")"; ctx.beginPath();ctx.arc(h.x,h.y,3+sp*3,0,Math.PI*2);ctx.fill(); }); } // ── Stones ── S.stones.forEach(function(st){ if(st.rolling)return; if(st.scanned&&!st.isCystine){ ctx.globalAlpha=0.25;ctx.fillStyle="#555";drawStone(ctx,st.x,st.y,st.shape,st.rollAngle);ctx.globalAlpha=1;return; } ctx.fillStyle=st.color; drawStone(ctx,st.x,st.y,st.shape,st.rollAngle); if(!st.scanned&&S.phase===1){ ctx.fillStyle="#fff7";ctx.font="11px sans-serif";ctx.textAlign="center";ctx.fillText("?",st.x,st.y-18); } }); // Rolling stone if(S.rollingStone){ var rs=S.rollingStone; ctx.fillStyle=rs.color; drawStone(ctx,rs.x,rs.y,rs.shape,rs.rollAngle); // Rolling dust if(frame%3===0){ ctx.fillStyle="#a0906840"; ctx.beginPath();ctx.arc(rs.x+rng(-8,8),rs.y+rng(8,14),rng(2,4),0,Math.PI*2);ctx.fill(); } } // Parked stone glow if(S.parkedStone){ var gl=Math.sin(frame*0.08)*0.3+0.5; ctx.strokeStyle="rgba(255,200,50,"+gl+")";ctx.lineWidth=2; ctx.beginPath();ctx.arc(S.parkedStone.x,S.parkedStone.y,STONE_R+8,0,Math.PI*2);ctx.stroke(); ctx.fillStyle=S.parkedStone.color; drawStone(ctx,S.parkedStone.x,S.parkedStone.y,S.parkedStone.shape,S.parkedStone.rollAngle); } // ── Player (Sisyphus) ── var bob=Math.sin(P.frame*2)*1.5; var sprinting=P.sprinting; // Shadow ctx.fillStyle="#00000028";ctx.beginPath();ctx.ellipse(P.x,P.y+20+bob,10,4,0,0,Math.PI*2);ctx.fill(); // Head ctx.fillStyle="#d4a060";ctx.beginPath();ctx.arc(P.x,P.y-14+bob,9,0,Math.PI*2);ctx.fill(); // Hair ctx.fillStyle="#5a3a1a";ctx.beginPath();ctx.arc(P.x,P.y-18+bob,6,Math.PI,Math.PI*2);ctx.fill(); // Body ctx.fillStyle=sprinting?"#7a5535":"#8b6040"; ctx.fillRect(P.x-6,P.y-5+bob,12,15); // Arms (extended when pushing) if(S.rollingStone){ var armDx=Math.cos(P.dir)*10,armDy=Math.sin(P.dir)*10; ctx.strokeStyle="#d4a060";ctx.lineWidth=3; ctx.beginPath();ctx.moveTo(P.x,P.y+2+bob);ctx.lineTo(P.x+armDx,P.y+2+bob+armDy);ctx.stroke(); } // Belt ctx.fillStyle="#4a3020";ctx.fillRect(P.x-7,P.y+3+bob,14,3); // Legs ctx.fillStyle="#6a4830"; var lOff=Math.sin(P.frame*3)*(sprinting?3:1.5); ctx.fillRect(P.x-5,P.y+10+bob+lOff,4,8); ctx.fillRect(P.x+1,P.y+10+bob-lOff,4,8); // Sprint trail if(sprinting&&frame%2===0){ ctx.fillStyle="#a0906830"; ctx.beginPath();ctx.arc(P.x-Math.cos(P.dir)*12,P.y-Math.sin(P.dir)*12+20,rng(2,4),0,Math.PI*2);ctx.fill(); } // ── Prompts ── ctx.font="bold 11px sans-serif";ctx.textAlign="center"; if(S.phase===1){ S.stones.forEach(function(st){ if(!st.scanned&&!st.rolling&&dist(P,st)<55){ ctx.fillStyle="#ffffffcc";ctx.fillText("[Leertaste] Aufheben",st.x,st.y-22); } }); if(S.rollingStone&&dist(P,net.lab)<65){ ctx.fillStyle="#00ff88cc";ctx.font="bold 12px sans-serif"; ctx.fillText("[Leertaste] Scannen!",net.lab.x,net.lab.y-48); } } if(S.phase===2){ if(S.parkedStone&&!S.rollingStone&&dist(P,S.parkedStone)<55){ ctx.fillStyle="#ffcc00cc";ctx.fillText("[Leertaste] Aufheben",S.parkedStone.x,S.parkedStone.y-STONE_R-14); } if(S.rollingStone&&S.rollingStone.isCystine){ ctx.fillStyle="#88aaffcc";ctx.font="10px sans-serif";ctx.fillText("[Leertaste] Parken",S.rollingStone.x,S.rollingStone.y-STONE_R-14); } if(dist(P,net.apo)<65){ ctx.fillStyle="#00ff88cc";ctx.fillText("[Leertaste] Auff\u00FCllen",net.apo.x,net.apo.y-48); } for(var ki=0;ki0){ ctx.fillStyle="#ffcc44cc";ctx.font="10px sans-serif"; ctx.fillText("[Leertaste] Spr\u00FChen",k.x,k.y-k.size*30-12); break; // only show one prompt } } } // ── Minimap ── drawMinimap(ctx,cam); ctx.restore(); } // ─── Draw helpers ────── function drawStone(ctx,x,y,shape,angle){ ctx.save();ctx.translate(x,y);ctx.rotate(angle||0); ctx.beginPath(); if(shape===0)ctx.ellipse(0,0,STONE_R,STONE_R*0.72,0,0,Math.PI*2); else if(shape===1){ctx.moveTo(-STONE_R,STONE_R*0.6);ctx.lineTo(-STONE_R*0.5,-STONE_R*0.8);ctx.lineTo(STONE_R*0.8,-STONE_R*0.5);ctx.lineTo(STONE_R,STONE_R*0.6);ctx.closePath();} else ctx.arc(0,0,STONE_R,0,Math.PI*2); ctx.fill();ctx.strokeStyle="#00000030";ctx.lineWidth=1;ctx.stroke(); // Stone texture ctx.fillStyle="#00000015";ctx.beginPath();ctx.arc(STONE_R*0.2,-STONE_R*0.2,STONE_R*0.3,0,Math.PI*2);ctx.fill(); ctx.fillStyle="#ffffff10";ctx.beginPath();ctx.arc(-STONE_R*0.3,-STONE_R*0.3,STONE_R*0.25,0,Math.PI*2);ctx.fill(); ctx.restore(); } function drawCrystal(ctx,x,y,r){ ctx.beginPath(); var spikes=7; for(var i=0;i