<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Mushroom Wanderer</title>
<style>
html, body {
margin: 0; padding: 0; overflow: hidden;
background: #0b0e14;
font-family: 'Georgia', serif;
height: 100%;
}
#game {
position: relative;
width: 100vw; height: 100vh;
overflow: hidden;
background: linear-gradient(to bottom, #10121c 0%, #1a1d2b 40%, #23283a 70%, #2b2f22 100%);
}
canvas { display: block; }
#hud {
position: absolute; top: 10px; left: 10px; color: #cfd8dc;
font-size: 14px; text-shadow: 0 0 4px #000;
background: rgba(10,12,20,0.5);
padding: 8px 12px; border-radius: 10px;
border: 1px solid #3a4050;
}
#statsPanel {
display: none;
position: absolute; top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 380px;
background: rgba(15,17,26,0.95);
border: 2px solid #6b5b8a;
border-radius: 14px;
padding: 18px 22px;
color: #e6e1f0;
box-shadow: 0 0 30px rgba(120,90,180,0.4);
z-index: 20;
}
#statsPanel h2 { margin-top:0; color:#c9a4ff; text-shadow: 0 0 6px #7a4fd6; }
#statsPanel .row { margin: 8px 0; font-size: 14px; }
.bar { background:#2a2a35; border-radius:6px; height:10px; overflow:hidden; border:1px solid #444; }
.bar-fill { height:100%; background: linear-gradient(90deg,#8fd694,#4caf50); }
.bar-fill.dmg { background: linear-gradient(90deg,#e07a7a,#c0392b); }
#mushTypes { display:flex; gap:8px; margin-top:10px; flex-wrap: wrap; }
.mushBtn {
background:#2a2438; border:1px solid #6b5b8a; color:#e6e1f0;
padding:6px 10px; border-radius:8px; cursor:pointer; font-size:12px;
}
.mushBtn.active { background:#6b4fb0; }
#closeStats {
position:absolute; top:8px; right:12px; cursor:pointer; color:#c9a4ff; font-size:18px;
}
#msg {
position:absolute; bottom:20px; left:50%; transform:translateX(-50%);
color:#f0e6ff; background:rgba(20,15,30,0.7); padding:8px 16px;
border-radius:10px; font-size:14px; display:none; border:1px solid #6b5b8a;
max-width: 80%; text-align:center;
}
</style>
</head>
<body>
<div id="game">
<canvas id="canvas"></canvas>
<div id="hud">
🍄 Seeds: <span id="seedCount">3</span> |
❤️ Health: <span id="healthText">100</span> |
Arrows/WASD to move, Space to jump, E to search trash, Click mushroom for stats
</div>
<div id="msg"></div>
<canvas id="miniMap" width="220" height="26" style="position:absolute; top:10px; right:10px; background:rgba(10,12,20,0.6); border:1px solid #3a4050; border-radius:8px;"></canvas>
<div id="statsPanel">
<span id="closeStats">✖</span>
<h2>🍄 Traveler Stats</h2>
<div class="row">Health
<div class="bar"><div class="bar-fill" id="healthBar" style="width:100%"></div></div>
</div>
<div class="row">Body Damage</div>
<div class="row" style="font-size:13px; color:#d8c8f0;" id="damageList"></div>
<div class="row">Mushroom Type: <b id="currentType">Cloak Wanderer</b></div>
<div id="mushTypes"></div>
<div class="row" style="margin-top:10px; color:#b0a0d0; font-size:12px;">
Different mushroom bodies will unlock new abilities like bounce-jumping and teleport seeds!
</div>
</div>
</div>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
function resize(){ canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);
const GROUND_Y = () => canvas.height - 100;
const GRAVITY = 0.6;
let keys = {};
window.addEventListener('keydown', e => { keys[e.key.toLowerCase()] = true; });
window.addEventListener('keyup', e => { keys[e.key.toLowerCase()] = false; });
let rain = [];
for (let i=0;i<150;i++){
rain.push({x: Math.random()*2000, y: Math.random()*800, len: 10+Math.random()*14, speed: 6+Math.random()*6});
}
let trashPiles = [];
function makeTrash(x){
return { x: x, y: 0, w: 60, h: 34, searched:false, rat: Math.random()<0.35, ratOut:false, ratTimer:0 };
}
for (let i=0;i<32;i++){
trashPiles.push(makeTrash(400 + i*360 + Math.random()*140));
}
let camX = 0;
const worldWidth = 12000;
const player = {
x: 150, y: 0, w: 28, h: 40,
vx: 0, vy: 0,
onGround: false,
facing: 1,
health: 100,
seeds: 3,
type: "Cloak Wanderer",
damage: { "Left Arm":0, "Right Arm":0, "Left Leg":0, "Right Leg":0, "Head":0 }
};
const mushroomTypes = [
{name:"Cloak Wanderer", desc:"Balanced explorer"},
{name:"Bounce Cap", desc:"Head-bounce jumps high"},
{name:"Spore Teleporter", desc:"Plant seeds to return"},
{name:"Parachute Cap", desc:"Floats gently down"}
];
function showMsg(text, time=2200){
const m = document.getElementById('msg');
m.textContent = text;
m.style.display = 'block';
clearTimeout(m._t);
m._t = setTimeout(()=> m.style.display='none', time);
}
function spawnRatFrom(pile){
pile.ratOut = true;
pile.ratTimer = 0;
}
window.addEventListener('keydown', e => {
if (e.code === 'Space' && player.onGround){
player.vy = -13;
player.onGround = false;
}
if (e.key.toLowerCase() === 'e'){
for (const pile of trashPiles){
const px = pile.x;
if (Math.abs((px) - (player.x)) < 60 && !pile.searched){
pile.searched = true;
if (pile.rat){
spawnRatFrom(pile);
showMsg("A rat scurries out of the trash! 🐀");
} else {
const found = Math.random();
if (found < 0.5){
player.seeds++;
document.getElementById('seedCount').textContent = player.seeds;
showMsg("You found a seed in the trash! 🌱");
} else {
showMsg("Just old junk and bones... nothing here.");
}
}
}
}
}
});
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const mx = e.clientX - rect.left;
const my = e.clientY - rect.top;
const screenX = player.x - camX;
const screenY = GROUND_Y() - player.h;
if (mx > screenX-20 && mx < screenX+player.w+20 && my > screenY-10 && my < screenY+player.h+10){
openStats();
}
});
function openStats(){
document.getElementById('statsPanel').style.display = 'block';
document.getElementById('healthBar').style.width = player.health + "%";
document.getElementById('currentType').textContent = player.type;
const dl = document.getElementById('damageList');
dl.innerHTML = Object.entries(player.damage).map(([part,val]) =>
`${part}: ${val>0 ? '🩸 damaged ('+val+')' : '✅ ok'}`
).join('<br>');
}
document.getElementById('closeStats').addEventListener('click', ()=>{
document.getElementById('statsPanel').style.display = 'none';
});
const typeDiv = document.getElementById('mushTypes');
mushroomTypes.forEach(t => {
const btn = document.createElement('div');
btn.className = 'mushBtn' + (t.name===player.type ? ' active':'');
btn.textContent = t.name;
btn.title = t.desc;
btn.onclick = () => {
player.type = t.name;
document.getElementById('currentType').textContent = t.name;
document.querySelectorAll('.mushBtn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
showMsg("You are now a " + t.name + "! (" + t.desc + ")");
};
typeDiv.appendChild(btn);
});
let birds = [];
for (let i=0;i<14;i++){
birds.push({x: Math.random()*worldWidth, y: 50+Math.random()*100, dir: Math.random()<0.5?1:-1, timer: Math.random()*200});
}
function update(){
const speed = 4.2;
if (keys['a'] || keys['arrowleft']){ player.vx = -speed; player.facing = -1; }
else if (keys['d'] || keys['arrowright']){ player.vx = speed; player.facing = 1; }
else { player.vx = 0; }
player.x += player.vx;
player.x = Math.max(20, Math.min(worldWidth-20, player.x));
player.vy += GRAVITY;
if (player.type === "Parachute Cap" && player.vy > 2 && !player.onGround){
player.vy = 2;
}
player.y += player.vy;
if (player.y > 0){ player.y = 0; player.vy = 0; player.onGround = true; }
camX = player.x - canvas.width/2;
camX = Math.max(0, Math.min(worldWidth - canvas.width, camX));
for (const d of rain){
d.y += d.speed;
d.x -= 1.5;
if (d.y > canvas.height){ d.y = -20; d.x = camX + Math.random()*canvas.width*1.5; }
}
for (const pile of trashPiles){
if (pile.ratOut){
pile.ratTimer++;
if (pile.ratTimer > 300){
const dist = Math.abs(pile.x - player.x);
if (dist < 40 && pile.ratTimer % 60 < 2){
player.health -= 3;
const parts = Object.keys(player.damage);
const p = parts[Math.floor(Math.random()*parts.length)];
player.damage[p] += 1;
document.getElementById('healthText').textContent = Math.max(0,player.health);
showMsg("The rat bit you!");
}
}
}
}
for (const b of birds){
b.timer++;
b.x += b.dir * 0.8;
if (b.timer > 400){
b.timer = 0;
b.dir *= -1;
}
if (Math.abs(b.x - player.x) < 30 && Math.random() < 0.002){
player.health -= 2;
document.getElementById('healthText').textContent = Math.max(0,player.health);
showMsg("A bird pecked you from above!");
}
}
player.health = Math.max(0, Math.min(100, player.health));
document.getElementById('healthText').textContent = Math.round(player.health);
}
function drawBackground(){
const g = ctx.createLinearGradient(0,0,0,canvas.height);
g.addColorStop(0,'#12141f');
g.addColorStop(0.5,'#1c2030');
g.addColorStop(1,'#20241c');
ctx.fillStyle = g;
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = 'rgba(10,12,18,0.6)';
for (let i=0;i<8;i++){
const x = (i*500 - camX*0.3) % (canvas.width+600) - 300;
ctx.beginPath();
ctx.moveTo(x, GROUND_Y());
ctx.lineTo(x+40, GROUND_Y()-180-Math.sin(i)*40);
ctx.lineTo(x+80, GROUND_Y());
ctx.fill();
}
ctx.fillStyle = '#181a14';
ctx.fillRect(0, GROUND_Y(), canvas.width, canvas.height-GROUND_Y());
ctx.fillStyle = '#232819';
ctx.fillRect(0, GROUND_Y(), canvas.width, 6);
ctx.strokeStyle = '#0a0a0a';
ctx.lineWidth = 2;
for (const b of birds){
const sx = b.x - camX;
if (sx < -50 || sx > canvas.width+50) continue;
ctx.beginPath();
ctx.moveTo(sx-8, b.y);
ctx.quadraticCurveTo(sx, b.y-6, sx+8, b.y);
ctx.stroke();
}
for (const pile of trashPiles){
const sx = pile.x - camX;
if (sx < -80 || sx > canvas.width+80) continue;
ctx.fillStyle = pile.searched ? '#2e2a20' : '#4a4230';
ctx.beginPath();
ctx.ellipse(sx, GROUND_Y()-10, pile.w/2, 16, 0, 0, Math.PI*2);
ctx.fill();
ctx.strokeStyle = '#1a1810';
ctx.lineWidth = 1;
ctx.stroke();
ctx.strokeStyle = '#0f0d08';
ctx.beginPath();
ctx.moveTo(sx-15, GROUND_Y()-16); ctx.lineTo(sx+10, GROUND_Y()-22);
ctx.moveTo(sx-5, GROUND_Y()-20); ctx.lineTo(sx+18, GROUND_Y()-10);
ctx.stroke();
if (pile.ratOut){
ctx.fillStyle = '#3a2f2f';
ctx.beginPath();
ctx.ellipse(sx+20, GROUND_Y()-6, 8, 5, 0, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#3a2f2f';
ctx.beginPath();
ctx.arc(sx+27, GROUND_Y()-9, 3, 0, Math.PI*2);
ctx.fill();
}
}
ctx.strokeStyle = 'rgba(180,200,230,0.35)';
ctx.lineWidth = 1;
for (const d of rain){
const sx = d.x - camX*0.5;
ctx.beginPath();
ctx.moveTo(sx, d.y);
ctx.lineTo(sx-3, d.y+d.len);
ctx.stroke();
}
}
function drawPlayer(){
const sx = player.x - camX;
const sy = GROUND_Y() + player.y - player.h;
ctx.save();
ctx.translate(sx, sy);
if (player.facing < 0){ ctx.scale(-1,1); }
ctx.fillStyle = '#2b2540';
ctx.beginPath();
ctx.moveTo(-12,10);
ctx.lineTo(12,10);
ctx.lineTo(8,40);
ctx.lineTo(-8,40);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = '#0c0a10';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(-4,36); ctx.lineTo(-6,44);
ctx.moveTo(4,36); ctx.lineTo(6,44);
ctx.stroke();
ctx.fillStyle = '#3a3252';
ctx.beginPath();
ctx.moveTo(-10,6);
ctx.lineTo(10,6);
ctx.lineTo(7,34);
ctx.lineTo(-7,34);
ctx.closePath();
ctx.fill();
ctx.fillStyle = player.type === "Bounce Cap" ? '#c0392b' : '#8b6bb8';
ctx.beginPath();
ctx.ellipse(0,0,14,10,0,Math.PI,0);
ctx.fill();
ctx.fillStyle = '#efe6ff';
ctx.fillRect(-14,-1,28,4);
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(-6,-4,1.5,0,Math.PI*2);
ctx.arc(4,-6,1.5,0,Math.PI*2);
ctx.fill();
ctx.strokeStyle = '#5a4632';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(13,10);
ctx.lineTo(20,46);
ctx.stroke();
ctx.fillStyle = '#6b4f2a';
ctx.beginPath();
ctx.arc(-9,24,4,0,Math.PI*2);
ctx.fill();
ctx.restore();
}
function draw(){
drawBackground();
drawPlayer();
}
const miniMap = document.getElementById('miniMap');
const mctx = miniMap.getContext('2d');
function drawMiniMap(){
mctx.clearRect(0,0,220,26);
mctx.fillStyle = '#8b6bb8';
const px = (player.x/worldWidth) * 220;
mctx.fillRect(px-2, 4, 4, 18);
mctx.strokeStyle = '#6b5b8a';
mctx.strokeRect(0,0,220,26);
}
function loop(){
update();
draw();
drawMiniMap();
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>