<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

    <title>Tiny Town Explorer</title>

    <style>

        :root {

            --bg-color: #2c3e50;

            --ui-bg: rgba(0, 0, 0, 0.6);

            --accent: #f1c40f;

            --text: #ecf0f1;

        }


        * {

            box-sizing: border-box;

            user-select: none;

            -webkit-user-select: none;

            touch-action: none;

        }


        body {

            margin: 0;

            padding: 0;

            overflow: hidden;

            background-color: var(--bg-color);

            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

            color: var(--text);

            width: 100vw;

            height: 100vh;

        }


        #game-container {

            position: relative;

            width: 100%;

            height: 100%;

            overflow: hidden;

            background: #81c784;

            cursor: grab;

        }


        #game-container:active { cursor: grabbing; }


        canvas {

            display: block;

            width: 100%;

            height: 100%;

        }


        /* Joystick Styling */

        #joystick-zone {

            position: absolute;

            left: 0;

            top: 0;

            width: 100%;

            height: 100%;

            pointer-events: none;

            z-index: 10;

        }


        .joystick {

            position: absolute;

            width: 140px;

            height: 140px;

            background: var(--ui-bg);

            backdrop-filter: blur(8px);

            border: 2px solid rgba(255,255,255,0.3);

            border-radius: 50%;

            transform: translate(-50%, -50%) scale(0);

            transition: transform 0.1s cubic-bezier(0.175, 0.885, 0.32, 1.275);

            pointer-events: none;

            box-shadow: 0 10px 30px rgba(0,0,0,0.3);

            display: flex;

            align-items: center;

            justify-content: center;

            z-index: 50;

        }


        .joystick.active { transform: translate(-50%, -50%) scale(1); }


        .joystick-knob {

            width: 50px;

            height: 50px;

            background: rgba(255, 255, 255, 0.95);

            border-radius: 50%;

            position: absolute;

            box-shadow: 0 4px 10px rgba(0,0,0,0.3);

            transition: transform 0.05s linear;

        }


        /* UI Overlay */

        #ui-layer {

            position: absolute;

            top: 20px;

            left: 20px;

            pointer-events: none;

            z-index: 40;

            text-shadow: 0 2px 4px rgba(0,0,0,0.5);

        }


        h1 {

            margin: 0;

            font-size: 1.2rem;

            font-weight: 800;

            letter-spacing: 1px;

            text-transform: uppercase;

        }


        .coords {

            font-family: monospace;

            font-size: 0.9rem;

            background: var(--ui-bg);

            padding: 4px 8px;

            border-radius: 4px;

            margin-top: 5px;

            display: inline-block;

            color: var(--accent);

        }


        /* Minimap */

        #minimap {

            position: absolute;

            bottom: 20px;

            right: 20px;

            width: 150px;

            height: 150px;

            background: rgba(0, 20, 0, 0.8);

            border: 2px solid rgba(255,255,255,0.3);

            border-radius: 50%;

            pointer-events: none;

            z-index: 30;

            overflow: hidden;

        }

        

        #minimap canvas {

            width: 100%;

            height: 100%;

            border-radius: 50%;

        }


        #tooltip {

            position: absolute;

            background: rgba(255, 255, 255, 0.95);

            color: #222;

            padding: 6px 12px;

            border-radius: 8px;

            font-size: 12px;

            font-weight: bold;

            pointer-events: none;

            opacity: 0;

            transition: opacity 0.2s, transform 0.1s;

            transform: translate(-50%, -160%) scale(0.9);

            z-index: 35;

            box-shadow: 0 4px 12px rgba(0,0,0,0.2);

        }


        #loader {

            position: absolute; inset: 0;

            background: #2d3436;

            display: flex; align-items: center; justify-content: center;

            z-index: 100; transition: opacity 0.5s;

        }

        .spinner {

            width: 40px; height: 40px;

            border: 4px solid rgba(255,255,255,0.2);

            border-top-color: var(--accent);

            border-radius: 50%;

            animation: spin 1s linear infinite;

        }

        @keyframes spin { to { transform: rotate(360deg); } }

    </style>

</head>

<body>


<div id="loader"><div class="spinner"></div></div>


<div id="game-container">

    <canvas id="world"></canvas>

    

    <div id="joystick-zone">

        <div id="joystick" class="joystick">

            <div id="joystick-knob" class="joystick-knob"></div>

        </div>

    </div>


    <div id="ui-layer">

        <h1>Tiny Town</h1>

        <div class="coords" id="coord-display">X: 0 | Y: 0</div>

    </div>


    <div id="minimap">

        <canvas id="minimap-canvas"></canvas>

    </div>


    <div id="tooltip"></div>

</div>


<script>

const canvas = document.getElementById('world');

const ctx = canvas.getContext('2d');

const mmCanvas = document.getElementById('minimap-canvas');

const mmCtx = mmCanvas.getContext('2d');

const uiCoords = document.getElementById('coord-display');


// Game Config

const WORLD_W = 2000;

const WORLD_H = 2000;

const TILE_SIZE = 40;

const PLAYER_SPEED = 5;

const MAX_JOYSTICK_DIST = 40;


// State

let width, height;

let camera = { x: 0, y: 0 };

let input = { x: 0, y: 0, active: false };

let joystickPos = { x: 0, y: 0 };

const player = { x: 1000, y: 1000, z: 0, color: '#FFD166' };


const worldObjects = [];

const collisionRects = [];


// Setup

function initWorld() {

    // Create Roads (Grid Pattern)

    const roadColor = '#d7ccc8';

    for(let x=0; x<WORLD_W; x+=400) {

        worldObjects.push({type:'road', x:x, y:0, w:40, h:WORLD_H, color:roadColor});

    }

    for(let y=0; y<WORLD_H; y+=400) {

        worldObjects.push({type:'road', x:0, y:y, w:WORLD_W, h:40, color:roadColor});

    }


    // Procedural Town Generation

    const positions = [

        [600,600], [1000,600], [1400,600],

        [600,1000], [1400,1000],

        [600,1400], [1000,1400], [1400,1400]

    ];


    positions.forEach((pos, i) => {

        const isBig = i === 4; // Center building

        createBuilding(pos[0], pos[1], isBig ? 120 : 80, isBig ? 100 : 70, 

            isBig ? '#c0392b' : '#d35400', 

            isBig ? '#2c3e50' : '#e67e22',

            isBig ? 'Town Hall' : 'House #' + (i+1)

        );

        

        // Surrounding trees

        createTree(pos[0] - 50, pos[1] - 50, 20);

        createTree(pos[0] + 100, pos[1] + 20, 20);

    });


    // Random park area

    for(let i=0; i<20; i++) {

        createTree(200 + Math.random()*300, 200 + Math.random()*300, 25 + Math.random()*10);

    }

    

    // Lake

    worldObjects.push({type:'lake', x:1600, y:300, r:150});

}


function createBuilding(x, y, w, h, wall, roof, name) {

    worldObjects.push({type:'building', x, y, w, h, wall, roof, name, z:y+h});

    collisionRects.push({x,y,w,h});

}


function createTree(x, y, r) {

    worldObjects.push({type:'tree', x, y, r, z:y+r});

    collisionRects.push({x: x-r/2, y:y-r/2, w:r, h:r});

}


// Input Handling

const joystickEl = document.getElementById('joystick');

const knobEl = document.getElementById('joystick-knob');


function setupInputs() {

    const container = document.getElementById('game-container');


    container.addEventListener('touchstart', (e) => {

        if(e.target.closest('#ui-layer')) return;

        const t = e.touches[0];

        joystickPos = {x: t.clientX, y: t.clientY};

        joystickEl.style.left = t.clientX + 'px';

        joystickEl.style.top = t.clientY + 'px';

        joystickEl.classList.add('active');

        input.active = true;

        updateJoystick(t.clientX, t.clientY);

    }, {passive:false});


    container.addEventListener('touchmove', (e) => {

        if(!input.active) return;

        e.preventDefault();

        updateJoystick(e.touches[0].clientX, e.touches[0].clientY);

    }, {passive:false});


    container.addEventListener('touchend', () => {

        input.active = false; input.x = 0; input.y = 0;

        joystickEl.classList.remove('active');

        knobEl.style.transform = `translate(0px,0px)`;

    });


    // Keyboard

    const keys = {};

    window.addEventListener('keydown', e => {

        keys[e.key] = true;

        updateKeyboard();

    });

    window.addEventListener('keyup', e => {

        keys[e.key] = false; 

        updateKeyboard();

    });


    function updateKeyboard() {

        input.x = 0; input.y = 0;

        if(keys['ArrowUp'] || keys['w']) input.y = -1;

        if(keys['ArrowDown'] || keys['s']) input.y = 1;

        if(keys['ArrowLeft'] || keys['a']) input.x = -1;

        if(keys['ArrowRight'] || keys['d']) input.x = 1;

        input.active = input.x !== 0 || input.y !== 0;

    }

}


function updateJoystick(cx, cy) {

    let dx = cx - joystickPos.x;

    let dy = cy - joystickPos.y;

    const dist = Math.sqrt(dx*dx + dy*dy);

    if(dist > MAX_JOYSTICK_DIST) {

        const ratio = MAX_JOYSTICK_DIST/dist;

        dx *= ratio; dy *= ratio;

    }

    knobEl.style.transform = `translate(${dx}px,${dy}px)`;

    input.x = dx/MAX_JOYSTICK_DIST;

    input.y = dy/MAX_JOYSTICK_DIST;

}


// Game Loop

function update() {

    // Movement

    if(input.active) {

        player.x += input.x * PLAYER_SPEED;

        player.y += input.y * PLAYER_SPEED;

    }


    // World Bounds

    player.x = Math.max(20, Math.min(WORLD_W-20, player.x));

    player.y = Math.max(20, Math.min(WORLD_H-20, player.y));


    player.z = player.y; // For sorting


    // Camera Logic (Center on player, clamp to world bounds)

    let targetCamX = player.x - width/2;

    let targetCamY = player.y - height/2;


    // Clamp Camera so we don't see void

    targetCamX = Math.max(0, Math.min(targetCamX, WORLD_W - width));

    targetCamY = Math.max(0, Math.min(targetCamY, WORLD_H - height));


    // Smooth Lerp

    camera.x += (targetCamX - camera.x) * 0.1;

    camera.y += (targetCamY - camera.y) * 0.1;


    // UI Updates

    uiCoords.textContent = `X: ${Math.floor(player.x)} | Y: ${Math.floor(player.y)}`;

}


function draw() {

    ctx.fillStyle = '#81c784';

    ctx.fillRect(0,0,width,height);


    ctx.save();

    ctx.translate(-Math.floor(camera.x), -Math.floor(camera.y));


    // Draw Grid (Visual proof of movement)

    ctx.strokeStyle = 'rgba(0,0,0,0.05)';

    ctx.lineWidth = 1;

    for(let x=0; x<=WORLD_W; x+=TILE_SIZE) {

        ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,WORLD_H); ctx.stroke();

    }

    for(let y=0; y<=WORLD_H; y+=TILE_SIZE) {

        ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(WORLD_W,y); ctx.stroke();

    }


    // World Border

    ctx.strokeStyle = '#2e7d32';

    ctx.lineWidth = 5;

    ctx.strokeRect(0,0,WORLD_W,WORLD_H);


    // Prepare Render List

    const renderList = [...worldObjects, 

        {...player, type:'player', z:player.y},

        {type:'tooltip', x:player.x, y:player.y-20, z:player.y} // Dummy for z-sort

    ];

    renderList.sort((a,b) => a.z - b.z);


    renderList.forEach(obj => {

        if(obj.type === 'road') drawRoad(obj);

        else if(obj.type === 'building') drawBuilding(obj);

        else if(obj.type === 'tree') drawTree(obj);

        else if(obj.type === 'lake') drawLake(obj);

        else if(obj.type === 'player') drawPlayer(obj);

    });


    ctx.restore();

    

    drawMinimap();

}


function drawRoad(r) {

    ctx.fillStyle = r.color;

    ctx.fillRect(r.x, r.y, r.w, r.h);

    // Dashed line center

    ctx.strokeStyle = 'rgba(0,0,0,0.1)';

    ctx.setLineDash([20, 20]);

    ctx.lineWidth = 2;

    ctx.beginPath();

    if(r.w > r.h) { // Horizontal

        ctx.moveTo(r.x, r.y+r.h/2);

        ctx.lineTo(r.x+r.w, r.y+r.h/2);

    } else {

        ctx.moveTo(r.x+r.w/2, r.y);

        ctx.lineTo(r.x+r.w/2, r.y+r.h);

    }

    ctx.stroke();

    ctx.setLineDash([]);

}


function drawBuilding(b) {

    // Shadow

    ctx.fillStyle = 'rgba(0,0,0,0.2)';

    ctx.fillRect(b.x+10, b.y+b.h, b.w, 8);


    // Walls

    ctx.fillStyle = b.wall;

    ctx.fillRect(b.x, b.y, b.w, b.h);

    

    // Trim

    ctx.fillStyle = 'rgba(0,0,0,0.1)';

    ctx.fillRect(b.x, b.y+b.h-10, b.w, 10);


    // Roof

    ctx.fillStyle = b.roof;

    ctx.beginPath();

    ctx.moveTo(b.x-10, b.y);

    ctx.lineTo(b.x+b.w+10, b.y);

    ctx.lineTo(b.x+b.w/2, b.y-40);

    ctx.fill();


    // Door

    ctx.fillStyle = '#4e342e';

    ctx.fillRect(b.x+b.w/2-12, b.y+b.h-30, 24, 30);

    

    // Label if close

    const dx = player.x - (b.x+b.w/2);

    const dy = player.y - (b.y+b.h/2);

    if(Math.sqrt(dx*dx+dy*dy) < 80) {

        ctx.fillStyle = 'rgba(0,0,0,0.7)';

        ctx.fillRect(b.x+b.w/2-40, b.y-60, 80, 20);

        ctx.fillStyle = 'white';

        ctx.font = '12px sans-serif';

        ctx.textAlign = 'center';

        ctx.fillText(b.name, b.x+b.w/2, b.y-46);

        

        // Update DOM tooltip position

        updateTooltip(b.x+b.w/2, b.y, b.name);

    }

}


function drawTree(t) {

    ctx.fillStyle = 'rgba(0,0,0,0.15)';

    ctx.beginPath();

    ctx.ellipse(t.x, t.y+t.r/2, t.r, t.r/3, 0, 0, Math.PI*2);

    ctx.fill();

    

    ctx.fillStyle = '#5d4037'; // Trunk

    ctx.fillRect(t.x-6, t.y, 12, t.r);

    

    ctx.fillStyle = '#2e7d32'; // Leaves

    ctx.beginPath();

    ctx.arc(t.x, t.y-t.r/3, t.r, 0, Math.PI*2);

    ctx.arc(t.x-t.r/2, t.y, t.r*0.7, 0, Math.PI*2);

    ctx.arc(t.x+t.r/2, t.y, t.r*0.7, 0, Math.PI*2);

    ctx.fill();

}


function drawLake(l) {

    ctx.fillStyle = '#0288d1';

    ctx.beginPath();

    ctx.arc(l.x, l.y, l.r, 0, Math.PI*2);

    ctx.fill();

    // Glint

    ctx.fillStyle = 'rgba(255,255,255,0.3)';

    ctx.beginPath();

    ctx.arc(l.x-20, l.y-20, 10, 0, Math.PI*2);

    ctx.fill();

}


function drawPlayer(p) {

    // Shadow

    ctx.fillStyle = 'rgba(0,0,0,0.3)';

    ctx.beginPath();

    ctx.ellipse(p.x, p.y+8, 10, 5, 0, 0, Math.PI*2);

    ctx.fill();

    

    // Body

    ctx.fillStyle = p.color;

    ctx.beginPath();

    ctx.arc(p.x, p.y, 12, 0, Math.PI*2);

    ctx.fill();

    

    // Direction indicator (hat)

    ctx.fillStyle = '#e53935';

    ctx.beginPath();

    ctx.arc(p.x, p.y-2, 12, Math.PI, 0);

    ctx.fill();

    

    // Walking wobble

    if(input.active) {

        ctx.fillStyle = 'rgba(0,0,0,0.5)';

        ctx.beginPath();

        ctx.arc(p.x + Math.cos(Date.now()/100)*5, p.y+6, 3, 0, Math.PI*2);

        ctx.fill();

    }

}


function updateTooltip(x, y, text) {

    const el = document.getElementById('tooltip');

    // Convert world coord to screen coord

    const sx = x - camera.x;

    const sy = y - camera.y;

    el.style.left = sx + 'px';

    el.style.top = sy + 'px';

    el.textContent = text;

    el.style.opacity = 1;

    el.style.transform = 'translate(-50%, -160%) scale(1)';

}


// Minimap

function drawMinimap() {

    // Clear

    mmCtx.fillStyle = '#1b5e20';

    mmCtx.fillRect(0,0,150,150);

    

    const scale = 150 / WORLD_W;

    

    // Draw Roads

    mmCtx.fillStyle = '#a1887f';

    worldObjects.filter(o => o.type === 'road').forEach(r => {

        mmCtx.fillRect(r.x*scale, r.y*scale, Math.max(r.w*scale, 2), Math.max(r.h*scale,2));

    });

    

    // Draw Buildings

    mmCtx.fillStyle = '#bf360c';

    worldObjects.filter(o => o.type === 'building').forEach(b => {

        mmCtx.fillRect(b.x*scale, b.y*scale, b.w*scale, b.h*scale);

    });

    

    // Draw Player

    mmCtx.fillStyle = '#ffd166';

    mmCtx.beginPath();

    mmCtx.arc(player.x*scale, player.y*scale, 3, 0, Math.PI*2);

    mmCtx.fill();

    

    // Viewport rect

    mmCtx.strokeStyle = 'white';

    mmCtx.lineWidth = 1;

    mmCtx.strokeRect(camera.x*scale, camera.y*scale, width*scale, height*scale);

}


// Init

function loop() {

    update();

    draw();

    requestAnimationFrame(loop);

}


window.onload = () => {

    width = window.innerWidth;

    height = window.innerHeight;

    canvas.width = width;

    canvas.height = height;

    mmCanvas.width = 150;

    mmCanvas.height = 150;

    

    initWorld();

    setupInputs();

    

    document.getElementById('loader').style.opacity = 0;

    setTimeout(()=>document.getElementById('loader').remove(), 500);

    

    loop();

};


window.addEventListener('resize', () => {

    width = window.innerWidth;

    height = window.innerHeight;

    canvas.width = width;

    canvas.height = height;

});

</script>

</body>

</html>