Meta Description" name="description" />

Share this result

Previews are deleted daily. Get a permanent share link sent to your inbox:
Script
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>SLIMERA – Devour & Evolve</title> <link href="https://fonts.googleapis.com/css2?family=Fredoka+One&family=Nunito:wght@400;600;700;800&display=swap" rel="stylesheet"> <style> *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} html,body{width:100%;height:100%;overflow:hidden;background:#1a3a1a;font-family:'Nunito',sans-serif} #gameCanvas{display:block;touch-action:none} #ui{position:fixed;top:0;left:0;width:100%;pointer-events:none;z-index:10} .hud{display:flex;align-items:flex-start;justify-content:space-between;padding:10px 14px;gap:8px} .hud-left{display:flex;flex-direction:column;gap:5px;pointer-events:all} .hud-right{display:flex;flex-direction:column;align-items:flex-end;gap:5px;pointer-events:all} .stat-bar-wrap{background:rgba(0,0,0,.45);border-radius:20px;padding:4px 8px;display:flex;align-items:center;gap:6px;min-width:160px} .stat-icon{font-size:13px} .stat-bar-bg{flex:1;height:10px;background:rgba(255,255,255,.18);border-radius:6px;overflow:hidden} .stat-bar-fill{height:100%;border-radius:6px;transition:width .3s} .bar-hp{background:linear-gradient(90deg,#e74c3c,#ff6b6b)} .bar-xp{background:linear-gradient(90deg,#f39c12,#f9ca24)} .stat-val{font-size:11px;color:#fff;font-weight:700;min-width:32px;text-align:right} .level-badge{background:rgba(0,0,0,.5);border:2px solid #f9ca24;border-radius:10px;padding:3px 10px;color:#f9ca24;font-family:'Fredoka One',sans-serif;font-size:14px} .ability-bar{display:flex;gap:5px;pointer-events:all} .ability-slot{width:42px;height:42px;background:rgba(0,0,0,.5);border:2px solid rgba(255,255,255,.25);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:20px;position:relative;cursor:pointer;transition:border-color .2s} .ability-slot.active{border-color:#00e5ff;box-shadow:0 0 8px #00e5ff88} .ability-slot.ready{border-color:#2ecc71} .ability-slot .cd-overlay{position:absolute;inset:0;background:rgba(0,0,0,.65);border-radius:8px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:11px;font-weight:800} .ability-slot .key-hint{position:absolute;bottom:2px;right:4px;font-size:8px;color:rgba(255,255,255,.5);font-weight:700} .msg-popup{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,.78);border:2px solid #f9ca24;border-radius:14px;padding:14px 22px;color:#fff;text-align:center;z-index:50;pointer-events:none;transition:opacity .5s;max-width:260px} .msg-popup h2{font-family:'Fredoka One',sans-serif;font-size:22px;color:#f9ca24;margin-bottom:4px} .msg-popup p{font-size:13px;color:#ddd;line-height:1.5} .msg-popup.hidden{opacity:0;pointer-events:none} /* Mobile controls */ #mobileControls{position:fixed;bottom:10px;left:0;width:100%;display:flex;justify-content:space-between;align-items:flex-end;padding:0 14px;z-index:20;pointer-events:none} .joystick-area{position:relative;width:110px;height:110px;pointer-events:all} #joystickBase{width:110px;height:110px;background:rgba(0,0,0,.3);border:2px solid rgba(255,255,255,.2);border-radius:50%;position:absolute} #joystickKnob{width:46px;height:46px;background:rgba(255,255,255,.35);border:2px solid rgba(255,255,255,.6);border-radius:50%;position:absolute;top:32px;left:32px;transition:background .1s} .action-btns{display:flex;flex-direction:column;gap:8px;pointer-events:all} .act-btn{width:58px;height:58px;border-radius:50%;border:3px solid rgba(255,255,255,.35);background:rgba(0,0,0,.4);color:#fff;font-size:11px;font-weight:800;font-family:'Fredoka One',sans-serif;text-align:center;line-height:1.2;display:flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent;user-select:none;letter-spacing:.5px} .act-btn:active{filter:brightness(1.4)} #btnDevour{border-color:#e74c3c;background:rgba(180,20,20,.4);font-size:13px} #btnAbility{border-color:#00e5ff;background:rgba(0,80,140,.4);font-size:22px} /* Overlays */ .screen{position:fixed;inset:0;z-index:100;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(10,25,10,.92);backdrop-filter:blur(6px)} .screen.hidden{display:none} .screen-title{font-family:'Fredoka One',sans-serif;font-size:clamp(36px,10vw,58px);letter-spacing:.05em;margin-bottom:6px} .screen-sub{font-size:14px;color:#aed6a0;margin-bottom:24px;text-align:center;max-width:300px;line-height:1.6} .big-btn{font-family:'Fredoka One',sans-serif;font-size:18px;padding:13px 38px;border:none;border-radius:50px;cursor:pointer;letter-spacing:.05em;transition:all .15s;margin:5px} .big-btn:hover,.big-btn:active{filter:brightness(1.15);transform:scale(1.03)} .btn-green{background:linear-gradient(135deg,#27ae60,#2ecc71);color:#fff;box-shadow:0 4px 16px #27ae6066} .btn-red{background:linear-gradient(135deg,#c0392b,#e74c3c);color:#fff;box-shadow:0 4px 16px #c0392b66} /* Ability unlock popup */ #abilityUnlock{position:fixed;top:18%;left:50%;transform:translateX(-50%);background:linear-gradient(135deg,#1a3a4a,#0d2233);border:3px solid #00e5ff;border-radius:18px;padding:16px 22px;z-index:60;pointer-events:none;text-align:center;min-width:220px;max-width:280px} #abilityUnlock h3{font-family:'Fredoka One',sans-serif;font-size:18px;color:#00e5ff;margin-bottom:4px} #abilityUnlock p{font-size:12px;color:#adf;line-height:1.5} #abilityUnlock.hidden{display:none} /* minimap */ #minimap{position:fixed;top:10px;right:10px;z-index:15;pointer-events:none} #minimapCanvas{border:2px solid rgba(255,255,255,.25);border-radius:6px;background:rgba(0,0,0,.3)} </style> </head> <body> <canvas id="gameCanvas"></canvas> <div id="ui"> <div class="hud"> <div class="hud-left"> <div class="level-badge" id="levelBadge">LV 1 – Micro Slime</div> <div class="stat-bar-wrap"><span class="stat-icon">❤️</span><div class="stat-bar-bg"><div class="stat-bar-fill bar-hp" id="hpBar" style="width:100%"></div></div><span class="stat-val" id="hpVal">100</span></div> <div class="stat-bar-wrap"><span class="stat-icon">⭐</span><div class="stat-bar-bg"><div class="stat-bar-fill bar-xp" id="xpBar" style="width:0%"></div></div><span class="stat-val" id="xpVal">0</span></div> </div> <div class="hud-right"> <div class="ability-bar" id="abilityBar"></div> </div> </div> </div> <div id="mobileControls"> <div class="joystick-area" id="joystickArea"> <div id="joystickBase"></div> <div id="joystickKnob"></div> </div> <div class="action-btns"> <div class="act-btn" id="btnAbility">✨</div> <div class="act-btn" id="btnDevour">EAT</div> </div> </div> <div class="msg-popup hidden" id="msgPopup"><h2 id="msgTitle"></h2><p id="msgBody"></p></div> <div id="abilityUnlock" class="hidden"><h3 id="unlockName"></h3><p id="unlockDesc"></p></div> <!-- START SCREEN --> <div class="screen" id="startScreen"> <div class="screen-title" style="color:#7dff7a;text-shadow:0 0 20px #7dff7a88">SLIMERA</div> <div class="screen-sub">You are a mysterious slime. Devour creatures, absorb their powers,<br>grow enormous & conquer the wild!</div> <button class="big-btn btn-green" id="startBtn">🌿 BEGIN JOURNEY</button> <div style="font-size:12px;color:#668866;margin-top:12px">WASD / Arrow keys or on-screen joystick<br>SPACE / EAT button to devour nearby creatures</div> </div> <!-- GAME OVER SCREEN --> <div class="screen hidden" id="gameOverScreen"> <div class="screen-title" style="color:#e74c3c">DEVOURED</div> <div class="screen-sub" id="gameOverText">You were overcome by a stronger creature.</div> <button class="big-btn btn-green" id="restartBtn">🔄 Try Again</button> </div> <script> "use strict"; // ───────────────────────────────────────────── // CONSTANTS & CONFIG // ───────────────────────────────────────────── const WORLD_W = 3200, WORLD_H = 3200; const TILE = 64; const COLS = WORLD_W / TILE, ROWS = WORLD_H / TILE; // Level thresholds (XP needed) const LEVEL_XP = [0,30,80,160,280,450,680,980,1360,1830,2400,3100,4000,5200,6700]; const LEVEL_NAMES = [ "Micro Slime","Tiny Slime","Small Slime","Blob","Plump Blob", "Pudding Slime","Bouncy Slime","Jelly Beast","Ooze Crawler","Gooey Horror", "Slime Titan","Ancient Ooze","Omega Blob","Void Slime","SLIMERA" ]; // ───────────────────────────────────────────── // CREATURE DEFINITIONS // ───────────────────────────────────────────── const CREATURES = [ // id, name, emoji-style (drawn via canvas), level, xp, hp, speed, size, color, rarity, zone, ability { id:"ant", name:"Ant", lv:1, xp:5, hp:8, spd:1.4, sz:6, col:"#a0522d",col2:"#7b3b1e", rare:false, zone:"grass", abilityId:null }, { id:"fly", name:"Fly", lv:1, xp:6, hp:6, spd:2.2, sz:5, col:"#555", col2:"#333", rare:false, zone:"grass", abilityId:"dash" }, { id:"beetle", name:"Beetle", lv:2, xp:12, hp:18, spd:0.9, sz:9, col:"#1a5c1a",col2:"#0d3d0d", rare:false, zone:"grass", abilityId:null }, { id:"butterfly", name:"Butterfly", lv:2, xp:10, hp:7, spd:2.0, sz:8, col:"#e056a0",col2:"#b0206a", rare:false, zone:"grass", abilityId:null }, { id:"worm", name:"Earthworm", lv:1, xp:8, hp:10, spd:0.7, sz:7, col:"#d27b5c",col2:"#b05a40", rare:false, zone:"grass", abilityId:null }, { id:"spider", name:"Spider", lv:3, xp:18, hp:22, spd:1.8, sz:10, col:"#1a1a2e",col2:"#0d0d1a", rare:false, zone:"forest",abilityId:"web" }, { id:"frog", name:"Frog", lv:3, xp:20, hp:28, spd:1.5, sz:12, col:"#3cb371",col2:"#267350", rare:false, zone:"water", abilityId:"jump" }, { id:"snail", name:"Snail", lv:2, xp:14, hp:20, spd:0.5, sz:10, col:"#c8a060",col2:"#8b5e20", rare:false, zone:"grass", abilityId:"shell" }, { id:"grasshopper",name:"Grasshopper",lv:3,xp:16, hp:16, spd:2.5, sz:11, col:"#5a8f2a",col2:"#3a6010", rare:false, zone:"grass", abilityId:"jump" }, { id:"ladybug", name:"Ladybug", lv:2, xp:11, hp:14, spd:1.2, sz:8, col:"#e02020",col2:"#a01010", rare:false, zone:"grass", abilityId:null }, { id:"mouse", name:"Mouse", lv:4, xp:28, hp:35, spd:2.0, sz:13, col:"#b0a090",col2:"#807060", rare:false, zone:"forest",abilityId:null }, { id:"rabbit", name:"Rabbit", lv:5, xp:40, hp:50, spd:2.8, sz:16, col:"#e8dcc8",col2:"#c4b090", rare:false, zone:"grass", abilityId:"dash" }, { id:"lizard", name:"Lizard", lv:5, xp:38, hp:55, spd:1.8, sz:15, col:"#5a9e40",col2:"#3a6e28", rare:false, zone:"forest",abilityId:null }, { id:"fish", name:"Goldfish", lv:3, xp:22, hp:24, spd:2.2, sz:11, col:"#ff7f00",col2:"#cc5500", rare:false, zone:"water", abilityId:null }, { id:"crab", name:"Crab", lv:5, xp:36, hp:60, spd:1.1, sz:15, col:"#e04020",col2:"#a02010", rare:false, zone:"water", abilityId:"pinch" }, { id:"turtle", name:"Turtle", lv:6, xp:50, hp:80, spd:0.7, sz:18, col:"#4a8040",col2:"#2a5020", rare:false, zone:"water", abilityId:"shell" }, { id:"snake_e", name:"Snake", lv:6, xp:55, hp:70, spd:2.0, sz:14, col:"#78a030",col2:"#4a6820", rare:false, zone:"forest",abilityId:"poison" }, { id:"crow", name:"Crow", lv:7, xp:65, hp:75, spd:3.0, sz:16, col:"#222", col2:"#111", rare:false, zone:"forest",abilityId:"dive" }, { id:"fox", name:"Fox", lv:8, xp:90, hp:100, spd:2.5, sz:20, col:"#d86820",col2:"#a04010", rare:false, zone:"forest",abilityId:"dash" }, { id:"owl", name:"Owl", lv:8, xp:85, hp:90, spd:2.0, sz:18, col:"#8b6b40",col2:"#5a4020", rare:false, zone:"forest",abilityId:"night" }, { id:"deer", name:"Deer", lv:9, xp:110, hp:130, spd:2.8, sz:22, col:"#c8a060",col2:"#906030", rare:false, zone:"forest",abilityId:null }, { id:"boar", name:"Boar", lv:10,xp:140, hp:160, spd:2.0, sz:24, col:"#7a5a40",col2:"#503a28", rare:false, zone:"forest",abilityId:"charge" }, // WATER SPECIALS { id:"jellyfish", name:"Jellyfish", lv:4, xp:30, hp:30, spd:0.8, sz:14, col:"#9b59b6",col2:"#7b39a0", rare:true, zone:"water", abilityId:"zap" }, { id:"octopus", name:"Octopus", lv:7, xp:80, hp:85, spd:1.5, sz:18, col:"#8e44ad",col2:"#5d2d80", rare:true, zone:"water", abilityId:"ink" }, // RARE { id:"firefly", name:"Firefly", lv:3, xp:25, hp:12, spd:2.5, sz:7, col:"#f0e040",col2:"#c0b000", rare:true, zone:"grass", abilityId:"glow" }, { id:"scorpion", name:"Scorpion", lv:6, xp:60, hp:65, spd:1.6, sz:14, col:"#c8a020",col2:"#907010", rare:true, zone:"grass", abilityId:"sting" }, { id:"chameleon", name:"Chameleon", lv:7, xp:75, hp:70, spd:1.3, sz:17, col:"#40c080",col2:"#208050", rare:true, zone:"forest",abilityId:"camouflage" }, { id:"dragonfly", name:"Dragonfly", lv:4, xp:32, hp:20, spd:3.5, sz:10, col:"#20aaff",col2:"#0080cc", rare:true, zone:"water", abilityId:"dash" }, { id:"mantis", name:"Mantis", lv:5, xp:45, hp:48, spd:2.0, sz:13, col:"#58b840",col2:"#308020", rare:true, zone:"grass", abilityId:"strike" }, { id:"axolotl", name:"Axolotl", lv:5, xp:50, hp:55, spd:1.4, sz:16, col:"#ff9ec8",col2:"#e06898", rare:true, zone:"water", abilityId:"regen" }, { id:"bearCub", name:"Bear Cub", lv:11,xp:180, hp:220, spd:1.8, sz:28, col:"#8b5e2c",col2:"#5a3a18", rare:false, zone:"forest",abilityId:"roar" }, { id:"wolf", name:"Wolf", lv:12,xp:220, hp:260, spd:3.0, sz:26, col:"#888", col2:"#555", rare:false, zone:"forest",abilityId:"howl" }, { id:"eagle", name:"Eagle", lv:13,xp:280, hp:300, spd:3.5, sz:28, col:"#8b6020",col2:"#5a3e10", rare:true, zone:"forest",abilityId:"dive" }, ]; // ───────────────────────────────────────────── // ABILITY DEFINITIONS // ───────────────────────────────────────────── const ABILITIES = { dash: { name:"Speed Dash", emoji:"💨", desc:"Burst forward at 3× speed for 1.5s", cd:8, duration:1500, type:"move" }, web: { name:"Sticky Web", emoji:"🕸️", desc:"Slow nearby enemies for 3s", cd:12, duration:3000, type:"area" }, jump: { name:"Leap", emoji:"🦘", desc:"Leap over obstacles & stun on land", cd:7, duration:800, type:"move" }, shell: { name:"Hard Shell", emoji:"🛡️", desc:"Block 80% damage for 2s", cd:15, duration:2000, type:"defend" }, poison: { name:"Venom Spit", emoji:"☠️", desc:"Spit poison, DoT 3 dmg/s for 4s", cd:10, duration:4000, type:"attack" }, zap: { name:"Zap", emoji:"⚡", desc:"Electric shock stuns nearest foe 2s", cd:9, duration:2000, type:"attack" }, ink: { name:"Ink Cloud", emoji:"🌑", desc:"Blind nearby enemies for 3s", cd:14, duration:3000, type:"area" }, glow: { name:"Glow Lure", emoji:"✨", desc:"Lure nearby creatures toward you", cd:11, duration:3000, type:"area" }, sting: { name:"Venom Sting", emoji:"🦂", desc:"Instant 30 damage to target", cd:8, duration:500, type:"attack" }, camouflage: { name:"Camouflage", emoji:"👻", desc:"Turn invisible to enemies for 4s", cd:18, duration:4000, type:"defend" }, strike: { name:"Power Strike", emoji:"⚔️", desc:"Double devour success chance", cd:10, duration:3000, type:"attack" }, regen: { name:"Regen", emoji:"💚", desc:"Regenerate 30 HP over 5s", cd:20, duration:5000, type:"heal" }, pinch: { name:"Crab Pinch", emoji:"🦀", desc:"Deal 25 damage + stun for 1s", cd:9, duration:1000, type:"attack" }, night: { name:"Night Vision", emoji:"🦉", desc:"Reveal all creatures on map for 8s", cd:25, duration:8000, type:"util" }, charge: { name:"Boar Charge", emoji:"🐗", desc:"Charge forward, stun & deal 40 dmg", cd:12, duration:1200, type:"attack" }, roar: { name:"Bear Roar", emoji:"🐻", desc:"Frighten all nearby creatures away", cd:20, duration:3000, type:"area" }, howl: { name:"Wolf Howl", emoji:"🐺", desc:"+50% speed & attack for 5s", cd:25, duration:5000, type:"buff" }, dive: { name:"Eagle Dive", emoji:"🦅", desc:"Instantly devour any creature ≤ your lv",cd:30, duration:1000, type:"attack" }, }; // ───────────────────────────────────────────── // GAME STATE // ───────────────────────────────────────────── const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); let W, H; function resize(){ W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; } resize(); window.addEventListener('resize', resize); // Camera const cam = { x:0, y:0 }; // Player const player = { x: WORLD_W/2, y: WORLD_H/2, vx:0, vy:0, hp:100, maxHp:100, xp:0, level:1, size:14, baseSpeed:2.2, color:"#7dff7a", color2:"#4acc45", abilities:[], activeAbilityIdx:0, abilityCooldowns:{}, abilityEndTimes:{}, activeEffects:{}, // effectId -> endTime wobble:0, wobbleV:0, facing:0, // angle devourAnim:0, dead:false, invincible:0, // ms }; // World objects let creatures = []; let particles = []; let floatingTexts = []; let tiles = []; // tile types let decorations = []; let msgTimer = 0; let gameRunning = false; let lastTime = 0; let keys = {}; let joystick = { active:false, dx:0, dy:0 }; let minimapCanvas, minimapCtx; // ───────────────────────────────────────────── // TILE / WORLD GENERATION // ───────────────────────────────────────────── function genWorld(){ tiles = []; for(let r=0;r<ROWS;r++){ tiles[r]=[]; for(let c=0;c<COLS;c++){ // Create zones: center=grass, pockets of water and forest const wx = c/COLS, wy = r/ROWS; // noise-ish const n = Math.sin(c*0.3)*Math.cos(r*0.2) + Math.sin(c*0.07+r*0.11)*1.5; let t; if(n > 1.1) t = 'water'; else if(n > 0.4) t = 'deep_grass'; else if(n < -0.9) t = 'forest'; else t = 'grass'; // border water if(c<2||c>COLS-3||r<2||r>ROWS-3) t='water'; tiles[r][c]=t; } } // Build decorations (trees, rocks, flowers, lily pads) decorations = []; for(let r=0;r<ROWS;r++){ for(let c=0;c<COLS;c++){ const t = tiles[r][c]; if(t==='forest' && Math.random()<0.35){ decorations.push({type:'tree',x:c*TILE+Math.random()*TILE,y:r*TILE+Math.random()*TILE,s:18+Math.random()*12,variant:Math.floor(Math.random()*3)}); } else if(t==='deep_grass' && Math.random()<0.25){ decorations.push({type:'bush',x:c*TILE+Math.random()*TILE,y:r*TILE+Math.random()*TILE,s:8+Math.random()*6}); } else if(t==='grass' && Math.random()<0.08){ decorations.push({type:'flower',x:c*TILE+Math.random()*TILE,y:r*TILE+Math.random()*TILE,col:['#ff69b4','#ffd700','#ff4500','#da70d6'][Math.floor(Math.random()*4)]}); } else if(t==='water' && Math.random()<0.06){ decorations.push({type:'lily',x:c*TILE+Math.random()*TILE,y:r*TILE+Math.random()*TILE,s:10+Math.random()*8}); } else if(t==='forest' && Math.random()<0.05){ decorations.push({type:'rock',x:c*TILE+Math.random()*TILE,y:r*TILE+Math.random()*TILE,s:7+Math.random()*8}); } } } } function tileAt(wx,wy){ const c = Math.floor(wx/TILE), r = Math.floor(wy/TILE); if(c<0||c>=COLS||r<0||r>=ROWS) return 'water'; return tiles[r][c]; } // ───────────────────────────────────────────── // SPAWN CREATURES // ───────────────────────────────────────────── function spawnCreatures(){ creatures = []; const total = 280; for(let i=0;i<total;i++){ spawnRandomCreature(); } } function spawnRandomCreature(){ // pick random creature def const defs = CREATURES; // bias toward lower level creatures let def; const r = Math.random(); if(r < 0.02) { // rare const rares = defs.filter(d=>d.rare); def = rares[Math.floor(Math.random()*rares.length)]; } else { // weighted by inv level const pool = defs.filter(d=>!d.rare); const weights = pool.map(d=> Math.max(0.2, 1/(d.lv))); const total = weights.reduce((a,b)=>a+b,0); let pick = Math.random()*total; def = pool[0]; for(let i=0;i<pool.length;i++){ pick -= weights[i]; if(pick<=0){def=pool[i];break;} } } // find valid position in matching zone let x,y,tries=0; do { x = 80 + Math.random()*(WORLD_W-160); y = 80 + Math.random()*(WORLD_H-160); tries++; } while(tileAt(x,y)!==def.zone && tileAt(x,y)!=='grass' && tries<30); const c = { def, x, y, vx: (Math.random()-0.5)*def.spd, vy: (Math.random()-0.5)*def.spd, hp: def.hp, maxHp: def.hp, size: def.sz, angle: Math.random()*Math.PI*2, wobble: Math.random()*Math.PI*2, wanderTimer: Math.random()*180, fleeing: false, fleeTimer: 0, stunned: 0, poisoned: 0, id: Math.random().toString(36).substr(2,8), highlighted: false, }; creatures.push(c); return c; } // ───────────────────────────────────────────── // DRAWING HELPERS // ───────────────────────────────────────────── const TILE_COLORS = { grass: ['#5db347','#6dc050','#4ea03a'], deep_grass: ['#3a8030','#44952e','#2e6820'], forest: ['#2a5c20','#346625','#1e4a18'], water: ['#3a8fbf','#4499cc','#2a7aaa'], }; function drawWorld(){ const startC = Math.max(0, Math.floor(cam.x/TILE)-1); const endC = Math.min(COLS, Math.ceil((cam.x+W)/TILE)+1); const startR = Math.max(0, Math.floor(cam.y/TILE)-1); const endR = Math.min(ROWS, Math.ceil((cam.y+H)/TILE)+1); for(let r=startR;r<endR;r++){ for(let c=startC;c<endC;c++){ const t = tiles[r][c]; const cols = TILE_COLORS[t] || TILE_COLORS.grass; // slight checkerboard const ci = (r+c)%3; ctx.fillStyle = cols[ci]; ctx.fillRect(c*TILE - cam.x, r*TILE - cam.y, TILE, TILE); } } } function drawDecorations(){ for(const d of decorations){ const sx = d.x - cam.x, sy = d.y - cam.y; if(sx < -60 || sx > W+60 || sy < -60 || sy > H+60) continue; ctx.save(); ctx.translate(sx, sy); if(d.type==='tree'){ // trunk ctx.fillStyle = '#6b4226'; ctx.fillRect(-3, 0, 6, 12); // canopy layers const greens = ['#1a5c10','#227a16','#2a9920']; ctx.fillStyle = greens[d.variant%3]; ctx.shadowColor='rgba(0,0,0,.25)';ctx.shadowBlur=6; ctx.beginPath();ctx.arc(0,-d.s*0.4,d.s,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#2eb820'; ctx.beginPath();ctx.arc(-d.s*0.25,-d.s*0.7,d.s*0.7,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(d.s*0.2,-d.s*0.65,d.s*0.65,0,Math.PI*2);ctx.fill(); ctx.shadowBlur=0; } else if(d.type==='bush'){ ctx.fillStyle='#3d8a28'; ctx.beginPath();ctx.ellipse(0,0,d.s*1.2,d.s*0.8,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#4da830'; ctx.beginPath();ctx.ellipse(-d.s*0.3,-d.s*0.3,d.s*0.7,d.s*0.6,0,0,Math.PI*2);ctx.fill(); } else if(d.type==='flower'){ const petals = 5; for(let i=0;i<petals;i++){ const a = i/petals*Math.PI*2; ctx.fillStyle = d.col; ctx.beginPath();ctx.ellipse(Math.cos(a)*4,Math.sin(a)*4,3,2,a,0,Math.PI*2);ctx.fill(); } ctx.fillStyle='#ffd700'; ctx.beginPath();ctx.arc(0,0,2.5,0,Math.PI*2);ctx.fill(); } else if(d.type==='lily'){ ctx.fillStyle='rgba(50,160,50,.7)'; ctx.beginPath();ctx.ellipse(0,0,d.s,d.s*0.7,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#ff69b4'; ctx.beginPath();ctx.arc(0,0,3,0,Math.PI*2);ctx.fill(); } else if(d.type==='rock'){ ctx.fillStyle='#888'; ctx.beginPath();ctx.ellipse(0,0,d.s,d.s*0.7,0.3,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#aaa'; ctx.beginPath();ctx.ellipse(-d.s*0.1,-d.s*0.2,d.s*0.5,d.s*0.35,0.5,0,Math.PI*2);ctx.fill(); } ctx.restore(); } } function drawCreature(c){ const sx = c.x - cam.x, sy = c.y - cam.y; if(sx<-80||sx>W+80||sy<-80||sy>H+80) return; const d = c.def; ctx.save(); ctx.translate(sx, sy); // stunned flash if(c.stunned > 0){ ctx.globalAlpha = 0.5 + 0.5*Math.sin(Date.now()*0.02); } const s = c.size; const wobble = Math.sin(c.wobble)*0.15; // Draw based on creature type const id = d.id; // === INSECT TYPES === if(id==='ant'){ drawAnt(ctx, s, d.col, d.col2); } else if(id==='fly'){ drawFly(ctx, s, d.col, d.col2); } else if(id==='beetle'){ drawBeetle(ctx, s, d.col, d.col2); } else if(id==='butterfly'){ drawButterfly(ctx, s, d.col, d.col2, c.wobble); } else if(id==='worm'){ drawWorm(ctx, s, d.col, d.col2, wobble); } else if(id==='spider'){ drawSpider(ctx, s, d.col, d.col2); } else if(id==='grasshopper'){ drawGrasshopper(ctx, s, d.col, d.col2); } else if(id==='ladybug'){ drawLadybug(ctx, s, d.col, d.col2); } else if(id==='snail'){ drawSnail(ctx, s, d.col, d.col2); } else if(id==='firefly'){ drawFirefly(ctx, s, d.col, d.col2); } else if(id==='scorpion'){ drawScorpion(ctx, s, d.col, d.col2); } else if(id==='mantis'){ drawMantis(ctx, s, d.col, d.col2); } else if(id==='dragonfly'){ drawDragonfly(ctx, s, d.col, d.col2); // === AMPHIBIAN / REPTILE === } else if(id==='frog'){ drawFrog(ctx, s, d.col, d.col2); } else if(id==='snake_e'){ drawSnakeCreature(ctx, s, d.col, d.col2, wobble); } else if(id==='lizard'){ drawLizard(ctx, s, d.col, d.col2); } else if(id==='turtle'){ drawTurtle(ctx, s, d.col, d.col2); } else if(id==='chameleon'){ drawChameleon(ctx, s, d.col, d.col2); } else if(id==='axolotl'){ drawAxolotl(ctx, s, d.col, d.col2); // === FISH / WATER === } else if(id==='fish'){ drawFish(ctx, s, d.col, d.col2); } else if(id==='jellyfish'){ drawJellyfish(ctx, s, d.col, d.col2, c.wobble); } else if(id==='crab'){ drawCrab(ctx, s, d.col, d.col2); } else if(id==='octopus'){ drawOctopus(ctx, s, d.col, d.col2, c.wobble); // === MAMMALS / BIRDS === } else if(id==='mouse'){ drawMouse(ctx, s, d.col, d.col2); } else if(id==='rabbit'){ drawRabbit(ctx, s, d.col, d.col2); } else if(id==='crow'){ drawCrow(ctx, s, d.col, d.col2); } else if(id==='fox'){ drawFox(ctx, s, d.col, d.col2); } else if(id==='owl'){ drawOwl(ctx, s, d.col, d.col2); } else if(id==='deer'){ drawDeer(ctx, s, d.col, d.col2); } else if(id==='boar'){ drawBoar(ctx, s, d.col, d.col2); } else if(id==='bearCub'){ drawBear(ctx, s, d.col, d.col2); } else if(id==='wolf'){ drawWolf(ctx, s, d.col, d.col2); } else if(id==='eagle'){ drawEagle(ctx, s, d.col, d.col2); } else { // generic ctx.fillStyle = d.col; ctx.beginPath(); ctx.arc(0,0,s,0,Math.PI*2); ctx.fill(); } // HP bar (only if damaged) if(c.hp < c.maxHp){ const bw = s*2.4, bh = 4; ctx.fillStyle='rgba(0,0,0,.5)'; ctx.fillRect(-bw/2, -s-10, bw, bh); ctx.fillStyle = c.hp/c.maxHp > 0.5 ? '#2ecc71' : c.hp/c.maxHp>0.25?'#f39c12':'#e74c3c'; ctx.fillRect(-bw/2, -s-10, bw*(c.hp/c.maxHp), bh); } // RARE badge if(d.rare){ ctx.fillStyle='rgba(0,0,0,.5)'; ctx.beginPath();ctx.arc(s+2,-s+2,5,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#f9ca24'; ctx.font='bold 7px sans-serif';ctx.textAlign='center';ctx.textBaseline='middle'; ctx.fillText('★',s+2,-s+2); } ctx.restore(); } // ───────────────────────────────────────────── // CREATURE DRAW FUNCTIONS // ───────────────────────────────────────────── function drawAnt(ctx,s,c1,c2){ ctx.fillStyle=c1; // 3 body segments ctx.beginPath();ctx.ellipse(0,-s*0.8,s*0.4,s*0.35,0,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(0,0,s*0.55,s*0.45,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(0,s*0.9,s*0.7,s*0.6,0,0,Math.PI*2);ctx.fill(); // legs ctx.strokeStyle=c2;ctx.lineWidth=1.2; for(let i=-1;i<=1;i+=1){ ctx.beginPath();ctx.moveTo(-s*0.5,i*s*0.3);ctx.lineTo(-s*1.3,i*s*0.5+s*0.2);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.5,i*s*0.3);ctx.lineTo(s*1.3,i*s*0.5+s*0.2);ctx.stroke(); } // antennae ctx.beginPath();ctx.moveTo(-s*0.2,-s*1.1);ctx.lineTo(-s*0.6,-s*1.8);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.2,-s*1.1);ctx.lineTo(s*0.6,-s*1.8);ctx.stroke(); } function drawFly(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.55,s*0.7,0,0,Math.PI*2);ctx.fill(); // wings ctx.fillStyle='rgba(180,220,255,.55)'; ctx.beginPath();ctx.ellipse(-s*1.0,-s*0.3,s*0.9,s*0.45,-.4,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*1.0,-s*0.3,s*0.9,s*0.45,.4,0,Math.PI*2);ctx.fill(); // eyes ctx.fillStyle='#e00'; ctx.beginPath();ctx.arc(-s*0.25,-s*0.5,s*0.22,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.25,-s*0.5,s*0.22,0,Math.PI*2);ctx.fill(); } function drawBeetle(ctx,s,c1,c2){ ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(0,0,s,s*0.85,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle=c1; // wing cases ctx.beginPath();ctx.ellipse(-s*0.4,s*0.1,s*0.5,s*0.75,-0.1,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.4,s*0.1,s*0.5,s*0.75,0.1,0,Math.PI*2);ctx.fill(); // head ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(0,-s*0.8,s*0.4,s*0.35,0,0,Math.PI*2);ctx.fill(); ctx.strokeStyle=c2;ctx.lineWidth=1; // legs for(let i=0;i<3;i++){ const y = -s*0.3 + i*s*0.35; ctx.beginPath();ctx.moveTo(-s,y);ctx.lineTo(-s*1.5,y+s*0.4);ctx.stroke(); ctx.beginPath();ctx.moveTo(s,y);ctx.lineTo(s*1.5,y+s*0.4);ctx.stroke(); } } function drawButterfly(ctx,s,c1,c2,t){ const fw = Math.abs(Math.sin(t*0.1))*0.4+0.6; ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(-s*fw,-s*0.5,s*1.1*fw,s*0.8,-.5,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*fw,-s*0.5,s*1.1*fw,s*0.8,.5,0,Math.PI*2);ctx.fill(); ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(-s*fw*.6,s*0.2,s*0.7*fw,s*0.55,-.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*fw*.6,s*0.2,s*0.7*fw,s*0.55,.3,0,Math.PI*2);ctx.fill(); // body ctx.fillStyle='#333'; ctx.beginPath();ctx.ellipse(0,0,s*0.22,s*0.95,0,0,Math.PI*2);ctx.fill(); // wing dots ctx.fillStyle='rgba(255,255,255,.5)'; ctx.beginPath();ctx.arc(-s*fw*0.5,-s*0.3,s*0.2,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*fw*0.5,-s*0.3,s*0.2,0,Math.PI*2);ctx.fill(); } function drawWorm(ctx,s,c1,c2,w){ ctx.strokeStyle=c1;ctx.lineWidth=s*0.9;ctx.lineCap='round'; ctx.beginPath();ctx.moveTo(-s*1.2+w*s*0.5,0); ctx.bezierCurveTo(-s*0.4,s*w*3,s*0.4,-s*w*3,s*1.2+w*s*0.3,0); ctx.stroke(); ctx.fillStyle=c2; ctx.beginPath();ctx.arc(-s*1.2+w*s*0.5,0,s*0.5,0,Math.PI*2);ctx.fill(); } function drawSpider(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.7,s*0.6,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(0,-s*0.85,s*0.5,s*0.45,0,0,Math.PI*2);ctx.fill(); ctx.strokeStyle=c2;ctx.lineWidth=1.2; // 8 legs for(let i=0;i<4;i++){ const a = -0.3 + i*0.22; ctx.beginPath();ctx.moveTo(-s*0.6,a*s*2);ctx.quadraticCurveTo(-s*1.4,a*s*2-s*0.8,-s*1.8,a*s*2+s*0.3);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.6,a*s*2);ctx.quadraticCurveTo(s*1.4,a*s*2-s*0.8,s*1.8,a*s*2+s*0.3);ctx.stroke(); } ctx.fillStyle='#e00'; ctx.beginPath();ctx.arc(-s*0.2,-s*0.25,s*0.15,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.2,-s*0.25,s*0.15,0,Math.PI*2);ctx.fill(); } function drawGrasshopper(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*1.1,s*0.5,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(-s*0.7,-s*0.35,s*0.4,s*0.38,-.4,0,Math.PI*2);ctx.fill(); // big back legs ctx.strokeStyle=c1;ctx.lineWidth=2.2; ctx.beginPath();ctx.moveTo(-s*0.3,s*0.4);ctx.lineTo(-s*0.9,s*1.3);ctx.lineTo(-s*0.2,s*1.8);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.3,s*0.4);ctx.lineTo(s*0.9,s*1.3);ctx.lineTo(s*0.2,s*1.8);ctx.stroke(); // antennae ctx.strokeStyle=c2;ctx.lineWidth=1; ctx.beginPath();ctx.moveTo(-s*0.8,-s*0.6);ctx.lineTo(-s*1.4,-s*1.5);ctx.stroke(); ctx.beginPath();ctx.moveTo(-s*0.6,-s*0.6);ctx.lineTo(-s*0.9,-s*1.5);ctx.stroke(); } function drawLadybug(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.arc(0,0,s,0,Math.PI*2);ctx.fill(); // split line ctx.strokeStyle='#111';ctx.lineWidth=1.5; ctx.beginPath();ctx.moveTo(0,-s);ctx.lineTo(0,s);ctx.stroke(); // dots ctx.fillStyle='#111'; ctx.beginPath();ctx.arc(-s*0.45,-s*0.1,s*0.22,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.45,-s*0.1,s*0.22,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(-s*0.35,s*0.5,s*0.18,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.35,s*0.5,s*0.18,0,Math.PI*2);ctx.fill(); // head ctx.fillStyle='#111'; ctx.beginPath();ctx.arc(0,-s*0.88,s*0.38,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#fff'; ctx.beginPath();ctx.arc(-s*0.15,-s*0.88,s*0.1,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.15,-s*0.88,s*0.1,0,Math.PI*2);ctx.fill(); } function drawSnail(ctx,s,c1,c2){ ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(s*0.2,s*0.3,s*0.9,s*0.45,0,0,Math.PI*2);ctx.fill(); // shell spiral ctx.fillStyle=c1; ctx.beginPath();ctx.arc(-s*0.2,-s*0.15,s*0.8,0,Math.PI*2);ctx.fill(); ctx.strokeStyle=c2;ctx.lineWidth=2.5; ctx.beginPath();ctx.arc(-s*0.2,-s*0.15,s*0.5,0,Math.PI*1.8);ctx.stroke(); ctx.strokeStyle=c2;ctx.lineWidth=1.5; ctx.beginPath();ctx.arc(-s*0.2,-s*0.15,s*0.25,0,Math.PI*1.5);ctx.stroke(); // antennae ctx.strokeStyle='#a06030';ctx.lineWidth=1; ctx.beginPath();ctx.moveTo(s*0.7,s*0.1);ctx.lineTo(s*0.9,-s*0.4);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.9,s*0.1);ctx.lineTo(s*1.1,-s*0.35);ctx.stroke(); ctx.fillStyle='#111'; ctx.beginPath();ctx.arc(s*0.9,-s*0.4,2,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*1.1,-s*0.35,2,0,Math.PI*2);ctx.fill(); } function drawFirefly(ctx,s,c1,c2){ ctx.fillStyle='#333'; ctx.beginPath();ctx.ellipse(0,0,s*0.5,s*0.75,0,0,Math.PI*2);ctx.fill(); // glow abdomen ctx.fillStyle=c1; ctx.shadowColor=c1;ctx.shadowBlur=10; ctx.beginPath();ctx.ellipse(0,s*0.4,s*0.38,s*0.38,0,0,Math.PI*2);ctx.fill(); ctx.shadowBlur=0; // wings ctx.fillStyle='rgba(200,240,180,.4)'; ctx.beginPath();ctx.ellipse(-s*0.7,-s*0.2,s*0.6,s*0.35,-.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.7,-s*0.2,s*0.6,s*0.35,.3,0,Math.PI*2);ctx.fill(); } function drawScorpion(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.75,s*0.6,0,0,Math.PI*2);ctx.fill(); // tail segments ctx.fillStyle=c2; for(let i=0;i<4;i++){ const ty = s*0.7+i*s*0.45; ctx.beginPath();ctx.ellipse(i*s*0.12,ty,s*0.3-i*s*0.04,s*0.27-i*s*0.03,0,0,Math.PI*2);ctx.fill(); } // stinger ctx.fillStyle='#f0d020'; ctx.beginPath();ctx.arc(s*0.45,s*2.4,s*0.2,0,Math.PI*2);ctx.fill(); // claws ctx.strokeStyle=c1;ctx.lineWidth=2.5; ctx.beginPath();ctx.moveTo(-s*0.7,-s*0.2);ctx.lineTo(-s*1.4,-s*0.8);ctx.stroke(); ctx.beginPath();ctx.arc(-s*1.4,-s*0.8,s*0.32,0,Math.PI*2);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.7,-s*0.2);ctx.lineTo(s*1.4,-s*0.8);ctx.stroke(); ctx.beginPath();ctx.arc(s*1.4,-s*0.8,s*0.32,0,Math.PI*2);ctx.stroke(); // legs ctx.strokeStyle=c2;ctx.lineWidth=1; for(let i=0;i<4;i++){ const y = -s*0.2+i*s*0.25; ctx.beginPath();ctx.moveTo(-s*0.7,y);ctx.lineTo(-s*1.3,y+s*0.3);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.7,y);ctx.lineTo(s*1.3,y+s*0.3);ctx.stroke(); } } function drawMantis(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,s*0.3,s*0.5,s*0.8,0,0,Math.PI*2);ctx.fill(); // head ctx.beginPath();ctx.ellipse(0,-s*0.6,s*0.38,s*0.4,0,0,Math.PI*2);ctx.fill(); // triangular body ctx.fillStyle=c2; ctx.beginPath();ctx.moveTo(-s*0.3,-s*0.2);ctx.lineTo(s*0.3,-s*0.2);ctx.lineTo(0,s*0.5);ctx.closePath();ctx.fill(); // raptorial arms ctx.strokeStyle=c2;ctx.lineWidth=2; ctx.beginPath();ctx.moveTo(-s*0.3,-s*0.3);ctx.lineTo(-s*1.0,-s*1.1);ctx.lineTo(-s*0.5,-s*0.5);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.3,-s*0.3);ctx.lineTo(s*1.0,-s*1.1);ctx.lineTo(s*0.5,-s*0.5);ctx.stroke(); // eyes ctx.fillStyle='#90ee00'; ctx.beginPath();ctx.arc(-s*0.15,-s*0.65,s*0.14,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.15,-s*0.65,s*0.14,0,Math.PI*2);ctx.fill(); } function drawDragonfly(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.28,s*1.2,0,0,Math.PI*2);ctx.fill(); // wings 4 ctx.fillStyle='rgba(150,220,255,.5)'; ctx.beginPath();ctx.ellipse(-s*1.1,-s*0.2,s*1.0,s*0.4,-.15,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*1.1,-s*0.2,s*1.0,s*0.4,.15,0,Math.PI*2);ctx.fill(); ctx.fillStyle='rgba(150,200,255,.4)'; ctx.beginPath();ctx.ellipse(-s*1.0,s*0.3,s*0.8,s*0.3,-.1,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*1.0,s*0.3,s*0.8,s*0.3,.1,0,Math.PI*2);ctx.fill(); // head + big eyes ctx.fillStyle=c2; ctx.beginPath();ctx.arc(0,-s*1.0,s*0.4,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#99ddff'; ctx.beginPath();ctx.arc(-s*0.22,-s*1.0,s*0.22,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.22,-s*1.0,s*0.22,0,Math.PI*2);ctx.fill(); } function drawFrog(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s,s*0.8,0,0,Math.PI*2);ctx.fill(); // back legs ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(-s*0.9,s*0.6,s*0.35,s*0.7,-.5,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.9,s*0.6,s*0.35,s*0.7,.5,0,Math.PI*2);ctx.fill(); // front legs ctx.beginPath();ctx.ellipse(-s*0.85,-s*0.1,s*0.25,s*0.5,-.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.85,-s*0.1,s*0.25,s*0.5,.3,0,Math.PI*2);ctx.fill(); // belly ctx.fillStyle='#c8f0a8'; ctx.beginPath();ctx.ellipse(0,s*0.1,s*0.6,s*0.45,0,0,Math.PI*2);ctx.fill(); // eyes on top ctx.fillStyle=c2; ctx.beginPath();ctx.arc(-s*0.45,-s*0.7,s*0.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.45,-s*0.7,s*0.3,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111'; ctx.beginPath();ctx.arc(-s*0.45,-s*0.7,s*0.15,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.45,-s*0.7,s*0.15,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#fff'; ctx.beginPath();ctx.arc(-s*0.5,-s*0.73,s*0.07,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.5,-s*0.73,s*0.07,0,Math.PI*2);ctx.fill(); } function drawSnakeCreature(ctx,s,c1,c2,w){ ctx.strokeStyle=c1;ctx.lineWidth=s*0.7;ctx.lineCap='round'; ctx.beginPath(); ctx.moveTo(-s*1.5+w*s,0); ctx.bezierCurveTo(-s*0.5,s*w*4,s*0.5,-s*w*4,s*1.5+w*s*0.5,0); ctx.stroke(); // scales pattern ctx.strokeStyle=c2;ctx.lineWidth=s*0.15; for(let i=-1;i<=1;i+=0.5){ ctx.beginPath();ctx.arc(i*s,0,s*0.25,0,Math.PI);ctx.stroke(); } // head ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(s*1.5+w*s*0.5,0,s*0.5,s*0.38,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#f0d030'; ctx.beginPath();ctx.arc(s*1.6+w*s*0.5,-s*0.15,s*0.12,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*1.6+w*s*0.5,s*0.15,s*0.12,0,Math.PI*2);ctx.fill(); } function drawLizard(ctx,s,c1,c2){ // body ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.9,s*0.5,0,0,Math.PI*2);ctx.fill(); // tail ctx.strokeStyle=c2;ctx.lineWidth=s*0.4;ctx.lineCap='round'; ctx.beginPath();ctx.moveTo(-s*0.8,0);ctx.bezierCurveTo(-s*1.3,s*0.7,-s*2.0,s*0.5,-s*2.4,s*0.2);ctx.stroke(); // head ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(s*0.9,0,s*0.55,s*0.38,0,0,Math.PI*2);ctx.fill(); // legs ctx.strokeStyle=c2;ctx.lineWidth=1.8; ctx.beginPath();ctx.moveTo(-s*0.4,s*0.4);ctx.lineTo(-s*0.7,s*1.0);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.3,s*0.4);ctx.lineTo(s*0.6,s*1.0);ctx.stroke(); ctx.beginPath();ctx.moveTo(-s*0.5,-s*0.4);ctx.lineTo(-s*0.9,-s*1.0);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.2,-s*0.4);ctx.lineTo(s*0.5,-s*1.0);ctx.stroke(); // eye ctx.fillStyle='#f0a020'; ctx.beginPath();ctx.arc(s*1.1,-s*0.12,s*0.15,0,Math.PI*2);ctx.fill(); } function drawTurtle(ctx,s,c1,c2){ // shell ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(0,0,s*0.9,s*0.85,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.75,s*0.7,0,0,Math.PI*2);ctx.fill(); // shell pattern ctx.strokeStyle=c2;ctx.lineWidth=1.5; ctx.beginPath();ctx.moveTo(0,-s*0.7);ctx.lineTo(0,s*0.7);ctx.stroke(); ctx.beginPath();ctx.moveTo(-s*0.7,0);ctx.lineTo(s*0.7,0);ctx.stroke(); ctx.beginPath();ctx.arc(0,0,s*0.38,0,Math.PI*2);ctx.stroke(); // head ctx.fillStyle='#5a8040'; ctx.beginPath();ctx.ellipse(s*0.85,0,s*0.35,s*0.28,0,0,Math.PI*2);ctx.fill(); // legs ctx.fillStyle='#5a8040'; ctx.beginPath();ctx.ellipse(-s*0.6,s*0.75,s*0.25,s*0.2,.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.5,s*0.75,s*0.25,s*0.2,-.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(-s*0.7,-s*0.7,s*0.2,s*0.18,.5,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.4,-s*0.7,s*0.2,s*0.18,-.5,0,Math.PI*2);ctx.fill(); } function drawChameleon(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.85,s*0.55,0,0,Math.PI*2);ctx.fill(); // curly tail ctx.strokeStyle=c2;ctx.lineWidth=s*0.35;ctx.lineCap='round'; ctx.beginPath();ctx.moveTo(-s*0.7,0);ctx.bezierCurveTo(-s*1.4,-s*0.5,-s*1.8,s*0.5,-s*1.4,s*1.0);ctx.stroke(); // crest ctx.strokeStyle=c2;ctx.lineWidth=1.5; for(let i=0;i<5;i++){ const x=-s*0.5+i*s*0.25,y=-s*0.5; ctx.beginPath();ctx.moveTo(x,y);ctx.lineTo(x,y-s*0.3);ctx.stroke(); } // head ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(s*1.1,0,s*0.55,s*0.4,0.2,0,Math.PI*2);ctx.fill(); // turret eye ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(s*1.2,-s*0.1,s*0.2,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111';ctx.beginPath();ctx.arc(s*1.22,-s*0.1,s*0.1,0,Math.PI*2);ctx.fill(); // legs ctx.strokeStyle=c2;ctx.lineWidth=2; ctx.beginPath();ctx.moveTo(-s*0.2,s*0.5);ctx.lineTo(-s*0.5,s*1.1);ctx.lineTo(-s*0.2,s*1.3);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.4,s*0.5);ctx.lineTo(s*0.7,s*1.1);ctx.lineTo(s*0.4,s*1.3);ctx.stroke(); } function drawAxolotl(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*1.1,s*0.65,0,0,Math.PI*2);ctx.fill(); // gills ctx.strokeStyle=c2;ctx.lineWidth=s*0.2;ctx.lineCap='round'; for(let i=0;i<3;i++){ const a = (-0.4+i*0.4)*Math.PI; ctx.beginPath();ctx.moveTo(-s*0.6,-s*0.45); ctx.lineTo(-s*0.6+Math.cos(a)*s*0.5,-s*0.45+Math.sin(a)*s*0.5);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.6,-s*0.45); ctx.lineTo(s*0.6-Math.cos(a)*s*0.5,-s*0.45+Math.sin(a)*s*0.5);ctx.stroke(); } // tail ctx.fillStyle=c2; ctx.beginPath();ctx.moveTo(-s*1.0,0);ctx.quadraticCurveTo(-s*1.7,s*0.4,-s*2.0,0);ctx.quadraticCurveTo(-s*1.7,-s*0.4,-s*1.0,0);ctx.fill(); // legs stubby ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(-s*0.5,s*0.6,s*0.25,s*0.18,.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.3,s*0.6,s*0.25,s*0.18,-.3,0,Math.PI*2);ctx.fill(); // face ctx.fillStyle='#111';ctx.beginPath();ctx.arc(-s*0.6,-s*0.1,s*0.12,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.6,-s*0.1,s*0.12,0,Math.PI*2);ctx.fill(); ctx.strokeStyle='#111';ctx.lineWidth=1.5; ctx.beginPath();ctx.arc(0,s*0.1,s*0.2,0.1*Math.PI,0.9*Math.PI);ctx.stroke(); } function drawFish(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*1.1,s*0.65,0,0,Math.PI*2);ctx.fill(); // tail ctx.fillStyle=c2; ctx.beginPath();ctx.moveTo(-s*1.0,0);ctx.lineTo(-s*1.6,s*0.6);ctx.lineTo(-s*1.6,-s*0.6);ctx.closePath();ctx.fill(); // fin top ctx.beginPath();ctx.moveTo(-s*0.3,-s*0.6);ctx.lineTo(s*0.1,-s*1.1);ctx.lineTo(s*0.5,-s*0.6);ctx.closePath();ctx.fill(); // scales hint ctx.strokeStyle=c2;ctx.lineWidth=1;ctx.globalAlpha=0.5; ctx.beginPath();ctx.arc(s*0.1,0,s*0.4,Math.PI*0.3,Math.PI*0.7);ctx.stroke(); ctx.beginPath();ctx.arc(-s*0.3,0,s*0.4,Math.PI*0.3,Math.PI*0.7);ctx.stroke(); ctx.globalAlpha=1; // eye ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(s*0.7,-s*0.1,s*0.22,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111';ctx.beginPath();ctx.arc(s*0.73,-s*0.1,s*0.11,0,Math.PI*2);ctx.fill(); } function drawJellyfish(ctx,s,c1,c2,t){ const bob = Math.sin(t*0.05)*s*0.2; // bell const g = ctx.createRadialGradient(0,-s*0.2+bob,s*0.1,0,-s*0.2+bob,s); g.addColorStop(0,c1+'dd');g.addColorStop(1,c2+'66'); ctx.fillStyle=g; ctx.beginPath();ctx.ellipse(0,-s*0.2+bob,s,s*0.75,0,0,Math.PI);ctx.fill(); // tentacles ctx.strokeStyle=c1+'99';ctx.lineWidth=1.5;ctx.lineCap='round'; for(let i=0;i<7;i++){ const tx = -s*0.8+i*s*0.27; const wave = Math.sin(t*0.08+i)*s*0.3; ctx.beginPath();ctx.moveTo(tx,s*0.5+bob);ctx.bezierCurveTo(tx+wave,s*1.0+bob,tx-wave,s*1.5+bob,tx+wave*0.5,s*2.0+bob);ctx.stroke(); } // inner glow ctx.fillStyle='rgba(255,255,255,.15)'; ctx.beginPath();ctx.ellipse(0,-s*0.35+bob,s*0.55,s*0.4,0,0,Math.PI*2);ctx.fill(); } function drawCrab(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*1.2,s*0.85,0,0,Math.PI*2);ctx.fill(); // carapace texture ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(0,0,s*0.7,s*0.5,0,0,Math.PI*2);ctx.fill(); // big claws ctx.strokeStyle=c1;ctx.lineWidth=s*0.4; ctx.beginPath();ctx.moveTo(-s*1.1,-s*0.1);ctx.lineTo(-s*1.9,-s*0.7);ctx.stroke(); ctx.beginPath();ctx.arc(-s*1.9,-s*0.7,s*0.45,0,Math.PI*2);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*1.1,-s*0.1);ctx.lineTo(s*1.9,-s*0.7);ctx.stroke(); ctx.beginPath();ctx.arc(s*1.9,-s*0.7,s*0.45,0,Math.PI*2);ctx.stroke(); // legs ctx.strokeStyle=c2;ctx.lineWidth=1.5; for(let i=0;i<4;i++){ const y = -s*0.3+i*s*0.22; ctx.beginPath();ctx.moveTo(-s,y);ctx.lineTo(-s*1.6,y+s*0.5);ctx.stroke(); ctx.beginPath();ctx.moveTo(s,y);ctx.lineTo(s*1.6,y+s*0.5);ctx.stroke(); } // eyes on stalks ctx.strokeStyle=c2;ctx.lineWidth=1.5; ctx.beginPath();ctx.moveTo(-s*0.3,-s*0.8);ctx.lineTo(-s*0.4,-s*1.2);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.3,-s*0.8);ctx.lineTo(s*0.4,-s*1.2);ctx.stroke(); ctx.fillStyle='#111'; ctx.beginPath();ctx.arc(-s*0.4,-s*1.2,s*0.14,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.4,-s*1.2,s*0.14,0,Math.PI*2);ctx.fill(); } function drawOctopus(ctx,s,c1,c2,t){ ctx.fillStyle=c1; ctx.beginPath();ctx.arc(0,0,s,0,Math.PI*2);ctx.fill(); // 8 arms ctx.strokeStyle=c2;ctx.lineWidth=s*0.32;ctx.lineCap='round'; for(let i=0;i<8;i++){ const a = i/8*Math.PI*2; const wave = Math.sin(t*0.06+i)*s*0.4; const ex = Math.cos(a)*s*2.2, ey = Math.sin(a)*s*2.2; ctx.beginPath(); ctx.moveTo(Math.cos(a)*s*0.8,Math.sin(a)*s*0.8); ctx.bezierCurveTo( Math.cos(a)*s*1.4+wave*Math.sin(a),Math.sin(a)*s*1.4+wave*Math.cos(a), ex,ey, ex,ey ); ctx.stroke(); } // suckers ctx.fillStyle='rgba(255,255,255,.3)'; for(let i=0;i<8;i++){ const a = i/8*Math.PI*2; ctx.beginPath();ctx.arc(Math.cos(a)*s*1.3,Math.sin(a)*s*1.3,s*0.1,0,Math.PI*2);ctx.fill(); } // eyes ctx.fillStyle='#fff'; ctx.beginPath();ctx.arc(-s*0.35,-s*0.25,s*0.28,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.35,-s*0.25,s*0.28,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111'; ctx.beginPath();ctx.arc(-s*0.35,-s*0.25,s*0.14,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.35,-s*0.25,s*0.14,0,Math.PI*2);ctx.fill(); } function drawMouse(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.85,s*0.65,0,0,Math.PI*2);ctx.fill(); // tail ctx.strokeStyle=c2;ctx.lineWidth=s*0.2;ctx.lineCap='round'; ctx.beginPath();ctx.moveTo(-s*0.7,0);ctx.bezierCurveTo(-s*1.2,s*0.5,-s*1.7,s*0.2,-s*1.9,-s*0.1);ctx.stroke(); // head ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(s*0.85,0,s*0.5,s*0.42,0,0,Math.PI*2);ctx.fill(); // ears ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(s*0.6,-s*0.6,s*0.3,s*0.35,-.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*1.0,-s*0.55,s*0.3,s*0.35,.2,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#ffb0b0'; ctx.beginPath();ctx.ellipse(s*0.6,-s*0.6,s*0.18,s*0.22,-.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*1.0,-s*0.55,s*0.18,s*0.22,.2,0,Math.PI*2);ctx.fill(); // eye & nose ctx.fillStyle='#111';ctx.beginPath();ctx.arc(s*1.1,-s*0.1,s*0.11,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#ffb0b0';ctx.beginPath();ctx.arc(s*1.3,s*0.05,s*0.1,0,Math.PI*2);ctx.fill(); } function drawRabbit(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.9,s*0.8,0,0,Math.PI*2);ctx.fill(); // ears ctx.beginPath();ctx.ellipse(-s*0.35,-s*1.3,s*0.22,s*0.65,-.15,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.35,-s*1.3,s*0.22,s*0.65,.15,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#ffb0b0'; ctx.beginPath();ctx.ellipse(-s*0.35,-s*1.3,s*0.11,s*0.5,-.15,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.35,-s*1.3,s*0.11,s*0.5,.15,0,Math.PI*2);ctx.fill(); // face ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(s*0.1,-s*0.1,s*0.6,s*0.52,0,0,Math.PI*2);ctx.fill(); // eyes ctx.fillStyle='#111';ctx.beginPath();ctx.arc(-s*0.25,-s*0.1,s*0.14,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.35,-s*0.1,s*0.14,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(-s*0.2,-s*0.14,s*0.06,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.4,-s*0.14,s*0.06,0,Math.PI*2);ctx.fill(); // nose ctx.fillStyle='#ffb0b0';ctx.beginPath();ctx.arc(s*0.1,s*0.18,s*0.1,0,Math.PI*2);ctx.fill(); // tail ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(-s*0.85,s*0.4,s*0.25,0,Math.PI*2);ctx.fill(); } function drawCrow(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.85,s*0.6,0,0,Math.PI*2);ctx.fill(); // wings folded ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(-s*0.2,s*0.1,s*0.75,s*0.4,-0.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.2,s*0.1,s*0.75,s*0.4,0.3,0,Math.PI*2);ctx.fill(); // tail feathers ctx.fillStyle=c2; ctx.beginPath();ctx.moveTo(-s*0.8,s*0.2);ctx.lineTo(-s*1.5,s*0.6);ctx.lineTo(-s*0.6,s*0.5);ctx.closePath();ctx.fill(); ctx.beginPath();ctx.moveTo(-s*0.8,s*0.1);ctx.lineTo(-s*1.6,s*0.3);ctx.lineTo(-s*0.6,s*0.4);ctx.closePath();ctx.fill(); // head ctx.fillStyle=c1; ctx.beginPath();ctx.arc(s*0.65,-s*0.35,s*0.42,0,Math.PI*2);ctx.fill(); // beak ctx.fillStyle='#888'; ctx.beginPath();ctx.moveTo(s*1.0,-s*0.35);ctx.lineTo(s*1.5,-s*0.28);ctx.lineTo(s*1.0,-s*0.22);ctx.closePath();ctx.fill(); // eye ctx.fillStyle='#333';ctx.beginPath();ctx.arc(s*0.75,-s*0.38,s*0.13,0,Math.PI*2);ctx.fill(); ctx.fillStyle='rgba(100,180,255,.4)';ctx.beginPath();ctx.arc(s*0.77,-s*0.37,s*0.05,0,Math.PI*2);ctx.fill(); } function drawFox(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s,s*0.65,0,0,Math.PI*2);ctx.fill(); // bushy tail ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(-s*1.3,s*0.2,s*0.75,s*0.45,.4,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#fff'; ctx.beginPath();ctx.ellipse(-s*1.3,s*0.3,s*0.4,s*0.25,.4,0,Math.PI*2);ctx.fill(); // head ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(s*0.9,-s*0.1,s*0.55,s*0.45,0,0,Math.PI*2);ctx.fill(); // pointy ears ctx.beginPath();ctx.moveTo(s*0.65,-s*0.5);ctx.lineTo(s*0.5,-s*1.1);ctx.lineTo(s*0.85,-s*0.55);ctx.closePath();ctx.fill(); ctx.beginPath();ctx.moveTo(s*1.05,-s*0.5);ctx.lineTo(s*1.2,-s*1.05);ctx.lineTo(s*1.25,-s*0.5);ctx.closePath();ctx.fill(); ctx.fillStyle='#111'; ctx.beginPath();ctx.moveTo(s*0.68,-s*0.56);ctx.lineTo(s*0.56,-s*1.0);ctx.lineTo(s*0.82,-s*0.58);ctx.closePath();ctx.fill(); ctx.beginPath();ctx.moveTo(s*1.07,-s*0.54);ctx.lineTo(s*1.18,-s*0.95);ctx.lineTo(s*1.22,-s*0.54);ctx.closePath();ctx.fill(); // face white ctx.fillStyle='#fff'; ctx.beginPath();ctx.ellipse(s*1.1,s*0.05,s*0.3,s*0.25,0,0,Math.PI*2);ctx.fill(); // nose ctx.fillStyle='#111';ctx.beginPath();ctx.arc(s*1.35,s*0.0,s*0.1,0,Math.PI*2);ctx.fill(); // eye ctx.beginPath();ctx.arc(s*0.92,-s*0.15,s*0.1,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#f0d020';ctx.beginPath();ctx.arc(s*0.92,-s*0.15,s*0.05,0,Math.PI*2);ctx.fill(); // legs ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(-s*0.3,s*0.7,s*0.18,s*0.28,0,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.4,s*0.7,s*0.18,s*0.28,0,0,Math.PI*2);ctx.fill(); } function drawOwl(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.8,s*1.0,0,0,Math.PI*2);ctx.fill(); // ear tufts ctx.beginPath();ctx.moveTo(-s*0.35,-s*0.9);ctx.lineTo(-s*0.55,-s*1.4);ctx.lineTo(-s*0.15,-s*0.85);ctx.closePath();ctx.fill(); ctx.beginPath();ctx.moveTo(s*0.35,-s*0.9);ctx.lineTo(s*0.55,-s*1.4);ctx.lineTo(s*0.15,-s*0.85);ctx.closePath();ctx.fill(); // wing pattern ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(-s*0.35,s*0.2,s*0.45,s*0.7,0.2,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.35,s*0.2,s*0.45,s*0.7,-0.2,0,Math.PI*2);ctx.fill(); // face disc ctx.fillStyle='#e8d090'; ctx.beginPath();ctx.ellipse(0,-s*0.2,s*0.6,s*0.55,0,0,Math.PI*2);ctx.fill(); // big eyes ctx.fillStyle='#f0c030'; ctx.beginPath();ctx.arc(-s*0.28,-s*0.25,s*0.28,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.28,-s*0.25,s*0.28,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111'; ctx.beginPath();ctx.arc(-s*0.28,-s*0.25,s*0.15,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.28,-s*0.25,s*0.15,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#fff'; ctx.beginPath();ctx.arc(-s*0.22,-s*0.3,s*0.06,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.22,-s*0.3,s*0.06,0,Math.PI*2);ctx.fill(); // beak ctx.fillStyle='#d4a020'; ctx.beginPath();ctx.moveTo(-s*0.12,s*0.0);ctx.lineTo(s*0.12,s*0.0);ctx.lineTo(0,s*0.18);ctx.closePath();ctx.fill(); } function drawDeer(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,s*0.1,s*0.9,s*0.65,0,0,Math.PI*2);ctx.fill(); // neck & head ctx.beginPath();ctx.ellipse(s*0.65,-s*0.35,s*0.28,s*0.5,0.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.9,-s*0.75,s*0.35,s*0.3,0,0,Math.PI*2);ctx.fill(); // antlers ctx.strokeStyle='#8b6020';ctx.lineWidth=2; ctx.beginPath();ctx.moveTo(s*0.75,-s*1.0);ctx.lineTo(s*0.5,-s*1.7);ctx.lineTo(s*0.3,-s*1.4);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.5,-s*1.55);ctx.lineTo(s*0.7,-s*1.45);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*1.05,-s*1.0);ctx.lineTo(s*1.2,-s*1.7);ctx.lineTo(s*1.35,-s*1.4);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*1.2,-s*1.55);ctx.lineTo(s*1.05,-s*1.45);ctx.stroke(); // belly ctx.fillStyle='#e8d0a0'; ctx.beginPath();ctx.ellipse(0,s*0.2,s*0.5,s*0.38,0,0,Math.PI*2);ctx.fill(); // legs ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(-s*0.5,s*0.9,s*0.15,s*0.38,0,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.2,s*0.9,s*0.15,s*0.38,0,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(-s*0.65,s*0.8,s*0.12,s*0.3,.2,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.55,s*0.8,s*0.12,s*0.3,-.2,0,Math.PI*2);ctx.fill(); // white rump ctx.fillStyle='#fff';ctx.beginPath();ctx.ellipse(-s*0.75,s*0.2,s*0.35,s*0.28,0,0,Math.PI*2);ctx.fill(); // eye ctx.fillStyle='#111';ctx.beginPath();ctx.arc(s*1.0,-s*0.78,s*0.1,0,Math.PI*2);ctx.fill(); } function drawBoar(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*1.1,s*0.75,0,0,Math.PI*2);ctx.fill(); // bristles/mane ctx.strokeStyle=c2;ctx.lineWidth=1.5; for(let i=0;i<8;i++){ const x = -s*0.8+i*s*0.22; ctx.beginPath();ctx.moveTo(x,-s*0.7);ctx.lineTo(x,-s*1.1);ctx.stroke(); } // head ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(s*1.0,0,s*0.6,s*0.5,0,0,Math.PI*2);ctx.fill(); // snout ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(s*1.45,s*0.1,s*0.3,s*0.25,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111';ctx.beginPath();ctx.arc(s*1.42,s*0.1,s*0.08,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*1.52,s*0.1,s*0.08,0,Math.PI*2);ctx.fill(); // tusks ctx.strokeStyle='#f0e0a0';ctx.lineWidth=2.5;ctx.lineCap='round'; ctx.beginPath();ctx.moveTo(s*1.35,s*0.3);ctx.lineTo(s*1.6,s*0.5);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*1.45,s*0.3);ctx.lineTo(s*1.7,s*0.5);ctx.stroke(); // eye ctx.fillStyle='#c00';ctx.beginPath();ctx.arc(s*0.95,-s*0.15,s*0.12,0,Math.PI*2);ctx.fill(); // legs ctx.fillStyle=c2; for(let i=0;i<4;i++){ const x=-s*0.5+i*s*0.35,y=s*0.75; ctx.beginPath();ctx.ellipse(x,y,s*0.15,s*0.32,0,0,Math.PI*2);ctx.fill(); } } function drawBear(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*1.1,s*1.0,0,0,Math.PI*2);ctx.fill(); // ears ctx.beginPath();ctx.arc(-s*0.7,-s*0.8,s*0.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.7,-s*0.8,s*0.3,0,Math.PI*2);ctx.fill(); ctx.fillStyle=c2; ctx.beginPath();ctx.arc(-s*0.7,-s*0.8,s*0.18,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.7,-s*0.8,s*0.18,0,Math.PI*2);ctx.fill(); // muzzle ctx.fillStyle='#c8a070'; ctx.beginPath();ctx.ellipse(0,s*0.15,s*0.55,s*0.45,0,0,Math.PI*2);ctx.fill(); // nose ctx.fillStyle='#111';ctx.beginPath();ctx.arc(0,-s*0.1,s*0.18,0,Math.PI*2);ctx.fill(); // eyes ctx.fillStyle='#111';ctx.beginPath();ctx.arc(-s*0.4,-s*0.35,s*0.14,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.4,-s*0.35,s*0.14,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#fff';ctx.beginPath();ctx.arc(-s*0.37,-s*0.37,s*0.05,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(s*0.37,-s*0.37,s*0.05,0,Math.PI*2);ctx.fill(); // paws ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(-s*0.9,s*0.8,s*0.3,s*0.22,.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.9,s*0.8,s*0.3,s*0.22,-.3,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(-s*0.95,-s*0.7,s*0.28,s*0.2,.5,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.95,-s*0.7,s*0.28,s*0.2,-.5,0,Math.PI*2);ctx.fill(); } function drawWolf(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s,s*0.7,0,0,Math.PI*2);ctx.fill(); // tail ctx.strokeStyle=c1;ctx.lineWidth=s*0.35;ctx.lineCap='round'; ctx.beginPath();ctx.moveTo(-s*0.9,0);ctx.bezierCurveTo(-s*1.5,-s*0.3,-s*1.8,s*0.2,-s*2.1,s*0.5);ctx.stroke(); ctx.strokeStyle='#fff';ctx.lineWidth=s*0.12; ctx.beginPath();ctx.moveTo(-s*1.6,s*0.1);ctx.lineTo(-s*2.0,s*0.45);ctx.stroke(); // head ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(s*0.85,-s*0.1,s*0.55,s*0.42,0,0,Math.PI*2);ctx.fill(); // ears pointy ctx.beginPath();ctx.moveTo(s*0.6,-s*0.45);ctx.lineTo(s*0.45,-s*1.0);ctx.lineTo(s*0.85,-s*0.48);ctx.closePath();ctx.fill(); ctx.beginPath();ctx.moveTo(s*1.0,-s*0.42);ctx.lineTo(s*1.15,-s*0.95);ctx.lineTo(s*1.25,-s*0.42);ctx.closePath();ctx.fill(); ctx.fillStyle=c2; ctx.beginPath();ctx.moveTo(s*0.63,-s*0.49);ctx.lineTo(s*0.52,-s*0.88);ctx.lineTo(s*0.82,-s*0.52);ctx.closePath();ctx.fill(); ctx.beginPath();ctx.moveTo(s*1.02,-s*0.46);ctx.lineTo(s*1.13,-s*0.84);ctx.lineTo(s*1.21,-s*0.46);ctx.closePath();ctx.fill(); // snout ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(s*1.28,s*0.05,s*0.28,s*0.22,0,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111';ctx.beginPath();ctx.arc(s*1.3,s*0.0,s*0.1,0,Math.PI*2);ctx.fill(); // eye ctx.fillStyle='#f0c020';ctx.beginPath();ctx.arc(s*0.9,-s*0.18,s*0.13,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111';ctx.beginPath();ctx.arc(s*0.9,-s*0.18,s*0.07,0,Math.PI*2);ctx.fill(); // legs ctx.fillStyle=c2; ctx.beginPath();ctx.ellipse(-s*0.4,s*0.8,s*0.17,s*0.32,0,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.ellipse(s*0.3,s*0.8,s*0.17,s*0.32,0,0,Math.PI*2);ctx.fill(); } function drawEagle(ctx,s,c1,c2){ ctx.fillStyle=c1; ctx.beginPath();ctx.ellipse(0,0,s*0.8,s*0.55,0,0,Math.PI*2);ctx.fill(); // wide wings spread ctx.fillStyle=c2; ctx.beginPath();ctx.moveTo(-s*0.6,-s*0.1);ctx.bezierCurveTo(-s*1.5,-s*0.8,-s*2.5,-s*0.2,-s*2.8,s*0.2);ctx.lineTo(-s*2.4,s*0.6);ctx.bezierCurveTo(-s*1.8,s*0.3,-s*0.8,s*0.4,-s*0.2,s*0.3);ctx.closePath();ctx.fill(); ctx.beginPath();ctx.moveTo(s*0.6,-s*0.1);ctx.bezierCurveTo(s*1.5,-s*0.8,s*2.5,-s*0.2,s*2.8,s*0.2);ctx.lineTo(s*2.4,s*0.6);ctx.bezierCurveTo(s*1.8,s*0.3,s*0.8,s*0.4,s*0.2,s*0.3);ctx.closePath();ctx.fill(); // wing feather lines ctx.strokeStyle='#5a3010';ctx.lineWidth=1; for(let i=1;i<5;i++){ ctx.beginPath();ctx.moveTo(-s*0.5+i*-s*0.4,-s*0.2);ctx.lineTo(-s*0.5+i*-s*0.55,s*0.4);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.5+i*s*0.4,-s*0.2);ctx.lineTo(s*0.5+i*s*0.55,s*0.4);ctx.stroke(); } // white head ctx.fillStyle='#fff'; ctx.beginPath();ctx.arc(s*0.3,-s*0.35,s*0.42,0,Math.PI*2);ctx.fill(); // beak ctx.fillStyle='#f0c020'; ctx.beginPath();ctx.moveTo(s*0.65,-s*0.3);ctx.lineTo(s*1.15,-s*0.45);ctx.lineTo(s*0.75,-s*0.15);ctx.closePath();ctx.fill(); // eye ctx.fillStyle='#f0c020';ctx.beginPath();ctx.arc(s*0.42,-s*0.42,s*0.14,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111';ctx.beginPath();ctx.arc(s*0.42,-s*0.42,s*0.07,0,Math.PI*2);ctx.fill(); // talons ctx.strokeStyle='#c09020';ctx.lineWidth=2; ctx.beginPath();ctx.moveTo(-s*0.3,s*0.5);ctx.lineTo(-s*0.5,s*0.9);ctx.stroke(); ctx.beginPath();ctx.moveTo(s*0.3,s*0.5);ctx.lineTo(s*0.5,s*0.9);ctx.stroke(); } // ───────────────────────────────────────────── // DRAW PLAYER SLIME // ───────────────────────────────────────────── function drawPlayer(){ const sx = player.x - cam.x, sy = player.y - cam.y; const s = player.size; const t = Date.now(); ctx.save(); ctx.translate(sx, sy); // camouflage if(player.activeEffects.camouflage && player.activeEffects.camouflage > t){ ctx.globalAlpha = 0.3 + 0.2*Math.sin(t*0.01); } // glow effect if(player.activeEffects.glow && player.activeEffects.glow > t){ ctx.shadowColor='#ffd700';ctx.shadowBlur=20; } else { ctx.shadowColor=player.color;ctx.shadowBlur=12; } // howl buff if(player.activeEffects.howl && player.activeEffects.howl > t){ ctx.shadowBlur=25;ctx.shadowColor='#ff4444'; } // Wobbly blob shape const w = player.wobble; ctx.fillStyle = player.color; ctx.beginPath(); const pts = 12; for(let i=0;i<pts;i++){ const a = i/pts*Math.PI*2; const r = s + Math.sin(a*3+w)*s*0.18 + Math.cos(a*5+w*1.3)*s*0.1; if(i===0) ctx.moveTo(Math.cos(a)*r, Math.sin(a)*r); else ctx.lineTo(Math.cos(a)*r, Math.sin(a)*r); } ctx.closePath();ctx.fill(); // inner highlight ctx.fillStyle = player.color2 || '#fff'; ctx.globalAlpha *= 0.4; ctx.beginPath();ctx.ellipse(-s*0.2,-s*0.2,s*0.5,s*0.4,0,0,Math.PI*2);ctx.fill(); ctx.globalAlpha = 1; // eyes ctx.shadowBlur = 0; const eyeA = player.facing; const e1x = Math.cos(eyeA-0.4)*s*0.55, e1y = Math.sin(eyeA-0.4)*s*0.55; const e2x = Math.cos(eyeA+0.4)*s*0.55, e2y = Math.sin(eyeA+0.4)*s*0.55; ctx.fillStyle = '#fff'; ctx.beginPath();ctx.arc(e1x,e1y,s*0.22,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(e2x,e2y,s*0.22,0,Math.PI*2);ctx.fill(); ctx.fillStyle='#111'; ctx.beginPath();ctx.arc(e1x+Math.cos(eyeA)*s*0.07,e1y+Math.sin(eyeA)*s*0.07,s*0.12,0,Math.PI*2);ctx.fill(); ctx.beginPath();ctx.arc(e2x+Math.cos(eyeA)*s*0.07,e2y+Math.sin(eyeA)*s*0.07,s*0.12,0,Math.PI*2);ctx.fill(); // devour animation - open mouth if(player.devourAnim > 0){ const ma = player.devourAnim/20 * 0.8; ctx.fillStyle='#111'; ctx.beginPath();ctx.arc(Math.cos(eyeA)*s*0.7,Math.sin(eyeA)*s*0.7,s*0.35*ma,0,Math.PI*2);ctx.fill(); ctx.fillStyle='rgba(255,80,80,.6)'; ctx.beginPath();ctx.arc(Math.cos(eyeA)*s*0.7,Math.sin(eyeA)*s*0.7,s*0.25*ma,0,Math.PI*2);ctx.fill(); } // shell shield if(player.activeEffects.shell && player.activeEffects.shell > t){ ctx.strokeStyle='rgba(180,220,255,.6)';ctx.lineWidth=3; ctx.beginPath();ctx.arc(0,0,s+6,0,Math.PI*2);ctx.stroke(); } ctx.restore(); } // ───────────────────────────────────────────── // PARTICLES // ───────────────────────────────────────────── function spawnParticles(x,y,color,count,type='circle'){ for(let i=0;i<count;i++){ const a = Math.random()*Math.PI*2; const sp = 0.8+Math.random()*2.5; particles.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,life:1,col:color,type,size:2+Math.random()*4}); } } function spawnFloatingText(x,y,text,color){ floatingTexts.push({x,y,text,color,life:1,vy:-1.2}); } function updateParticles(dt){ for(let i=particles.length-1;i>=0;i--){ const p=particles[i]; p.x+=p.vx; p.y+=p.vy; p.vy+=0.06; p.life-=0.025; if(p.life<=0) particles.splice(i,1); } for(let i=floatingTexts.length-1;i>=0;i--){ const t=floatingTexts[i]; t.y+=t.vy; t.life-=0.018; if(t.life<=0) floatingTexts.splice(i,1); } } function drawParticlesAndTexts(){ for(const p of particles){ const sx=p.x-cam.x, sy=p.y-cam.y; ctx.save();ctx.globalAlpha=p.life;ctx.fillStyle=p.col; ctx.shadowColor=p.col;ctx.shadowBlur=4; ctx.beginPath();ctx.arc(sx,sy,p.size*p.life,0,Math.PI*2);ctx.fill(); ctx.restore(); } for(const t of floatingTexts){ const sx=t.x-cam.x, sy=t.y-cam.y; ctx.save();ctx.globalAlpha=t.life; ctx.font=`bold ${12+Math.round(t.life*4)}px 'Nunito',sans-serif`; ctx.textAlign='center';ctx.textBaseline='middle'; ctx.fillStyle=t.col;ctx.shadowColor=t.col;ctx.shadowBlur=6; ctx.fillText(t.text,sx,sy); ctx.restore(); } } // ───────────────────────────────────────────── // CREATURE AI // ───────────────────────────────────────────── function updateCreatureAI(c, dt){ if(c.stunned > 0){ c.stunned -= dt; return; } const d = c.def; const dist = Math.hypot(player.x-c.x, player.y-c.y); const stronger = d.lv > player.level + 1; const weaker = d.lv <= player.level - 1; const edible = dist < (player.size + c.size)*2.5; const cam_eff = player.activeEffects.camouflage && player.activeEffects.camouflage > Date.now(); // flee if outmatched if(weaker && dist < 160 && !cam_eff){ c.fleeing = true; c.fleeTimer = 180; } if(c.fleeTimer > 0) c.fleeTimer--; else c.fleeing = false; // hunt if stronger if(stronger && dist < 200 && !cam_eff && d.zone!=='water'){ const a = Math.atan2(player.y-c.y, player.x-c.x); c.vx += Math.cos(a)*d.spd*0.08; c.vy += Math.sin(a)*d.spd*0.08; } else if(c.fleeing){ const a = Math.atan2(c.y-player.y, c.x-player.x); c.vx += Math.cos(a)*d.spd*0.12; c.vy += Math.sin(a)*d.spd*0.12; } else { // wander c.wanderTimer--; if(c.wanderTimer <= 0){ const a = Math.random()*Math.PI*2; c.vx = Math.cos(a)*d.spd*(0.4+Math.random()*0.6); c.vy = Math.sin(a)*d.spd*(0.4+Math.random()*0.6); c.wanderTimer = 80+Math.random()*140; } } // glow lure if(player.activeEffects.glow && player.activeEffects.glow > Date.now() && dist < 200){ const a = Math.atan2(player.y-c.y, player.x-c.x); c.vx += Math.cos(a)*0.08; c.vy += Math.sin(a)*0.08; } // clamp speed const spd = Math.hypot(c.vx,c.vy); if(spd > d.spd){ c.vx=c.vx/spd*d.spd; c.vy=c.vy/spd*d.spd; } // friction c.vx *= 0.9; c.vy *= 0.9; c.x += c.vx; c.y += c.vy; c.wobble += 0.08; // wall bounce if(c.x<c.size){c.x=c.size;c.vx=Math.abs(c.vx);} if(c.x>WORLD_W-c.size){c.x=WORLD_W-c.size;c.vx=-Math.abs(c.vx);} if(c.y<c.size){c.y=c.size;c.vy=Math.abs(c.vy);} if(c.y>WORLD_H-c.size){c.y=WORLD_H-c.size;c.vy=-Math.abs(c.vy);} // stronger creature attacks player if(stronger && dist < (player.size+c.size)*1.2 && !cam_eff){ const now = Date.now(); if(!c.lastAttack || now-c.lastAttack > 800){ c.lastAttack = now; let dmg = Math.max(2, d.lv*3 - player.level*1.5); // shell if(player.activeEffects.shell && player.activeEffects.shell > now) dmg *= 0.2; player.hp -= dmg; player.invincible = 400; spawnParticles(player.x, player.y,'#ff4444',8); spawnFloatingText(player.x, player.y-player.size-10, `-${Math.round(dmg)}`, '#ff4444'); if(player.hp <= 0){ player.hp=0; endGame(); } } } // poisoned tick if(c.poisoned > 0){ c.poisoned -= dt; const now = Date.now(); if(!c.lastPoison || now-c.lastPoison > 500){ c.lastPoison=now; c.hp -= 3; spawnParticles(c.x,c.y,'#50c030',3); if(c.hp <= 0) killCreature(c); } } } // ───────────────────────────────────────────── // PLAYER LOGIC // ───────────────────────────────────────────── function updatePlayer(dt){ if(player.dead) return; const now = Date.now(); // input let mx=0,my=0; if(keys['ArrowLeft']||keys['a']||keys['A']) mx-=1; if(keys['ArrowRight']||keys['d']||keys['D']) mx+=1; if(keys['ArrowUp']||keys['w']||keys['W']) my-=1; if(keys['ArrowDown']||keys['s']||keys['S']) my+=1; if(joystick.active){ mx=joystick.dx; my=joystick.dy; } let spd = player.baseSpeed * (1 - (player.size-14)*0.008); spd = Math.max(0.8, spd); // dash if(player.activeEffects.dash && player.activeEffects.dash > now) spd*=3; if(player.activeEffects.howl && player.activeEffects.howl > now) spd*=1.5; const mag = Math.hypot(mx,my); if(mag>0){ mx/=mag; my/=mag; } player.vx += mx*spd*0.35; player.vy += my*spd*0.35; player.vx *= 0.82; player.vy *= 0.82; const ps = Math.hypot(player.vx,player.vy); if(ps>spd){ player.vx=player.vx/ps*spd; player.vy=player.vy/ps*spd; } if(mag>0.05) player.facing = Math.atan2(my,mx); player.x += player.vx; player.y += player.vy; player.x = Math.max(player.size, Math.min(WORLD_W-player.size, player.x)); player.y = Math.max(player.size, Math.min(WORLD_H-player.size, player.y)); player.wobble += 0.05 + ps*0.02; if(player.devourAnim>0) player.devourAnim--; // regen if(player.activeEffects.regen && player.activeEffects.regen > now){ player.hp = Math.min(player.maxHp, player.hp + 0.006*dt); } // web effect on nearby creatures if(player.activeEffects.web && player.activeEffects.web > now){ for(const c of creatures){ if(Math.hypot(player.x-c.x,player.y-c.y) < 120){ c.vx *= 0.85; c.vy *= 0.85; } } } // camera cam.x = player.x - W/2; cam.y = player.y - H/2; cam.x = Math.max(0, Math.min(WORLD_W-W, cam.x)); cam.y = Math.max(0, Math.min(WORLD_H-H, cam.y)); updateHUD(); } function tryDevour(){ if(player.dead) return; const now = Date.now(); player.devourAnim = 20; // eagle dive – instant devour ≤ player level const eagleDive = player.activeEffects.eagleDive && player.activeEffects.eagleDive > now; // find closest creature in range const range = player.size * 2.8; let closest=null, closestDist=Infinity; for(const c of creatures){ const dist = Math.hypot(player.x-c.x, player.y-c.y); if(dist < range + c.size && dist < closestDist){ closestDist = dist; closest = c; } } if(!closest) return; const c = closest; const d = c.def; // success chance let chance = 1.0; if(d.lv > player.level){ const diff = d.lv - player.level; chance = Math.max(0.05, 1 - diff*0.28); } if(player.activeEffects.strike && player.activeEffects.strike > now) chance = Math.min(1, chance*2); if(eagleDive && d.lv <= player.level) chance = 1.0; if(Math.random() < chance){ // SUCCESS killCreature(c, true); } else { // FAIL spawnFloatingText(player.x, player.y-player.size-15, 'TOO STRONG!', '#ff6b6b'); spawnParticles(player.x, player.y, '#ff8844', 6); // recoil damage const dmg = Math.ceil(d.lv*2.5); player.hp -= dmg; spawnFloatingText(player.x, player.y-player.size-28, `-${dmg}`, '#ff4444'); if(player.hp <= 0){ player.hp=0; endGame(); } } } function killCreature(c, devoured=false){ const d = c.def; spawnParticles(c.x, c.y, d.col, 16, 'circle'); if(devoured){ // XP player.xp += d.xp; spawnFloatingText(c.x, c.y-c.size-10, `+${d.xp} XP`, '#f9ca24'); // Grow player.size = Math.min(55, player.size + d.sz*0.18); // Color shift morphPlayerColor(d.col); // Level up? checkLevelUp(); // Grant ability if(d.abilityId){ const abil = ABILITIES[d.abilityId]; if(abil && !player.abilities.includes(d.abilityId)){ player.abilities.push(d.abilityId); showAbilityUnlock(d.abilityId, d.name); spawnParticles(player.x,player.y,'#00e5ff',20); } } // Rare bonus if(d.rare){ showMsg('✨ RARE CREATURE!', `Absorbed ${d.name}'s power!`); } // Sting: instant damage already used ability if(d.abilityId==='sting'){ // visual only } } // remove const idx = creatures.indexOf(c); if(idx>-1) creatures.splice(idx,1); // respawn setTimeout(()=> spawnRandomCreature(), 4000+Math.random()*6000); } function morphPlayerColor(targetCol){ // blend toward target color const parse = (hex) => { const n = parseInt(hex.replace('#',''),16); return [(n>>16)&255,(n>>8)&255,n&255]; }; const blend = (a,b,t) => Math.round(a+(b-a)*t); const pc = parse(player.color||'#7dff7a'); const tc = parse(targetCol); const nc = [blend(pc[0],tc[0],0.25),blend(pc[1],tc[1],0.25),blend(pc[2],tc[2],0.25)]; player.color = '#'+nc.map(v=>v.toString(16).padStart(2,'0')).join(''); const nc2 = [blend(pc[0],tc[0],0.12),blend(pc[1],tc[1],0.12),blend(pc[2],tc[2],0.12)]; player.color2 = '#'+nc2.map(v=>Math.max(0,v-30).toString(16).padStart(2,'0')).join(''); } function checkLevelUp(){ const maxLv = LEVEL_XP.length; while(player.level < maxLv && player.xp >= LEVEL_XP[player.level]){ player.level++; player.maxHp += 20; player.hp = Math.min(player.maxHp, player.hp + 30); const name = LEVEL_NAMES[player.level-1] || 'Slime Lord'; showMsg(`LEVEL UP! Lv.${player.level}`, `You are now a ${name}!`); spawnParticles(player.x, player.y, '#ffd700', 30); spawnFloatingText(player.x, player.y-player.size-20, `LEVEL ${player.level}!`, '#ffd700'); } } function useAbility(idx){ if(!player.abilities[idx]) return; const id = player.abilities[idx]; const abil = ABILITIES[id]; if(!abil) return; const now = Date.now(); const cdEnd = player.abilityCooldowns[id] || 0; if(now < cdEnd) return; player.abilityCooldowns[id] = now + abil.cd*1000; player.abilityEndTimes[id] = now + abil.duration; player.activeEffects[id] = now + abil.duration; spawnFloatingText(player.x, player.y-player.size-22, abil.emoji+' '+abil.name+'!', '#00e5ff'); spawnParticles(player.x, player.y, '#00e5ff', 12); // Instant effects if(id==='sting'||id==='zap'||id==='charge'||id==='pinch'){ // Deal damage to nearest creature let nearest=null,nd=Infinity; for(const c of creatures){ const dist=Math.hypot(player.x-c.x,player.y-c.y); if(dist<nd&&dist<200){nd=dist;nearest=c;} } if(nearest){ const dmg = id==='sting'?30:id==='zap'?25:id==='charge'?40:25; nearest.hp -= dmg; nearest.stunned = id==='zap'||id==='charge'?2000:1000; spawnParticles(nearest.x,nearest.y,'#ffaa00',10); spawnFloatingText(nearest.x,nearest.y-nearest.size-10,`-${dmg}`,nearest.def.col); if(nearest.hp<=0) killCreature(nearest); } } if(id==='roar'){ for(const c of creatures){ const dist=Math.hypot(player.x-c.x,player.y-c.y); if(dist<180){ const a=Math.atan2(c.y-player.y,c.x-player.x); c.vx+=Math.cos(a)*5; c.vy+=Math.sin(a)*5; c.fleeing=true; c.fleeTimer=300; } } } if(id==='regen'){ // just activates via activeEffects } if(id==='poison'){ let nearest=null,nd=Infinity; for(const c of creatures){ const dist=Math.hypot(player.x-c.x,player.y-c.y); if(dist<nd&&dist<180){nd=dist;nearest=c;} } if(nearest) nearest.poisoned=4000; } if(id==='ink'){ spawnParticles(player.x,player.y,'#330044',30); // blind effect: creatures wander randomly for(const c of creatures){ if(Math.hypot(player.x-c.x,player.y-c.y)<160){ c.stunned=1500; } } } updateAbilityBar(); } // ───────────────────────────────────────────── // HUD UPDATE // ───────────────────────────────────────────── function updateHUD(){ const hp = document.getElementById('hpBar'); const xpBar = document.getElementById('xpBar'); const hpVal = document.getElementById('hpVal'); const xpVal = document.getElementById('xpVal'); const lvBadge = document.getElementById('levelBadge'); const hpPct = Math.max(0,(player.hp/player.maxHp)*100); hp.style.width = hpPct+'%'; hpVal.textContent = Math.ceil(player.hp)+'/'+player.maxHp; const lv = player.level, maxLv=LEVEL_XP.length; const curXp = player.xp - (LEVEL_XP[lv-1]||0); const needXp = (LEVEL_XP[lv]||LEVEL_XP[maxLv-1]) - (LEVEL_XP[lv-1]||0); const xpPct = lv>=maxLv ? 100 : Math.min(100,(curXp/needXp)*100); xpBar.style.width = xpPct+'%'; xpVal.textContent = Math.floor(curXp)+'/'+needXp; lvBadge.textContent = `LV ${lv} – ${LEVEL_NAMES[lv-1]||'Slimera'}`; updateAbilityBar(); updateMinimap(); } function updateAbilityBar(){ const bar = document.getElementById('abilityBar'); bar.innerHTML=''; const now = Date.now(); for(let i=0;i<Math.min(5,player.abilities.length);i++){ const id = player.abilities[i]; const abil = ABILITIES[id]; const cdEnd = player.abilityCooldowns[id]||0; const isActive = player.activeEffects[id] && player.activeEffects[id]>now; const cdLeft = Math.max(0,(cdEnd-now)/1000); const slot = document.createElement('div'); slot.className = 'ability-slot'+(i===player.activeAbilityIdx?' active':'')+(cdLeft<=0?' ready':''); slot.innerHTML = `${abil.emoji}${cdLeft>0?`<div class="cd-overlay">${cdLeft.toFixed(1)}</div>`:''}${isActive?`<div class="cd-overlay" style="background:rgba(0,200,80,.5)">⚡</div>`:''} <span class="key-hint">${i+1}</span>`; slot.addEventListener('click',()=>useAbility(i)); bar.appendChild(slot); } } // ───────────────────────────────────────────── // MINIMAP // ───────────────────────────────────────────── function initMinimap(){ minimapCanvas = document.createElement('canvas'); minimapCanvas.width = 100; minimapCanvas.height = 100; minimapCanvas.id = 'minimapCanvas'; const div = document.createElement('div'); div.id = 'minimap'; div.appendChild(minimapCanvas); document.body.appendChild(div); minimapCtx = minimapCanvas.getContext('2d'); } function updateMinimap(){ if(!minimapCtx) return; const mc=minimapCtx, mw=100, mh=100; mc.clearRect(0,0,mw,mh); // bg mc.fillStyle='rgba(0,0,0,.5)'; mc.fillRect(0,0,mw,mh); // tiles const stepX = WORLD_W/mw, stepY = WORLD_H/mh; for(let y=0;y<mh;y+=2) for(let x=0;x<mw;x+=2){ const t = tileAt(x*stepX, y*stepY); mc.fillStyle = t==='water'?'#2a6090':t==='forest'?'#1a4a10':t==='deep_grass'?'#2a6020':'#3a8030'; mc.fillRect(x,y,2,2); } // creatures as dots const nightVision = player.activeEffects.night && player.activeEffects.night > Date.now(); for(const c of creatures){ if(!nightVision && Math.hypot(player.x-c.x,player.y-c.y)>400) continue; const mx2 = c.x/WORLD_W*mw, my2 = c.y/WORLD_H*mh; mc.fillStyle = c.def.rare ? '#ffd700' : c.def.col; mc.fillRect(mx2-1,my2-1,2,2); } // player dot const px=player.x/WORLD_W*mw, py=player.y/WORLD_H*mh; mc.fillStyle=player.color; mc.shadowColor=player.color; mc.shadowBlur=5; mc.beginPath();mc.arc(px,py,3,0,Math.PI*2);mc.fill(); mc.shadowBlur=0; // viewport box mc.strokeStyle='rgba(255,255,255,.4)';mc.lineWidth=1; mc.strokeRect(cam.x/WORLD_W*mw, cam.y/WORLD_H*mh, W/WORLD_W*mw, H/WORLD_H*mh); } // ───────────────────────────────────────────── // MESSAGES // ───────────────────────────────────────────── let msgTimeout=null; function showMsg(title, body, dur=2800){ const el = document.getElementById('msgPopup'); document.getElementById('msgTitle').textContent=title; document.getElementById('msgBody').textContent=body; el.classList.remove('hidden'); if(msgTimeout) clearTimeout(msgTimeout); msgTimeout = setTimeout(()=>el.classList.add('hidden'), dur); } function showAbilityUnlock(id, fromName){ const abil = ABILITIES[id]; const el = document.getElementById('abilityUnlock'); document.getElementById('unlockName').textContent = abil.emoji+' '+abil.name+' UNLOCKED!'; document.getElementById('unlockDesc').textContent = `Absorbed from ${fromName}! ${abil.desc}`; el.classList.remove('hidden'); setTimeout(()=>el.classList.add('hidden'), 3500); } // ───────────────────────────────────────────── // GAME LOOP // ───────────────────────────────────────────── function gameLoop(ts){ if(!gameRunning) return; const dt = Math.min(50, ts - lastTime); lastTime = ts; updatePlayer(dt); for(const c of creatures) updateCreatureAI(c, dt); updateParticles(dt); // Draw ctx.clearRect(0,0,W,H); drawWorld(); drawDecorations(); for(const c of creatures) drawCreature(c); drawPlayer(); drawParticlesAndTexts(); requestAnimationFrame(gameLoop); } // ───────────────────────────────────────────── // START / END GAME // ───────────────────────────────────────────── function startGame(){ document.getElementById('startScreen').classList.add('hidden'); document.getElementById('gameOverScreen').classList.add('hidden'); // Reset player Object.assign(player,{ x:WORLD_W/2,y:WORLD_H/2,vx:0,vy:0, hp:100,maxHp:100,xp:0,level:1, size:14,baseSpeed:2.2, color:'#7dff7a',color2:'#4acc45', abilities:[],activeAbilityIdx:0, abilityCooldowns:{},abilityEndTimes:{}, activeEffects:{},wobble:0, facing:0,devourAnim:0,dead:false,invincible:0 }); genWorld(); spawnCreatures(); initMinimap(); gameRunning=true; lastTime=performance.now(); requestAnimationFrame(gameLoop); showMsg('🌿 Welcome to Slimera!','Find and devour creatures to grow & evolve!',3000); } function endGame(){ gameRunning=false; player.dead=true; const el = document.getElementById('gameOverScreen'); document.getElementById('gameOverText').textContent = `You reached Level ${player.level} (${LEVEL_NAMES[player.level-1]}) with ${Math.floor(player.xp)} XP and ${player.abilities.length} abilities!`; el.classList.remove('hidden'); } // ───────────────────────────────────────────── // INPUT // ───────────────────────────────────────────── document.addEventListener('keydown',e=>{ keys[e.key]=true; if(e.key===' '||e.key==='e'||e.key==='E'){ e.preventDefault(); tryDevour(); } if(e.key==='1') useAbility(0); if(e.key==='2') useAbility(1); if(e.key==='3') useAbility(2); if(e.key==='4') useAbility(3); if(e.key==='5') useAbility(4); }); document.addEventListener('keyup',e=>{ keys[e.key]=false; }); // Mobile joystick const joystickArea = document.getElementById('joystickArea'); const joystickKnob = document.getElementById('joystickKnob'); let joystickOrigin = null; joystickArea.addEventListener('touchstart',e=>{ e.preventDefault(); const t=e.touches[0]; const r=joystickArea.getBoundingClientRect(); joystickOrigin={x:r.left+r.width/2, y:r.top+r.height/2}; joystick.active=true; },{passive:false}); joystickArea.addEventListener('touchmove',e=>{ e.preventDefault(); if(!joystickOrigin) return; const t=e.touches[0]; const dx=t.clientX-joystickOrigin.x, dy=t.clientY-joystickOrigin.y; const dist=Math.hypot(dx,dy); const maxR=40; const fx=dist>maxR?dx/dist:dx/maxR; const fy=dist>maxR?dy/dist:dy/maxR; joystick.dx=fx; joystick.dy=fy; const kx=Math.min(maxR,dist)*fx, ky=Math.min(maxR,dist)*fy; joystickKnob.style.left=(55+kx-23)+'px'; joystickKnob.style.top=(55+ky-23)+'px'; },{passive:false}); ['touchend','touchcancel'].forEach(ev=>joystickArea.addEventListener(ev,e=>{ joystick.active=false;joystick.dx=0;joystick.dy=0; joystickKnob.style.left='32px';joystickKnob.style.top='32px'; })); document.getElementById('btnDevour').addEventListener('touchstart',e=>{ e.preventDefault(); tryDevour(); },{passive:false}); document.getElementById('btnDevour').addEventListener('click',()=>tryDevour()); document.getElementById('btnAbility').addEventListener('touchstart',e=>{ e.preventDefault(); useAbility(player.activeAbilityIdx); },{passive:false}); document.getElementById('btnAbility').addEventListener('click',()=>useAbility(player.activeAbilityIdx)); // swipe ability switch let abSwipe=null; document.getElementById('btnAbility').addEventListener('touchstart',e=>{ abSwipe={x:e.touches[0].clientX}; }); document.getElementById('btnAbility').addEventListener('touchend',e=>{ if(!abSwipe) return; const dx=e.changedTouches[0].clientX-abSwipe.x; if(Math.abs(dx)>30){ if(dx>0) player.activeAbilityIdx=Math.min(player.abilities.length-1,player.activeAbilityIdx+1); else player.activeAbilityIdx=Math.max(0,player.activeAbilityIdx-1); updateAbilityBar(); } abSwipe=null; }); document.getElementById('startBtn').addEventListener('click',startGame); document.getElementById('restartBtn').addEventListener('click',startGame); </script> </body> </html>
Landing Page
This ad does not have a landing page available
Network Timeline
Performance Summary

4

Requests

3

Domains

142KB

Transfer Size

149KB

Content Size

208.0ms

Dom Content Loaded

400.0ms

First Paint

294.0ms

Load Time
Domain Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...
Timings (ms)
Loading...
Total Time
Loading...
Content Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...