๐ŸŽฎArcadeLab

Mini Minecraft 2D

by ShadowLegend17
2403 lines122.1 KB
โ–ถ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Mini Minecraft 2D</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent;}
html,body{width:100%;height:100%;overflow:hidden;background:#000;font-family:monospace;user-select:none;}
#cvs{position:absolute;inset:0;width:100%;height:100%;cursor:crosshair;touch-action:none;}
#ui{position:absolute;inset:0;pointer-events:none;}
.btn{pointer-events:all;position:absolute;display:flex;align-items:center;justify-content:center;
  background:rgba(15,15,15,0.88);color:#fff;border:2px solid rgba(255,255,255,0.2);
  border-radius:14px;font-weight:bold;font-size:20px;cursor:pointer;touch-action:none;user-select:none;}
#inv-overlay{display:none;position:absolute;inset:0;background:rgba(0,0,0,0.82);
  align-items:center;justify-content:center;z-index:20;pointer-events:all;backdrop-filter:blur(4px);}
#inv-overlay.open{display:flex;}
#inv-box{background:linear-gradient(150deg,#232333,#181828);border:2px solid rgba(255,255,255,0.12);
  border-radius:18px;padding:16px;display:flex;flex-direction:column;gap:10px;
  box-shadow:0 8px 40px rgba(0,0,0,0.9);max-width:96vw;max-height:90vh;overflow-y:auto;width:min(580px,96vw);}
.inv-tab{flex:1;text-align:center;padding:8px 0;font-weight:bold;font-size:13px;
  background:transparent;color:rgba(255,255,255,0.38);border-radius:8px;cursor:pointer;border:none;}
.inv-tab.active{background:rgba(255,255,255,0.1);color:#fff;}
.slot{width:52px;height:52px;border:2px solid rgba(255,255,255,0.14);border-radius:8px;
  cursor:pointer;position:relative;display:flex;align-items:flex-end;justify-content:flex-end;
  overflow:hidden;flex-shrink:0;transition:border-color .1s;}
.slot.selected{border-color:#ffe066;box-shadow:0 0 14px rgba(255,224,102,.65);}
.slot-num{position:absolute;top:2px;left:4px;font-size:8px;color:rgba(255,255,255,.38);}
.slot-count{font-size:9px;color:#fff;font-weight:bold;text-shadow:1px 1px 2px #000;padding:1px 3px;}
.recipe-row{background:rgba(20,20,30,.45);border:1px solid rgba(255,255,255,.07);border-radius:10px;
  padding:8px;display:flex;align-items:center;gap:8px;}
.recipe-row.can{background:rgba(30,60,30,.5);border-color:rgba(70,160,70,.25);}
.craft-btn{padding:7px 10px;border-radius:8px;font-size:11px;font-weight:bold;cursor:pointer;
  background:rgba(40,40,45,.7);color:rgba(255,255,255,.25);border:1.5px solid rgba(255,255,255,.06);
  white-space:nowrap;font-family:monospace;}
.craft-btn.can{background:rgba(45,145,45,.9);color:#fff;border-color:rgba(80,200,80,.35);}
#dead{display:none;position:absolute;inset:0;background:rgba(80,0,0,.85);flex-direction:column;
  align-items:center;justify-content:center;z-index:50;pointer-events:all;backdrop-filter:blur(4px);}
#dead.show{display:flex;}
#respawn-btn{padding:14px 36px;background:rgba(50,180,50,.9);color:#fff;font-weight:bold;
  font-size:18px;border-radius:12px;cursor:pointer;border:2px solid rgba(80,220,80,.5);pointer-events:all;}
</style>
</head>
<body>
<canvas id="cvs"></canvas>
<div id="ui">
  <!-- Touch D-Pad left -->
  <div id="btn-sleep" class="btn" style="bottom:94px;left:14px;width:152px;height:50px;font-size:16px;border-radius:12px;opacity:0.35;">๐Ÿ˜ด Sleep</div>
  <div id="btn-eat" class="btn" style="bottom:94px;right:8px;width:90px;height:50px;font-size:15px;border-radius:12px;background:rgba(30,100,30,.85);border-color:rgba(80,200,80,.4);opacity:0.35;">๐ŸŽ Eat</div>
  <div id="btn-l" class="btn" style="bottom:14px;left:14px;width:70px;height:70px;">โ—€</div>
  <div id="btn-r" class="btn" style="bottom:14px;left:94px;width:70px;height:70px;">โ–ถ</div>
  <!-- Right buttons -->
  <div id="btn-atk" class="btn" style="bottom:14px;right:220px;width:70px;height:70px;background:rgba(120,20,20,.9);border-color:rgba(255,80,80,.4);">โš”๏ธ</div>
  <div id="btn-mode" class="btn" style="bottom:14px;right:112px;width:100px;height:70px;background:rgba(160,48,18,.9);">โ› Mine</div>
  <div id="btn-jump" class="btn" style="bottom:14px;right:8px;width:98px;height:70px;">โ†‘ Jump</div>
  <div id="btn-down" class="btn" style="bottom:14px;right:114px;width:80px;height:70px;display:none;background:rgba(30,30,120,.88);border-color:rgba(100,100,255,.4);">โฌ‡ Down</div>
  <div id="btn-noclip" class="btn" style="bottom:94px;left:174px;width:110px;height:50px;font-size:13px;border-radius:12px;display:none;background:rgba(120,0,120,.88);border-color:rgba(200,80,255,.5);">๐Ÿ‘ป Noclip: ON</div>
  <!-- Bag -->
  <div id="btn-bag" class="btn" style="top:10px;right:10px;height:38px;padding:0 14px;border-radius:10px;font-size:12px;letter-spacing:.5px;pointer-events:all;">๐ŸŽ’ Bag [E]</div>
  <div id="btn-menu" class="btn" style="top:56px;right:10px;height:38px;padding:0 14px;border-radius:10px;font-size:12px;pointer-events:all;background:rgba(80,40,0,.88);border-color:rgba(255,180,50,.35);">๐Ÿ’พ Menu</div>
</div>

<!-- Inventory Overlay -->
<div id="inv-overlay">
  <div id="inv-box">
    <div style="display:flex;align-items:center;gap:8px;">
      <span style="color:#fff;font-weight:bold;font-size:15px;flex:1;">๐ŸŽ’ Inventory & Crafting</span>
      <span id="inv-tip" style="color:#ffe066;font-size:11px;background:rgba(255,224,102,.1);padding:2px 8px;border-radius:5px;display:none;"></span>
      <span id="inv-close" style="color:rgba(255,255,255,.45);font-size:20px;cursor:pointer;padding:0 4px;pointer-events:all;">โœ•</span>
    </div>
    <div style="display:flex;gap:3px;background:rgba(0,0,0,.3);padding:4px;border-radius:10px;">
      <button class="inv-tab active" id="tab-items" onclick="switchTab('items')">๐ŸŽ’ Items</button>
      <button class="inv-tab" id="tab-craft" onclick="switchTab('craft')">โš’๏ธ Craft</button>
    </div>
    <div id="tab-items-content">
      <p style="color:rgba(255,255,255,.28);font-size:9px;text-align:center;margin-bottom:6px;">Tap to select ยท tap another to move/swap</p>
      <div style="color:rgba(255,255,255,.4);font-size:10px;margin-bottom:4px;">BACKPACK</div>
      <div id="inv-grid" style="display:grid;grid-template-columns:repeat(8,1fr);gap:3px;"></div>
      <div style="height:1px;background:rgba(255,255,255,.07);margin:8px 0;"></div>
      <div style="color:rgba(255,255,255,.4);font-size:10px;margin-bottom:4px;">HOTBAR</div>
      <div id="hot-grid" style="display:flex;gap:3px;flex-wrap:wrap;"></div>
    </div>
    <div id="tab-craft-content" style="display:none;">
      <div id="craft-msg" style="display:none;background:rgba(50,160,50,.18);border:1px solid rgba(70,180,70,.3);color:#8ddd50;font-size:12px;padding:6px 10px;border-radius:8px;text-align:center;margin-bottom:6px;"></div>
      <div id="recipe-list" style="display:flex;flex-direction:column;gap:5px;"></div>
    </div>
  </div>
</div>

<!-- Main Menu -->
<div id="menu" style="position:absolute;inset:0;z-index:100;display:flex;flex-direction:column;
  align-items:center;justify-content:center;
  background:linear-gradient(180deg,#1a3a1a 0%,#0a1a0a 60%,#000 100%);
  font-family:monospace;">
  <!-- Pixel art sky strip -->
  <div style="position:absolute;top:0;left:0;right:0;height:38%;background:linear-gradient(180deg,#1a6090 0%,#3390cc 100%);"></div>
  <!-- Ground strip -->
  <div style="position:absolute;bottom:0;left:0;right:0;height:30%;background:linear-gradient(180deg,#5c9e30 0%,#8B6340 30%,#555 100%);"></div>
  <!-- Title -->
  <div style="position:relative;z-index:2;text-align:center;margin-bottom:32px;">
    <div style="font-size:clamp(28px,6vw,52px);font-weight:bold;color:#fff;
      text-shadow:4px 4px 0 #000,-2px -2px 0 #000,2px -2px 0 #000,-2px 2px 0 #000;
      letter-spacing:2px;line-height:1.1;">โ› Mini Minecraft 2D</div>
    <div style="color:#ffe066;font-size:14px;margin-top:8px;text-shadow:1px 1px 2px #000;">Choose your game mode</div>
  </div>
  <!-- Mode buttons -->
  <div style="position:relative;z-index:2;display:flex;flex-direction:column;gap:14px;width:min(340px,88vw);">
  <div id="btn-resume" style="padding:16px 24px;background:rgba(30,80,130,.95);color:#fff;
    border:3px solid rgba(80,180,255,.55);border-radius:14px;cursor:pointer;text-align:center;
    font-size:17px;font-weight:bold;letter-spacing:1px;display:none;
    box-shadow:0 4px 20px rgba(50,150,255,.25);-webkit-tap-highlight-color:transparent;">
    โ–ถ Back to World
    <div style="font-size:11px;color:rgba(255,255,255,.55);font-weight:normal;margin-top:3px;">Resume where you left off</div>
  </div>
  <div id="btn-continue" style="padding:14px 24px;background:rgba(140,100,30,.92);color:#fff;
    border:3px solid rgba(255,200,80,.5);border-radius:14px;cursor:pointer;text-align:center;
    font-size:16px;font-weight:bold;letter-spacing:1px;display:none;
    box-shadow:0 4px 20px rgba(200,150,0,.2);-webkit-tap-highlight-color:transparent;">
    ๐ŸŒ Continue World
    <div style="font-size:11px;color:rgba(255,255,255,.6);font-weight:normal;margin-top:4px;" id="save-info">Loadingโ€ฆ</div>
  </div>
    <div id="btn-survival" style="padding:18px 24px;background:rgba(60,120,60,.92);color:#fff;
      border:3px solid rgba(100,220,100,.5);border-radius:14px;cursor:pointer;text-align:center;
      font-size:18px;font-weight:bold;letter-spacing:1px;
      box-shadow:0 4px 20px rgba(0,200,0,.2);transition:all .15s;
      -webkit-tap-highlight-color:transparent;">
      โš”๏ธ Survival
      <div style="font-size:11px;color:rgba(255,255,255,.6);font-weight:normal;margin-top:4px;">Mine, craft, fight to survive</div>
    </div>
    <div id="btn-creative" style="padding:18px 24px;background:rgba(60,60,180,.92);color:#fff;
      border:3px solid rgba(100,150,255,.5);border-radius:14px;cursor:pointer;text-align:center;
      font-size:18px;font-weight:bold;letter-spacing:1px;
      box-shadow:0 4px 20px rgba(80,80,255,.2);transition:all .15s;
      -webkit-tap-highlight-color:transparent;">
      ๐ŸŒŸ Creative
      <div style="font-size:11px;color:rgba(255,255,255,.6);font-weight:normal;margin-top:4px;">Infinite items ยท Fly ยท No damage</div>
    </div>
    <div id="btn-flat" style="padding:14px 24px;background:rgba(100,60,20,.92);color:#fff;
      border:3px solid rgba(200,140,60,.5);border-radius:14px;cursor:pointer;text-align:center;
      font-size:16px;font-weight:bold;letter-spacing:1px;
      box-shadow:0 4px 20px rgba(150,100,30,.2);-webkit-tap-highlight-color:transparent;">
      ๐ŸŒ Flat World
      <div style="font-size:11px;color:rgba(255,255,255,.6);font-weight:normal;margin-top:4px;">Totally flat ยท Creative mode</div>
    </div>
  </div>
  <!-- Footer -->
  <div style="position:relative;z-index:2;margin-top:28px;color:rgba(255,255,255,.3);font-size:10px;">
    Use creator code CHROME-MAKER-41 on arcadelab.ai
  </div>
</div>

<!-- Chest overlay -->
<div id="chest-overlay" style="display:none;position:absolute;inset:0;background:rgba(0,0,0,.82);
  align-items:center;justify-content:center;z-index:20;pointer-events:all;backdrop-filter:blur(4px);">
  <div id="chest-box" style="background:linear-gradient(150deg,#2a1a0a,#1a1008);border:2px solid rgba(200,144,26,.35);
    border-radius:18px;padding:16px;display:flex;flex-direction:column;gap:10px;
    box-shadow:0 8px 40px rgba(0,0,0,.9);max-width:96vw;max-height:90vh;overflow-y:auto;width:min(560px,96vw);">
    <div style="display:flex;align-items:center;gap:8px;">
      <span style="color:#f0c060;font-weight:bold;font-size:15px;flex:1;">๐Ÿ“ฆ Chest</span>
      <span id="chest-tip" style="color:#ffe066;font-size:11px;background:rgba(255,224,102,.1);padding:2px 8px;border-radius:5px;display:none;"></span>
      <span id="chest-close" style="color:rgba(255,255,255,.45);font-size:20px;cursor:pointer;padding:0 4px;pointer-events:all;">โœ•</span>
    </div>
    <p style="color:rgba(255,255,255,.28);font-size:9px;text-align:center;margin:0;">Tap to select ยท tap another to move or swap</p>
    <div>
      <div style="color:rgba(255,200,80,.5);font-size:10px;margin-bottom:4px;">CHEST</div>
      <div id="chest-grid" style="display:grid;grid-template-columns:repeat(6,1fr);gap:3px;"></div>
    </div>
    <div style="height:1px;background:rgba(255,255,255,.07);"></div>
    <div>
      <div style="color:rgba(255,255,255,.4);font-size:10px;margin-bottom:4px;">BACKPACK</div>
      <div id="chest-inv-grid" style="display:grid;grid-template-columns:repeat(8,1fr);gap:3px;"></div>
    </div>
    <div style="height:1px;background:rgba(255,255,255,.07);"></div>
    <div>
      <div style="color:rgba(255,255,255,.4);font-size:10px;margin-bottom:4px;">HOTBAR</div>
      <div id="chest-hot-grid" style="display:flex;gap:3px;flex-wrap:wrap;"></div>
    </div>
  </div>
</div>

<!-- Death screen -->
<div id="dead">
  <div style="font-size:52px;margin-bottom:12px;">๐Ÿ’€</div>
  <div style="color:#ff4444;font-weight:bold;font-size:28px;margin-bottom:8px;text-shadow:0 0 20px #f00;">YOU DIED</div>
  <div style="color:rgba(255,255,255,.5);font-size:14px;margin-bottom:28px;">The zombies got you!</div>
  <div id="respawn-btn">โ†ฉ Respawn</div>
</div>

<script>
// โ”€โ”€ Constants โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const BS=40,WW=130,WH=64,PW=20,PH=38;
const GRAVITY=.45,MAX_FALL=16,MOVE_SPD=4.2,JUMP_VEL=-6;
const DAY_LEN=4200,NIGHT_LEN=2800,CYCLE=DAY_LEN+NIGHT_LEN;
const MAX_HP=10,MAX_ZOMBIES=10,ZOMBIE_SPD=1.8,ZOMBIE_HP=6,ZW=18,ZH=36;
const MAX_CREEPERS=5,CREEPER_SPD=1.5,CREEPER_HP=4,CW=16,CH=34,CREEPER_FUSE=180,CREEPER_RANGE=2.2*40;

const B={AIR:0,GRASS:1,DIRT:2,STONE:3,WOOD:4,LEAVES:5,SAND:6,COAL:7,BEDROCK:8,PLANK:9,GLASS:10,IRON:11,TORCH:12,DOOR:13,STICK:14,WOOD_PICK:15,STONE_PICK:16,IRON_PICK:17,WOOD_AXE:18,STONE_AXE:19,IRON_AXE:20,IRON_SWORD:21,REDSTONE:22,EMERALD:23,DIAMOND:24,RS_PICK:25,RS_AXE:26,RS_SWORD:27,EM_PICK:28,EM_AXE:29,EM_SWORD:30,DM_PICK:31,DM_AXE:32,DM_SWORD:33,IR_HELM:34,IR_CHEST:35,IR_BOOT:36,RS_HELM:37,RS_CHEST:38,RS_BOOT:39,EM_HELM:40,EM_CHEST:41,EM_BOOT:42,DM_HELM:43,DM_CHEST:44,DM_BOOT:45,BED:46,BOW:47,ARROW:48,DISPENSER:49,STONE_BRICK:50,CHEST:51,GUNPOWDER:52,TNT:53,FLINT:54,FLINT_STEEL:55,END_STONE:56,END_PORTAL:57,OBSIDIAN:58,DRAGON_EGG:59,EYE_END:60,BREAD:61,APPLE:62,MEAT:63,STEW:64,SUPPORT:65,FENCE:66,EGG_ZOMBIE:67,EGG_CREEPER:68,LAVA:69,BARRIER:70,PISTON:71,STICKY_PISTON:72,GATE:73,TRAPDOOR:74,CONV_R:75,CONV_L:76};

const DATA={
  [B.GRASS]:{col:"#5c9e30",top:"#6ecf35",name:"Grass",mt:22,drop:B.DIRT,pl:true},
  [B.DIRT]:{col:"#8B6340",name:"Dirt",mt:22,drop:B.DIRT,pl:true},
  [B.STONE]:{col:"#787878",name:"Stone",mt:80,drop:B.STONE_BRICK,pl:true},
  [B.WOOD]:{col:"#8B6510",top:"#c8a020",name:"Wood",mt:55,drop:B.WOOD,pl:true},
  [B.LEAVES]:{col:"#2d7a20",name:"Leaves",mt:8,drop:B.LEAVES,pl:true},
  [B.SAND]:{col:"#d4c47a",name:"Sand",mt:22,drop:B.SAND,pl:true},
  [B.COAL]:{col:"#484848",name:"Coal",mt:90,drop:B.COAL,pl:true},
  [B.BEDROCK]:{col:"#1c1c1c",name:"Bedrock",mt:9999,drop:B.AIR,pl:true},
  [B.PLANK]:{col:"#c8a440",name:"Plank",mt:40,drop:B.PLANK,pl:true},
  [B.GLASS]:{col:"#90d0e8",name:"Glass",mt:15,drop:B.AIR,pl:true},
  [B.IRON]:{col:"#c0b8a8",name:"Iron Ore",mt:110,drop:B.IRON,pl:true},
  [B.REDSTONE]:{col:"#6a1818",name:"Redstone Ore",mt:100,drop:B.REDSTONE,pl:true},
  [B.EMERALD]:{col:"#1a5c2a",name:"Emerald Ore",mt:140,drop:B.EMERALD,pl:true},
  [B.DIAMOND]:{col:"#1a5a7a",name:"Diamond Ore",mt:200,drop:B.DIAMOND,pl:true},
  [B.TORCH]:{col:"#ff9020",name:"Torch",mt:5,drop:B.TORCH,pl:true,pass:true},
  [B.DOOR]:{col:"#a07040",name:"Door",mt:30,drop:B.DOOR,pl:true,isDoor:true},
  [B.BED]:{col:"#d04040",name:"Bed",mt:20,drop:B.BED,pl:true,isBed:true},
  [B.STICK]:{col:"#a07040",name:"Stick",icon:"๐Ÿชต",pl:false},
  [B.WOOD_PICK]:{col:"#c8a060",name:"Wood Pickaxe",icon:"โ›",pl:false,tc:"pick",tm:2},
  [B.STONE_PICK]:{col:"#909090",name:"Stone Pickaxe",icon:"โ›",pl:false,tc:"pick",tm:4},
  [B.IRON_PICK]:{col:"#d0e0f0",name:"Iron Pickaxe",icon:"โ›",pl:false,tc:"pick",tm:8},
  [B.WOOD_AXE]:{col:"#c8a060",name:"Wood Axe",icon:"๐Ÿช“",pl:false,tc:"axe",tm:2},
  [B.STONE_AXE]:{col:"#909090",name:"Stone Axe",icon:"๐Ÿช“",pl:false,tc:"axe",tm:4},
  [B.IRON_AXE]:{col:"#d0e0f0",name:"Iron Axe",icon:"๐Ÿช“",pl:false,tc:"axe",tm:8},
  [B.IRON_SWORD]:{col:"#d0e0f0",name:"Iron Sword",icon:"โš”๏ธ",pl:false,tc:"sword",tm:1,sdmg:2},
  [B.RS_PICK]:{col:"#cc2200",name:"Redstone Pickaxe",icon:"โ›",pl:false,tc:"pick",tm:12},
  [B.RS_AXE]:{col:"#cc2200",name:"Redstone Axe",icon:"๐Ÿช“",pl:false,tc:"axe",tm:12},
  [B.RS_SWORD]:{col:"#cc2200",name:"Redstone Sword",icon:"โš”๏ธ",pl:false,tc:"sword",tm:1,sdmg:3},
  [B.EM_PICK]:{col:"#20aa50",name:"Emerald Pickaxe",icon:"โ›",pl:false,tc:"pick",tm:18},
  [B.EM_AXE]:{col:"#20aa50",name:"Emerald Axe",icon:"๐Ÿช“",pl:false,tc:"axe",tm:18},
  [B.EM_SWORD]:{col:"#20aa50",name:"Emerald Sword",icon:"โš”๏ธ",pl:false,tc:"sword",tm:1,sdmg:4},
  [B.DM_PICK]:{col:"#20c8e8",name:"Diamond Pickaxe",icon:"โ›",pl:false,tc:"pick",tm:28},
  [B.DM_AXE]:{col:"#20c8e8",name:"Diamond Axe",icon:"๐Ÿช“",pl:false,tc:"axe",tm:28},
  [B.DM_SWORD]:{col:"#20c8e8",name:"Diamond Sword",icon:"โš”๏ธ",pl:false,tc:"sword",tm:1,sdmg:6},
  [B.IR_HELM]:{col:"#c0b8a8",name:"Iron Helmet",icon:"๐Ÿช–",pl:false,def:1},
  [B.IR_CHEST]:{col:"#c0b8a8",name:"Iron Chestplate",icon:"๐Ÿ›ก๏ธ",pl:false,def:2},
  [B.IR_BOOT]:{col:"#c0b8a8",name:"Iron Boots",icon:"๐Ÿ‘Ÿ",pl:false,def:1},
  [B.RS_HELM]:{col:"#cc2200",name:"Redstone Helmet",icon:"๐Ÿช–",pl:false,def:2},
  [B.RS_CHEST]:{col:"#cc2200",name:"Redstone Chestplate",icon:"๐Ÿ›ก๏ธ",pl:false,def:4},
  [B.RS_BOOT]:{col:"#cc2200",name:"Redstone Boots",icon:"๐Ÿ‘Ÿ",pl:false,def:2},
  [B.EM_HELM]:{col:"#20aa50",name:"Emerald Helmet",icon:"๐Ÿช–",pl:false,def:3},
  [B.EM_CHEST]:{col:"#20aa50",name:"Emerald Chestplate",icon:"๐Ÿ›ก๏ธ",pl:false,def:6},
  [B.EM_BOOT]:{col:"#20aa50",name:"Emerald Boots",icon:"๐Ÿ‘Ÿ",pl:false,def:3},
  [B.DM_HELM]:{col:"#20c8e8",name:"Diamond Helmet",icon:"๐Ÿช–",pl:false,def:5},
  [B.DM_CHEST]:{col:"#20c8e8",name:"Diamond Chestplate",icon:"๐Ÿ›ก๏ธ",pl:false,def:10},
  [B.DM_BOOT]:{col:"#20c8e8",name:"Diamond Boots",icon:"๐Ÿ‘Ÿ",pl:false,def:5},
  [B.BOW]:{col:"#a07040",name:"Bow",icon:"๐Ÿน",pl:false,isBow:true},
  [B.ARROW]:{col:"#c8a060",name:"Arrow",icon:"โžถ",pl:false,isArrow:true},
  [B.DISPENSER]:{col:"#606060",name:"Dispenser",mt:60,drop:B.DISPENSER,pl:true,isDispenser:true},
  [B.STONE_BRICK]:{col:"#888",name:"Stone Brick",mt:80,drop:B.STONE_BRICK,pl:true},
  [B.CHEST]:{col:"#c8901a",name:"Chest",mt:30,drop:B.CHEST,pl:true,isChest:true},
  [B.GUNPOWDER]:{col:"#888",name:"Gunpowder",icon:"๐Ÿ’ฃ",pl:false},
  [B.TNT]:{col:"#cc2222",name:"TNT",mt:10,drop:B.TNT,pl:true,isTNT:true},
  [B.FLINT]:{col:"#555",name:"Flint",icon:"๐Ÿชจ",pl:false},
  [B.FLINT_STEEL]:{col:"#aaa",name:"Flint & Steel",icon:"๐Ÿ”ฅ",pl:false,isFlintSteel:true},
  [B.END_STONE]:{col:"#d4d48a",name:"End Stone",mt:50,drop:B.END_STONE,pl:true},
  [B.END_PORTAL]:{col:"#1a004a",name:"End Portal",mt:9999,drop:B.AIR,pl:true,isPortal:true},
  [B.OBSIDIAN]:{col:"#1a0a2e",name:"Obsidian",mt:200,drop:B.OBSIDIAN,pl:true},
  [B.DRAGON_EGG]:{col:"#220033",name:"Dragon Egg",icon:"๐Ÿฅš",pl:false},
  [B.EYE_END]:{col:"#60ff80",name:"Eye of Ender",icon:"๐Ÿ‘๏ธ",pl:false},
  [B.SUPPORT]:{col:"#8B6510",name:"Support Beam",mt:40,drop:B.WOOD,pl:false,pass:true},
  [B.FENCE]:{col:"#c8a440",name:"Fence",mt:30,drop:B.FENCE,pl:true,pass:true},
  [B.GATE]:{col:"#c8a440",name:"Fence Gate",mt:30,drop:B.GATE,pl:true,isDoor:true},
  [B.TRAPDOOR]:{col:"#c8a440",name:"Trapdoor",mt:30,drop:B.TRAPDOOR,pl:true,isTrapdoor:true},
  [B.CONV_R]:{col:"#555",name:"Conveyor (Right)",mt:40,drop:B.CONV_R,pl:true,conveyor:1},
  [B.CONV_L]:{col:"#555",name:"Conveyor (Left)",mt:40,drop:B.CONV_L,pl:true,conveyor:-1},
  [B.LAVA]:{col:"#ff5500",name:"Lava",mt:9999,drop:B.AIR,pl:true,pass:true,isLava:true},
  [B.BARRIER]:{col:"rgba(255,50,50,.15)",name:"Barrier",mt:9999,drop:B.AIR,pl:true,isBarrier:true},
  [B.PISTON]:{col:"#8a8a8a",name:"Piston",mt:50,drop:B.PISTON,pl:true,isPiston:true},
  [B.STICKY_PISTON]:{col:"#4a7a4a",name:"Sticky Piston",mt:50,drop:B.STICKY_PISTON,pl:true,isPiston:true,isSticky:true},
  [B.EGG_ZOMBIE]:{col:"#3a8c3a",name:"Zombie Spawn Egg",icon:"๐Ÿฅš",pl:false,spawnEgg:"zombie"},
  [B.EGG_CREEPER]:{col:"#2d7a20",name:"Creeper Spawn Egg",icon:"๐Ÿฅš",pl:false,spawnEgg:"creeper"},
  [B.BREAD]:{col:"#c8903a",name:"Bread",icon:"๐Ÿž",pl:false,food:3},
  [B.APPLE]:{col:"#dd2020",name:"Apple",icon:"๐ŸŽ",pl:false,food:2},
  [B.MEAT]:{col:"#8B2500",name:"Cooked Meat",icon:"๐Ÿ–",pl:false,food:4},
  [B.STEW]:{col:"#8B5a00",name:"Mushroom Stew",icon:"๐Ÿฒ",pl:false,food:5},
};

const PICK_SET=new Set([B.STONE,B.COAL,B.IRON,B.BEDROCK,B.REDSTONE,B.EMERALD,B.DIAMOND,B.STONE_BRICK,B.END_STONE,B.OBSIDIAN]);
// Silk Touch: maps block โ†’ what it drops with ST
const SILK_DROPS={[B.GRASS]:B.GRASS,[B.STONE]:B.STONE,[B.GLASS]:B.GLASS,[B.LEAVES]:B.LEAVES};
const AXE_SET=new Set([B.WOOD,B.LEAVES,B.PLANK,B.DOOR]);

const RECIPES=[
  {name:"Planks",res:{b:B.PLANK,q:4},ing:[{b:B.WOOD,q:1}]},
  {name:"Sticks",res:{b:B.STICK,q:4},ing:[{b:B.PLANK,q:2}]},
  {name:"Torches x4",res:{b:B.TORCH,q:4},ing:[{b:B.COAL,q:1},{b:B.STICK,q:1}]},
  {name:"Door x2",res:{b:B.DOOR,q:2},ing:[{b:B.PLANK,q:6}]},
  {name:"Wood Pickaxe",res:{b:B.WOOD_PICK,q:1},ing:[{b:B.PLANK,q:3},{b:B.STICK,q:2}]},
  {name:"Stone Pickaxe",res:{b:B.STONE_PICK,q:1},ing:[{b:B.STONE,q:3},{b:B.STICK,q:2}]},
  {name:"Stone Pickaxe (Brick)",res:{b:B.STONE_PICK,q:1},ing:[{b:B.STONE_BRICK,q:3},{b:B.STICK,q:2}]},
  {name:"Iron Pickaxe",res:{b:B.IRON_PICK,q:1},ing:[{b:B.IRON,q:3},{b:B.STICK,q:2}]},
  {name:"Wood Axe",res:{b:B.WOOD_AXE,q:1},ing:[{b:B.PLANK,q:2},{b:B.STICK,q:2}]},
  {name:"Stone Axe",res:{b:B.STONE_AXE,q:1},ing:[{b:B.STONE,q:2},{b:B.STICK,q:2}]},
  {name:"Iron Axe",res:{b:B.IRON_AXE,q:1},ing:[{b:B.IRON,q:2},{b:B.STICK,q:2}]},
  {name:"Iron Sword",res:{b:B.IRON_SWORD,q:1},ing:[{b:B.IRON,q:2},{b:B.STICK,q:1}]},
  {name:"Redstone Pickaxe",res:{b:B.RS_PICK,q:1},ing:[{b:B.REDSTONE,q:3},{b:B.STICK,q:2}]},
  {name:"Redstone Axe",res:{b:B.RS_AXE,q:1},ing:[{b:B.REDSTONE,q:2},{b:B.STICK,q:2}]},
  {name:"Redstone Sword",res:{b:B.RS_SWORD,q:1},ing:[{b:B.REDSTONE,q:2},{b:B.STICK,q:1}]},
  {name:"Emerald Pickaxe",res:{b:B.EM_PICK,q:1},ing:[{b:B.EMERALD,q:3},{b:B.STICK,q:2}]},
  {name:"Emerald Axe",res:{b:B.EM_AXE,q:1},ing:[{b:B.EMERALD,q:2},{b:B.STICK,q:2}]},
  {name:"Emerald Sword",res:{b:B.EM_SWORD,q:1},ing:[{b:B.EMERALD,q:2},{b:B.STICK,q:1}]},
  {name:"Diamond Pickaxe",res:{b:B.DM_PICK,q:1},ing:[{b:B.DIAMOND,q:3},{b:B.STICK,q:2}]},
  {name:"Diamond Axe",res:{b:B.DM_AXE,q:1},ing:[{b:B.DIAMOND,q:2},{b:B.STICK,q:2}]},
  {name:"Diamond Sword",res:{b:B.DM_SWORD,q:1},ing:[{b:B.DIAMOND,q:2},{b:B.STICK,q:1}]},
  {name:"Iron Helmet",res:{b:B.IR_HELM,q:1},ing:[{b:B.IRON,q:3}]},
  {name:"Iron Chestplate",res:{b:B.IR_CHEST,q:1},ing:[{b:B.IRON,q:5}]},
  {name:"Iron Boots",res:{b:B.IR_BOOT,q:1},ing:[{b:B.IRON,q:2}]},
  {name:"Redstone Helmet",res:{b:B.RS_HELM,q:1},ing:[{b:B.REDSTONE,q:3}]},
  {name:"Redstone Chestplate",res:{b:B.RS_CHEST,q:1},ing:[{b:B.REDSTONE,q:5}]},
  {name:"Redstone Boots",res:{b:B.RS_BOOT,q:1},ing:[{b:B.REDSTONE,q:2}]},
  {name:"Emerald Helmet",res:{b:B.EM_HELM,q:1},ing:[{b:B.EMERALD,q:3}]},
  {name:"Emerald Chestplate",res:{b:B.EM_CHEST,q:1},ing:[{b:B.EMERALD,q:5}]},
  {name:"Emerald Boots",res:{b:B.EM_BOOT,q:1},ing:[{b:B.EMERALD,q:2}]},
  {name:"Diamond Helmet",res:{b:B.DM_HELM,q:1},ing:[{b:B.DIAMOND,q:3}]},
  {name:"Diamond Chestplate",res:{b:B.DM_CHEST,q:1},ing:[{b:B.DIAMOND,q:5}]},
  {name:"Diamond Boots",res:{b:B.DM_BOOT,q:1},ing:[{b:B.DIAMOND,q:2}]},
  {name:"Bed",res:{b:B.BED,q:1},ing:[{b:B.PLANK,q:3},{b:B.LEAVES,q:3}]},
  {name:"Bow",res:{b:B.BOW,q:1},ing:[{b:B.STICK,q:3},{b:B.LEAVES,q:3}]},
  {name:"Arrow x4",res:{b:B.ARROW,q:4},ing:[{b:B.STICK,q:1},{b:B.PLANK,q:1},{b:B.LEAVES,q:1}]},
  {name:"Dispenser",res:{b:B.DISPENSER,q:1},ing:[{b:B.STONE,q:9}]},
  {name:"TNT",res:{b:B.TNT,q:1},ing:[{b:B.SAND,q:1},{b:B.GUNPOWDER,q:1}]},
  {name:"Flint",res:{b:B.FLINT,q:1},ing:[{b:B.STONE,q:2}]},
  {name:"Flint & Steel",res:{b:B.FLINT_STEEL,q:1},ing:[{b:B.FLINT,q:1},{b:B.IRON,q:1}]},
  {name:"Eye of Ender",res:{b:B.EYE_END,q:1},ing:[{b:B.EMERALD,q:1},{b:B.REDSTONE,q:2}]},
  {name:"End Portal",res:{b:B.END_PORTAL,q:1},ing:[{b:B.EYE_END,q:4},{b:B.OBSIDIAN,q:4}]},
  {name:"Obsidian",res:{b:B.OBSIDIAN,q:2},ing:[{b:B.DIAMOND,q:1},{b:B.STONE,q:4}]},
  {name:"Bread (x2)",res:{b:B.BREAD,q:2},ing:[{b:B.WOOD,q:2},{b:B.COAL,q:1}]},
  {name:"Fence (x2)",res:{b:B.FENCE,q:2},ing:[{b:B.STICK,q:4}]},
  {name:"Gate (x2)",res:{b:B.GATE,q:2},ing:[{b:B.STICK,q:2},{b:B.PLANK,q:2}]},
  {name:"Trapdoor (x2)",res:{b:B.TRAPDOOR,q:2},ing:[{b:B.PLANK,q:3}]},
  {name:"Conveyor Right",res:{b:B.CONV_R,q:4},ing:[{b:B.IRON,q:2},{b:B.REDSTONE,q:1}]},
  {name:"Conveyor Left",res:{b:B.CONV_L,q:4},ing:[{b:B.IRON,q:2},{b:B.REDSTONE,q:1}]},
  {name:"Piston",res:{b:B.PISTON,q:1},ing:[{b:B.STONE_BRICK,q:3},{b:B.IRON,q:2},{b:B.PLANK,q:2}]},
  {name:"Sticky Piston",res:{b:B.STICKY_PISTON,q:1},ing:[{b:B.PISTON,q:1},{b:B.EMERALD,q:1}]},
];

// โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const empty=()=>({block:B.AIR,count:0});
function lerp(a,b,t){return a+(b-a)*t;}
function lerpC(c1,c2,t){
  const p=(s,i)=>parseInt(s.slice(i,i+2),16);
  return`rgb(${Math.round(lerp(p(c1,1),p(c2,1),t))},${Math.round(lerp(p(c1,3),p(c2,3),t))},${Math.round(lerp(p(c1,5),p(c2,5),t))})`;
}
function rng(x,y,s){let h=s^(x*374761393)^(y*668265263);h=Math.imul(h^(h>>>13),1274126177);return((h^(h>>>16))>>>0)/4294967296;}
function s1d(x,s){const xi=Math.floor(x),f=x-xi,t=f*f*(3-2*f);return rng(xi,0,s)*(1-t)+rng(xi+1,0,s)*t;}
function frac(x,s){let v=0,a=1,f=1,m=0;for(let i=0;i<4;i++){v+=s1d(x*f*.025,s+i*77)*a;m+=a;a*=.5;f*=2;}return v/m;}
function countItem(hot,inv,b){return[...hot,...inv].reduce((s,sl)=>s+(sl.block===b?sl.count:0),0);}
function consume(hot,inv,b,n){let r=n;for(const s of[...hot,...inv]){if(s.block===b&&r>0){const t=Math.min(s.count,r);s.count-=t;r-=t;if(!s.count)s.block=B.AIR;}}}
function addItem(hot,inv,b,n=1,enchant=null){let r=n;for(const s of[...hot,...inv])if(s.block===b&&s.count>0&&s.count<999&&r>0){const a=Math.min(999-s.count,r);s.count+=a;r-=a;}for(const s of[...inv,...hot])if(!s.count&&r>0){s.block=b;s.count=Math.min(r,999);if(enchant)s.enchant=enchant;r-=s.count;}}
function inLava(x,y,w,h){
  const bx1=Math.floor((x-w/2)/BS),bx2=Math.floor((x+w/2)/BS);
  const by1=Math.floor((y-h)/BS),by2=Math.floor(y/BS);
  for(let by=by1;by<=by2;by++)for(let bx=bx1;bx<=bx2;bx++){
    if(by<0||by>=WH||bx<0||bx>=WW)continue;
    if(G.world[by][bx]===B.LAVA)return true;
  }
  return false;
}
function mineTime(bt,eq){const base=DATA[bt]?.mt??20;if(!eq?.count)return base;const d=DATA[eq.block];if(!d?.tc)return base;const ok=d.tc==="pick"?PICK_SET.has(bt):d.tc==="axe"?AXE_SET.has(bt):false;return ok?Math.max(2,Math.floor(base/d.tm)):base;}

// โ”€โ”€ World Gen โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function genWorld(seed){
  const world=Array.from({length:WH},()=>new Uint8Array(WW)),surf=[];
  for(let x=0;x<WW;x++)surf[x]=Math.round(WH*.45+frac(x,seed)*14);
  for(let x=0;x<WW;x++)for(let y=0;y<WH;y++){
    if(y===WH-1){world[y][x]=B.BEDROCK;continue;}
    if(y>surf[x]+4){const r=rng(x,y,seed+1),depth=y-surf[x];if(depth>20&&r<.012)world[y][x]=B.DIAMOND;else if(depth>13&&r<.022)world[y][x]=B.EMERALD;else if(depth>7&&r<.04)world[y][x]=B.REDSTONE;else if(r<.045)world[y][x]=B.COAL;else if(r<.068)world[y][x]=B.IRON;else world[y][x]=B.STONE;}
    else if(y>surf[x])world[y][x]=B.DIRT;
    else if(y===surf[x])world[y][x]=surf[x]>Math.round(WH*.58)?B.SAND:B.GRASS;
  }
  for(let x=2;x<WW-2;x++){
    if(world[surf[x]][x]===B.GRASS&&rng(x,9999,seed)<.18){
      const h=4+Math.floor(rng(x,9998,seed)*3);
      for(let ty=surf[x]-h;ty<surf[x];ty++)if(ty>=0)world[ty][x]=B.WOOD;
      for(let ly=surf[x]-h-2;ly<=surf[x]-h+1;ly++)for(let lx=x-2;lx<=x+2;lx++){
        if(ly<0||lx<0||lx>=WW)continue;
        if(Math.abs(ly-(surf[x]-h-1))+Math.abs(lx-x)<=3&&!world[ly][lx])world[ly][lx]=B.LEAVES;
      }
    }
  }
  // Dungeon rooms
  const dungeonTries=6;
  for(let d=0;d<dungeonTries;d++){
    const dx=4+Math.floor(rng(d,55,seed)*(WW-12));
    const depth=10+Math.floor(rng(d,56,seed)*18);
    const dy=Math.min(WH-8,surf[dx]+depth);
    const rw=7+Math.floor(rng(d,57,seed)*4); // 7-10 wide
    const rh=5;
    // Clear room
    for(let ry=dy;ry<dy+rh;ry++)for(let rx=dx;rx<dx+rw;rx++){
      if(rx<0||rx>=WW||ry<0||ry>=WH)continue;
      world[ry][rx]=B.AIR;
    }
    // Stone brick walls/floor/ceiling
    for(let ry=dy-1;ry<=dy+rh;ry++)for(let rx=dx-1;rx<=dx+rw;rx++){
      if(rx<0||rx>=WW||ry<0||ry>=WH)continue;
      if(ry===dy-1||ry===dy+rh||rx===dx-1||rx===dx+rw){
        if(world[ry][rx]!==B.AIR)world[ry][rx]=B.STONE_BRICK;
      }
    }
    // Place chest in middle
    const cx2=dx+Math.floor(rw/2),cy2=dy+rh-1;
    if(cx2>=0&&cx2<WW&&cy2>=0&&cy2<WH)world[cy2][cx2]=B.CHEST;
    // Add a torch or two
    if(dx+1<WW&&dy<WH)world[dy][dx+1]=B.TORCH;
    if(dx+rw-2<WW&&dy<WH)world[dy][dx+rw-2]=B.TORCH;
  }
  // Mineshafts - horizontal tunnels underground
  const shaftCount=4;
  for(let s=0;s<shaftCount;s++){
    const startX=5+Math.floor(rng(s,80,seed)*(WW-30));
    const depth=12+Math.floor(rng(s,81,seed)*14); // 12-26 blocks deep
    const startY=Math.min(WH-8,surf[startX]+depth);
    const len=20+Math.floor(rng(s,82,seed)*25); // 20-45 blocks long
    const dir=rng(s,83,seed)>0.5?1:-1;

    for(let i=0;i<len;i++){
      const tx=startX+i*dir;
      if(tx<1||tx>=WW-1)break;
      // Carve 2-tall tunnel
      for(let ty=startY-1;ty<=startY;ty++){
        if(ty>=0&&ty<WH)world[ty][tx]=B.AIR;
      }
      // Plank floor
      if(startY+1<WH)world[startY+1][tx]=B.PLANK;
      // Wood support beam every 6 blocks - extends down to ground, passable
      if(i%6===0&&i>0){
        // Top crossbeam (passable support)
        if(startY-2>=0&&world[startY-2][tx]===B.AIR)world[startY-2][tx]=B.SUPPORT;
        // Pillar extends down from floor until hitting solid block
        for(let py=startY-1;py<WH-1;py++){
          if(world[py][tx]!==B.AIR)break;
          world[py][tx]=B.SUPPORT;
        }
      }
      // Torch every 8 blocks on ceiling
      if(i%8===4&&startY-2>=0&&world[startY-2][tx]===B.AIR)world[startY-2][tx]=B.TORCH;
      // Chest at 1/3 and 2/3 along tunnel
      if((i===Math.floor(len/3)||i===Math.floor(2*len/3))&&startY>=0&&world[startY][tx]===B.AIR){
        world[startY][tx]=B.CHEST;
      }
    }
    // Branch tunnel perpendicular
    const branchX=startX+Math.floor(len/2)*dir;
    const branchLen=10+Math.floor(rng(s,84,seed)*12);
    for(let i=0;i<branchLen;i++){
      const ty2=branchX+i;
      if(ty2<1||ty2>=WH-1)break;
      // Note: branch goes vertically in world array (different axis)
      // Actually carve horizontally in the other direction
      const bx2=branchX,by2=startY;
      const tx2=startX+Math.floor(len/2)*dir+i-Math.floor(branchLen/2);
      if(tx2>=1&&tx2<WW-1){
        if(by2-1>=0)world[by2-1][tx2]=B.AIR;
        if(by2>=0)world[by2][tx2]=B.AIR;
        if(by2+1<WH)world[by2+1][tx2]=B.PLANK;
      }
    }
  }
  // Lava pools deep underground
  for(let x=2;x<WW-2;x++){
    if(rng(x,200,seed)<0.04){
      const lavaY=Math.min(WH-3,surf[x]+22+Math.floor(rng(x,201,seed)*12));
      const w2=2+Math.floor(rng(x,202,seed)*4);
      for(let lx=x;lx<Math.min(x+w2,WW-1);lx++){
        if(world[lavaY][lx]===B.AIR||world[lavaY][lx]===B.STONE)world[lavaY][lx]=B.LAVA;
        // Fill one block deep
        if(lavaY+1<WH&&(world[lavaY+1][lx]===B.AIR||world[lavaY+1][lx]===B.STONE))world[lavaY+1][lx]=B.LAVA;
      }
    }
  }
  return{world,surf};
}

// โ”€โ”€ Sky โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function skyColors(t){
  const night=t>DAY_LEN/CYCLE;
  const dt=night?0:t/(DAY_LEN/CYCLE);
  const nt=night?(t-DAY_LEN/CYCLE)/(NIGHT_LEN/CYCLE):0;
  if(!night){
    if(dt<.1){const f=dt/.1;return{top:lerpC("#1a1040","#3390cc",f),bot:lerpC("#884422","#87ceeb",f),dark:f<.5};}
    if(dt>.9){const f=(dt-.9)/.1;return{top:lerpC("#3390cc","#1a1040",f),bot:lerpC("#87ceeb","#884422",f),dark:f>.5};}
    return{top:"#3390cc",bot:"#87ceeb",dark:false};
  }
  if(nt<.05){const f=nt/.05;return{top:lerpC("#1a1040","#080818",f),bot:lerpC("#884422","#0a0828",f),dark:true};}
  if(nt>.9){const f=(nt-.9)/.1;return{top:lerpC("#080818","#1a1040",f),bot:lerpC("#0a0828","#884422",f),dark:true};}
  return{top:"#080818",bot:"#0a0828",dark:true};
}

// โ”€โ”€ Canvas Draw โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function drawCreeper(ctx,cx2,cy2,fuse,flash){
  const ft=fuse/CREEPER_FUSE;
  if(flash||ft>0.5){ctx.save();ctx.globalAlpha=0.4+0.6*((Date.now()/80)%1);}
  // Shadow
  ctx.fillStyle="rgba(0,0,0,.18)";ctx.beginPath();ctx.ellipse(cx2,cy2+2,CW*.55,3,0,0,Math.PI*2);ctx.fill();
  // Legs
  ctx.fillStyle="#2a6e2a";ctx.fillRect(cx2-CW/2+1,cy2-CH*.45,CW/2-2,CH*.45);
  ctx.fillStyle="#1e5e1e";ctx.fillRect(cx2+1,cy2-CH*.45,CW/2-2,CH*.45);
  // Body
  ctx.fillStyle=ft>0.5?"#aaff66":"#3aaa3a";ctx.fillRect(cx2-CW/2,cy2-CH,CW,CH*.55);
  // Head
  ctx.fillStyle=ft>0.5?"#ccff88":"#4abf4a";ctx.fillRect(cx2-CW/2+1,cy2-CH,CW-2,CH*.38);
  // Eyes
  ctx.fillStyle="#111";ctx.fillRect(cx2-CW*.35,cy2-CH+CH*.1,5,5);ctx.fillRect(cx2+CW*.1,cy2-CH+CH*.1,5,5);
  // Mouth
  ctx.fillStyle="#111";ctx.fillRect(cx2-CW*.25,cy2-CH+CH*.28,CW*.5,3);
  if(flash||ft>0.5)ctx.restore();
  // Fuse bar
  if(fuse>0){
    const bw=30,bx=cx2-bw/2,by=cy2-CH-14;
    ctx.fillStyle="rgba(0,0,0,.5)";ctx.fillRect(bx,by,bw,6);
    ctx.fillStyle=ft>0.7?"#ff2200":ft>0.4?"#ff8800":"#ffdd00";
    ctx.fillRect(bx,by,bw*ft,6);
    // Flash symbol
    ctx.font="11px serif";ctx.textAlign="center";ctx.fillText("๐Ÿ’ฅ",cx2,by-2);ctx.textAlign="left";
  }
}
function drawBlock(ctx,sx,sy,type,mine,hover,open){
  const d=DATA[type];if(!d)return;
  if(type===B.TORCH){
    ctx.fillStyle="#7a5020";ctx.fillRect(sx+BS/2-2,sy+BS*.4,4,BS*.6);
    ctx.fillStyle="#ff5500";ctx.beginPath();ctx.arc(sx+BS/2,sy+BS*.3,7,0,Math.PI*2);ctx.fill();
    ctx.fillStyle="#ffcc00";ctx.beginPath();ctx.arc(sx+BS/2,sy+BS*.22,4,0,Math.PI*2);ctx.fill();
    if(hover){ctx.strokeStyle="rgba(255,255,255,.45)";ctx.lineWidth=1.5;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.DOOR){
    if(open){ctx.fillStyle="#8B6340";ctx.fillRect(sx+1,sy,5,BS);}
    else{
      ctx.fillStyle=d.col;ctx.fillRect(sx+2,sy+1,BS-4,BS-2);
      ctx.fillStyle="rgba(0,0,0,.15)";ctx.fillRect(sx+5,sy+4,BS-10,Math.floor(BS*.43));
      ctx.fillRect(sx+5,sy+Math.floor(BS*.52),BS-10,Math.floor(BS*.43));
      ctx.strokeStyle="#7a5020";ctx.lineWidth=1.5;ctx.strokeRect(sx+2,sy+1,BS-4,BS-2);
      ctx.fillStyle="#f0c030";ctx.fillRect(sx+Math.floor(BS*.7),sy+Math.floor(BS*.42),5,10);
    }
    if(hover){ctx.strokeStyle="rgba(255,255,255,.5)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.PISTON||type===B.STICKY_PISTON){
    const ps=G?.pistonStates?.[`${Math.floor((sx+G.camera.x)/BS)},${Math.floor((sy+G.camera.y)/BS)}`]||{dir:1,extended:false};
    const sticky=type===B.STICKY_PISTON;
    const ext=ps.extended;
    const dir=ps.dir; // 1=right,-1=left,2=down,-2=up
    // Body
    ctx.fillStyle=sticky?"#3a6a3a":"#7a7a7a";ctx.fillRect(sx+1,sy+1,BS-2,BS-2);
    ctx.fillStyle="rgba(255,255,255,.08)";ctx.fillRect(sx+1,sy+1,BS-2,3);
    ctx.fillStyle="rgba(0,0,0,.2)";ctx.fillRect(sx+1,sy+BS-4,BS-2,3);
    // Face plate
    const faceCol=sticky?"#2da82d":"#aaaaaa";
    if(dir===1){ctx.fillStyle=faceCol;ctx.fillRect(sx+BS-8,sy+4,7,BS-8);}
    else if(dir===-1){ctx.fillStyle=faceCol;ctx.fillRect(sx+1,sy+4,7,BS-8);}
    else if(dir===2){ctx.fillStyle=faceCol;ctx.fillRect(sx+4,sy+BS-8,BS-8,7);}
    else{ctx.fillStyle=faceCol;ctx.fillRect(sx+4,sy+1,BS-8,7);}
    // Arrow showing direction
    ctx.fillStyle="#fff";ctx.font=`${BS*.45}px sans-serif`;ctx.textAlign="center";
    const arrows={"1":"โ†’","-1":"โ†","2":"โ†“","-2":"โ†‘"};
    ctx.fillText(arrows[dir]||"โ†’",sx+BS/2,sy+BS*.68);ctx.textAlign="left";
    // Extended arm
    if(ext){
      ctx.fillStyle="#c8a440";
      if(dir===1)ctx.fillRect(sx+BS-2,sy+BS*.3,BS*.6,BS*.4);
      else if(dir===-1)ctx.fillRect(sx-BS*.6+2,sy+BS*.3,BS*.6,BS*.4);
      else if(dir===2)ctx.fillRect(sx+BS*.3,sy+BS-2,BS*.4,BS*.6);
      else ctx.fillRect(sx+BS*.3,sy-BS*.6+2,BS*.4,BS*.6);
      // Head
      ctx.fillStyle=sticky?"#2da82d":"#aaaaaa";
      if(dir===1)ctx.fillRect(sx+BS*.95+BS*.55,sy+BS*.2,BS*.1,BS*.6);
      else if(dir===-1)ctx.fillRect(sx-BS*.6,sy+BS*.2,BS*.1,BS*.6);
      else if(dir===2)ctx.fillRect(sx+BS*.2,sy+BS*.95+BS*.55,BS*.6,BS*.1);
      else ctx.fillRect(sx+BS*.2,sy-BS*.6,BS*.6,BS*.1);
    }
    ctx.strokeStyle="rgba(0,0,0,.2)";ctx.lineWidth=1;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.BARRIER){
    if(G?.creative){
      // Visible ghost outline in creative only
      ctx.fillStyle="rgba(255,50,50,.08)";ctx.fillRect(sx,sy,BS,BS);
      ctx.strokeStyle="rgba(255,80,80,.55)";ctx.lineWidth=2;
      ctx.setLineDash([5,5]);ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);ctx.setLineDash([]);
      // X mark
      ctx.strokeStyle="rgba(255,80,80,.35)";ctx.lineWidth=1.5;
      ctx.beginPath();ctx.moveTo(sx+8,sy+8);ctx.lineTo(sx+BS-8,sy+BS-8);
      ctx.moveTo(sx+BS-8,sy+8);ctx.lineTo(sx+8,sy+BS-8);ctx.stroke();
    }
    // In survival: completely invisible (nothing drawn)
    return;
  }
  if(type===B.LAVA){
    const anim=Date.now()*.002;
    // Base orange
    ctx.fillStyle="#dd3300";ctx.fillRect(sx,sy,BS,BS);
    // Animated bright patches
    for(let i=0;i<3;i++){
      const ox=8+i*10+Math.sin(anim+i)*6,oy=8+Math.cos(anim*1.3+i)*8;
      const grad=ctx.createRadialGradient(sx+ox,sy+oy,0,sx+ox,sy+oy,12);
      grad.addColorStop(0,"rgba(255,220,50,.9)");
      grad.addColorStop(1,"rgba(255,80,0,0)");
      ctx.fillStyle=grad;ctx.fillRect(sx,sy,BS,BS);
    }
    // Top glow line
    ctx.fillStyle="rgba(255,180,0,.7)";ctx.fillRect(sx,sy,BS,4);
    // Subtle grid
    ctx.strokeStyle="rgba(0,0,0,.1)";ctx.lineWidth=.5;ctx.strokeRect(sx,sy,BS,BS);
    if(hover){ctx.strokeStyle="rgba(255,255,255,.5)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.CONV_R||type===B.CONV_L){
    const dir=type===B.CONV_R?1:-1;
    const spd=Date.now()*.003*dir; // animation offset
    // Base
    ctx.fillStyle="#444";ctx.fillRect(sx,sy,BS,BS);
    ctx.fillStyle="#555";ctx.fillRect(sx,sy,BS,BS-4);
    // Animated belt stripes
    const stripeW=10,gap=14;
    ctx.save();ctx.beginPath();ctx.rect(sx+1,sy+1,BS-2,BS-2);ctx.clip();
    for(let i=-2;i<4;i++){
      const ox=((i*(stripeW+gap)+spd*60)%(stripeW+gap)*2+2*(stripeW+gap))%(stripeW+gap)*2 - (stripeW+gap);
      ctx.fillStyle="rgba(255,255,255,.12)";
      ctx.beginPath();
      ctx.moveTo(sx+ox,sy+BS-6);
      ctx.lineTo(sx+ox+stripeW,sy+BS-6);
      ctx.lineTo(sx+ox+stripeW-6,sy+2);
      ctx.lineTo(sx+ox-6,sy+2);
      ctx.closePath();ctx.fill();
    }
    ctx.restore();
    // Arrow
    ctx.fillStyle="rgba(255,200,50,.9)";
    ctx.font=`bold ${BS*.55}px sans-serif`;ctx.textAlign="center";
    ctx.fillText(dir>0?"โ†’":"โ†",sx+BS/2,sy+BS*.7);ctx.textAlign="left";
    // Edge highlight
    ctx.fillStyle="rgba(255,255,255,.1)";ctx.fillRect(sx,sy,BS,2);
    ctx.fillStyle="rgba(0,0,0,.3)";ctx.fillRect(sx,sy+BS-4,BS,4);
    ctx.strokeStyle="rgba(0,0,0,.2)";ctx.lineWidth=.5;ctx.strokeRect(sx,sy,BS,BS);
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.TRAPDOOR){
    if(open){
      // Open: thin vertical strip on the left edge
      ctx.fillStyle="#c8a440";ctx.fillRect(sx,sy,6,BS);
      ctx.fillStyle="rgba(255,255,255,.12)";ctx.fillRect(sx,sy,6,2);
      ctx.fillStyle="rgba(0,0,0,.2)";ctx.fillRect(sx+5,sy,1,BS);
      // Hinge dot
      ctx.fillStyle="#888";ctx.beginPath();ctx.arc(sx+3,sy+8,2.5,0,Math.PI*2);ctx.fill();
    }else{
      // Closed: horizontal plank filling bottom half of block
      ctx.fillStyle="#c8a440";ctx.fillRect(sx,sy+BS-10,BS,10);
      // Wood grain
      ctx.fillStyle="rgba(0,0,0,.15)";
      ctx.fillRect(sx,sy+BS-10,BS,1);
      [sx+BS*.25,sx+BS*.5,sx+BS*.75].forEach(lx=>{
        ctx.fillRect(lx,sy+BS-10,1,10);
      });
      ctx.fillStyle="rgba(255,255,255,.12)";ctx.fillRect(sx,sy+BS-10,BS,2);
      // Hinge
      ctx.fillStyle="#888";ctx.beginPath();ctx.arc(sx+8,sy+BS-5,2.5,0,Math.PI*2);ctx.fill();
      ctx.beginPath();ctx.arc(sx+BS-8,sy+BS-5,2.5,0,Math.PI*2);ctx.fill();
      // Handle
      ctx.fillStyle="#f0c030";ctx.fillRect(sx+BS/2-4,sy+BS-8,8,4);
    }
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.GATE){
    const fc="#c8a440",fd="rgba(0,0,0,.22)";
    if(open){
      // Open gate: folded to the side - just two thin posts
      ctx.fillStyle=fc;ctx.fillRect(sx+2,sy,5,BS);ctx.fillRect(sx+BS-7,sy,5,BS);
      ctx.fillStyle=fd;ctx.fillRect(sx+6,sy,1,BS);ctx.fillRect(sx+BS-3,sy,1,BS);
    }else{
      // Closed gate: posts + horizontal rails (like fence but with latch)
      ctx.fillStyle=fc;ctx.fillRect(sx+2,sy,5,BS);ctx.fillRect(sx+BS-7,sy,5,BS);
      ctx.fillStyle=fc;ctx.fillRect(sx,sy+7,BS,5);ctx.fillRect(sx,sy+BS*0.56,BS,5);
      ctx.fillStyle=fd;ctx.fillRect(sx,sy+11,BS,1);ctx.fillRect(sx,sy+BS*0.56+4,BS,1);
      // Centre latch
      ctx.fillStyle="#f0c030";ctx.fillRect(sx+BS/2-3,sy+BS/2-4,6,8);
      ctx.fillStyle="rgba(0,0,0,.3)";ctx.fillRect(sx+BS/2-1,sy+BS/2-2,2,4);
    }
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.FENCE){
    const fc="#c8a440",fd="rgba(0,0,0,.2)";
    // Left post
    ctx.fillStyle=fc;ctx.fillRect(sx+3,sy,5,BS);
    ctx.fillStyle=fd;ctx.fillRect(sx+7,sy,1,BS);
    // Right post
    ctx.fillStyle=fc;ctx.fillRect(sx+BS-8,sy,5,BS);
    ctx.fillStyle=fd;ctx.fillRect(sx+BS-4,sy,1,BS);
    // Top rail
    ctx.fillStyle=fc;ctx.fillRect(sx,sy+6,BS,5);
    ctx.fillStyle=fd;ctx.fillRect(sx,sy+10,BS,1);
    // Bottom rail
    ctx.fillStyle=fc;ctx.fillRect(sx,sy+BS*0.55,BS,5);
    ctx.fillStyle=fd;ctx.fillRect(sx,sy+BS*0.55+4,BS,1);
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.SUPPORT){
    // Thin vertical beam - drawn as a centered strip
    ctx.fillStyle="#8B6510";ctx.fillRect(sx+BS*.35,sy,BS*.3,BS);
    ctx.fillStyle="rgba(255,255,255,.12)";ctx.fillRect(sx+BS*.35,sy,BS*.06,BS);
    ctx.fillStyle="rgba(0,0,0,.2)";ctx.fillRect(sx+BS*.59,sy,BS*.06,BS);
    // Wood grain lines
    ctx.strokeStyle="rgba(0,0,0,.15)";ctx.lineWidth=1;
    ctx.beginPath();ctx.moveTo(sx+BS*.42,sy+BS*.2);ctx.lineTo(sx+BS*.42,sy+BS*.8);ctx.stroke();
    ctx.beginPath();ctx.moveTo(sx+BS*.55,sy+BS*.15);ctx.lineTo(sx+BS*.55,sy+BS*.85);ctx.stroke();
    return;
  }
  if(type===B.END_STONE){
    ctx.fillStyle="#d4d48a";ctx.fillRect(sx,sy,BS,BS);
    ctx.fillStyle="rgba(0,0,0,.08)";
    [[4,6,8,8],[18,14,10,6],[6,24,12,8]].forEach(([dx,dy,w,h])=>ctx.fillRect(sx+dx,sy+dy,w,h));
    ctx.strokeStyle="rgba(0,0,0,.09)";ctx.lineWidth=.5;ctx.strokeRect(sx,sy,BS,BS);
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.OBSIDIAN){
    ctx.fillStyle="#1a0a2e";ctx.fillRect(sx,sy,BS,BS);
    ctx.fillStyle="rgba(120,80,200,.2)";ctx.fillRect(sx,sy,BS,BS);
    ctx.fillStyle="rgba(255,255,255,.04)";ctx.fillRect(sx,sy,BS,2);ctx.fillRect(sx,sy,2,BS);
    ctx.strokeStyle="rgba(80,40,120,.3)";ctx.lineWidth=.5;ctx.strokeRect(sx,sy,BS,BS);
    if(hover){ctx.strokeStyle="rgba(180,100,255,.6)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.END_PORTAL){
    // Animated swirling portal
    const t2=Date.now()*.001;
    ctx.fillStyle="#0a0020";ctx.fillRect(sx,sy,BS,BS);
    for(let i=0;i<3;i++){
      const a=t2*2+i*2.1,r=BS*.28+i*4;
      ctx.fillStyle=`rgba(${80+i*40},${20+i*60},${200+i*20},.6)`;
      ctx.beginPath();ctx.arc(sx+BS/2+Math.cos(a)*r*.5,sy+BS/2+Math.sin(a)*r*.5,6-i,0,Math.PI*2);ctx.fill();
    }
    ctx.strokeStyle="rgba(120,60,255,.8)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);
    if(hover){ctx.strokeStyle="#fff";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.STONE_BRICK){
    ctx.fillStyle="#888";ctx.fillRect(sx,sy,BS,BS);
    ctx.strokeStyle="rgba(0,0,0,.3)";ctx.lineWidth=1.5;
    ctx.strokeRect(sx+2,sy+2,BS/2-3,BS/2-3);ctx.strokeRect(sx+BS/2+1,sy+2,BS/2-3,BS/2-3);
    ctx.strokeRect(sx+2,sy+BS/2+1,BS/2-3,BS/2-3);ctx.strokeRect(sx+BS/2+1,sy+BS/2+1,BS/2-3,BS/2-3);
    ctx.fillStyle="rgba(255,255,255,.06)";ctx.fillRect(sx,sy,BS,2);ctx.fillRect(sx,sy,2,BS);
    ctx.fillStyle="rgba(0,0,0,.15)";ctx.fillRect(sx,sy+BS-2,BS,2);ctx.fillRect(sx+BS-2,sy,2,BS);
    ctx.strokeStyle="rgba(0,0,0,.08)";ctx.lineWidth=.5;ctx.strokeRect(sx,sy,BS,BS);
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.CHEST){
    const ck=`${Math.floor((sx+G.camera.x)/BS)},${Math.floor((sy+G.camera.y)/BS)}`;const open=G?.chestContents?.[ck]&&G.chestContents[ck].some(s=>!s.count);
    ctx.fillStyle="#c8901a";ctx.fillRect(sx+2,sy+4,BS-4,BS-6);
    ctx.fillStyle="#a06010";ctx.fillRect(sx+2,sy+4,BS-4,8);
    ctx.fillStyle="#7a4a0a";ctx.fillRect(sx+2,sy+4,BS-4,2);
    // Latch
    ctx.fillStyle=open?"#555":"#f0c030";ctx.fillRect(sx+BS/2-4,sy+10,8,6);
    // Planks texture
    ctx.strokeStyle="rgba(0,0,0,.2)";ctx.lineWidth=1;
    ctx.beginPath();ctx.moveTo(sx+2,sy+BS/2);ctx.lineTo(sx+BS-2,sy+BS/2);ctx.stroke();
    ctx.fillStyle="rgba(255,255,255,.08)";ctx.fillRect(sx+2,sy+4,BS-4,2);
    if(open){ctx.fillStyle="rgba(0,0,0,.3)";ctx.fillRect(sx+4,sy+6,BS-8,BS-10);}
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.TNT){
    // Red body
    ctx.fillStyle="#cc2222";ctx.fillRect(sx+1,sy+1,BS-2,BS-2);
    // Top & bottom stripes
    ctx.fillStyle="#e8e8e8";ctx.fillRect(sx+1,sy+1,BS-2,7);ctx.fillRect(sx+1,sy+BS-8,BS-2,7);
    // TNT text area
    ctx.fillStyle="#cc2222";ctx.fillRect(sx+2,sy+9,BS-4,BS-18);
    ctx.font=`bold ${BS*.28}px monospace`;ctx.fillStyle="#fff";
    ctx.textAlign="center";ctx.fillText("TNT",sx+BS/2,sy+BS*.58);ctx.textAlign="left";
    // Fuse dot on top
    ctx.fillStyle="#333";ctx.beginPath();ctx.arc(sx+BS/2,sy+3,2,0,Math.PI*2);ctx.fill();
    ctx.fillStyle="rgba(255,255,255,.1)";ctx.fillRect(sx+1,sy+1,BS-2,2);ctx.fillRect(sx+1,sy+1,2,BS-2);
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.DISPENSER){
    ctx.fillStyle="#606060";ctx.fillRect(sx,sy,BS,BS);
    // Stone texture
    ctx.fillStyle="rgba(255,255,255,.08)";ctx.fillRect(sx,sy,BS,2);ctx.fillRect(sx,sy,2,BS);
    ctx.fillStyle="rgba(0,0,0,.2)";ctx.fillRect(sx,sy+BS-3,BS,3);ctx.fillRect(sx+BS-3,sy,3,BS);
    ctx.strokeStyle="rgba(0,0,0,.15)";ctx.lineWidth=.5;ctx.strokeRect(sx,sy,BS,BS);
    // Arrow slot (center hole)
    ctx.fillStyle="#222";ctx.fillRect(sx+BS*.3,sy+BS*.25,BS*.4,BS*.5);
    // Arrow pointing right
    ctx.fillStyle="#c8a060";ctx.fillRect(sx+BS*.35,sy+BS*.44,BS*.25,5);
    ctx.fillStyle="#e0e0a0";ctx.beginPath();ctx.moveTo(sx+BS*.6,sy+BS*.38);ctx.lineTo(sx+BS*.72,sy+BS*.5);ctx.lineTo(sx+BS*.6,sy+BS*.62);ctx.fill();
    // Arrow count
    const stored=(G&&G.dispenserArrows)?G.dispenserArrows[`${Math.floor(sx/BS+G.camera.x/BS)},${Math.floor(sy/BS+G.camera.y/BS)}`]||0:0;
    ctx.font=`bold ${BS*0.22}px monospace`;ctx.fillStyle=stored>0?"#ffe066":"#888";
    ctx.textAlign="center";ctx.fillText(stored>0?`โžถ${stored}`:"empty",sx+BS/2,sy+BS*0.92);ctx.textAlign="left";
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  if(type===B.BED){
    // Frame
    ctx.fillStyle="#7a4010";ctx.fillRect(sx+1,sy+BS*0.35,BS-2,BS*0.6);
    // Mattress
    ctx.fillStyle="#d04040";ctx.fillRect(sx+3,sy+BS*0.38,BS-6,BS*0.55);
    // Pillow
    ctx.fillStyle="#f0e0e0";ctx.fillRect(sx+4,sy+BS*0.4,BS*0.35,BS*0.4);
    // Blanket stripe
    ctx.fillStyle="#b03030";ctx.fillRect(sx+BS*0.5,sy+BS*0.4,BS*0.46,BS*0.4);
    if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
    return;
  }
  ctx.fillStyle=d.col;ctx.fillRect(sx,sy,BS,BS);
  if(d.top){ctx.fillStyle=d.top;ctx.fillRect(sx,sy,BS,6);}
  ctx.fillStyle="rgba(255,255,255,.11)";ctx.fillRect(sx,sy,BS,2);ctx.fillRect(sx,sy,2,BS);
  ctx.fillStyle="rgba(0,0,0,.18)";ctx.fillRect(sx,sy+BS-3,BS,3);ctx.fillRect(sx+BS-3,sy,3,BS);
  const oreCol={[B.COAL]:"#111",[B.IRON]:"#e8e0d0",[B.REDSTONE]:"#ff3020",[B.EMERALD]:"#30ff70",[B.DIAMOND]:"#50e8ff"};
  if(oreCol[type]){ctx.fillStyle=oreCol[type];[[7,9,7,7],[21,19,6,6],[12,27,8,8]].forEach(([dx,dy,w,h])=>ctx.fillRect(sx+dx,sy+dy,w,h));
  if(type===B.DIAMOND||type===B.EMERALD){ctx.fillStyle="rgba(255,255,255,.45)";ctx.fillRect(sx+10,sy+11,3,3);ctx.fillRect(sx+23,sy+21,2,2);}}
  if(type===B.GLASS){ctx.fillStyle="rgba(255,255,255,.28)";ctx.fillRect(sx,sy,BS,BS);}
  if(type===B.WOOD){ctx.strokeStyle="#5a4010";ctx.lineWidth=1.5;ctx.beginPath();ctx.ellipse(sx+BS/2,sy+BS/2,BS*.32,BS*.1,0,0,Math.PI*2);ctx.stroke();}
  ctx.strokeStyle="rgba(0,0,0,.09)";ctx.lineWidth=.5;ctx.strokeRect(sx,sy,BS,BS);
  if(mine>0){
    ctx.fillStyle=`rgba(0,0,0,${mine*.6})`;ctx.fillRect(sx,sy,BS,BS);
    ctx.strokeStyle=`rgba(255,255,255,${mine*.85})`;ctx.lineWidth=1.5;ctx.beginPath();
    if(mine>.1){ctx.moveTo(sx+BS*.4,sy+BS*.1);ctx.lineTo(sx+BS*.5,sy+BS*.5);ctx.lineTo(sx+BS*.8,sy+BS*.3);}
    if(mine>.4){ctx.moveTo(sx+BS*.1,sy+BS*.6);ctx.lineTo(sx+BS*.5,sy+BS*.5);ctx.lineTo(sx+BS*.6,sy+BS*.9);}
    if(mine>.7){ctx.moveTo(sx+BS*.2,sy+BS*.2);ctx.lineTo(sx+BS*.5,sy+BS*.5);}
    ctx.stroke();
  }
  if(hover){ctx.strokeStyle="rgba(255,255,255,.55)";ctx.lineWidth=2;ctx.strokeRect(sx+1,sy+1,BS-2,BS-2);}
}

function drawPlayer(ctx,px,py,dir,flash){
  if(flash){ctx.save();ctx.globalAlpha=.65;}
  ctx.fillStyle="rgba(0,0,0,.18)";ctx.beginPath();ctx.ellipse(px,py+2,PW*.55,4,0,0,Math.PI*2);ctx.fill();
  ctx.fillStyle=flash?"#cc3333":"#2060a0";ctx.fillRect(px-PW/2+1,py-PH*.52,PW/2-2,PH*.52);
  ctx.fillStyle=flash?"#aa2222":"#1a5090";ctx.fillRect(px+1,py-PH*.52,PW/2-2,PH*.52);
  ctx.fillStyle=flash?"#ff5555":"#4a8fd9";ctx.fillRect(px-PW/2,py-PH,PW,PH*.48);
  ctx.fillStyle="#f5c895";ctx.fillRect(px-PW/2+1,py-PH,PW-2,PH*.42);
  ctx.fillStyle="#7a4515";ctx.fillRect(px-PW/2+1,py-PH,PW-2,5);
  ctx.fillStyle="#333";const ex=dir>0?px+2:px-8;ctx.fillRect(ex,py-PH+8,5,5);
  ctx.fillStyle="#fff";ctx.fillRect(ex+1,py-PH+9,2,2);
  ctx.fillStyle=flash?"#ff4444":"#3a7fc9";ctx.fillRect(dir>0?px-PW/2-5:px+PW/2,py-PH+2,5,PH*.38);
  if(flash)ctx.restore();
}

function drawZombie(ctx,zx,zy,dir,flash,hp,onFire){
  if(hp<=0)return;
  if(flash){ctx.save();ctx.globalAlpha=.5;}
  ctx.fillStyle="rgba(0,0,0,.2)";ctx.beginPath();ctx.ellipse(zx,zy+2,ZW*.55,3,0,0,Math.PI*2);ctx.fill();
  ctx.fillStyle="#2a5e2a";ctx.fillRect(zx-ZW/2+1,zy-ZH*.48,ZW/2-2,ZH*.48);
  ctx.fillStyle="#1e4e1e";ctx.fillRect(zx+1,zy-ZH*.48,ZW/2-2,ZH*.48);
  ctx.fillStyle="#3a8c3a";ctx.fillRect(zx-ZW/2,zy-ZH,ZW,ZH*.52);
  ctx.fillStyle="#5abf5a";ctx.fillRect(zx-ZW/2+1,zy-ZH,ZW-2,ZH*.4);
  ctx.fillStyle="#cc2222";const ez=dir>0?zx+1:zx-7;ctx.fillRect(ez,zy-ZH+6,4,4);
  ctx.fillStyle="#ff4444";ctx.fillRect(ez+1,zy-ZH+7,2,2);
  ctx.fillStyle="#3a8c3a";ctx.fillRect(dir>0?zx-ZW/2-7:zx+ZW/2,zy-ZH+4,7,ZH*.35);
  if(flash)ctx.restore();
  // Fire emoji when burning
  if(onFire){ctx.font='16px serif';ctx.fillText('๐Ÿ”ฅ',zx-8,zy-ZH-14);}
  const bw=28,bx=zx-bw/2,by=zy-ZH-(onFire?26:8);
  ctx.fillStyle='rgba(0,0,0,.5)';ctx.fillRect(bx,by,bw,4);
  ctx.fillStyle=`hsl(${(hp/ZOMBIE_HP)*120},90%,45%)`;ctx.fillRect(bx,by,bw*(hp/ZOMBIE_HP),4);
}

// โ”€โ”€ HUD draw on canvas โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function drawHUD(ctx,W,H,g){
  const p=g.player,isNight=g.dayTime>=DAY_LEN;
  // Creative badge
  if(g.creative){ctx.font="bold 12px monospace";ctx.fillStyle="#ffe066";ctx.textAlign="right";ctx.fillText("๐ŸŒŸ CREATIVE",W-10,36);ctx.textAlign="left";}
  // Armor bar
  const totalDef2=[...g.hotbar,...g.inventory].reduce((s,sl)=>s+(sl.count>0&&DATA[sl.block]?.def?DATA[sl.block].def:0),0);
  if(totalDef2>0){
    ctx.font='14px serif';ctx.globalAlpha=1;
    ctx.fillStyle='rgba(0,0,0,.45)';roundRect(ctx,10,42,totalDef2*14+16,22,6);ctx.fill();
    for(let i=0;i<Math.min(totalDef2,20);i++){ctx.globalAlpha=1;ctx.fillText('๐Ÿ›ก๏ธ',14+i*14,58);}
    ctx.globalAlpha=1;
  }
  // HP hearts
  for(let i=0;i<MAX_HP;i++){
    ctx.font="18px serif";ctx.globalAlpha=i<p.hp?1:.2;
    ctx.fillText("โค๏ธ",12+i*22,36);
  }
  ctx.globalAlpha=1;
  // Day/Night badge
  const badgeTxt=isNight?"๐ŸŒ™ NIGHT":"โ˜€๏ธ Day";
  const badgeCol=isNight?"rgba(40,0,0,.88)":"rgba(40,30,0,.78)";
  const tw=ctx.measureText(badgeTxt).width;
  ctx.font="bold 13px monospace";
  const bw=tw+24,bx=(W-bw)/2;
  ctx.fillStyle=badgeCol;roundRect(ctx,bx,10,bw,28,14);ctx.fill();
  ctx.fillStyle=isNight?"#ff6060":"#ffe066";ctx.fillText(badgeTxt,bx+12,29);
  // Hotbar
  const hs=46,hgap=4,hotW=g.hotbar.length*(hs+hgap)-hgap;
  const hx=(W-hotW)/2,hy=H-90;
  ctx.fillStyle="rgba(0,0,0,.72)";roundRect(ctx,hx-8,hy-6,hotW+16,hs+12,10);ctx.fill();
  g.hotbar.forEach((sl,i)=>{
    const sx=hx+i*(hs+hgap),sy=hy;
    const d=DATA[sl.block];
    ctx.fillStyle=sl.count>0&&d&&d.pl?d.col:"#181818";
    roundRect(ctx,sx,sy,hs,hs,7);ctx.fill();
    if(i===g.slot){ctx.strokeStyle="#fff";ctx.lineWidth=2.5;}
    else{ctx.strokeStyle="rgba(255,255,255,.15)";ctx.lineWidth=2;}
    roundRect(ctx,sx,sy,hs,hs,7);ctx.stroke();
    if(sl.count>0&&d){
      if(d.top&&d.pl){ctx.fillStyle=d.top;roundRect(ctx,sx,sy,hs,7,7);ctx.fill();}
      if(!d.pl){ctx.font="24px serif";ctx.fillText(d.icon,sx+hs*.5-10,sy+hs*.5+8);}
      ctx.font="bold 9px monospace";ctx.fillStyle="#fff";ctx.shadowColor="#000";ctx.shadowBlur=3;
      ctx.fillText(sl.count,sx+hs-18,sy+hs-4);ctx.shadowBlur=0;
    }
    ctx.font="8px monospace";ctx.fillStyle="rgba(255,255,255,.38)";ctx.fillText(i+1,sx+4,sy+12);
  });
  // Selected item name
  const eq=g.hotbar[g.slot];
  if(eq?.count>0){
    const nm=(DATA[eq.block]?.name||"")+(eq.enchant==="silk_touch"?" โœจ Silk Touch":"");
    ctx.font="11px monospace";ctx.fillStyle="#fff";
    const nw=ctx.measureText(nm).width;
    ctx.fillStyle=eq.enchant?"rgba(80,0,120,.75)":"rgba(0,0,0,.55)";roundRect(ctx,(W-nw-20)/2,H-100,nw+20,18,5);ctx.fill();
    ctx.fillStyle=eq.enchant?"#ddaaff":"#fff";ctx.fillText(nm,(W-nw)/2,H-86);
  }
}

function roundRect(ctx,x,y,w,h,r){
  ctx.beginPath();ctx.moveTo(x+r,y);ctx.lineTo(x+w-r,y);ctx.quadraticCurveTo(x+w,y,x+w,y+r);
  ctx.lineTo(x+w,y+h-r);ctx.quadraticCurveTo(x+w,y+h,x+w-r,y+h);ctx.lineTo(x+r,y+h);
  ctx.quadraticCurveTo(x,y+h,x,y+h-r);ctx.lineTo(x,y+r);ctx.quadraticCurveTo(x,y,x+r,y);ctx.closePath();
}

// โ”€โ”€ Game State โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
let G=null,stars=[],tmMode="mine",invOpen=false,activeTab="items",selSlot=null,raf=null;
const keys=new Set(),touch={left:false,right:false,jump:false,down:false};

function fillCreativeInventory(){
  // Fill hotbar with placeable blocks
  const placeables=[B.DIRT,B.STONE,B.WOOD,B.PLANK,B.GLASS,B.SAND,B.STONE_BRICK,B.OBSIDIAN];
  G.hotbar=placeables.map(b=>({block:b,count:9999}));
  // Fill inventory with all items
  const allItems=[
    B.LEAVES,B.COAL,B.IRON,B.REDSTONE,B.EMERALD,B.DIAMOND,
    B.TORCH,B.DOOR,B.BED,B.CHEST,B.DISPENSER,B.TNT,B.FENCE,
    B.IRON_PICK,B.STONE_PICK,B.DIAMOND,B.DM_PICK,B.DM_SWORD,B.DM_AXE,
    B.BOW,B.ARROW,B.BREAD,B.APPLE,B.MEAT,B.STEW,
    B.LAVA,B.BARRIER,B.PISTON,B.STICKY_PISTON,B.GATE,B.TRAPDOOR,B.CONV_R,B.CONV_L,B.EGG_ZOMBIE,B.EGG_CREEPER,
    B.IR_HELM,B.IR_CHEST,B.IR_BOOT,B.DM_HELM,B.DM_CHEST,B.DM_BOOT,
  ];
  G.inventory=Array.from({length:32},(_,i)=>i<allItems.length?{block:allItems[i],count:9999}:{block:B.AIR,count:0});
}
function genFlatWorld(){
  const world=Array.from({length:WH},()=>new Uint8Array(WW));
  const flatY=Math.floor(WH*0.6); // surface level
  const surf=Array(WW).fill(flatY);
  for(let x=0;x<WW;x++){
    world[flatY][x]=B.GRASS;                            // grass on top
    for(let y=flatY+1;y<flatY+4;y++) world[y][x]=B.DIRT; // 3 dirt layers
    for(let y=flatY+4;y<WH-1;y++)  world[y][x]=B.STONE; // stone below
    world[WH-1][x]=B.BEDROCK;                           // bedrock at bottom
  }
  return{world,surf};
}
function initGame(){
  const seed=Date.now()%100000;
  const{world,surf}=genWorld(seed);
  const sx=Math.floor(WW/2);
  stars=Array.from({length:80},(_,i)=>({x:rng(i,0,seed+42)*WW*BS,y:rng(i,1,seed+42)*WH*.4*BS,r:1+rng(i,2,seed+42)*2}));
  G={world,surf,
    spawnX:(sx+.5)*BS,spawnY:surf[sx]*BS,
    player:{x:(sx+.5)*BS,y:surf[sx]*BS,vx:0,vy:0,onGround:false,dir:1,hp:MAX_HP,dmgCD:0,flash:0,atkCD:0,bowCharge:0,bowCharging:false},
    camera:{x:0,y:0},
    hotbar:Array.from({length:8},()=>empty()),
    inventory:Array.from({length:32},()=>empty()),
    slot:0,mining:null,hover:null,mouseDown:false,openDoors:new Set(),
    dayTime:0,zombies:[],zombieTimer:0,creepers:[],creeperTimer:0,arrows:[],dispenserTick:0,dispenserArrows:{},chestContents:{},playerPlacedChests:new Set(),litTNT:[],creative:false,flying:false,noclip:true,lastDispenserTap:null,pistonStates:{},sleeping:false,sleepTimer:0,sleepMsg:"",
    dim:"over",endWorld:null,endSurf:null,overworldPos:null,
    dragon:null,endFireballs:[],dragonDefeated:false,
  };
  document.getElementById('dead').classList.remove('show');
  if(G.creative)fillCreativeInventory();
  updateModeBtn();
  buildRecipeUI();
}

// โ”€โ”€ Solid / Physics โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function solid(l,t,w,h){
  if(G?.creative&&G?.noclip)return false;
  for(let by=Math.floor(t/BS);by<=Math.floor((t+h)/BS);by++)
    for(let bx=Math.floor(l/BS);bx<=Math.floor((l+w)/BS);bx++){
      if(by<0||by>=WH||bx<0||bx>=WW)continue;
      const blk=G.world[by][bx];if(!blk||DATA[blk]?.pass)continue;
      if((blk===B.DOOR||blk===B.GATE||blk===B.TRAPDOOR)&&G.openDoors.has(`${bx},${by}`))continue;
      return true;
    }
  return false;
}

function moveEnt(e,w,h){
  const nx=e.x+e.vx;
  if(!solid(nx-w/2,e.y-h+2,w,h-4))e.x=nx;
  else{e.vx=0;if(e.onGround)e.vy=JUMP_VEL;}
  e.x=Math.max(w/2,Math.min(WW*BS-w/2,e.x));
  const ny=e.y+e.vy;e.onGround=false;
  if(e.vy>0){
    if(!solid(e.x-w/2+1,ny-.5,w-2,1))e.y=ny;
    else{e.y=Math.floor(ny/BS)*BS;e.vy=0;e.onGround=true;}
  }else if(e.vy<0){
    if(!solid(e.x-w/2+1,ny-h,w-2,.5))e.y=ny;
    else{e.y=(Math.floor((ny-h)/BS)+1)*BS+h;e.vy=0;}
  }
}

// โ”€โ”€ Mine / Place โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function decayLeaves(){
  // Scan every leaf in the world - remove any with no wood within 5 blocks
  for(let y=0;y<WH;y++)for(let x=0;x<WW;x++){
    if(G.world[y][x]!==B.LEAVES)continue;
    let wood=false;
    for(let sy=-5;sy<=5&&!wood;sy++)for(let sx=-5;sx<=5&&!wood;sx++){
      const wx=x+sx,wy=y+sy;
      if(wx>=0&&wx<WW&&wy>=0&&wy<WH&&G.world[wy][wx]===B.WOOD)wood=true;
    }
    if(!wood)G.world[y][x]=B.AIR;
  }
}
function hasLOS(x1,y1,x2,y2){
  // Bresenham ray - returns true if no solid block between two world points
  let bx1=Math.floor(x1/BS),by1=Math.floor(y1/BS);
  const bx2=Math.floor(x2/BS),by2=Math.floor(y2/BS);
  const dx=Math.abs(bx2-bx1),dy=Math.abs(by2-by1);
  const sx=bx1<bx2?1:-1,sy=by1<by2?1:-1;
  let err=dx-dy;
  while(bx1!==bx2||by1!==by2){
    if(bx1>=0&&bx1<WW&&by1>=0&&by1<WH){
      const blk=G.world[by1][bx1];
      if(blk&&!DATA[blk]?.pass&&blk!==B.DOOR)return false;
    }
    const e2=err*2;
    if(e2>-dy){err-=dy;bx1+=sx;}
    if(e2<dx){err+=dx;by1+=sy;}
  }
  return true;
}
function isShielded(zx,zy){
  // Check if any solid block exists above the zombie's head (open doors don't count)
  const bx=Math.floor(zx/BS);
  for(let by=Math.floor((zy-ZH)/BS)-1;by>=0;by--){
    if(bx<0||bx>=WW)break;
    const blk=G.world[by][bx];
    if(blk&&!DATA[blk]?.pass){
      if((blk===B.DOOR||blk===B.GATE||blk===B.TRAPDOOR)&&G.openDoors.has(`${bx},${by}`))continue;
      return true;
    }
  }
  return false;
}
function tryMine(bx,by){
  if(bx<0||bx>=WW||by<0||by>=WH||!G.world[by][bx])return;
  if(G.world[by][bx]===B.BARRIER&&!G.creative)return; // can't mine barriers in survival
  G.mining={bx,by,t:0,max:G.creative?0:mineTime(G.world[by][bx],G.hotbar[G.slot])};
}
function tryEat(){
  if(!G)return;
  const fsl=G.hotbar[G.slot];
  if(!fsl?.count||!DATA[fsl.block]?.food){G.sleepMsg="No food in hand!";setTimeout(()=>G.sleepMsg="",1500);return;}
  if(G.player.hp>=MAX_HP){G.sleepMsg="Already at full health!";setTimeout(()=>G.sleepMsg="",1500);return;}
  const heal=DATA[fsl.block].food;
  const fname=DATA[fsl.block].name;
  G.player.hp=Math.min(MAX_HP,G.player.hp+heal);
  if(!G.creative){fsl.count--;if(!fsl.count)fsl.block=B.AIR;}
  G.sleepMsg=`+${heal} โค๏ธ ${fname}`;setTimeout(()=>G.sleepMsg="",1500);
}
function tryPlace(bx,by){
  // Spawn egg usage
  const eggSl=G.hotbar[G.slot];
  const eggType=eggSl?.count>0?DATA[eggSl.block]?.spawnEgg:null;
  if(eggType){
    const wx=(bx+.5)*BS,wy=by*BS;
    if(eggType==="zombie"){
      G.zombies.push({x:wx,y:wy,vx:0,vy:0,onGround:false,hp:ZOMBIE_HP,flash:0,dir:1,dmgT:0});
      if(!G.creative){eggSl.count--;if(!eggSl.count)eggSl.block=B.AIR;}
      G.sleepMsg="๐ŸงŸ Zombie spawned!";setTimeout(()=>G.sleepMsg="",1200);
    } else if(eggType==="creeper"){
      G.creepers.push({x:wx,y:wy,vx:0,vy:0,onGround:false,hp:CREEPER_HP,fuse:0,flash:0,dir:1,dmgT:0,burnT:0,onFire:false});
      if(!G.creative){eggSl.count--;if(!eggSl.count)eggSl.block=B.AIR;}
      G.sleepMsg="๐Ÿ’š Creeper spawned!";setTimeout(()=>G.sleepMsg="",1200);
    }
    return;
  }
  if(bx<0||bx>=WW||by<0||by>=WH)return;
  if(G.world[by][bx]){
    const blk=G.world[by][bx];
    const k=`${bx},${by}`;
    if(blk===B.DOOR){G.openDoors.has(k)?G.openDoors.delete(k):G.openDoors.add(k);}
    else if(blk===B.DISPENSER){
      const sl=G.hotbar[G.slot];
      const now=Date.now();
      const lastTap=G.lastDispenserTap;
      const isDoubleTap=lastTap&&lastTap.k===k&&(now-lastTap.t)<400;
      G.lastDispenserTap={k,t:now};
      if(sl?.count>0&&sl.block===B.ARROW){
        // Creative double-tap: load ALL arrows at once
        const loadAmt=(G.creative&&isDoubleTap)?sl.count:1;
        G.dispenserArrows[k]=(G.dispenserArrows[k]||0)+loadAmt;
        if(!G.creative){sl.count-=loadAmt;if(!sl.count)sl.block=B.AIR;}
        const t=G.dispenserArrows[k];
        G.sleepMsg=loadAmt>1?`โžœ Loaded all ${loadAmt} arrows! (${t} total)`:`โžœ ${t} arrow${t!==1?"s":""}  (tap empty-handed to remove)`;
        setTimeout(()=>G.sleepMsg="",1500);
      } else {
        // Remove ONE arrow (or ALL in creative double-tap)
        const stored=G.dispenserArrows[k]||0;
        if(stored>0){
          const removeAmt=(G.creative&&isDoubleTap)?stored:1;
          G.dispenserArrows[k]=stored-removeAmt;
          if(!G.creative)addItem(G.hotbar,G.inventory,B.ARROW,removeAmt);
          const left=G.dispenserArrows[k];
          G.sleepMsg=removeAmt>1?`Removed all ${removeAmt} arrows`:left>0?`โžœ ${left} arrow${left!==1?"s":""}  (tap empty-handed to remove)`:"Dispenser empty";
        } else {
          G.sleepMsg="Dispenser empty โ€” hold arrows & tap to load";
        }
        setTimeout(()=>G.sleepMsg="",1500);
      }
    }
    else if(blk===B.CHEST){
      // Init chest contents if first time
      if(!G.chestContents[k]){
        G.chestContents[k]=Array.from({length:18},()=>empty());
        // Pre-placed dungeon chests always get loot; player-crafted chests start empty
        if(!G.playerPlacedChests||!G.playerPlacedChests.has(k)){
          const lootPool=[
            {block:B.IRON,count:5},{block:B.DIAMOND,count:2},
            {block:B.EMERALD,count:3},{block:B.REDSTONE,count:8},
            {block:B.ARROW,count:12},{block:B.TORCH,count:6},
            {block:B.IRON_PICK,count:1},{block:B.IRON_SWORD,count:1},
            {block:B.COAL,count:10},{block:B.FLINT,count:3},
          ];
          // Detect mineshaft chests (plank floor below) vs dungeon chests
          const isMineshaft=by+1<WH&&G.world[by+1]?.[bx]===B.PLANK;
          if(isMineshaft){
            // Mineshaft loot: lots of food + basic supplies
            const foodPool=[{block:B.BREAD,count:3},{block:B.APPLE,count:2},{block:B.MEAT,count:2},{block:B.STEW,count:1},{block:B.BREAD,count:4}];
            const extraPool=[{block:B.TORCH,count:8},{block:B.COAL,count:6},{block:B.IRON,count:3},{block:B.ARROW,count:8},{block:B.STICK,count:10}];
            [0,1,2].forEach(si=>{const it=foodPool[Math.floor(Math.random()*foodPool.length)];G.chestContents[k][si]={block:it.block,count:it.count};});
            [4,6,8].forEach(si=>{const it=extraPool[Math.floor(Math.random()*extraPool.length)];G.chestContents[k][si]={block:it.block,count:it.count};});
          } else {
          // Dungeon chest: silk touch + rare loot
          G.chestContents[k][0]={block:B.IRON_PICK,count:1,enchant:"silk_touch"};
          [2,4,7,10,14,16].forEach(si=>{
            const it=lootPool[Math.floor(Math.random()*lootPool.length)];
            G.chestContents[k][si]={block:it.block,count:it.count};
          });
          }
        }
      }
      openChest(k);
    }
    else if(blk===B.END_PORTAL){
      if(G.dim==="over") enterEnd();
      else leaveEnd();
    }
    else if(blk===B.TNT){
      const heldSl=G.hotbar[G.slot];
      if(heldSl?.count>0&&DATA[heldSl.block]?.isFlintSteel){
        G.world[by][bx]=B.AIR;
        G.litTNT.push({x:(bx+.5)*BS,y:by*BS,vx:0,vy:0,fuse:90});
      } else {
        G.sleepMsg="Equip Flint & Steel to ignite TNT!";
        setTimeout(()=>G.sleepMsg="",2000);
      }
    }
    else if(blk===B.PISTON||blk===B.STICKY_PISTON){togglePiston(bx,by);}
    else if(blk===B.BED){
      if(G.dayTime>=DAY_LEN){G.sleeping=true;G.sleepTimer=0;}
      else{if(!G.sleepMsg){G.sleepMsg="You can only sleep at night!";setTimeout(()=>G.sleepMsg="",2500);}}
    }
    return;
  }
  const p=G.player;
  if(bx>=Math.floor((p.x-PW/2)/BS)&&bx<=Math.floor((p.x+PW/2)/BS)&&by>=Math.floor((p.y-PH)/BS)&&by<=Math.floor(p.y/BS))return;
  const sl=G.hotbar[G.slot];
  if(!sl?.count||!DATA[sl.block]?.pl)return;
  const placedBlock=sl.block;
  G.world[by][bx]=sl.block;if(!G.creative){sl.count--;if(!sl.count)sl.block=B.AIR;}
  if(placedBlock===B.CHEST)G.playerPlacedChests.add(`${bx},${by}`);
  if(placedBlock===B.PISTON||placedBlock===B.STICKY_PISTON){
    const px2=G.player.x,py2=G.player.y;
    const adx=Math.abs(px2-(bx+.5)*BS),ady=Math.abs(py2-(by+.5)*BS);
    const dir=ady>adx?(py2<(by+.5)*BS?2:-2):(px2<(bx+.5)*BS?1:-1);
    G.pistonStates[`${bx},${by}`]={dir,extended:false};
  }
  if(sl.block===B.AIR&&G.world[by][bx]===B.DISPENSER)G.dispenserArrows[`${bx},${by}`]=0;
}
function tryAttack(){
  // Melee only โ€” bow uses startBowCharge/releaseBow
  const p=G.player;if(p.atkCD>0)return;
  const eq=G.hotbar[G.slot];
  if(eq?.count&&DATA[eq.block]?.isBow)return; // bow uses charge system
  const dmg=(eq?.count&&DATA[eq.block]?.sdmg)||1;
  const range=BS*1.8;let hit=false;
  G.zombies.forEach(z=>{if(Math.abs(z.x-p.x)<range&&Math.abs(z.y-p.y)<range){z.hp-=dmg;z.flash=8;hit=true;}});
  if(G.dim==="end"&&G.dragon&&Math.hypot(G.dragon.x-p.x,G.dragon.y-p.y)<70){G.dragon.hp-=dmg;G.dragon.flashT=15;hit=true;}
  G.creepers.forEach(c=>{if(Math.abs(c.x-p.x)<range&&Math.abs(c.y-p.y)<range){c.hp-=dmg;c.fuse=Math.max(0,c.fuse-20);c.flash=8;hit=true;}});
  G.zombies=G.zombies.filter(z=>z.hp>0);
  G.creepers=G.creepers.filter(c=>c.hp>0);
  if(hit)p.atkCD=20;
}
function startBowCharge(){
  const eq=G.hotbar[G.slot];
  if(!G||!eq?.count||!DATA[eq.block]?.isBow)return;
  G.player.bowCharging=true;
}
function releaseBow(){
  const p=G.player;if(!p.bowCharging)return;
  const charge=p.bowCharge/60;
  if(charge>0.1){
    // Check for arrow ammo
    const arrowCount=countItem(G.hotbar,G.inventory,B.ARROW);
    if(arrowCount<=0){G.sleepMsg="No arrows!";setTimeout(()=>G.sleepMsg="",1500);p.bowCharging=false;p.bowCharge=0;return;}
    consume(G.hotbar,G.inventory,B.ARROW,1);
    const spd=6+charge*14;
    const dmg=Math.round(1+charge*4);
    G.arrows.push({x:p.x,y:p.y-PH*0.6,vx:p.dir*spd,vy:-charge*3,life:Math.round(50+charge*60),dmg});
  }
  p.bowCharging=false;p.bowCharge=0;
}

// โ”€โ”€ Canvas coords โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const cvs=document.getElementById('cvs');
function toBlk(cx,cy){
  const r=cvs.getBoundingClientRect();
  return{bx:Math.floor((cx-r.left+G.camera.x)/BS),by:Math.floor((cy-r.top+G.camera.y)/BS)};
}

// โ”€โ”€ Mouse โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
cvs.addEventListener('mousedown',e=>{
  if(invOpen)return;
  G.mouseDown=true;const{bx,by}=toBlk(e.clientX,e.clientY);
  e.button===0?tryMine(bx,by):tryPlace(bx,by);
});
cvs.addEventListener('mousemove',e=>{
  if(invOpen||!G)return;
  const{bx,by}=toBlk(e.clientX,e.clientY);G.hover={bx,by};
  if(G.mouseDown&&G.mining&&(G.mining.bx!==bx||G.mining.by!==by))tryMine(bx,by);
});
cvs.addEventListener('mouseup',()=>{G.mouseDown=false;G.mining=null;releaseBow();});
cvs.addEventListener('contextmenu',e=>e.preventDefault());

// โ”€โ”€ Touch on canvas โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
cvs.addEventListener('touchstart',e=>{
  if(invOpen||chestOpen){e.preventDefault();return;}e.preventDefault();
  const t=e.changedTouches[0],{bx,by}=toBlk(t.clientX,t.clientY);
  G.hover={bx,by};
  const eq=G.hotbar[G.slot];
  if(eq?.count&&DATA[eq.block]?.isBow){startBowCharge();return;}
  if(tmMode==="mine"){tryMine(bx,by);G.mouseDown=true;}else tryPlace(bx,by);
},{passive:false});
cvs.addEventListener('touchmove',e=>{
  if(invOpen||chestOpen){e.preventDefault();return;}e.preventDefault();
  const t=e.changedTouches[0],{bx,by}=toBlk(t.clientX,t.clientY);
  G.hover={bx,by};
  if(tmMode==="mine"&&G.mouseDown&&G.mining&&(G.mining.bx!==bx||G.mining.by!==by))tryMine(bx,by);
},{passive:false});
cvs.addEventListener('touchend',e=>{e.preventDefault();G.mouseDown=false;G.mining=null;releaseBow();},{passive:false});
cvs.addEventListener('touchcancel',e=>{e.preventDefault();G.mouseDown=false;G.mining=null;releaseBow();},{passive:false});

// โ”€โ”€ Keyboard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
window.addEventListener('keydown',e=>{
  keys.add(e.key);
  if(e.key>='1'&&e.key<='8'){G.slot=+e.key-1;}
  if(e.key==='e'||e.key==='E')toggleInv();
  if(e.key==='f'||e.key==='F'){const eq=G?.hotbar[G.slot];(eq?.count&&DATA[eq.block]?.isBow)?startBowCharge():tryAttack();}
  if(['ArrowLeft','ArrowRight','ArrowUp',' '].includes(e.key))e.preventDefault();
});
window.addEventListener('keyup',e=>{keys.delete(e.key);if(e.key==='f'||e.key==='F')releaseBow();});

// โ”€โ”€ Touch buttons โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function holdBtn(el,k){
  el.addEventListener('touchstart',e=>{e.stopPropagation();e.preventDefault();touch[k]=true;},{passive:false});
  el.addEventListener('touchend',e=>{e.stopPropagation();e.preventDefault();touch[k]=false;},{passive:false});
  el.addEventListener('touchcancel',e=>{e.stopPropagation();e.preventDefault();touch[k]=false;},{passive:false});
  el.addEventListener('mousedown',e=>{e.stopPropagation();touch[k]=true;});
  el.addEventListener('mouseup',e=>{e.stopPropagation();touch[k]=false;});
  el.addEventListener('mouseleave',e=>{touch[k]=false;});
}
holdBtn(document.getElementById('btn-l'),'left');
holdBtn(document.getElementById('btn-r'),'right');
holdBtn(document.getElementById('btn-jump'),'jump');
holdBtn(document.getElementById('btn-down'),'down');
document.getElementById('btn-noclip').addEventListener('touchstart',e=>{e.stopPropagation();e.preventDefault();toggleNoclip();},{passive:false});
document.getElementById('btn-noclip').addEventListener('mousedown',e=>{e.stopPropagation();toggleNoclip();});

document.getElementById('btn-eat').addEventListener('touchstart',e=>{e.stopPropagation();e.preventDefault();tryEat();},{passive:false});
document.getElementById('btn-eat').addEventListener('mousedown',e=>{e.stopPropagation();tryEat();});
document.getElementById('btn-atk').addEventListener('touchstart',e=>{e.stopPropagation();e.preventDefault();const eq=G?.hotbar[G.slot];(eq?.count&&DATA[eq.block]?.isBow)?startBowCharge():tryAttack();},{passive:false});
document.getElementById('btn-atk').addEventListener('touchend',e=>{e.stopPropagation();e.preventDefault();releaseBow();},{passive:false});
document.getElementById('btn-atk').addEventListener('touchcancel',e=>{e.stopPropagation();e.preventDefault();releaseBow();},{passive:false});
document.getElementById('btn-atk').addEventListener('mousedown',e=>{e.stopPropagation();const eq=G?.hotbar[G.slot];(eq?.count&&DATA[eq.block]?.isBow)?startBowCharge():tryAttack();});
document.getElementById('btn-atk').addEventListener('mouseup',e=>{e.stopPropagation();releaseBow();});

document.getElementById('btn-mode').addEventListener('touchstart',e=>{e.stopPropagation();e.preventDefault();toggleMode();},{passive:false});
document.getElementById('btn-mode').addEventListener('mousedown',e=>{e.stopPropagation();toggleMode();});

document.getElementById('btn-bag').addEventListener('touchstart',e=>{e.stopPropagation();e.preventDefault();toggleInv();},{passive:false});
document.getElementById('btn-bag').addEventListener('mousedown',e=>{e.stopPropagation();toggleInv();});

document.getElementById('inv-close').addEventListener('click',()=>toggleInv(false));
document.getElementById('inv-overlay').addEventListener('click',e=>{if(e.target===document.getElementById('inv-overlay'))toggleInv(false);});
document.getElementById('btn-sleep').addEventListener('touchstart',e=>{e.stopPropagation();e.preventDefault();trySleep();},{passive:false});
document.getElementById('btn-sleep').addEventListener('mousedown',e=>{e.stopPropagation();trySleep();});
document.getElementById('respawn-btn').addEventListener('click',respawn);
document.getElementById('respawn-btn').addEventListener('touchend',e=>{e.preventDefault();respawn();},{passive:false});

// โ”€โ”€ Mode toggle โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function toggleMode(){
  tmMode=tmMode==="mine"?"place":"mine";updateModeBtn();
}
function updateModeBtn(){
  const btn=document.getElementById('btn-mode');
  btn.textContent=tmMode==="mine"?"โ› Mine":"๐Ÿงฑ Place";
  btn.style.background=tmMode==="mine"?"rgba(160,48,18,.9)":"rgba(28,110,28,.9)";
}

// โ”€โ”€ Inventory โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function toggleInv(force){
  invOpen=force!==undefined?force:!invOpen;
  document.getElementById('inv-overlay').classList.toggle('open',invOpen);
  if(invOpen)renderInv();
}
function switchTab(t){
  activeTab=t;
  document.getElementById('tab-items').classList.toggle('active',t==='items');
  document.getElementById('tab-craft').classList.toggle('active',t==='craft');
  document.getElementById('tab-items-content').style.display=t==='items'?'':'none';
  document.getElementById('tab-craft-content').style.display=t==='craft'?'':'none';
  if(t==='craft')renderCraft();
}

function makeSlotEl(sl,area,idx){
  const d=sl.count>0?DATA[sl.block]:null;
  const isTool=d&&!d.pl;
  const el=document.createElement('div');
  el.className='slot'+(selSlot&&selSlot.area===area&&selSlot.idx===idx?' selected':'');
  el.style.background=isTool?'#12121e':(d?d.col:'rgba(0,0,0,.38)');
  if(d){
    if(isTool){const ic=document.createElement('span');ic.textContent=d.icon;ic.style.fontSize='22px';el.appendChild(ic);}
    else if(d.top){const tp=document.createElement('div');tp.style.cssText=`position:absolute;top:0;left:0;right:0;height:7px;background:${d.top};`;el.appendChild(tp);}
    const cnt=document.createElement('span');cnt.className='slot-count';cnt.textContent=sl.count;el.appendChild(cnt);
  }
  el.addEventListener('click',()=>area==='chest'?onChestSlotClick(area,idx):onSlotClick(area,idx));
  el.addEventListener('touchend',e=>{e.preventDefault();area==='chest'?onChestSlotClick(area,idx):onSlotClick(area,idx);},{passive:false});
  return el;
}

function renderInv(){
  const ig=document.getElementById('inv-grid');ig.innerHTML='';
  const hg=document.getElementById('hot-grid');hg.innerHTML='';
  G.inventory.forEach((sl,i)=>ig.appendChild(makeSlotEl(sl,'inv',i)));
  G.hotbar.forEach((sl,i)=>{const e=makeSlotEl(sl,'hot',i);const n=document.createElement('span');n.className='slot-num';n.textContent=i+1;e.appendChild(n);hg.appendChild(e);});
}

function quickUse(area,idx){
  // Double-tap: eat food, or move to hotbar
  const arr=area==='inv'?G.inventory:G.hotbar;
  const sl=arr[idx];
  if(!sl||!sl.count)return;
  const d=DATA[sl.block];
  if(d?.food){
    // Eat it directly from inventory
    if(G.player.hp<MAX_HP){
      G.player.hp=Math.min(MAX_HP,G.player.hp+d.food);
      if(!G.creative){sl.count--;if(!sl.count)sl.block=B.AIR;}
      G.sleepMsg=`+${d.food} โค๏ธ ${d.name}`;setTimeout(()=>G.sleepMsg='',1500);
    } else {
      G.sleepMsg='Already at full health!';setTimeout(()=>G.sleepMsg='',1500);
    }
  } else if(area==='inv'){
    // Move to first available hotbar slot (empty or same block)
    let target=-1;
    for(let i=0;i<G.hotbar.length;i++)if(G.hotbar[i].block===sl.block&&G.hotbar[i].count<999){target=i;break;}
    if(target===-1)for(let i=0;i<G.hotbar.length;i++)if(!G.hotbar[i].count){target=i;break;}
    if(target!==-1){
      if(G.hotbar[target].block===sl.block){
        const add=Math.min(999-G.hotbar[target].count,sl.count);
        G.hotbar[target].count+=add;sl.count-=add;if(!sl.count)sl.block=B.AIR;
      }else{const t={...G.hotbar[target]};G.hotbar[target]={...sl};arr[idx]=t;}
      G.slot=target;
      G.sleepMsg=`โ†’ Hotbar: ${d?.name||'Item'}`;setTimeout(()=>G.sleepMsg='',1000);
    } else {
      G.sleepMsg='Hotbar is full!';setTimeout(()=>G.sleepMsg='',1200);
    }
  } else {
    // Already in hotbar โ€” just select it
    G.slot=idx;
    G.sleepMsg=d?.name||'';setTimeout(()=>G.sleepMsg='',800);
  }
  selSlot=null;document.getElementById('inv-tip').style.display='none';
  renderInv();
}

function onSlotClick(area,idx){
  const arr=area==='inv'?G.inventory:G.hotbar;
  if(!selSlot){
    if(arr[idx].count>0){
      // Double-tap same slot = quick use
      if(selSlot===null&&area===area){} // fall through
      selSlot={area,idx};
      const tip=document.getElementById('inv-tip');
      tip.textContent=(DATA[arr[idx].block]?.name||'')+'  (tap again to use/equip)';
      tip.style.display='';
    }
  }else{
    if(selSlot.area===area&&selSlot.idx===idx){
      // Second tap on same slot = quick use
      quickUse(area,idx);
      return;
    }
    const src=selSlot.area==='inv'?G.inventory:G.hotbar;
    const dst=area==='inv'?G.inventory:G.hotbar;
    const si=selSlot.idx,di=idx;
    if(src[si].block===dst[di].block&&dst[di].count>0){
      const add=Math.min(999-dst[di].count,src[si].count);dst[di].count+=add;src[si].count-=add;
      if(!src[si].count)src[si].block=B.AIR;
    }else{const t={...src[si]};src[si]={...dst[di]};dst[di]=t;}
    if(area==='hot')G.slot=di;
    selSlot=null;document.getElementById('inv-tip').style.display='none';
  }
  renderInv();
}

// โ”€โ”€ Crafting UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function buildRecipeUI(){
  const list=document.getElementById('recipe-list');list.innerHTML='';
  RECIPES.forEach((rec,ri)=>{
    const rd=DATA[rec.res.b];
    const row=document.createElement('div');row.className='recipe-row';row.id=`rec-${ri}`;
    // Icon
    const ic=document.createElement('div');
    ic.style.cssText=`width:38px;height:38px;flex-shrink:0;border-radius:6px;overflow:hidden;position:relative;background:${rd?.pl?rd.col:'#12121e'};border:1px solid rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;`;
    if(rd?.icon){const s=document.createElement('span');s.textContent=rd.icon;s.style.fontSize='20px';ic.appendChild(s);}
    row.appendChild(ic);
    // Info
    const info=document.createElement('div');info.style.flex='1';
    const nm=document.createElement('div');nm.style.cssText='color:rgba(255,255,255,.4);font-size:11px;font-weight:bold;margin-bottom:4px;';
    nm.textContent=rec.name+(rec.res.q>1?` ร—${rec.res.q}`:'');info.appendChild(nm);
    const ings=document.createElement('div');ings.style.cssText='display:flex;flex-wrap:wrap;gap:3px;';
    rec.ing.forEach(ing=>{
      const id=DATA[ing.b];const sp=document.createElement('span');
      sp.style.cssText='font-size:9px;padding:1px 5px;border-radius:4px;';
      sp.textContent=`${id?.icon||''}${ing.q}ร— ${id?.name}`;
      sp.dataset.ingB=ing.b;sp.dataset.ingQ=ing.q;ings.appendChild(sp);
    });
    info.appendChild(ings);row.appendChild(info);
    // Craft button
    const btn=document.createElement('button');btn.className='craft-btn';btn.textContent='โ€”';
    btn.addEventListener('click',()=>doCraft(ri));btn.addEventListener('touchend',e=>{e.preventDefault();doCraft(ri);},{passive:false});
    btn.id=`cbtn-${ri}`;row.appendChild(btn);
    list.appendChild(row);
  });
  renderCraft();
}

function renderCraft(){
  RECIPES.forEach((rec,ri)=>{
    const can=rec.ing.every(i=>countItem(G.hotbar,G.inventory,i.b)>=i.q);
    const row=document.getElementById(`rec-${ri}`);if(!row)return;
    row.classList.toggle('can',can);
    const nm=row.querySelector('div>div');if(nm)nm.style.color=can?'#fff':'rgba(255,255,255,.4)';
    const ings=row.querySelectorAll('span[data-ing-b]');
    ings.forEach(sp=>{
      const b=+sp.dataset.ingB,q=+sp.dataset.ingQ;
      const have=countItem(G.hotbar,G.inventory,b);
      sp.style.color=have>=q?'#7ddd60':'#dd6050';
      sp.style.background=have>=q?'rgba(60,200,60,.1)':'rgba(200,60,60,.1)';
      sp.textContent=`${DATA[b]?.icon||''}${q}ร— ${DATA[b]?.name} (${have})`;
    });
    const btn=document.getElementById(`cbtn-${ri}`);if(!btn)return;
    btn.className='craft-btn'+(can?' can':'');btn.textContent=can?'โš’ Craft':'โ€”';
  });
}

function doCraft(ri){
  const rec=RECIPES[ri];
  if(!rec.ing.every(i=>countItem(G.hotbar,G.inventory,i.b)>=i.q))return;
  rec.ing.forEach(i=>consume(G.hotbar,G.inventory,i.b,i.q));
  addItem(G.hotbar,G.inventory,rec.res.b,rec.res.q);
  const msg=document.getElementById('craft-msg');
  msg.textContent=`โœ“ Crafted ${rec.res.q>1?rec.res.q+'ร— ':''}${rec.name}!`;
  msg.style.display='';setTimeout(()=>msg.style.display='none',2500);
  renderCraft();renderInv();
}

function trySleep(){
  if(!G)return;
  if(G.dayTime>=DAY_LEN){G.sleeping=true;G.sleepTimer=0;}
  else{G.sleepMsg="You can only sleep at night!";setTimeout(()=>G.sleepMsg="",2500);}
}
function toggleNoclip(){
  if(!G)return;
  G.noclip=!G.noclip;
  updateFlightBtns();
}
function updateFlightBtns(){
  if(!G)return;
  const downBtn=document.getElementById('btn-down');
  const jumpBtn=document.getElementById('btn-jump');
  if(!downBtn||!jumpBtn)return;
  const noclipBtn=document.getElementById('btn-noclip');
  if(G.creative){
    downBtn.style.display='flex';
    jumpBtn.textContent='โฌ† Up';
    if(noclipBtn){
      noclipBtn.style.display='flex';
      noclipBtn.textContent=G.noclip?"๐Ÿ‘ป Noclip: ON":"๐Ÿงฑ Noclip: OFF";
      noclipBtn.style.background=G.noclip?"rgba(120,0,120,.9)":"rgba(40,40,40,.9)";
      noclipBtn.style.borderColor=G.noclip?"rgba(200,80,255,.5)":"rgba(255,255,255,.2)";
    }
  } else {
    downBtn.style.display='none';
    jumpBtn.textContent='โ†‘ Jump';
    if(noclipBtn)noclipBtn.style.display='none';
  }
}
function updateAtkBtn(){
  const btn=document.getElementById('btn-atk');if(!G||!btn)return;
  const eq=G.hotbar[G.slot];
  const isBow=eq?.count&&DATA[eq.block]?.isBow;
  btn.textContent=isBow?"๐Ÿน":"โš”๏ธ";
  btn.title=isBow?"Hold to charge, release to fire":"Melee attack";
}
function updateEatBtn(){
  const btn=document.getElementById('btn-eat');if(!btn||!G)return;
  const eq=G.hotbar[G.slot];
  const hasFood=eq?.count>0&&DATA[eq.block]?.food;
  btn.style.opacity=hasFood?"1":"0.3";
  if(hasFood){
    btn.textContent=DATA[eq.block].icon+' Eat';
    btn.style.background="rgba(30,120,30,.9)";
    btn.style.borderColor="rgba(80,220,80,.5)";
  }else{
    btn.textContent="๐ŸŽ Eat";
    btn.style.background="rgba(15,15,15,.7)";
    btn.style.borderColor="rgba(255,255,255,.2)";
  }
}
function updateSleepBtn(){
  const btn=document.getElementById('btn-sleep');
  if(!btn||!G)return;
  // Show bright only at night and when bed is in inventory/hotbar or placed nearby
  const hasBed=[...G.hotbar,...G.inventory].some(s=>s.count>0&&s.block===B.BED);
  const night=G.dayTime>=DAY_LEN;
  btn.style.opacity=night&&hasBed?"1":"0.3";
  btn.style.background=night&&hasBed?"rgba(80,0,120,0.9)":"rgba(15,15,15,0.7)";
  btn.style.borderColor=night&&hasBed?"rgba(180,100,255,0.6)":"rgba(255,255,255,0.2)";
}

let chestOpen=false,chestKey=null,chestSel=null;

function openChest(k){
  chestKey=k;chestOpen=true;chestSel=null;
  document.getElementById('chest-overlay').style.display='flex';
  renderChest();
}
function closeChest(){
  chestOpen=false;chestKey=null;chestSel=null;
  document.getElementById('chest-overlay').style.display='none';
  document.getElementById('chest-tip').style.display='none';
}
function renderChest(){
  if(!chestKey||!G)return;
  const slots=G.chestContents[chestKey]||[];
  const cg=document.getElementById('chest-grid');cg.innerHTML='';
  slots.forEach((sl,i)=>cg.appendChild(makeSlotEl(sl,'chest',i)));
  const ig=document.getElementById('chest-inv-grid');ig.innerHTML='';
  G.inventory.forEach((sl,i)=>ig.appendChild(makeSlotEl(sl,'inv',i)));
  const hg=document.getElementById('chest-hot-grid');hg.innerHTML='';
  G.hotbar.forEach((sl,i)=>{const e=makeSlotEl(sl,'hot',i);const n=document.createElement('span');n.className='slot-num';n.textContent=i+1;e.appendChild(n);hg.appendChild(e);});
}
function onChestSlotClick(area,idx){
  const getArr=a=>a==='chest'?G.chestContents[chestKey]:a==='inv'?G.inventory:G.hotbar;
  const arr=getArr(area);
  const tip=document.getElementById('chest-tip');
  if(!chestSel){
    if(arr[idx]?.count>0){
      chestSel={area,idx};
      tip.textContent=DATA[arr[idx].block]?.name||'';tip.style.display='';
    }
  } else {
    if(chestSel.area===area&&chestSel.idx===idx){chestSel=null;tip.style.display='none';renderChest();return;}
    const src=getArr(chestSel.area),dst=getArr(area);
    const si=chestSel.idx,di=idx;
    if(src[si].block===dst[di].block&&dst[di].count>0){
      const add=Math.min(999-dst[di].count,src[si].count);dst[di].count+=add;src[si].count-=add;
      if(!src[si].count){src[si].block=B.AIR;delete src[si].enchant;}
    } else {
      const t={...src[si]};src[si]={...dst[di]};dst[di]=t;
    }
    if(area==='hot')G.slot=idx;
    chestSel=null;tip.style.display='none';
  }
  renderChest();
}

document.getElementById('chest-close').addEventListener('click',closeChest);
document.getElementById('chest-close').addEventListener('touchend',e=>{e.preventDefault();closeChest();},{passive:false});
document.getElementById('chest-overlay').addEventListener('click',e=>{if(e.target===document.getElementById('chest-overlay'))closeChest();});


// โ”€โ”€ End dimension โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const EW=80,EH=32; // end world size

function generateEnd(){
  const w=Array.from({length:EH},()=>new Uint8Array(EW));
  // Floating island 25 blocks wide at y=22
  const islandX=Math.floor(EW/2)-12,islandW=25;
  for(let x=islandX;x<islandX+islandW;x++){
    for(let y=22;y<26;y++) w[y][x]=B.END_STONE;
    w[21][x]=B.END_STONE;
  }
  // Central obsidian pillar
  const px2=Math.floor(EW/2);
  for(let y=2;y<=21;y++) w[y][px2]=B.OBSIDIAN;
  w[1][px2]=B.OBSIDIAN;
  // Return portal in centre of island
  w[21][px2]=B.END_PORTAL;
  return w;
}

function enterEnd(){
  if(!G.endWorld) G.endWorld=generateEnd();
  G.overworldPos={x:G.player.x,y:G.player.y};
  G.dim="end";
  const spx=EW/2*BS,spy=20*BS;
  G.player.x=spx;G.player.y=spy;G.player.vx=0;G.player.vy=0;
  if(!G.dragonDefeated&&!G.dragon){
    G.dragon={
      x:EW/2*BS,y:4*BS,vx:3,vy:0,hp:60,maxHp:60,
      angle:0,phase:"circle",fireCD:0,flashT:0,
      diveTarget:null,diveCD:200,
    };
  }
  G.endFireballs=[];
  G.sleepMsg="โšก You entered The End!";setTimeout(()=>G.sleepMsg="",2500);
}

function leaveEnd(){
  if(G.dim!=="end")return;
  G.dim="over";
  if(G.overworldPos){G.player.x=G.overworldPos.x;G.player.y=G.overworldPos.y;}
  G.player.vx=0;G.player.vy=0;
  G.sleepMsg="๐ŸŒ Returned to the Overworld";setTimeout(()=>G.sleepMsg="",2000);
}

function solidEnd(l,t,w2,h2){
  const ew=G.endWorld;if(!ew)return false;
  for(let by=Math.floor(t/BS);by<=Math.floor((t+h2)/BS);by++)
    for(let bx=Math.floor(l/BS);bx<=Math.floor((l+w2)/BS);bx++){
      if(by<0||by>=EH||bx<0||bx>=EW)continue;
      if(ew[by][bx]&&!DATA[ew[by][bx]]?.pass&&!DATA[ew[by][bx]]?.isPortal)return true;
    }
  return false;
}

function drawDragon(ctx,dx,dy,dir,flash,W){
  if(flash){ctx.save();ctx.globalAlpha=.5;}
  const t=Date.now()*.003;
  // Wings
  const ws=40+Math.sin(t*3)*8;
  ctx.fillStyle=flash?"#cc44ff":"#4a006a";
  // Left wing
  ctx.beginPath();ctx.moveTo(dx,dy);ctx.lineTo(dx-ws,dy-18+Math.sin(t*3)*6);ctx.lineTo(dx-ws+10,dy+10);ctx.closePath();ctx.fill();
  // Right wing
  ctx.beginPath();ctx.moveTo(dx,dy);ctx.lineTo(dx+ws,dy-18+Math.sin(t*3)*6);ctx.lineTo(dx+ws-10,dy+10);ctx.closePath();ctx.fill();
  // Body
  ctx.fillStyle=flash?"#dd55ff":"#6a0090";
  ctx.beginPath();ctx.ellipse(dx,dy,28,16,0,0,Math.PI*2);ctx.fill();
  // Head
  const hx=dx+(dir>0?30:-30),hy=dy-5;
  ctx.fillStyle=flash?"#dd55ff":"#5a0080";
  ctx.beginPath();ctx.ellipse(hx,hy,14,10,0,0,Math.PI*2);ctx.fill();
  // Eyes
  ctx.fillStyle="#ff2200";ctx.beginPath();ctx.arc(hx+(dir>0?5:-5),hy-2,3,0,Math.PI*2);ctx.fill();
  ctx.fillStyle="#ffff00";ctx.beginPath();ctx.arc(hx+(dir>0?5:-5),hy-2,1.5,0,Math.PI*2);ctx.fill();
  // Tail
  ctx.strokeStyle=flash?"#cc44ff":"#4a006a";ctx.lineWidth=6;
  ctx.beginPath();ctx.moveTo(dx+(dir>0?-28:28),dy);ctx.quadraticCurveTo(dx+(dir>0?-50:50),dy+20,dx+(dir>0?-70:70),dy+5);ctx.stroke();
  if(flash)ctx.restore();
  // HP bar
  if(G.dragon){
    const bw=Math.min(300,W*.6),bx2=(W-bw)/2,by2=14;
    ctx.fillStyle="rgba(0,0,0,.7)";roundRect(ctx,bx2-2,by2-2,bw+4,20,8);ctx.fill();
    ctx.fillStyle="#6600aa";ctx.fillRect(bx2,by2,bw*(G.dragon.hp/G.dragon.maxHp),16);
    ctx.fillStyle="#aa44ff";ctx.fillRect(bx2,by2,bw*(G.dragon.hp/G.dragon.maxHp),4);
    ctx.font="bold 12px monospace";ctx.fillStyle="#fff";ctx.textAlign="center";
    ctx.fillText(`๐Ÿ‰ Ender Dragon  ${G.dragon.hp}/${G.dragon.maxHp}`,W/2,by2+12);ctx.textAlign="left";
  }
}

function renderEnd(ctx,W,H){
  const p=G.player;
  // Camera
  const cx=Math.max(0,Math.min(EW*BS-W,p.x-W/2));
  const cy=Math.max(0,Math.min(EH*BS-H,p.y-H*.5));
  // Void sky
  const sky=ctx.createLinearGradient(0,0,0,H);
  sky.addColorStop(0,"#050010");sky.addColorStop(1,"#100020");
  ctx.fillStyle=sky;ctx.fillRect(0,0,W,H);
  // End stars
  for(let i=0;i<60;i++){
    const sx=((i*173+17)%W),sy=((i*251+31)%(H*.6));
    ctx.fillStyle=`rgba(${150+i%80},${100+i%60},255,${.4+.3*Math.sin(Date.now()*.001+i)})`;
    ctx.beginPath();ctx.arc(sx,sy,1+i%2,0,Math.PI*2);ctx.fill();
  }
  // Blocks
  const bx0=Math.max(0,Math.floor(cx/BS)-1),bx1=Math.min(EW-1,Math.ceil((cx+W)/BS)+1);
  const by0=Math.max(0,Math.floor(cy/BS)-1),by1=Math.min(EH-1,Math.ceil((cy+H)/BS)+1);
  for(let by=by0;by<=by1;by++)for(let bx=bx0;bx<=bx1;bx++){
    const blk=G.endWorld[by][bx];if(!blk)continue;
    drawBlock(ctx,bx*BS-cx,by*BS-cy,blk,0,false,false);
  }
  // Fireballs
  G.endFireballs.forEach(fb=>{
    const fx=Math.round(fb.x-cx),fy=Math.round(fb.y-cy);
    ctx.fillStyle="#ff4400";ctx.beginPath();ctx.arc(fx,fy,8,0,Math.PI*2);ctx.fill();
    ctx.fillStyle="#ffcc00";ctx.beginPath();ctx.arc(fx,fy,4,0,Math.PI*2);ctx.fill();
  });
  // Dragon
  if(G.dragon&&G.dragon.hp>0){
    const dr=G.dragon;
    drawDragon(ctx,Math.round(dr.x-cx),Math.round(dr.y-cy),dr.vx>0?1:-1,dr.flashT>0,W);
  }
  // Player
  drawPlayer(ctx,Math.round(p.x-cx),Math.round(p.y-cy),p.dir,p.flash>0);
  // HUD
  drawHUD(ctx,W,H,G);
  if(G.dragonDefeated){
    ctx.font="bold 16px monospace";ctx.fillStyle="#aa44ff";ctx.textAlign="center";
    ctx.fillText("๐Ÿ‰ Dragon Defeated โ€” Step on the portal!",W/2,H-60);ctx.textAlign="left";
  }
  if(G.sleepMsg){
    ctx.textAlign="center";ctx.font="bold 14px monospace";
    const tw=ctx.measureText(G.sleepMsg).width;
    ctx.fillStyle="rgba(0,0,0,.7)";roundRect(ctx,(W-tw-24)/2,H/2-20,tw+24,34,10);ctx.fill();
    ctx.fillStyle="#fff";ctx.fillText(G.sleepMsg,W/2,H/2+4);ctx.textAlign="left";
  }
}

function respawn(){
  G.player.x=G.spawnX;G.player.y=G.spawnY;G.player.vx=G.player.vy=0;
  G.player.hp=MAX_HP;G.player.dmgCD=G.player.flash=0;
  G.zombies=[];G.dayTime=0;
  if(G.dim==='end'){G.dim='over';if(G.overworldPos){G.player.x=G.overworldPos.x;G.player.y=G.overworldPos.y;}}
  document.getElementById('dead').classList.remove('show');
}

// โ”€โ”€ Main Loop โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function loop(ts){
  const canvas=cvs,W=canvas.width,H=canvas.height;
  if(!G){raf=requestAnimationFrame(loop);return;}
  const ctx=canvas.getContext('2d'),p=G.player;

  // Day/Night
  G.dayTime=(G.dayTime+1)%CYCLE;
  const cycT=G.dayTime/CYCLE,isNight=G.dayTime>=DAY_LEN;
  if(G.dayTime%30===0){updateSleepBtn();updateAtkBtn();updateEatBtn();updateFlightBtns();}
  const sc=skyColors(cycT);

  if(G.dim==="over"){
  // Zombie spawning
  if(isNight&&G.zombies.length<MAX_ZOMBIES){
    G.zombieTimer++;
    if(G.zombieTimer>90){G.zombieTimer=0;
      for(let a=0;a<15;a++){
        const sx=Math.floor(Math.random()*WW),sy=G.surf[sx];
        const wx=(sx+.5)*BS,wy=sy*BS;
        if(Math.abs(wx-p.x)>20*BS){G.zombies.push({x:wx,y:wy,vx:0,vy:0,onGround:false,hp:ZOMBIE_HP,flash:0,dir:1,dmgT:0});break;}
      }
    }
  }
  // Zombie & creeper lava damage
  if(G.dim==="over"){
    G.zombies.forEach(z=>{if(inLava(z.x,z.y,ZW,ZH)){z.lavaT=(z.lavaT||0)+1;if(z.lavaT%20===0){z.hp--;z.flash=8;}}else z.lavaT=0;
    if(z.onGround){const cbx=Math.floor(z.x/BS),cby=Math.floor(z.y/BS);if(cby>=0&&cby<WH&&cbx>=0&&cbx<WW){const cv=DATA[G.world[cby][cbx]]?.conveyor;if(cv)z.x+=cv*3.5;}}
  });
    G.creepers.forEach(c=>{if(inLava(c.x,c.y,CW,CH)){c.hp--;c.flash=8;}
    if(c.onGround){const cbx=Math.floor(c.x/BS),cby=Math.floor(c.y/BS);if(cby>=0&&cby<WH&&cbx>=0&&cbx<WW){const cv=DATA[G.world[cby][cbx]]?.conveyor;if(cv)c.x+=cv*3.5;}}
  });
  }
  // Zombie AI - burn first, then filter dead ones
  G.zombies.forEach(z=>{
    z.vy=Math.min(z.vy+GRAVITY,MAX_FALL);
    const dx=p.x-z.x;z.dir=dx>0?1:-1;z.vx=z.dir*ZOMBIE_SPD*(Math.abs(dx)>5?1:0);
    moveEnt(z,ZW,ZH);if(z.flash>0)z.flash--;z.dmgT=Math.max(0,z.dmgT-1);
    // Burn in daylight unless sheltered by a block or door above
    if(!isNight&&!isShielded(z.x,z.y)){z.burnT=(z.burnT||0)+1;if(z.burnT%30===0){z.hp=Math.max(0,z.hp-1);z.flash=25;z.onFire=true;}}
    else{z.burnT=0;z.onFire=false;}
    if(Math.abs(z.x-p.x)<ZW/2+PW/2+4&&Math.abs(z.y-p.y)<ZH/2+PH/2+4&&z.dmgT===0&&p.hp>0){
      const totalDef=[...G.hotbar,...G.inventory].reduce((s,sl)=>s+(sl.count>0&&DATA[sl.block]?.def?DATA[sl.block].def:0),0);
      const blockChance=Math.min(0.85,totalDef*0.05);
      if(!G.creative&&Math.random()>blockChance){p.hp=Math.max(0,p.hp-1);}
      p.dmgCD=20;p.flash=12;z.dmgT=90;
      p.vx=(p.x>z.x?1:-1)*5;p.vy=-4;
      if(p.hp===0)document.getElementById('dead').classList.add('show');
    }
  });
  // Remove dead zombies after processing
  G.zombies=G.zombies.filter(z=>z.hp>0);

  // Creeper spawn (night only)
  if(isNight&&G.creepers.length<MAX_CREEPERS){
    G.creeperTimer++;
    if(G.creeperTimer>150){G.creeperTimer=0;
      for(let a=0;a<15;a++){
        const sx2=Math.floor(Math.random()*WW),sy2=G.surf[sx2];
        const wx=(sx2+.5)*BS,wy=sy2*BS;
        if(Math.abs(wx-p.x)>25*BS){G.creepers.push({x:wx,y:wy,vx:0,vy:0,onGround:false,hp:CREEPER_HP,fuse:0,flash:0,dmgT:0});break;}
      }
    }
  }
  if(!isNight)G.creepers=[];

  // Creeper AI
  G.creepers.forEach(c=>{
    c.vy=Math.min(c.vy+GRAVITY,MAX_FALL);
    const dx=p.x-c.x,dist=Math.hypot(dx,p.y-c.y);
    const dir=dx>0?1:-1;
    const los=hasLOS(c.x,c.y-CH/2,p.x,p.y-PH/2);
    if(dist>CREEPER_RANGE||!los){c.vx=dir*CREEPER_SPD;c.fuse=Math.max(0,c.fuse-2);}
    else{c.vx=0;c.fuse=Math.min(CREEPER_FUSE,c.fuse+1);}
    moveEnt(c,CW,CH);
    if(c.flash>0)c.flash--;
    // Explode!
    if(c.fuse>=CREEPER_FUSE){
      // Damage player
      if(!G.creative){p.hp=Math.max(0,p.hp-1);p.flash=20;p.dmgCD=20;
      if(!p.hp)document.getElementById("dead").classList.add("show");}
      // Destroy blocks in radius 3
      const bx=Math.floor(c.x/BS),by2=Math.floor(c.y/BS);
      for(let dy=-3;dy<=3;dy++)for(let dx2=-3;dx2<=3;dx2++){
        if(dx2*dx2+dy*dy>9)continue;
        const tx=bx+dx2,ty=by2+dy;
        if(tx>=0&&tx<WW&&ty>=0&&ty<WH&&G.world[ty][tx]!==B.BEDROCK)G.world[ty][tx]=B.AIR;
      }
      c.hp=0;
    }
  });
  G.creepers.forEach(c=>{if(c.hp<=0&&c.fuse<CREEPER_FUSE)addItem(G.hotbar,G.inventory,B.GUNPOWDER,1+Math.floor(Math.random()*2));});
  G.creepers=G.creepers.filter(c=>c.hp>0);

  // Lit TNT physics & explosion
  G.litTNT.forEach(t=>{
    t.vy=Math.min(t.vy+GRAVITY,MAX_FALL);
    const ny=t.y+t.vy;
    const bby=Math.floor((ny)/BS),bbx=Math.floor(t.x/BS);
    if(t.vy>0&&bbx>=0&&bbx<WW&&bby>=0&&bby<WH&&G.world[bby][bbx]&&!DATA[G.world[bby][bbx]]?.pass){t.y=bby*BS;t.vy=0;}
    else t.y=ny;
    t.fuse--;
    if(t.fuse<=0){
      // Big explosion: 5 block radius โ€” chain-lights nearby TNT
      const ebx=Math.floor(t.x/BS),eby=Math.floor(t.y/BS);
      for(let dy=-5;dy<=5;dy++)for(let dx2=-5;dx2<=5;dx2++){
        if(dx2*dx2+dy*dy>25)continue;
        const tx=ebx+dx2,ty=eby+dy;
        if(tx<0||tx>=WW||ty<0||ty>=WH)continue;
        if(G.world[ty][tx]===B.TNT){
          G.world[ty][tx]=B.AIR;
          G.litTNT.push({x:(tx+.5)*BS,y:ty*BS,vx:0,vy:0,fuse:20+Math.floor(Math.random()*40)});
        }else if(G.world[ty][tx]!==B.BEDROCK){G.world[ty][tx]=B.AIR;}
      }
      // Damage player
      if(!G.creative&&Math.hypot(G.player.x-t.x,G.player.y-t.y)<5*BS){G.player.hp=Math.max(0,G.player.hp-4);G.player.flash=25;G.player.dmgCD=20;if(!G.player.hp)document.getElementById("dead").classList.add("show");}
      // Kill zombies & creepers
      G.zombies.forEach(z=>{if(Math.hypot(z.x-t.x,z.y-t.y)<5*BS){z.hp=0;}});
      G.creepers.forEach(c=>{if(Math.hypot(c.x-t.x,c.y-t.y)<5*BS){c.hp=0;}});
      t.fuse=-1;
    }
  });
  G.litTNT=G.litTNT.filter(t=>t.fuse>0);

  // Dispenser firing (every 90 frames ~1.5s)
  G.dispenserTick++;
  if(G.dispenserTick>=90){
    G.dispenserTick=0;
    for(let dy=Math.max(0,Math.floor(G.camera.y/BS)-2);dy<=Math.min(WH-1,Math.ceil((G.camera.y+800)/BS)+2);dy++){
      for(let dx=0;dx<WW;dx++){
        if(G.world[dy][dx]!==B.DISPENSER)continue;
        const wx=(dx+.5)*BS,wy=(dy+.5)*BS;
        // Find nearest zombie within 20 blocks
        let nearest=null,bestDist=20*BS;
        G.zombies.forEach(z=>{const d=Math.hypot(z.x-wx,z.y-wy);if(d<bestDist){bestDist=d;nearest=z;}});
        const dk=`${dx},${dy}`;
        const stored=G.dispenserArrows[dk]||0;
        if(nearest&&stored>0){
          G.dispenserArrows[dk]=stored-1;
          const ang=Math.atan2(nearest.y-wy,nearest.x-wx);
          G.arrows.push({x:wx,y:wy,vx:Math.cos(ang)*13,vy:Math.sin(ang)*13,life:90,dmg:2,fromDispenser:true});
        }
      }
    }
  }
  // Arrow physics & collision
  G.arrows.forEach(a=>{
    a.vy+=0.25;a.x+=a.vx;a.y+=a.vy;a.life--;
    // Hit zombie or creeper
    G.zombies.forEach(z=>{
      if(a.life>0&&Math.abs(a.x-z.x)<ZW&&Math.abs(a.y-z.y)<ZH){z.hp-=(a.dmg||3);z.flash=12;a.life=0;}
    });
    G.creepers.forEach(c=>{
      if(a.life>0&&Math.abs(a.x-c.x)<CW&&Math.abs(a.y-c.y)<CH){c.hp-=(a.dmg||3);c.flash=12;a.life=0;}
    });
    // Hit solid block
    const bx=Math.floor(a.x/BS),by=Math.floor(a.y/BS);
    if(bx>=0&&bx<WW&&by>=0&&by<WH){
      const blk=G.world[by][bx];
      if(blk&&!DATA[blk]?.pass&&!(blk===B.DOOR&&G.openDoors.has(`${bx},${by}`)))a.life=0;
    }
  });
  G.zombies=G.zombies.filter(z=>z.hp>0);
  G.arrows=G.arrows.filter(a=>a.life>0);

  // Player
  if(p.atkCD>0)p.atkCD--;if(p.flash>0)p.flash--;if(p.dmgCD>0)p.dmgCD--;
  // Lava damage (2 hearts/sec = every 30 frames)
  if(!G.creative&&G.dim==="over"&&inLava(p.x,p.y,PW,PH)){
    p.lavaT=(p.lavaT||0)+1;
    if(p.lavaT%30===0){p.hp=Math.max(0,p.hp-2);p.flash=10;p.dmgCD=5;
    if(!p.hp)document.getElementById("dead").classList.add("show");}
  } else p.lavaT=0;
  // Conveyor belt effect on player
  if(p.onGround&&G.dim==="over"){
    const cbx=Math.floor(p.x/BS),cby=Math.floor(p.y/BS);
    if(cby>=0&&cby<WH&&cbx>=0&&cbx<WW){
      const cvDir=DATA[G.world[cby][cbx]]?.conveyor;
      if(cvDir)p.x+=cvDir*3.5;
    }
  }
  // Heal 1 heart every 10 seconds during the day
  if(!isNight&&p.hp>0&&p.hp<MAX_HP){p.regenT=(p.regenT||0)+1;if(p.regenT>=600){p.hp=Math.min(MAX_HP,p.hp+1);p.regenT=0;}}
  // Bow charging
  const equippedBow=G.hotbar[G.slot]?.count>0&&DATA[G.hotbar[G.slot].block]?.isBow;
  if(p.bowCharging&&equippedBow)p.bowCharge=Math.min(60,p.bowCharge+1);
  else if(!equippedBow){p.bowCharging=false;p.bowCharge=0;}
  const goL=keys.has('ArrowLeft')||keys.has('a')||keys.has('A')||touch.left;
  const goR=keys.has('ArrowRight')||keys.has('d')||keys.has('D')||touch.right;
  const goJ=keys.has('ArrowUp')||keys.has('w')||keys.has('W')||keys.has(' ')||touch.jump;
  if(goL){p.vx=-MOVE_SPD;p.dir=-1;}else if(goR){p.vx=MOVE_SPD;p.dir=1;}else p.vx*=.7;
  if(G.creative){
    const jumpJustPressed=goJ&&!p.prevJump;
    p.prevJump=goJ;
    const goDown=keys.has("ShiftLeft")||keys.has("Shift")||touch.down;
    if(!p.onGround){
      if(goJ){p.vy=-7;p.flyHover=true;}
      else if(goDown){p.vy=5;p.flyHover=false;}
      else if(p.flyHover){p.vy=0;}
      else p.vy=Math.min(p.vy+0.18,3);
    }else{
      p.flyHover=false;
      if(goJ){p.vy=JUMP_VEL;p.onGround=false;p.flyHover=true;}
      else p.vy=Math.min(p.vy+GRAVITY,MAX_FALL);
    }
  }else{
    if(goJ&&p.onGround){p.vy=JUMP_VEL;p.onGround=false;}
    p.vy=Math.min(p.vy+GRAVITY,MAX_FALL);
  }
  moveEnt(p,PW,PH);
  if(p.y>WH*BS+200){p.x=G.spawnX;p.y=G.spawnY;p.vx=p.vy=0;}
  // Sleep when standing on top of a bed
  if(!G.sleeping){
    const bx1=Math.floor((p.x-PW/2)/BS),bx2=Math.floor((p.x+PW/2)/BS);
    const footBy=Math.floor(p.y/BS); // block directly under feet
    let onBed=false;
    for(let bx=bx1;bx<=bx2&&!onBed;bx++)
      if(bx>=0&&bx<WW&&footBy>=0&&footBy<WH&&G.world[footBy][bx]===B.BED)onBed=true;
    if(onBed&&G.hotbar[G.slot]?.block!==B.BED){
      if(G.dayTime>=DAY_LEN){G.sleeping=true;G.sleepTimer=0;}
      else{if(!G.sleepMsg){G.sleepMsg="You can only sleep at night!";setTimeout(()=>G.sleepMsg="",2500);}}
    }
  }

  } // end G.dim==="over" block

  // โ”€โ”€ Player + camera (both dims) โ”€โ”€
  if(G.dim==="over"){
    G.camera.x=Math.max(0,Math.min(WW*BS-W,p.x-W/2));
    G.camera.y=Math.max(0,Math.min(WH*BS-H,p.y-H*.5));
  } else if(G.dim==="end"&&G.endWorld){
    // End physics
    const goLe=keys.has('ArrowLeft')||keys.has('a')||keys.has('A')||touch.left;
    const goRe=keys.has('ArrowRight')||keys.has('d')||keys.has('D')||touch.right;
    const goJe=keys.has('ArrowUp')||keys.has('w')||keys.has('W')||keys.has(' ')||touch.jump;
    if(goLe){p.vx=-MOVE_SPD;p.dir=-1;}else if(goRe){p.vx=MOVE_SPD;p.dir=1;}else p.vx*=.7;
    if(goJe&&p.onGround){p.vy=JUMP_VEL;p.onGround=false;}
    p.vy=Math.min(p.vy+GRAVITY,MAX_FALL);
    const nx3=p.x+p.vx;
    if(!solidEnd(nx3-PW/2,p.y-PH+2,PW,PH-4))p.x=nx3;else p.vx=0;
    p.x=Math.max(PW/2,Math.min(EW*BS-PW/2,p.x));
    const ny3=p.y+p.vy;p.onGround=false;
    if(p.vy>0){
      if(!solidEnd(p.x-PW/2+1,ny3-.5,PW-2,1))p.y=ny3;
      else{p.y=Math.floor(ny3/BS)*BS;p.vy=0;p.onGround=true;}
    }else if(p.vy<0){
      if(!solidEnd(p.x-PW/2+1,ny3-PH,PW-2,.5))p.y=ny3;
      else{p.y=(Math.floor((ny3-PH)/BS)+1)*BS+PH;p.vy=0;}
    }
    if(p.y>EH*BS+100){p.hp=0;document.getElementById("dead").classList.add("show");}
    // Dragon AI
    if(G.dragon&&G.dragon.hp>0){
      const dr=G.dragon;dr.flashT=Math.max(0,dr.flashT-1);
      const distP=Math.hypot(p.x-dr.x,p.y-dr.y);
      dr.angle+=0.015;
      if(dr.hp<dr.maxHp*.6&&(dr.diveCD||200)>0){dr.diveCD=(dr.diveCD||200)-1;}
      if(dr.hp<dr.maxHp*.6&&dr.diveCD<=0){
        const tx=p.x,ty=p.y-PH*.5;
        dr.vx+=(tx-dr.x)*.008;dr.vy+=(ty-dr.y)*.008;
        const spd=Math.hypot(dr.vx,dr.vy);if(spd>8){dr.vx=dr.vx/spd*8;dr.vy=dr.vy/spd*8;}
        if(distP<40||dr.y>18*BS){dr.vx*=.6;dr.vy*=.6;dr.diveCD=300;}
      }else{
        const rx=EW*BS/2+Math.cos(dr.angle)*18*BS,ry=5*BS+Math.sin(dr.angle*2)*.5*BS;
        dr.vx=(rx-dr.x)*.04;dr.vy=(ry-dr.y)*.04;
      }
      dr.x+=dr.vx;dr.y+=dr.vy;
      dr.x=Math.max(3*BS,Math.min((EW-3)*BS,dr.x));
      dr.y=Math.max(2*BS,Math.min(15*BS,dr.y));
      dr.fireCD=(dr.fireCD||0)-1;
      if(dr.fireCD<=0){dr.fireCD=150;const ang3=Math.atan2(p.y-dr.y,p.x-dr.x);G.endFireballs.push({x:dr.x,y:dr.y,vx:Math.cos(ang3)*7,vy:Math.sin(ang3)*7,life:120});}
      if(!G.creative&&distP<40&&p.dmgCD<=0){p.hp=Math.max(0,p.hp-2);p.flash=20;p.dmgCD=40;if(!p.hp)document.getElementById("dead").classList.add("show");}
    }
    G.endFireballs=G.endFireballs||[];
    G.endFireballs.forEach(fb=>{
      fb.x+=fb.vx;fb.y+=fb.vy;fb.vy+=.1;fb.life--;
      if(!G.creative&&Math.hypot(fb.x-p.x,fb.y-p.y)<22&&p.dmgCD<=0){p.hp=Math.max(0,p.hp-2);p.flash=20;p.dmgCD=40;if(!p.hp)document.getElementById("dead").classList.add("show");fb.life=0;}
      if(solidEnd(fb.x-4,fb.y-4,8,8))fb.life=0;
    });
    G.endFireballs=G.endFireballs.filter(f=>f.life>0);
    G.arrows.forEach(a=>{
      if(G.dragon&&G.dragon.hp>0&&a.life>0&&Math.hypot(a.x-G.dragon.x,a.y-G.dragon.y)<40){G.dragon.hp-=(a.dmg||3);G.dragon.flashT=15;a.life=0;}
    });
    if(G.dragon&&G.dragon.hp<=0&&!G.dragonDefeated){
      G.dragonDefeated=true;G.dragon=null;
      addItem(G.hotbar,G.inventory,B.DRAGON_EGG,1);
      G.sleepMsg="๐Ÿ‰ Dragon defeated! Step on the portal to return home.";setTimeout(()=>G.sleepMsg="",5000);
    }
    G.camera.x=Math.max(0,Math.min(EW*BS-W,p.x-W/2));
    G.camera.y=Math.max(0,Math.min(EH*BS-H,p.y-H*.5));
  }

  // Mining tick
  if(G.mining&&G.mouseDown){
    G.mining.t++;
    if(G.mining.t>=G.mining.max){
      const{bx,by}=G.mining,minedBlock=G.world[by][bx];
      const eq=G.hotbar[G.slot];
      const hasSilk=eq?.enchant==="silk_touch";
      const drop=hasSilk&&SILK_DROPS[minedBlock]!=null?SILK_DROPS[minedBlock]:DATA[minedBlock]?.drop;
      const wasWood=G.world[by][bx]===B.WOOD;
      const mKey=`${bx},${by}`;
      if(G.world[by][bx]===B.DISPENSER){const stored=G.dispenserArrows[mKey]||0;if(stored>0)addItem(G.hotbar,G.inventory,B.ARROW,stored);delete G.dispenserArrows[mKey];}
      if(G.world[by][bx]===B.CHEST){const cs=G.chestContents[mKey];if(cs)cs.forEach(s=>{if(s.count>0)addItem(G.hotbar,G.inventory,s.block,s.count,s.enchant);});delete G.chestContents[mKey];}
      G.world[by][bx]=B.AIR;G.openDoors.delete(mKey);G.mining=null;
      if(wasWood)decayLeaves();
      if(drop&&drop!==B.AIR)addItem(G.hotbar,G.inventory,drop,1);
    }
  }else if(!G.mouseDown)G.mining=null;

  // โ”€โ”€ Render โ”€โ”€
  const cx=G.camera.x,cy=G.camera.y;
  if(G.dim==="end"){renderEnd(ctx,W,H);raf=requestAnimationFrame(loop);return;}

  const sky=ctx.createLinearGradient(0,0,0,H);sky.addColorStop(0,sc.top);sky.addColorStop(1,sc.bot);
  ctx.fillStyle=sky;ctx.fillRect(0,0,W,H);

  if(sc.dark){
    const na=Math.min(.55,(G.dayTime-DAY_LEN)/300*.55);
    // Stars
    const st=Math.min(1,(G.dayTime-DAY_LEN)/60);
    stars.forEach(s=>{
      const sx2=s.x-cx,sy2=s.y-cy;
      if(sx2<-10||sx2>W+10||sy2<-10||sy2>H*.6)return;
      ctx.fillStyle=`rgba(255,255,255,${.7*st})`;ctx.beginPath();ctx.arc(sx2,sy2,s.r,0,Math.PI*2);ctx.fill();
    });
    // Moon
    ctx.fillStyle="#e8e0c0";ctx.beginPath();ctx.arc(W*.75,H*.14,22,0,Math.PI*2);ctx.fill();
    ctx.fillStyle=sc.top;ctx.beginPath();ctx.arc(W*.75+8,H*.14-4,18,0,Math.PI*2);ctx.fill();
    // Darkness
    ctx.fillStyle=`rgba(0,0,20,${na})`;ctx.fillRect(0,0,W,H);
    // Torch glow
    const bx0=Math.max(0,Math.floor(cx/BS)-1),bx1=Math.min(WW-1,Math.ceil((cx+W)/BS)+1);
    const by0=Math.max(0,Math.floor(cy/BS)-1),by1=Math.min(WH-1,Math.ceil((cy+H)/BS)+1);
    for(let by=by0;by<=by1;by++)for(let bx=bx0;bx<=bx1;bx++){
      if(G.world[by][bx]===B.TORCH){
        const lx=bx*BS+BS/2-cx,ly=by*BS+BS/2-cy;
        const gr=ctx.createRadialGradient(lx,ly,0,lx,ly,BS*3.5);
        gr.addColorStop(0,"rgba(255,150,30,.45)");gr.addColorStop(1,"rgba(0,0,0,0)");
        ctx.fillStyle=gr;ctx.fillRect(lx-BS*3.5,ly-BS*3.5,BS*7,BS*7);
      }
    }
    // Player ambient glow
    const plx=Math.round(p.x-cx),ply=Math.round(p.y-cy);
    const pg=ctx.createRadialGradient(plx,ply,0,plx,ply,BS*3);
    pg.addColorStop(0,"rgba(255,160,40,.18)");pg.addColorStop(1,"rgba(0,0,0,0)");
    ctx.fillStyle=pg;ctx.fillRect(plx-BS*3,ply-BS*3,BS*6,BS*6);
  }else{
    // Sun
    const dp=G.dayTime/DAY_LEN;
    const sunx=lerp(W*.05,W*.95,dp),suny=H*.1+Math.sin(dp*Math.PI)*(-H*.08);
    ctx.fillStyle="#ffe060";ctx.beginPath();ctx.arc(sunx,suny,20,0,Math.PI*2);ctx.fill();
    ctx.fillStyle="rgba(255,220,80,.22)";ctx.beginPath();ctx.arc(sunx,suny,32,0,Math.PI*2);ctx.fill();
    // Clouds
    ctx.fillStyle="rgba(255,255,255,.82)";
    for(let i=0;i<5;i++){
      const ox=((i*270+(ts*.008))%(W+500))-150,oy=40+i*38;
      ctx.beginPath();ctx.ellipse(ox,oy,72,26,0,0,Math.PI*2);ctx.fill();
      ctx.beginPath();ctx.ellipse(ox+48,oy-14,46,20,0,0,Math.PI*2);ctx.fill();
      ctx.beginPath();ctx.ellipse(ox+24,oy-20,32,16,0,0,Math.PI*2);ctx.fill();
    }
  }

  // Blocks
  const bx0=Math.max(0,Math.floor(cx/BS)-1),bx1=Math.min(WW-1,Math.ceil((cx+W)/BS)+1);
  const by0=Math.max(0,Math.floor(cy/BS)-1),by1=Math.min(WH-1,Math.ceil((cy+H)/BS)+1);
  for(let by=by0;by<=by1;by++)for(let bx=bx0;bx<=bx1;bx++){
    const blk=G.world[by][bx];if(!blk)continue;
    const mp=G.mining&&G.mining.bx===bx&&G.mining.by===by?G.mining.t/G.mining.max:0;
    drawBlock(ctx,bx*BS-cx,by*BS-cy,blk,mp,!!(G.hover?.bx===bx&&G.hover?.by===by),G.openDoors.has(`${bx},${by}`));
  }

  // Zombies & Player
  G.zombies.forEach(z=>drawZombie(ctx,Math.round(z.x-cx),Math.round(z.y-cy),z.dir,z.flash>0,z.hp,z.onFire));
  G.creepers.forEach(c=>drawCreeper(ctx,Math.round(c.x-cx),Math.round(c.y-cy),c.fuse,c.flash>0));
  // Draw arrows
  G.arrows.forEach(a=>{
    const ax=Math.round(a.x-cx),ay=Math.round(a.y-cy);
    const angle=Math.atan2(a.vy,a.vx);
    ctx.save();ctx.translate(ax,ay);ctx.rotate(angle);
    ctx.fillStyle="#a07040";ctx.fillRect(-10,-2,20,3);
    ctx.fillStyle="#e0e0a0";ctx.beginPath();ctx.moveTo(10,-3);ctx.lineTo(14,0);ctx.lineTo(10,3);ctx.fill();
    ctx.restore();
  });
  // Bow charge bar above player
  if(p.bowCharging&&p.bowCharge>0){
    const px2=Math.round(p.x-cx),py2=Math.round(p.y-cy);
    const t=p.bowCharge/60;
    const bw=36,bx2=px2-bw/2,by2=py2-PH-18;
    ctx.fillStyle="rgba(0,0,0,.6)";ctx.fillRect(bx2-1,by2-1,bw+2,10);
    const col=t<0.5?`rgb(255,${Math.round(200*t*2)},0)`:"rgb(255,200,0)";
    if(t>=1){ctx.fillStyle="#fff";ctx.fillRect(bx2-1,by2-1,bw+2,10);}
    ctx.fillStyle=col;ctx.fillRect(bx2,by2,bw*t,8);
    if(t>=1){ctx.font="12px serif";ctx.textAlign="center";ctx.fillText("๐Ÿน",px2,by2-4);ctx.textAlign="left";}
  }
  // Draw lit TNT
  G.litTNT.forEach(t=>{
    const tx=Math.round(t.x-cx)-BS/2,ty=Math.round(t.y-cy)-BS;
    const flash=Math.floor(t.fuse/8)%2===0;
    ctx.fillStyle=flash?"#ff8888":"#cc2222";ctx.fillRect(tx,ty,BS,BS);
    ctx.fillStyle="#fff";ctx.font=`bold ${BS*.28}px monospace`;ctx.textAlign="center";
    ctx.fillText("TNT",tx+BS/2,ty+BS*.58);ctx.textAlign="left";
    // Fuse spark
    ctx.fillStyle="#ffcc00";ctx.beginPath();ctx.arc(tx+BS/2,ty-3+(Math.sin(Date.now()*.05)*3),3,0,Math.PI*2);ctx.fill();
    // Fuse countdown bar
    const bw=32,bx2=tx+BS/2-bw/2,by2=ty-14;
    ctx.fillStyle="rgba(0,0,0,.5)";ctx.fillRect(bx2,by2,bw,5);
    ctx.fillStyle="#ff4400";ctx.fillRect(bx2,by2,bw*(t.fuse/90),5);
  });
  drawPlayer(ctx,Math.round(p.x-cx),Math.round(p.y-cy),p.dir,p.flash>0);

  // HUD
  // Sleep overlay
  if(G.sleeping){
    G.sleepTimer++;
    const fadeIn=Math.min(1,G.sleepTimer/40);
    const fadeOut=G.sleepTimer>80?Math.max(0,1-(G.sleepTimer-80)/20):1;
    const a=fadeIn*fadeOut;
    ctx.fillStyle=`rgba(5,5,20,${a*0.97})`;ctx.fillRect(0,0,W,H);
    if(G.sleepTimer>20&&G.sleepTimer<90){
      ctx.textAlign="center";ctx.font="bold 28px monospace";
      ctx.fillStyle=`rgba(255,255,255,${a})`;ctx.fillText("๐Ÿ’ค Sleeping...",W/2,H/2);
      ctx.font="16px monospace";ctx.fillStyle=`rgba(200,200,255,${a*0.7})`;
      ctx.fillText("Skipping to morning",W/2,H/2+36);ctx.textAlign="left";
    }
    if(G.sleepTimer===60){G.dayTime=0;G.zombies=[];}
    if(G.sleepTimer>=100){G.sleeping=false;G.sleepTimer=0;G.sleepMsg="โ˜€๏ธ Good morning!";setTimeout(()=>G.sleepMsg="",2500);}
  }
  // Sleep/info message
  if(G.sleepMsg){
    ctx.textAlign="center";ctx.font="bold 14px monospace";
    const tw=ctx.measureText(G.sleepMsg).width;
    ctx.fillStyle="rgba(0,0,0,.7)";roundRect(ctx,(W-tw-24)/2,H/2-20,tw+24,34,10);ctx.fill();
    ctx.fillStyle="#fff";ctx.fillText(G.sleepMsg,W/2,H/2+4);ctx.textAlign="left";
  }
  drawHUD(ctx,W,H,G);

  raf=requestAnimationFrame(loop);
}

// โ”€โ”€ Resize & Start โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
function resize(){
  cvs.width=cvs.offsetWidth;cvs.height=cvs.offsetHeight;
  // Reposition UI buttons for screen size
  const W=cvs.offsetWidth;
  document.getElementById('btn-bag').style.right='10px';
}
function startFlatGame(){
  document.getElementById('menu').style.display='none';
  document.getElementById('btn-resume').style.display='none';
  G=null;
  // Use flat world gen
  const{world,surf}=genFlatWorld();
  const seed=Date.now()%100000;
  const sx=Math.floor(WW/2);
  stars=Array.from({length:80},(_,i)=>({x:rng(i,0,seed+42)*WW*BS,y:rng(i,1,seed+42)*WH*.4*BS,r:1+rng(i,2,seed+42)*2}));
  G={world,surf,
    spawnX:(sx+.5)*BS,spawnY:surf[sx]*BS,
    player:{x:(sx+.5)*BS,y:surf[sx]*BS,vx:0,vy:0,onGround:false,dir:1,hp:MAX_HP,dmgCD:0,flash:0,atkCD:0,bowCharge:0,bowCharging:false},
    camera:{x:0,y:0},
    hotbar:Array.from({length:8},()=>empty()),
    inventory:Array.from({length:32},()=>empty()),
    slot:0,mining:null,hover:null,mouseDown:false,openDoors:new Set(),
    dayTime:0,zombies:[],zombieTimer:0,creepers:[],creeperTimer:0,arrows:[],dispenserTick:0,dispenserArrows:{},chestContents:{},playerPlacedChests:new Set(),litTNT:[],creative:false,flying:false,noclip:true,lastDispenserTap:null,pistonStates:{},sleeping:false,sleepTimer:0,sleepMsg:"",
    dim:"over",endWorld:null,endSurf:null,overworldPos:null,
    dragon:null,endFireballs:[],dragonDefeated:false,
  };
  document.getElementById('dead').classList.remove('show');
  G.creative=true;
  fillCreativeInventory();
  updateModeBtn();buildRecipeUI();updateFlightBtns();
  saveWorld();
}
function startGame(creative){
  document.getElementById('menu').style.display='none';
  document.getElementById('btn-resume').style.display='none';
  G=null;
  initGame();
  G.creative=creative;
  saveWorld();// Save fresh world
  if(creative){
    fillCreativeInventory();
    G.player.hp=MAX_HP;
  }
  updateFlightBtns();
}



// โ”€โ”€ Pistons โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const CANNOT_PUSH=new Set([B.BEDROCK,B.END_PORTAL,B.OBSIDIAN]);

function pistonExtend(bx,by,dir){
  const dx=Math.abs(dir)===1?dir:0, dy=dir>1?1:dir<-1?-1:0;
  // Collect blocks in push direction (up to 5)
  const chain=[];
  let tx=bx+dx,ty=by+dy;
  while(tx>=0&&tx<WW&&ty>=0&&ty<WH&&G.world[ty][tx]!==B.AIR&&chain.length<5){
    if(CANNOT_PUSH.has(G.world[ty][tx]))return; // immovable
    chain.push({x:tx,y:ty,block:G.world[ty][tx]});
    tx+=dx;ty+=dy;
  }
  // Check destination is empty
  const ex=tx,ey=ty;
  if(ex<0||ex>=WW||ey<0||ey>=WH)return;
  // Shift blocks forward
  for(let i=chain.length-1;i>=0;i--){
    const nx2=chain[i].x+dx,ny2=chain[i].y+dy;
    G.world[ny2][nx2]=chain[i].block;
    if(G.pistonStates[`${chain[i].x},${chain[i].y}`]){
      G.pistonStates[`${nx2},${ny2}`]={...G.pistonStates[`${chain[i].x},${chain[i].y}`]};
      delete G.pistonStates[`${chain[i].x},${chain[i].y}`];
    }
  }
  G.world[by+dy][bx+dx]=B.AIR; // piston arm space (arm is visual only)
  const k=`${bx},${by}`;
  G.pistonStates[k].extended=true;
}

function pistonRetract(bx,by,dir,sticky){
  const k=`${bx},${by}`;
  const dx=Math.abs(dir)===1?dir:0, dy=dir>1?1:dir<-1?-1:0;
  G.pistonStates[k].extended=false;
  if(sticky){
    // Pull the block adjacent to arm back
    const ax=bx+dx*2,ay=by+dy*2;
    if(ax>=0&&ax<WW&&ay>=0&&ay<WH&&G.world[ay][ax]!==B.AIR&&!CANNOT_PUSH.has(G.world[ay][ax])){
      G.world[by+dy][bx+dx]=G.world[ay][ax];
      if(G.pistonStates[`${ax},${ay}`]){
        G.pistonStates[`${bx+dx},${by+dy}`]={...G.pistonStates[`${ax},${ay}`]};
        delete G.pistonStates[`${ax},${ay}`];
      }
      G.world[ay][ax]=B.AIR;
    }
  }
}

function togglePiston(bx,by){
  const k=`${bx},${by}`;
  const blk=G.world[by][bx];
  if(blk!==B.PISTON&&blk!==B.STICKY_PISTON)return;
  if(!G.pistonStates[k])G.pistonStates[k]={dir:1,extended:false};
  const ps=G.pistonStates[k];
  if(!ps.extended) pistonExtend(bx,by,ps.dir);
  else pistonRetract(bx,by,ps.dir,blk===B.STICKY_PISTON);
}

// โ”€โ”€ Save / Load โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
async function saveWorld(){
  if(!G)return;
  const data={
    world:Array.from(G.world,row=>Array.from(row)),
    surf:Array.from(G.surf),
    player:{x:G.player.x,y:G.player.y,hp:G.player.hp},
    hotbar:G.hotbar.map(s=>({...s})),
    inventory:G.inventory.map(s=>({...s})),
    chestContents:Object.fromEntries(Object.entries(G.chestContents).map(([k,v])=>[k,v.map(s=>({...s}))])),
    dispenserArrows:{...G.dispenserArrows},
    openDoors:[...G.openDoors],
    pistonStates:JSON.parse(JSON.stringify(G.pistonStates||{})),
    playerPlacedChests:[...(G.playerPlacedChests||new Set())],
    dayTime:G.dayTime,creative:G.creative,
    spawnX:G.spawnX,spawnY:G.spawnY,
    savedAt:new Date().toLocaleString(),
  };
  try{
    await window.storage.set('mc_save',JSON.stringify(data));
    G.sleepMsg="๐Ÿ’พ World saved!";setTimeout(()=>G.sleepMsg="",2000);
    checkSave();
  }catch(e){G.sleepMsg="Save failed!";setTimeout(()=>G.sleepMsg="",2000);}
}

async function loadWorld(){
  try{
    const res=await window.storage.get('mc_save');
    if(!res)return null;
    return JSON.parse(res.value);
  }catch(e){return null;}
}

async function checkSave(){
  try{
    const res=await window.storage.get('mc_save');
    if(res){
      const d=JSON.parse(res.value);
      document.getElementById('btn-continue').style.display='';
      const info=document.getElementById('save-info');
      if(info)info.textContent=(d.creative?'๐ŸŒŸ Creative':'โš”๏ธ Survival')+' ยท '+d.savedAt;
    }
  }catch(e){}
}

async function continueWorld(){
  const data=await loadWorld();
  if(!data)return;
  document.getElementById('menu').style.display='none';
  G=null;initGame();
  // Restore world
  G.world=data.world.map(row=>new Uint8Array(row));
  G.surf=data.surf;
  G.player.x=data.player.x;G.player.y=data.player.y;G.player.hp=data.player.hp||MAX_HP;
  G.hotbar=data.hotbar;G.inventory=data.inventory;
  G.chestContents=Object.fromEntries(Object.entries(data.chestContents).map(([k,v])=>[k,v]));
  G.dispenserArrows=data.dispenserArrows||{};
  G.openDoors=new Set(data.openDoors||[]);
  G.pistonStates=data.pistonStates||{};
  G.playerPlacedChests=new Set(data.playerPlacedChests||[]);
  G.dayTime=data.dayTime||0;
  G.creative=data.creative||false;
  G.spawnX=data.spawnX;G.spawnY=data.spawnY;
  if(G.creative)fillCreativeInventory();
  updateFlightBtns();buildRecipeUI();
}

function goToMenu(){
  saveWorld();
  setTimeout(()=>{
    document.getElementById('menu').style.display='flex';
    document.getElementById('btn-resume').style.display='';
  },400);
}

document.getElementById('btn-menu').addEventListener('touchstart',e=>{e.stopPropagation();e.preventDefault();goToMenu();},{passive:false});
document.getElementById('btn-menu').addEventListener('mousedown',e=>{e.stopPropagation();goToMenu();});

function resumeWorld(){
  if(!G)return;
  document.getElementById('menu').style.display='none';
}
document.getElementById('btn-resume').addEventListener('click',resumeWorld);
document.getElementById('btn-resume').addEventListener('touchend',e=>{e.preventDefault();resumeWorld();},{passive:false});
document.getElementById('btn-continue').addEventListener('click',continueWorld);
document.getElementById('btn-continue').addEventListener('touchend',e=>{e.preventDefault();continueWorld();},{passive:false});
// Auto-save every 60 seconds
setInterval(()=>{if(G&&!document.getElementById('menu').style.display.includes('flex'))saveWorld();},60000);
// Check for save on load
checkSave();

document.getElementById('btn-survival').addEventListener('click',()=>startGame(false));
document.getElementById('btn-survival').addEventListener('touchend',e=>{e.preventDefault();startGame(false);},{passive:false});
document.getElementById('btn-creative').addEventListener('click',()=>startGame(true));
document.getElementById('btn-creative').addEventListener('touchend',e=>{e.preventDefault();startGame(true);},{passive:false});
document.getElementById('btn-flat').addEventListener('click',startFlatGame);
document.getElementById('btn-flat').addEventListener('touchend',e=>{e.preventDefault();startFlatGame();},{passive:false});
window.addEventListener('resize',resize);
resize();
raf=requestAnimationFrame(loop); // loop starts but waits for G to be set
</script>
</body>
</html>

Game Source: Mini Minecraft 2D

Creator: ShadowLegend17

Libraries: none

Complexity: complex (2403 lines, 122.1 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: mini-minecraft-2d-shadowlegend17-mr71mvs3" to link back to the original. Then publish at arcadelab.ai/publish.