Skip to content

Instantly share code, notes, and snippets.

@mode-mercury
Created December 26, 2025 20:38
Show Gist options
  • Select an option

  • Save mode-mercury/393b5dc0eaf391ff1c5349dc13b3e7d9 to your computer and use it in GitHub Desktop.

Select an option

Save mode-mercury/393b5dc0eaf391ff1c5349dc13b3e7d9 to your computer and use it in GitHub Desktop.
Untitled
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fractal Realms - Enhanced 8-Bit RPG</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Courier New', monospace;
image-rendering: pixelated;
-webkit-tap-highlight-color: transparent;
-webkit-user-select: none;
user-select: none;
}
body {
background: #0a0a1a;
color: #e0e0ff;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
background:
radial-gradient(circle at 20% 30%, #1a1a3a 0%, #0a0a1a 70%),
radial-gradient(circle at 80% 70%, #2a1a3a 0%, #0a0a1a 70%);
touch-action: none;
}
#game-container {
width: 800px;
height: 600px;
position: relative;
border: 4px solid #4a3a8d;
border-radius: 8px;
overflow: hidden;
box-shadow:
0 0 0 2px #2a2a5a,
0 0 30px rgba(100, 70, 200, 0.5),
inset 0 0 20px rgba(0, 0, 0, 0.5);
background: #151530;
}
#game-screen {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
#world-map {
width: 1600px;
height: 1600px;
position: absolute;
top: -500px;
left: -500px;
background:
linear-gradient(45deg, #0f0f25 25%, #1a1a35 25%, #1a1a35 50%, #0f0f25 50%, #0f0f25 75%, #1a1a35 75%),
linear-gradient(45deg, #0f0f25 25%, #1a1a35 25%, #1a1a35 50%, #0f0f25 50%, #0f0f25 75%, #1a1a35 75%);
background-size: 100px 100px, 100px 100px;
background-position: 0 0, 50px 50px;
}
#player {
position: absolute;
width: 32px;
height: 32px;
z-index: 10;
transition: all 0.15s ease;
filter: drop-shadow(0 0 5px rgba(100, 255, 100, 0.7));
}
.player-sprite {
width: 100%;
height: 100%;
background: #70c070;
position: relative;
border-radius: 4px;
box-shadow:
inset 0 -6px 0 #508050,
inset 0 6px 0 #90e090,
0 0 0 2px #406040;
animation: player-idle 2s infinite ease-in-out;
}
.player-sprite::before {
content: '';
position: absolute;
top: 6px;
left: 6px;
width: 20px;
height: 20px;
background: #406040;
border-radius: 2px;
box-shadow:
inset 0 -3px 0 #304030,
inset 0 3px 0 #508050;
}
.player-sprite::after {
content: '';
position: absolute;
top: 10px;
left: 10px;
width: 12px;
height: 12px;
background: #a0f0a0;
border-radius: 1px;
box-shadow: 0 0 5px #a0f0a0;
animation: player-glow 2s infinite alternate;
}
.enemy {
position: absolute;
width: 32px;
height: 32px;
z-index: 9;
animation: float 3s infinite ease-in-out;
filter: drop-shadow(0 0 5px rgba(255, 100, 100, 0.7));
}
.enemy-sprite {
width: 100%;
height: 100%;
background: #c07070;
position: relative;
border-radius: 50%;
box-shadow:
inset 0 -6px 0 #805050,
inset 0 6px 0 #e09090,
0 0 0 2px #604040;
}
.enemy-sprite::before {
content: '';
position: absolute;
top: 8px;
left: 8px;
width: 16px;
height: 16px;
background: #604040;
border-radius: 50%;
box-shadow:
inset 0 -3px 0 #503030,
inset 0 3px 0 #705050;
}
.enemy-sprite::after {
content: '';
position: absolute;
top: 10px;
left: 10px;
width: 12px;
height: 12px;
background: #f0a0a0;
border-radius: 50%;
box-shadow: 0 0 5px #f0a0a0;
animation: enemy-glow 2s infinite alternate;
}
.portal {
position: absolute;
width: 48px;
height: 48px;
z-index: 8;
animation: portal-spin 4s infinite linear;
filter: drop-shadow(0 0 10px rgba(200, 100, 255, 0.8));
}
.portal-inner {
width: 100%;
height: 100%;
background:
radial-gradient(circle at 30% 30%, rgba(180, 100, 255, 0.9) 0%, transparent 50%),
radial-gradient(circle at 70% 70%, rgba(120, 80, 220, 0.9) 0%, transparent 50%);
border-radius: 50%;
position: relative;
box-shadow:
inset 0 0 15px rgba(150, 80, 255, 0.8),
0 0 15px rgba(150, 80, 255, 0.8);
}
.portal-inner::before {
content: '';
position: absolute;
top: 6px;
left: 6px;
right: 6px;
bottom: 6px;
border: 2px dashed rgba(200, 150, 255, 0.7);
border-radius: 50%;
animation: portal-pulse 2s infinite alternate;
}
.npc {
position: absolute;
width: 32px;
height: 32px;
z-index: 8;
animation: npc-bob 3s infinite ease-in-out;
filter: drop-shadow(0 0 5px rgba(100, 150, 255, 0.7));
}
.npc-sprite {
width: 100%;
height: 100%;
background: #7070c0;
position: relative;
border-radius: 4px;
box-shadow:
inset 0 -6px 0 #505080,
inset 0 6px 0 #9090e0,
0 0 0 2px #404060;
}
.npc-sprite::before {
content: '';
position: absolute;
top: 6px;
left: 6px;
width: 20px;
height: 20px;
background: #404060;
border-radius: 2px;
box-shadow:
inset 0 -3px 0 #303050,
inset 0 3px 0 #505080;
}
.npc-sprite::after {
content: '?';
position: absolute;
top: 8px;
left: 8px;
width: 16px;
height: 16px;
color: #b0b0ff;
font-size: 10px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
}
.environment {
position: absolute;
z-index: 7;
}
.tree {
width: 32px;
height: 48px;
background: #406040;
border-radius: 2px 2px 8px 8px;
box-shadow:
inset 0 -8px 0 #305030,
0 0 0 2px #304030;
}
.tree::before {
content: '';
position: absolute;
top: -20px;
left: -8px;
width: 48px;
height: 32px;
background: #308030;
border-radius: 50%;
box-shadow:
inset 0 -6px 0 #206020,
0 0 0 2px #205020;
}
.rock {
width: 24px;
height: 24px;
background: #606060;
border-radius: 4px;
box-shadow:
inset 0 -4px 0 #505050,
0 0 0 2px #404040;
}
.building {
width: 64px;
height: 48px;
background: #705050;
border-radius: 4px;
box-shadow:
inset 0 -8px 0 #604040,
0 0 0 2px #503030;
}
.building::before {
content: '';
position: absolute;
top: 8px;
left: 8px;
width: 16px;
height: 24px;
background: #403020;
border-radius: 2px;
}
.building::after {
content: '';
position: absolute;
top: 8px;
right: 8px;
width: 16px;
height: 24px;
background: #403020;
border-radius: 2px;
}
#ui-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 120px;
background: rgba(10, 10, 30, 0.85);
border-top: 3px solid #4a3a8d;
display: flex;
z-index: 20;
box-shadow: 0 -5px 15px rgba(0, 0, 0, 0.7);
}
#text-box {
flex: 3;
padding: 12px;
font-size: 14px;
line-height: 1.4;
overflow-y: auto;
border-right: 2px solid #3a2a7d;
}
#stats-panel {
flex: 1;
padding: 12px;
background: rgba(20, 15, 40, 0.8);
display: flex;
flex-direction: column;
justify-content: space-between;
}
.stat-bar {
height: 16px;
background: rgba(40, 40, 80, 0.7);
border-radius: 8px;
overflow: hidden;
margin-bottom: 6px;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.5);
}
.stat-fill {
height: 100%;
border-radius: 8px;
transition: width 0.5s ease;
}
.hp-fill {
background: linear-gradient(90deg, #c05050, #ff7070);
box-shadow: 0 0 5px #ff7070;
}
.mp-fill {
background: linear-gradient(90deg, #5050c0, #7070ff);
box-shadow: 0 0 5px #7070ff;
}
.stat-label {
font-size: 10px;
margin-bottom: 2px;
color: #b0b0ff;
}
#timeline-display {
padding: 6px;
background: rgba(30, 20, 60, 0.8);
border-radius: 4px;
text-align: center;
font-size: 12px;
margin-top: 8px;
box-shadow: 0 0 5px rgba(100, 70, 200, 0.5);
}
#combat-menu {
position: absolute;
bottom: 130px;
left: 50%;
transform: translateX(-50%);
display: none;
background: rgba(20, 15, 40, 0.95);
border: 2px solid #5a4a9d;
border-radius: 6px;
padding: 8px;
z-index: 25;
box-shadow: 0 0 15px rgba(100, 70, 200, 0.7);
}
.combat-option {
padding: 10px 16px;
margin: 6px;
background: linear-gradient(135deg, #4a3a8d, #3a2a7d);
border: 2px solid #6a5aad;
border-radius: 4px;
color: #e0e0ff;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
min-width: 100px;
box-shadow: 0 3px 0 #2a1a5d;
}
.combat-option:hover {
background: linear-gradient(135deg, #5a4a9d, #4a3a8d);
transform: translateY(2px);
box-shadow: 0 1px 0 #2a1a5d;
}
.combat-option:active {
transform: translateY(3px);
box-shadow: 0 0 0 #2a1a5d;
}
/* Mobile Controls */
#mobile-controls {
position: absolute;
bottom: 130px;
left: 20px;
display: none;
z-index: 30;
}
#d-pad {
display: grid;
grid-template-areas:
". up ."
"left center right"
". down .";
gap: 8px;
background: rgba(30, 20, 60, 0.7);
border-radius: 50%;
padding: 15px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.d-pad-btn {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #4a3a8d, #3a2a7d);
border: 2px solid #6a5aad;
border-radius: 8px;
color: #e0e0ff;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: bold;
box-shadow: 0 3px 0 #2a1a5d;
touch-action: manipulation;
}
.d-pad-btn:active {
transform: translateY(3px);
box-shadow: 0 0 0 #2a1a5d;
}
#btn-up { grid-area: up; }
#btn-left { grid-area: left; }
#btn-right { grid-area: right; }
#btn-down { grid-area: down; }
#action-buttons {
position: absolute;
bottom: 130px;
right: 20px;
display: none;
flex-direction: column;
gap: 10px;
z-index: 30;
}
.action-btn {
width: 60px;
height: 60px;
background: linear-gradient(135deg, #4a3a8d, #3a2a7d);
border: 2px solid #6a5aad;
border-radius: 50%;
color: #e0e0ff;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
box-shadow: 0 4px 0 #2a1a5d;
touch-action: manipulation;
text-align: center;
line-height: 1.2;
}
.action-btn:active {
transform: translateY(4px);
box-shadow: 0 0 0 #2a1a5d;
}
#btn-interact::after {
content: "INTERACT";
}
#btn-menu::after {
content: "MENU";
}
#main-menu {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 30% 40%, rgba(40, 20, 80, 0.9) 0%, rgba(10, 5, 30, 0.95) 70%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 30;
}
.menu-title {
font-size: 42px;
color: #d0b0ff;
text-shadow:
0 0 8px #a080ff,
0 0 16px #8060ff,
0 0 24px #6040ff;
margin-bottom: 30px;
letter-spacing: 3px;
}
.menu-option {
padding: 12px 24px;
margin: 12px;
background: linear-gradient(135deg, #4a3a8d, #3a2a7d);
border: 2px solid #6a5aad;
border-radius: 6px;
color: #e0e0ff;
font-size: 18px;
cursor: pointer;
transition: all 0.2s ease;
min-width: 180px;
text-align: center;
box-shadow: 0 4px 0 #2a1a5d;
}
.menu-option:hover {
background: linear-gradient(135deg, #5a4a9d, #4a3a8d);
transform: translateY(2px);
box-shadow: 0 2px 0 #2a1a5d;
}
.menu-option:active {
transform: translateY(4px);
box-shadow: 0 0 0 #2a1a5d;
}
.damage-popup {
position: absolute;
color: #ff6060;
font-size: 16px;
font-weight: bold;
text-shadow: 0 0 5px #ff0000;
z-index: 15;
animation: popup 1.5s ease-out forwards;
}
.heal-popup {
position: absolute;
color: #60ff60;
font-size: 16px;
font-weight: bold;
text-shadow: 0 0 5px #00ff00;
z-index: 15;
animation: popup 1.5s ease-out forwards;
}
#mini-map {
position: absolute;
top: 10px;
right: 10px;
width: 120px;
height: 120px;
background: rgba(10, 10, 30, 0.7);
border: 2px solid #4a3a8d;
border-radius: 4px;
z-index: 15;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.scanlines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
to bottom,
transparent 50%,
rgba(0, 0, 0, 0.1) 50%
);
background-size: 100% 4px;
z-index: 5;
pointer-events: none;
}
.vignette {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: inset 0 0 80px rgba(0, 0, 0, 0.6);
z-index: 6;
pointer-events: none;
}
.path-marker {
position: absolute;
width: 8px;
height: 8px;
background: rgba(100, 255, 100, 0.5);
border-radius: 50%;
z-index: 4;
pointer-events: none;
}
@keyframes player-idle {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-2px); }
}
@keyframes player-glow {
0% { opacity: 0.7; }
100% { opacity: 1; }
}
@keyframes enemy-glow {
0% { opacity: 0.7; }
100% { opacity: 1; }
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@keyframes portal-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes portal-pulse {
0% { transform: scale(1); opacity: 0.7; }
100% { transform: scale(1.1); opacity: 1; }
}
@keyframes npc-bob {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
@keyframes popup {
0% { transform: translateY(0); opacity: 1; }
100% { transform: translateY(-40px); opacity: 0; }
}
/* Mobile detection */
@media (max-width: 768px), (pointer: coarse) {
#mobile-controls, #action-buttons {
display: flex;
}
#game-container {
width: 100vw;
height: 100vh;
border: none;
border-radius: 0;
}
}
</style>
</head>
<body>
<div id="game-container">
<div id="game-screen">
<div id="world-map"></div>
<div id="player">
<div class="player-sprite"></div>
</div>
<div id="mini-map"></div>
<div class="scanlines"></div>
<div class="vignette"></div>
</div>
<!-- Mobile Controls -->
<div id="mobile-controls">
<div id="d-pad">
<div class="d-pad-btn" id="btn-up"></div>
<div class="d-pad-btn" id="btn-left"></div>
<div class="d-pad-btn" id="btn-right"></div>
<div class="d-pad-btn" id="btn-down"></div>
</div>
</div>
<div id="action-buttons">
<div class="action-btn" id="btn-interact"></div>
<div class="action-btn" id="btn-menu"></div>
</div>
<div id="ui-container">
<div id="text-box">
<p>FRACTAL REALMS - Reality is collapsing...</p>
<p>Find the Codex Crystals to stabilize the timelines!</p>
</div>
<div id="stats-panel">
<div>
<div class="stat-label">HP</div>
<div class="stat-bar">
<div class="stat-fill hp-fill" style="width: 100%"></div>
</div>
<div class="stat-label">MP</div>
<div class="stat-bar">
<div class="stat-fill mp-fill" style="width: 100%"></div>
</div>
</div>
<div id="timeline-display">ATLAS TIMELINE</div>
</div>
</div>
<div id="combat-menu">
<div class="combat-option" onclick="playerAttack()">ATTACK</div>
<div class="combat-option" onclick="playerSpecial()">SPECIAL</div>
<div class="combat-option" onclick="playerItem()">ITEM</div>
<div class="combat-option" onclick="playerFlee()">FLEE</div>
</div>
<div id="main-menu">
<h1 class="menu-title">FRACTAL REALMS</h1>
<div class="menu-option" onclick="startGame()">NEW GAME</div>
<div class="menu-option" onclick="loadGame()">CONTINUE</div>
<div class="menu-option" onclick="showControls()">CONTROLS</div>
</div>
</div>
<script>
// Game State
const gameState = {
scene: "menu",
player: {
x: 400,
y: 300,
hp: 100,
maxHp: 100,
mp: 50,
maxMp: 50,
atk: 10,
def: 5,
level: 1,
exp: 0,
items: ["Potion", "Ether", "Codex Crystal"],
timeline: "Atlas",
quests: [
{ id: 1, name: "Find the First Crystal", completed: false },
{ id: 2, name: "Defeat 5 Spore Creatures", completed: false, progress: 0, target: 5 }
]
},
enemies: [],
portals: [],
npcs: [],
environment: [],
inCombat: false,
currentEnemy: null,
timelines: [
{ name: "Atlas", color: "#7050ff" },
{ name: "Alkali", color: "#50ff70" },
{ name: "Aether", color: "#ff9050" },
{ name: "Deep Space", color: "#9050ff" }
],
currentTimeline: 0,
mapOffset: { x: 0, y: 0 },
isMoving: false,
moveTarget: null,
pathMarkers: []
};
// DOM Elements
const gameScreen = document.getElementById("game-screen");
const worldMap = document.getElementById("world-map");
const player = document.getElementById("player");
const textBox = document.getElementById("text-box");
const combatMenu = document.getElementById("combat-menu");
const mainMenu = document.getElementById("main-menu");
const hpFill = document.querySelector(".hp-fill");
const mpFill = document.querySelector(".mp-fill");
const timelineDisplay = document.getElementById("timeline-display");
const miniMap = document.getElementById("mini-map");
// Mobile control elements
const mobileControls = document.getElementById("mobile-controls");
const actionButtons = document.getElementById("action-buttons");
const btnUp = document.getElementById("btn-up");
const btnLeft = document.getElementById("btn-left");
const btnRight = document.getElementById("btn-right");
const btnDown = document.getElementById("btn-down");
const btnInteract = document.getElementById("btn-interact");
const btnMenu = document.getElementById("btn-menu");
// Initialize Game
function initGame() {
mainMenu.style.display = "flex";
gameState.scene = "menu";
generateWorld();
setupMobileControls();
// Check if mobile device
if ('ontouchstart' in window || navigator.maxTouchPoints) {
mobileControls.style.display = "flex";
actionButtons.style.display = "flex";
}
}
// Setup Mobile Controls
function setupMobileControls() {
// D-pad controls
btnUp.addEventListener('touchstart', () => movePlayer(0, -1));
btnUp.addEventListener('touchend', stopMovement);
btnLeft.addEventListener('touchstart', () => movePlayer(-1, 0));
btnLeft.addEventListener('touchend', stopMovement);
btnRight.addEventListener('touchstart', () => movePlayer(1, 0));
btnRight.addEventListener('touchend', stopMovement);
btnDown.addEventListener('touchstart', () => movePlayer(0, 1));
btnDown.addEventListener('touchend', stopMovement);
// Action buttons
btnInteract.addEventListener('touchstart', handleInteract);
btnMenu.addEventListener('touchstart', toggleMenu);
// Click to move
gameScreen.addEventListener('click', handleClickToMove);
}
// Generate World
function generateWorld() {
// Clear existing entities
gameState.enemies = [];
gameState.portals = [];
gameState.npcs = [];
gameState.environment = [];
// Remove existing entities from DOM
document.querySelectorAll('.enemy, .portal, .npc, .environment, .path-marker').forEach(el => el.remove());
// Add environment objects
for (let i = 0; i < 20; i++) {
spawnEnvironment();
}
// Add enemies
for (let i = 0; i < 8; i++) {
spawnEnemy();
}
// Add portals
for (let i = 0; i < 4; i++) {
spawnPortal();
}
// Add NPCs
for (let i = 0; i < 3; i++) {
spawnNPC();
}
updateMiniMap();
}
// Spawn Environment Objects
function spawnEnvironment() {
const types = ['tree', 'rock', 'building'];
const type = types[Math.floor(Math.random() * types.length)];
const env = document.createElement("div");
env.className = `environment ${type}`;
const x = Math.floor(Math.random() * 1400) + 100;
const y = Math.floor(Math.random() * 1400) + 100;
env.style.left = `${x}px`;
env.style.top = `${y}px`;
const envSprite = document.createElement("div");
envSprite.className = type;
env.appendChild(envSprite);
gameScreen.appendChild(env);
gameState.environment.push({
element: env,
x: x,
y: y,
type: type
});
}
// Spawn Enemy
function spawnEnemy() {
const enemy = document.createElement("div");
enemy.className = "enemy";
const x = Math.floor(Math.random() * 1400) + 100;
const y = Math.floor(Math.random() * 1400) + 100;
enemy.style.left = `${x}px`;
enemy.style.top = `${y}px`;
const enemySprite = document.createElement("div");
enemySprite.className = "enemy-sprite";
enemy.appendChild(enemySprite);
gameScreen.appendChild(enemy);
gameState.enemies.push({
element: enemy,
x: x,
y: y,
hp: 30,
maxHp: 30,
type: "Spore"
});
}
// Spawn Portal
function spawnPortal() {
const portal = document.createElement("div");
portal.className = "portal";
const x = Math.floor(Math.random() * 1400) + 100;
const y = Math.floor(Math.random() * 1400) + 100;
portal.style.left = `${x}px`;
portal.style.top = `${y}px`;
const portalInner = document.createElement("div");
portalInner.className = "portal-inner";
portal.appendChild(portalInner);
gameScreen.appendChild(portal);
gameState.portals.push({
element: portal,
x: x,
y: y
});
}
// Spawn NPC
function spawnNPC() {
const npc = document.createElement("div");
npc.className = "npc";
const x = Math.floor(Math.random() * 1400) + 100;
const y = Math.floor(Math.random() * 1400) + 100;
npc.style.left = `${x}px`;
npc.style.top = `${y}px`;
const npcSprite = document.createElement("div");
npcSprite.className = "npc-sprite";
npc.appendChild(npcSprite);
gameScreen.appendChild(npc);
gameState.npcs.push({
element: npc,
x: x,
y: y,
name: ["Mason", "Nyx", "Check"][Math.floor(Math.random() * 3)],
dialog: [
"The timelines are collapsing! Find the Codex Crystals!",
"Beware of the Conglomerate - they feed on stories!",
"I've seen reality glitch. It's happening more often."
][Math.floor(Math.random() * 3)],
quest: Math.random() > 0.5 ? {
id: 3,
name: "Find the Lost Artifact",
completed: false
} : null
});
}
// Start Game
function startGame() {
mainMenu.style.display = "none";
gameState.scene = "explore";
updatePlayerPosition();
textBox.innerHTML = "<p>You awaken in the Atlas timeline. Reality feels... thin.</p><p>Find portals to travel between dimensions!</p>";
// Start game loop for smooth movement
requestAnimationFrame(gameLoop);
}
// Game Loop for Smooth Movement
function gameLoop() {
if (gameState.scene === "explore" && !gameState.inCombat) {
// Handle continuous movement for mobile
if (gameState.isMoving) {
movePlayer(gameState.moveTarget.x, gameState.moveTarget.y);
}
// Handle pathfinding movement
if (gameState.pathMarkers.length > 0) {
followPath();
}
}
requestAnimationFrame(gameLoop);
}
// Mobile Movement
function movePlayer(dx, dy) {
if (gameState.inCombat) return;
const speed = 5;
gameState.player.x = Math.max(20, Math.min(1580, gameState.player.x + dx * speed));
gameState.player.y = Math.max(20, Math.min(1580, gameState.player.y + dy * speed));
updatePlayerPosition();
checkInteractions();
}
// Stop Movement
function stopMovement() {
gameState.isMoving = false;
gameState.moveTarget = null;
}
// Click to Move with Pathfinding
function handleClickToMove(e) {
if (gameState.inCombat) return;
const rect = gameScreen.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const clickY = e.clientY - rect.top;
// Convert screen coordinates to game world coordinates
const worldX = clickX - gameState.mapOffset.x;
const worldY = clickY - gameState.mapOffset.y;
// Simple pathfinding - create a direct path with intermediate points
createPath(gameState.player.x, gameState.player.y, worldX, worldY);
}
// Create Path with Intermediate Points
function createPath(startX, startY, endX, endY) {
// Clear existing path
gameState.pathMarkers.forEach(marker => {
if (marker.element && marker.element.parentNode) {
marker.element.parentNode.removeChild(marker.element);
}
});
gameState.pathMarkers = [];
const steps = 10;
for (let i = 1; i <= steps; i++) {
const t = i / steps;
const x = startX + (endX - startX) * t;
const y = startY + (endY - startY) * t;
const marker = document.createElement("div");
marker.className = "path-marker";
marker.style.left = `${x}px`;
marker.style.top = `${y}px`;
gameScreen.appendChild(marker);
gameState.pathMarkers.push({
element: marker,
x: x,
y: y
});
}
}
// Follow Path
function followPath() {
if (gameState.pathMarkers.length === 0) return;
const nextPoint = gameState.pathMarkers[0];
const dx = nextPoint.x - gameState.player.x;
const dy = nextPoint.y - gameState.player.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
// Reached point, remove it
if (nextPoint.element && nextPoint.element.parentNode) {
nextPoint.element.parentNode.removeChild(nextPoint.element);
}
gameState.pathMarkers.shift();
} else {
// Move toward point
const speed = 5;
gameState.player.x += (dx / distance) * speed;
gameState.player.y += (dy / distance) * speed;
updatePlayerPosition();
checkInteractions();
}
}
// Handle Interaction
function handleInteract() {
checkInteractions(true);
}
// Check for Interactions
function checkInteractions(forceInteract = false) {
// Check for enemy collision
for (const enemy of gameState.enemies) {
const dx = Math.abs(gameState.player.x - enemy.x);
const dy = Math.abs(gameState.player.y - enemy.y);
if (dx < 30 && dy < 30) {
startCombat(enemy);
return;
}
}
// Check for portal collision
for (const portal of gameState.portals) {
const dx = Math.abs(gameState.player.x - portal.x);
const dy = Math.abs(gameState.player.y - portal.y);
if (dx < 40 && dy < 40) {
enterPortal(portal);
return;
}
}
// Check for NPC interaction
for (const npc of gameState.npcs) {
const dx = Math.abs(gameState.player.x - npc.x);
const dy = Math.abs(gameState.player.y - npc.y);
if ((dx < 40 && dy < 40) || forceInteract) {
talkToNPC(npc);
return;
}
}
}
// Toggle Menu
function toggleMenu() {
if (gameState.scene === "explore") {
textBox.innerHTML = `
<p>GAME MENU</p>
<p>Quests:</p>
<ul>
${gameState.player.quests.map(q =>
`<li>${q.name} ${q.completed ? '✓' : q.progress ? `(${q.progress}/${q.target})` : ''}</li>`
).join('')}
</ul>
<p>Inventory: ${gameState.player.items.join(', ')}</p>
`;
}
}
// Update Player Position
function updatePlayerPosition() {
player.style.left = `${gameState.player.x - 16}px`;
player.style.top = `${gameState.player.y - 16}px`;
// Update map position (simple parallax)
const mapX = -gameState.player.x + 400;
const mapY = -gameState.player.y + 300;
worldMap.style.transform = `translate(${mapX}px, ${mapY}px)`;
updateMiniMap();
}
// Update Mini Map
function updateMiniMap() {
miniMap.innerHTML = '';
// Draw player on mini map
const playerDot = document.createElement("div");
playerDot.style.position = "absolute";
playerDot.style.width = "4px";
playerDot.style.height = "4px";
playerDot.style.background = "#70c070";
playerDot.style.borderRadius = "50%";
playerDot.style.left = "50%";
playerDot.style.top = "50%";
playerDot.style.transform = "translate(-50%, -50%)";
miniMap.appendChild(playerDot);
// Draw enemies on mini map
gameState.enemies.forEach(enemy => {
const enemyDot = document.createElement("div");
enemyDot.style.position = "absolute";
enemyDot.style.width = "3px";
enemyDot.style.height = "3px";
enemyDot.style.background = "#c07070";
enemyDot.style.borderRadius = "50%";
const relX = (enemy.x - gameState.player.x) / 16 + 60;
const relY = (enemy.y - gameState.player.y) / 16 + 60;
enemyDot.style.left = `${relX}px`;
enemyDot.style.top = `${relY}px`;
miniMap.appendChild(enemyDot);
});
// Draw portals on mini map
gameState.portals.forEach(portal => {
const portalDot = document.createElement("div");
portalDot.style.position = "absolute";
portalDot.style.width = "4px";
portalDot.style.height = "4px";
portalDot.style.background = "#a070ff";
portalDot.style.borderRadius = "50%";
const relX = (portal.x - gameState.player.x) / 16 + 60;
const relY = (portal.y - gameState.player.y) / 16 + 60;
portalDot.style.left = `${relX}px`;
portalDot.style.top = `${relY}px`;
miniMap.appendChild(portalDot);
});
}
// Enter Portal
function enterPortal(portal) {
gameState.currentTimeline = (gameState.currentTimeline + 1) % gameState.timelines.length;
const newTimeline = gameState.timelines[gameState.currentTimeline];
gameState.player.timeline = newTimeline.name;
timelineDisplay.textContent = `${newTimeline.name.toUpperCase()} TIMELINE`;
timelineDisplay.style.boxShadow = `0 0 8px ${newTimeline.color}`;
textBox.innerHTML = `<p>TIMESHIFT: You enter the ${newTimeline.name} timeline. Reality shimmers around you.</p>`;
// Clear path when changing timelines
gameState.pathMarkers.forEach(marker => {
if (marker.element && marker.element.parentNode) {
marker.element.parentNode.removeChild(marker.element);
}
});
gameState.pathMarkers = [];
// Remove the used portal
portal.element.style.display = "none";
gameState.portals = gameState.portals.filter(p => p !== portal);
// Add new portal after a delay
setTimeout(() => {
spawnPortal();
}, 3000);
}
// Talk to NPC
function talkToNPC(npc) {
textBox.innerHTML = `<p>${npc.name}: "${npc.dialog}"</p>`;
// Handle quests
if (npc.quest && !npc.quest.completed) {
const existingQuest = gameState.player.quests.find(q => q.id === npc.quest.id);
if (!existingQuest) {
gameState.player.quests.push({...npc.quest});
textBox.innerHTML += `<p>New Quest: ${npc.quest.name}</p>`;
}
}
// Sometimes NPCs give items
if (Math.random() > 0.7 && gameState.player.items.length < 5) {
const newItem = ["Potion", "Ether"][Math.floor(Math.random() * 2)];
gameState.player.items.push(newItem);
textBox.innerHTML += `<p>You received a ${newItem}!</p>`;
}
}
// Start Combat
function startCombat(enemy) {
gameState.inCombat = true;
gameState.currentEnemy = enemy;
combatMenu.style.display = "flex";
textBox.innerHTML = `<p>A ${enemy.type} attacks! Prepare for battle!</p>`;
// Clear path when combat starts
gameState.pathMarkers.forEach(marker => {
if (marker.element && marker.element.parentNode) {
marker.element.parentNode.removeChild(marker.element);
}
});
gameState.pathMarkers = [];
}
// Player Attack
function playerAttack() {
const damage = Math.floor(Math.random() * gameState.player.atk) + 5;
gameState.currentEnemy.hp -= damage;
// Create damage popup
const popup = document.createElement("div");
popup.className = "damage-popup";
popup.textContent = `-${damage}`;
popup.style.left = `${gameState.currentEnemy.x}px`;
popup.style.top = `${gameState.currentEnemy.y}px`;
gameScreen.appendChild(popup);
textBox.innerHTML = `<p>You attack for ${damage} damage!</p>`;
setTimeout(() => {
if (gameState.currentEnemy.hp <= 0) {
textBox.innerHTML += `<p>Enemy defeated! +10 EXP</p>`;
gameState.player.exp += 10;
// Update quest progress
const defeatQuest = gameState.player.quests.find(q => q.id === 2);
if (defeatQuest && !defeatQuest.completed) {
defeatQuest.progress++;
if (defeatQuest.progress >= defeatQuest.target) {
defeatQuest.completed = true;
textBox.innerHTML += `<p>Quest Complete: ${defeatQuest.name}!</p>`;
}
}
// Remove enemy
gameScreen.removeChild(gameState.currentEnemy.element);
gameState.enemies = gameState.enemies.filter(e => e !== gameState.currentEnemy);
// Check for level up
if (gameState.player.exp >= gameState.player.level * 20) {
levelUp();
}
gameState.inCombat = false;
combatMenu.style.display = "none";
gameState.currentEnemy = null;
// Spawn new enemy after a delay
setTimeout(spawnEnemy, 2000);
} else {
// Enemy attacks back
const enemyDamage = Math.floor(Math.random() * 8) + 2;
gameState.player.hp -= enemyDamage;
updateHPBar();
// Create damage popup on player
const playerPopup = document.createElement("div");
playerPopup.className = "damage-popup";
playerPopup.textContent = `-${enemyDamage}`;
playerPopup.style.left = `${gameState.player.x}px`;
playerPopup.style.top = `${gameState.player.y}px`;
gameScreen.appendChild(playerPopup);
textBox.innerHTML += `<p>The enemy retaliates! You take ${enemyDamage} damage!</p>`;
if (gameState.player.hp <= 0) {
textBox.innerHTML += `<p>YOU HAVE FALLEN IN BATTLE.</p>`;
setTimeout(resetGame, 2000);
}
}
}, 1000);
}
// Player Special
function playerSpecial() {
if (gameState.player.mp < 10) {
textBox.innerHTML = "<p>Not enough MP!</p>";
return;
}
gameState.player.mp -= 10;
updateMPBar();
const damage = Math.floor(Math.random() * 15) + 10;
gameState.currentEnemy.hp -= damage;
// Create damage popup
const popup = document.createElement("div");
popup.className = "damage-popup";
popup.textContent = `-${damage}`;
popup.style.left = `${gameState.currentEnemy.x}px`;
popup.style.top = `${gameState.currentEnemy.y}px`;
popup.style.color = "#70a0ff";
gameScreen.appendChild(popup);
textBox.innerHTML = `<p>You use CODE BLAST for ${damage} damage!</p>`;
setTimeout(() => {
textBox.innerHTML += `<p>Enemy defeated! +15 EXP</p>`;
gameState.player.exp += 15;
// Update quest progress
const defeatQuest = gameState.player.quests.find(q => q.id === 2);
if (defeatQuest && !defeatQuest.completed) {
defeatQuest.progress++;
if (defeatQuest.progress >= defeatQuest.target) {
defeatQuest.completed = true;
textBox.innerHTML += `<p>Quest Complete: ${defeatQuest.name}!</p>`;
}
}
// Remove enemy
gameScreen.removeChild(gameState.currentEnemy.element);
gameState.enemies = gameState.enemies.filter(e => e !== gameState.currentEnemy);
// Check for level up
if (gameState.player.exp >= gameState.player.level * 20) {
levelUp();
}
gameState.inCombat = false;
combatMenu.style.display = "none";
gameState.currentEnemy = null;
// Spawn new enemy after a delay
setTimeout(spawnEnemy, 2000);
}, 1000);
}
// Player Item
function playerItem() {
if (gameState.player.items.length === 0) {
textBox.innerHTML = "<p>No items left!</p>";
return;
}
const item = gameState.player.items.pop();
if (item === "Potion") {
gameState.player.hp = Math.min(gameState.player.hp + 30, gameState.player.maxHp);
updateHPBar();
// Create heal popup
const popup = document.createElement("div");
popup.className = "heal-popup";
popup.textContent = "+30 HP";
popup.style.left = `${gameState.player.x}px`;
popup.style.top = `${gameState.player.y}px`;
gameScreen.appendChild(popup);
textBox.innerHTML = `<p>Used POTION! HP: ${gameState.player.hp}/${gameState.player.maxHp}</p>`;
} else if (item === "Ether") {
gameState.player.mp = Math.min(gameState.player.mp + 20, gameState.player.maxMp);
updateMPBar();
// Create heal popup
const popup = document.createElement("div");
popup.className = "heal-popup";
popup.textContent = "+20 MP";
popup.style.left = `${gameState.player.x}px`;
popup.style.top = `${gameState.player.y}px`;
gameScreen.appendChild(popup);
textBox.innerHTML = `<p>Used ETHER! MP: ${gameState.player.mp}/${gameState.player.maxMp}</p>`;
} else if (item === "Codex Crystal") {
gameState.player.hp = gameState.player.maxHp;
gameState.player.mp = gameState.player.maxMp;
updateHPBar();
updateMPBar();
// Create heal popup
const popup = document.createElement("div");
popup.className = "heal-popup";
popup.textContent = "FULL RESTORE!";
popup.style.left = `${gameState.player.x}px`;
popup.style.top = `${gameState.player.y}px`;
gameScreen.appendChild(popup);
textBox.innerHTML = `<p>Used CODEX CRYSTAL! HP and MP fully restored!</p>`;
}
}
// Player Flee
function playerFlee() {
if (Math.random() > 0.5) {
textBox.innerHTML = "<p>You successfully escaped!</p>";
gameState.inCombat = false;
combatMenu.style.display = "none";
gameState.currentEnemy = null;
} else {
textBox.innerHTML = "<p>Escape failed!</p>";
const enemyDamage = Math.floor(Math.random() * 8) + 2;
gameState.player.hp -= enemyDamage;
updateHPBar();
// Create damage popup on player
const playerPopup = document.createElement("div");
playerPopup.className = "damage-popup";
playerPopup.textContent = `-${enemyDamage}`;
playerPopup.style.left = `${gameState.player.x}px`;
playerPopup.style.top = `${gameState.player.y}px`;
gameScreen.appendChild(playerPopup);
textBox.innerHTML += `<p>You take ${enemyDamage} damage!</p>`;
if (gameState.player.hp <= 0) {
textBox.innerHTML += `<p>YOU HAVE FALLEN IN BATTLE.</p>`;
setTimeout(resetGame, 2000);
}
}
}
// Level Up
function levelUp() {
gameState.player.level++;
gameState.player.maxHp += 20;
gameState.player.maxMp += 10;
gameState.player.atk += 3;
gameState.player.def += 2;
gameState.player.hp = gameState.player.maxHp;
gameState.player.mp = gameState.player.maxMp;
updateHPBar();
updateMPBar();
textBox.innerHTML = `<p>LEVEL UP! You are now Level ${gameState.player.level}!</p>`;
}
// Update HP Bar
function updateHPBar() {
const hpPercent = (gameState.player.hp / gameState.player.maxHp) * 100;
hpFill.style.width = `${hpPercent}%`;
}
// Update MP Bar
function updateMPBar() {
const mpPercent = (gameState.player.mp / gameState.player.maxMp) * 100;
mpFill.style.width = `${mpPercent}%`;
}
// Reset Game
function resetGame() {
gameState.player.hp = gameState.player.maxHp;
gameState.player.mp = gameState.player.maxMp;
gameState.inCombat = false;
combatMenu.style.display = "none";
if (gameState.currentEnemy) {
gameScreen.removeChild(gameState.currentEnemy.element);
gameState.currentEnemy = null;
}
updateHPBar();
updateMPBar();
generateWorld();
}
// Show Controls
function showControls() {
textBox.innerHTML = `
<p><strong>CONTROLS:</strong></p>
<p>Arrow Keys - Move (Desktop)</p>
<p>D-Pad - Move (Mobile)</p>
<p>Click/Tap - Move to location</p>
<p>Space/Interact Button - Interact with objects/NPCs</p>
<p>Find portals to travel between timelines!</p>
`;
}
// Keyboard Controls (for desktop)
document.addEventListener("keydown", (e) => {
if (gameState.scene !== "explore" || gameState.inCombat) return;
const speed = 8;
let moved = false;
switch (e.key) {
case "ArrowUp":
gameState.player.y = Math.max(20, gameState.player.y - speed);
moved = true;
break;
case "ArrowDown":
gameState.player.y = Math.min(1580, gameState.player.y + speed);
moved = true;
break;
case "ArrowLeft":
gameState.player.x = Math.max(20, gameState.player.x - speed);
moved = true;
break;
case "ArrowRight":
gameState.player.x = Math.min(1580, gameState.player.x + speed);
moved = true;
break;
case " ":
checkInteractions(true);
break;
case "Escape":
toggleMenu();
break;
}
if (moved) {
updatePlayerPosition();
checkInteractions();
}
});
// Start Game
window.onload = initGame;
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment