Diff
checker
Text
Text
Images
Documents
Excel
Folders
Legal
Enterprise
Desktop
Pricing
Sign in
Download Diffchecker Desktop
Compare text
Find the difference between two text files
Tools
History
Real-time editor
Hide unchanged lines
Disable line wrap
Layout
Split
Unified
Diff precision
Smart
Word
Char
Syntax highlighting
Choose syntax
Ignore
Transform text
Go to first change
Edit input
Diffchecker Desktop
The most secure way to run Diffchecker. Get the Diffchecker Desktop app: your diffs never leave your computer!
Get Desktop
abyssalfarm.html
Created
15 hours ago
Diff never expires
Clear
Export
Share
Explain
1 removal
Lines
Total
Removed
Characters
Total
Removed
To continue using this feature, upgrade to
Diff
checker
Pro
View Pricing
1 line
Copy
929 additions
Lines
Total
Added
Characters
Total
Added
To continue using this feature, upgrade to
Diff
checker
Pro
View Pricing
929 lines
Copy
Copy
Copied
Copy
Copied
1
<!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>ABYSSAL FARM — Void Breeder</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
:root {
--bg: #0a0a0f;
--panel: #12121a;
--panel2: #1a1a25;
--accent: #8b0000;
--accent2: #4a0080;
--accent3: #1a5c1a;
--text: #c0c0c0;
--text-dim: #555;
--border: #2a2a35;
--glow-red: 0 0 8px #8b0000;
--glow-purple: 0 0 8px #4a0080;
--glow-green: 0 0 8px #1a5c1a;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body {
margin: 0; padding: 0;
background: var(--bg);
color: var(--text);
font-family: 'VT323', monospace;
font-size: 18px;
overflow: hidden;
height: 100vh;
width: 100vw;
touch-action: manipulation;
}
/* CRT overlay */
body::before {
content: "";
position: fixed; top:0; left:0; right:0; bottom:0;
background: repeating-linear-gradient(
0deg,
rgba(0,0,0,0.15),
rgba(0,0,0,0.15) 1px,
transparent 1px,
transparent 2px
);
pointer-events: none;
z-index: 9999;
}
body::after {
content: "";
position: fixed; top:0; left:0; right:0; bottom:0;
background: radial-gradient(circle, transparent 60%, rgba(0,0,0,0.6) 100%);
pointer-events: none;
z-index: 9998;
}
#app {
display: flex;
flex-direction: column;
height: 100vh;
max-width: 600px;
margin: 0 auto;
position: relative;
z-index: 1;
}
/* Header */
.header {
background: var(--panel);
border-bottom: 2px solid var(--border);
padding: 8px 12px;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
}
.header h1 {
margin: 0;
font-size: 1.4rem;
color: var(--accent);
text-shadow: var(--glow-red);
letter-spacing: 2px;
}
.resources {
display: flex;
gap: 12px;
font-size: 0.95rem;
}
.res-item {
display: flex;
align-items: center;
gap: 4px;
}
.res-icon {
width: 14px; height: 14px;
display: inline-block;
}
/* Tabs */
.tabs {
display: flex;
background: var(--panel);
border-bottom: 2px solid var(--border);
flex-shrink: 0;
}
.tab {
flex: 1;
padding: 10px;
text-align: center;
cursor: pointer;
border: none;
background: transparent;
color: var(--text-dim);
font-family: 'VT323', monospace;
font-size: 1rem;
border-bottom: 3px solid transparent;
transition: all 0.2s;
}
.tab.active {
color: var(--text);
border-bottom-color: var(--accent);
background: var(--panel2);
}
.tab:hover { color: var(--text); }
/* Content area */
.content {
flex: 1;
overflow-y: auto;
padding: 12px;
position: relative;
}
.content::-webkit-scrollbar { width: 6px; }
.content::-webkit-scrollbar-thumb { background: var(--accent); }
/* Monster Cards */
.monster-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px;
}
.monster-card {
background: var(--panel);
border: 2px solid var(--border);
border-radius: 4px;
padding: 8px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
position: relative;
overflow: hidden;
}
.monster-card:hover {
border-color: var(--accent);
box-shadow: var(--glow-red);
}
.monster-card.selected {
border-color: var(--accent2);
box-shadow: var(--glow-purple);
}
.monster-canvas {
width: 64px; height: 64px;
image-rendering: pixelated;
margin: 0 auto 6px;
display: block;
}
.monster-name {
font-size: 0.9rem;
color: var(--accent);
margin-bottom: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.monster-lvl {
font-size: 0.8rem;
color: var(--text-dim);
}
.monster-bar {
width: 100%; height: 4px;
background: #222;
margin-top: 4px;
border-radius: 2px;
overflow: hidden;
}
.monster-bar-fill {
height: 100%;
background: var(--accent);
transition: width 0.3s;
}
/* Hatch Button */
.hatch-area {
text-align: center;
padding: 20px;
margin-bottom: 16px;
}
.hatch-btn {
width: 120px; height: 120px;
border-radius: 50%;
border: 3px solid var(--accent);
background: var(--panel);
color: var(--accent);
font-family: 'VT323', monospace;
font-size: 1.2rem;
cursor: pointer;
position: relative;
overflow: hidden;
box-shadow: var(--glow-red);
transition: transform 0.1s;
display: inline-flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.hatch-btn:active { transform: scale(0.92); }
.hatch-btn .cost {
font-size: 0.8rem;
color: var(--text-dim);
margin-top: 4px;
}
.hatch-btn::before {
content: "";
position: absolute;
width: 80%; height: 80%;
border: 2px dashed var(--accent);
border-radius: 50%;
animation: spin 8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Upgrades / Automation */
.upgrade-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.upgrade-item {
background: var(--panel);
border: 2px solid var(--border);
border-radius: 4px;
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: all 0.2s;
}
.upgrade-item:hover { border-color: var(--accent2); }
.upgrade-item.disabled {
opacity: 0.4;
cursor: not-allowed;
}
.upgrade-info { flex: 1; }
.upgrade-name { font-size: 1rem; color: var(--accent2); }
.upgrade-desc { font-size: 0.8rem; color: var(--text-dim); }
.upgrade-cost {
background: var(--panel2);
padding: 4px 10px;
border-radius: 4px;
border: 1px solid var(--border);
font-size: 0.9rem;
white-space: nowrap;
}
/* Farm Zones */
.zone-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.zone-card {
background: var(--panel);
border: 2px solid var(--border);
border-radius: 4px;
padding: 12px;
cursor: pointer;
position: relative;
}
.zone-card:hover { border-color: var(--accent3); }
.zone-name { font-size: 1.1rem; color: var(--accent3); margin-bottom: 4px; }
.zone-desc { font-size: 0.85rem; color: var(--text-dim); }
.zone-reward { font-size: 0.9rem; color: var(--text); margin-top: 6px; }
.zone-danger {
position: absolute;
top: 8px; right: 8px;
font-size: 0.75rem;
color: var(--accent);
}
/* Detail Modal */
.modal-overlay {
position: fixed;
top:0; left:0; right:0; bottom:0;
background: rgba(0,0,0,0.85);
display: none;
align-items: center;
justify-content: center;
z-index: 10000;
padding: 20px;
}
.modal-overlay.active { display: flex; }
.modal {
background: var(--panel);
border: 2px solid var(--accent);
border-radius: 8px;
padding: 20px;
max-width: 400px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
position: relative;
}
.modal-close {
position: absolute;
top: 8px; right: 12px;
background: none;
border: none;
color: var(--text);
font-size: 1.5rem;
cursor: pointer;
font-family: 'VT323', monospace;
}
.modal-title {
font-size: 1.3rem;
color: var(--accent);
margin-bottom: 12px;
text-align: center;
}
.modal-canvas {
width: 96px; height: 96px;
image-rendering: pixelated;
margin: 0 auto 12px;
display: block;
}
.stat-row {
display: flex;
justify-content: space-between;
padding: 4px 0;
border-bottom: 1px solid var(--border);
font-size: 0.95rem;
}
.action-btn {
width: 100%;
padding: 10px;
margin-top: 10px;
border: 2px solid var(--accent);
background: var(--panel2);
color: var(--text);
font-family: 'VT323', monospace;
font-size: 1rem;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s;
}
.action-btn:hover { background: var(--accent); color: #fff; }
.action-btn:disabled { opacity: 0.3; cursor: not-allowed; }
/* Floating text */
.float-text {
position: absolute;
color: var(--accent);
font-size: 1rem;
pointer-events: none;
animation: floatUp 1s ease-out forwards;
z-index: 100;
}
@keyframes floatUp {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-40px); }
}
/* Log */
.log-area {
background: var(--panel);
border: 2px solid var(--border);
border-radius: 4px;
padding: 8px;
height: 120px;
overflow-y: auto;
font-size: 0.85rem;
color: var(--text-dim);
}
.log-entry { margin-bottom: 2px; }
.log-entry.new { color: var(--text); }
/* Glitch effect on title */
.glitch {
animation: glitch 3s infinite;
}
@keyframes glitch {
0%, 90%, 100% { text-shadow: var(--glow-red); }
92% { text-shadow: 2px 0 #0ff, -2px 0 #f0f; }
94% { text-shadow: -2px 0 #0ff, 2px 0 #f0f; }
96% { text-shadow: var(--glow-red); }
}
/* Notification toast */
.toast {
position: fixed;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
background: var(--panel);
border: 2px solid var(--accent);
padding: 10px 20px;
border-radius: 4px;
font-size: 1rem;
z-index: 10001;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
white-space: nowrap;
}
.toast.show { opacity: 1; }
/* Evolution tree */
.evo-tree {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin: 12px 0;
flex-wrap: wrap;
}
.evo-node {
width: 48px; height: 48px;
border: 2px solid var(--border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
color: var(--text-dim);
background: var(--panel2);
}
.evo-node.active {
border-color: var(--accent);
color: var(--accent);
box-shadow: var(--glow-red);
}
.evo-arrow { color: var(--text-dim); font-size: 1.2rem; }
/* Particle canvas */
#particleCanvas {
position: fixed;
top: 0; left: 0;
pointer-events: none;
z-index: 9997;
}
/* Responsive */
@media (max-width: 400px) {
.monster-grid { grid-template-columns: repeat(2, 1fr); }
.header h1 { font-size: 1.1rem; }
}
</style>
</head>
<body>
<canvas id="particleCanvas"></canvas>
<div id="app">
<div class="header">
<h1 class="glitch">ABYSSAL FARM</h1>
<div class="resources">
<div class="res-item"><span style="color:#8b0000">◆</span> <span id="resDread">0</span></div>
<div class="res-item"><span style="color:#4a0080">◆</span> <span id="resSouls">0</span></div>
<div class="res-item"><span style="color:#1a5c1a">◆</span> <span id="resShards">0</span></div>
</div>
</div>
<div class="tabs">
<button class="tab active" data-tab="hatch">HATCH</button>
<button class="tab" data-tab="monsters">MONSTERS</button>
<button class="tab" data-tab="upgrades">VOID SHOP</button>
<button class="tab" data-tab="zones">REALMS</button>
</div>
<div class="content" id="content">
<!-- Dynamic content -->
</div>
<div class="log-area" id="logArea">
<div class="log-entry">Welcome to the Abyss. Hatch your first abomination.</div>
</div>
</div>
<!-- Modal -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal" id="modal">
<button class="modal-close" onclick="closeModal()">×</button>
<div id="modalContent"></div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
// ==================== GAME STATE ====================
const state = {
dread: 0,
souls: 0,
shards: 0,
totalDread: 0,
monsters: [],
nextId: 1,
selectedMonster: null,
tab: 'hatch',
upgrades: {
extractor: { level: 0, cost: 50, costScale: 1.6, name: "Void Extractor", desc: "Auto-generates +1 Dread/sec", effect: 1 },
harvester: { level: 0, cost: 200, costScale: 1.7, name: "Soul Harvester", desc: "Auto-generates +0.5 Souls/sec", effect: 0.5 },
incubator: { level: 0, cost: 500, costScale: 1.8, name: "Corrupt Incubator", desc: "Monsters hatch 1 tier higher base", effect: 1 },
amplifier: { level: 0, cost: 1000, costScale: 1.9, name: "Dread Amplifier", desc: "All monster production ×1.5", effect: 1.5 },
voidgate: { level: 0, cost: 5000, costScale: 2.0, name: "Void Gate", desc: "Unlocks Realm farming", effect: 1 },
mutagen: { level: 0, cost: 10000, costScale: 2.2, name: "Mutagen Vat", desc: "Evolution cost reduced by 10%", effect: 0.9 },
},
zones: {
crypt: { unlocked: true, name: "Forgotten Crypt", desc: "Ancient burial grounds teeming with weak spirits", dreadRate: 5, soulRate: 0, danger: 0.05, reward: "+5 Dread/sec" },
swamp: { unlocked: false, name: "Rotting Swamp", desc: "Bubbling miasma corrupts everything it touches", dreadRate: 15, soulRate: 1, danger: 0.1, reward: "+15 Dread, +1 Soul/sec" },
abyss: { unlocked: false, name: "The Abyss", desc: "Stare into the void. It stares back.", dreadRate: 50, soulRate: 5, danger: 0.2, reward: "+50 Dread, +5 Souls/sec" },
nullspace: { unlocked: false, name: "Null Space", desc: "Reality unravels. Only the strongest survive.", dreadRate: 200, soulRate: 20, danger: 0.35, reward: "+200 Dread, +20 Souls/sec" },
},
activeZone: null,
zoneProgress: 0,
zoneHealth: 100,
};
// ==================== MONSTER DATA ====================
const MONSTER_TYPES = [
// Tier 0 - Larvae
{ tier: 0, name: "Grubspawn", hp: 10, atk: 1, def: 0, spd: 1, prod: 0.5, evo: "Mawling", palette: [[0,80,0],[40,120,40],[20,60,20]] },
{ tier: 0, name: "Boneling", hp: 8, atk: 2, def: 0, spd: 2, prod: 0.4, evo: "Ribcage", palette: [[200,200,200],[150,150,150],[100,100,100]] },
{ tier: 0, name: "Sludge", hp: 15, atk: 1, def: 1, spd: 0, prod: 0.6, evo: "Muckfiend", palette: [[60,40,20],[100,80,40],[80,60,30]] },
// Tier 1 - Juveniles
{ tier: 1, name: "Mawling", hp: 30, atk: 4, def: 1, spd: 3, prod: 1.5, evo: "Goremaw", palette: [[120,0,0],[180,20,20],[80,0,0]] },
{ tier: 1, name: "Ribcage", hp: 25, atk: 5, def: 2, spd: 4, prod: 1.2, evo: "Ossuary", palette: [[220,220,220],[180,180,180],[120,120,120]] },
{ tier: 1, name: "Muckfiend", hp: 40, atk: 3, def: 3, spd: 1, prod: 1.8, evo: "Rotlord", palette: [[80,60,30],[120,100,50],[60,40,10]] },
{ tier: 1, name: "Shadelet", hp: 20, atk: 6, def: 0, spd: 5, prod: 1.0, evo: "Dreadwraith", palette: [[40,0,60],[80,20,100],[20,0,40]] },
// Tier 2 - Adults
{ tier: 2, name: "Goremaw", hp: 80, atk: 10, def: 3, spd: 5, prod: 4.0, evo: "Fleshbeast", palette: [[160,0,0],[220,40,40],[100,0,0]] },
{ tier: 2, name: "Ossuary", hp: 70, atk: 12, def: 5, spd: 6, prod: 3.5, evo: "Catacomb", palette: [[240,240,240],[200,200,200],[140,140,140]] },
{ tier: 2, name: "Rotlord", hp: 100, atk: 8, def: 6, spd: 2, prod: 5.0, evo: "Plaguefather", palette: [[100,80,40],[140,120,60],[80,60,20]] },
{ tier: 2, name: "Dreadwraith", hp: 60, atk: 15, def: 1, spd: 10, prod: 3.0, evo: "Voidreaper", palette: [[60,0,80],[100,30,120],[40,0,60]] },
// Tier 3 - Elders
{ tier: 3, name: "Fleshbeast", hp: 200, atk: 25, def: 8, spd: 6, prod: 12.0, evo: "Abomination", palette: [[180,20,20],[240,60,60],[120,0,0]] },
{ tier: 3, name: "Catacomb", hp: 180, atk: 28, def: 12, spd: 8, prod: 10.0, evo: "Gravemind", palette: [[255,255,255],[220,220,220],[160,160,160]] },
{ tier: 3, name: "Plaguefather", hp: 250, atk: 20, def: 15, spd: 3, prod: 15.0, evo: "Pestilence", palette: [[120,100,50],[160,140,70],[100,80,30]] },
{ tier: 3, name: "Voidreaper", hp: 150, atk: 35, def: 4, spd: 15, prod: 9.0, evo: "Null Emperor", palette: [[80,0,100],[120,40,140],[60,0,80]] },
// Tier 4 - Apex
{ tier: 4, name: "Abomination", hp: 600, atk: 60, def: 20, spd: 8, prod: 40.0, evo: null, palette: [[200,0,0],[255,50,50],[150,0,0]] },
{ tier: 4, name: "Gravemind", hp: 550, atk: 65, def: 25, spd: 10, prod: 35.0, evo: null, palette: [[255,255,255],[230,230,230],[180,180,180]] },
{ tier: 4, name: "Pestilence", hp: 700, atk: 50, def: 30, spd: 4, prod: 50.0, evo: null, palette: [[140,120,60],[180,160,80],[120,100,40]] },
{ tier: 4, name: "Null Emperor", hp: 500, atk: 80, def: 10, spd: 20, prod: 30.0, evo: null, palette: [[100,0,120],[140,50,160],[80,0,100]] },
];
const HATCH_COST_BASE = 10;
const HATCH_COST_SCALE = 1.15;
let hatchCount = 0;
// ==================== PIXEL ART GENERATOR ====================
function generateMonsterSprite(type, size = 64) {
const canvas = document.createElement('canvas');
canvas.width = size; canvas.height = size;
const ctx = canvas.getContext('2d');
const seed = type.name.split('').reduce((a,b)=>a+b.charCodeAt(0),0);
const rng = mulberry32(seed + Date.now() % 10000);
const palette = type.palette;
const px = size / 16;
// Background glow
ctx.fillStyle = `rgba(${palette[0][0]},${palette[0][1]},${palette[0][2]},0.1)`;
ctx.fillRect(0,0,size,size);
// Body shape based on tier
const bodyColor = `rgb(${palette[0][0]},${palette[0][1]},${palette[0][2]})`;
const detailColor = `rgb(${palette[1][0]},${palette[1][1]},${palette[1][2]})`;
const darkColor = `rgb(${palette[2][0]},${palette[2][1]},${palette[2][2]})`;
ctx.fillStyle = bodyColor;
// Procedural body
const cx = 8, cy = 8;
const bodyW = 6 + type.tier + Math.floor(rng()*4);
const bodyH = 6 + type.tier + Math.floor(rng()*4);
for(let y=0; y<16; y++) {
for(let x=0; x<16; x++) {
const dx = x - cx;
const dy = y - cy;
const dist = Math.sqrt(dx*dx + dy*dy);
const noise = rng();
if (dist < bodyW/2 + noise) {
// Core body
ctx.fillStyle = bodyColor;
ctx.fillRect(x*px, y*px, px, px);
} else if (dist < bodyW/2 + 2 && noise > 0.4) {
// Details / limbs
ctx.fillStyle = detailColor;
ctx.fillRect(x*px, y*px, px, px);
} else if (dist < bodyW/2 + 3 && noise > 0.7) {
// Dark accents
ctx.fillStyle = darkColor;
ctx.fillRect(x*px, y*px, px, px);
}
}
}
// Eyes
const eyeY = cy - 1 + Math.floor(rng()*3);
const eyeX1 = cx - 2 - Math.floor(rng()*2);
const eyeX2 = cx + 2 + Math.floor(rng()*2);
ctx.fillStyle = '#ff0000';
if (rng() > 0.3) ctx.fillRect(eyeX1*px, eyeY*px, px, px);
if (rng() > 0.3) ctx.fillRect(eyeX2*px, eyeY*px, px, px);
// Mouth
if (type.tier >= 1) {
ctx.fillStyle = darkColor;
for(let i=-2; i<=2; i++) {
if (rng() > 0.2) ctx.fillRect((cx+i)*px, (cy+3)*px, px, px);
}
}
// Horns / spikes for higher tiers
if (type.tier >= 2) {
ctx.fillStyle = detailColor;
ctx.fillRect((cx-3)*px, (cy-4)*px, px, px*2);
ctx.fillRect((cx+3)*px, (cy-4)*px, px, px*2);
}
if (type.tier >= 3) {
ctx.fillStyle = '#fff';
ctx.fillRect((cx-1)*px, (cy-5)*px, px, px*2);
ctx.fillRect((cx+1)*px, (cy-5)*px, px, px*2);
}
return canvas;
}
function mulberry32(a) {
return function() {
let t = a += 0x6D2B79F5;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
}
// ==================== CORE FUNCTIONS ====================
function getHatchCost() {
return Math.floor(HATCH_COST_BASE * Math.pow(HATCH_COST_SCALE, hatchCount));
}
function hatchMonster() {
const cost = getHatchCost();
if (state.dread < cost) {
showToast("Not enough Dread!");
return;
}
state.dread -= cost;
hatchCount++;
const bonus = state.upgrades.incubator.level;
const available = MONSTER_TYPES.filter(m => m.tier <= Math.min(bonus, 4));
const type = available[Math.floor(Math.random() * available.length)];
const monster = {
id: state.nextId++,
type: type,
level: 1,
xp: 0,
xpToLevel: 10,
hp: type.hp,
maxHp: type.hp,
atk: type.atk,
def: type.def,
spd: type.spd,
prod: type.prod,
sprite: null,
};
monster.sprite = generateMonsterSprite(type);
state.monsters.push(monster);
log(`Hatched ${type.name}!`);
showToast(`Hatched: ${type.name}`);
spawnParticles(window.innerWidth/2, window.innerHeight/2, type.palette[0]);
render();
}
function levelUpMonster(m) {
const cost = Math.floor(m.xpToLevel * 0.5);
if (state.dread < cost) {
showToast("Not enough Dread!");
return;
}
state.dread -= cost;
m.level++;
m.xp = 0;
m.xpToLevel = Math.floor(m.xpToLevel * 1.5);
m.maxHp = Math.floor(m.maxHp * 1.2);
m.hp = m.maxHp;
m.atk = Math.floor(m.atk * 1.15);
m.def = Math.floor(m.def * 1.1);
m.spd = Math.floor(m.spd * 1.1);
m.prod = m.prod * 1.1;
log(`${m.type.name} reached level ${m.level}!`);
showToast(`${m.type.name} Lv.${m.level}`);
render();
}
function evolveMonster(m) {
if (!m.type.evo) {
showToast("Cannot evolve further!");
return;
}
const evoName = m.type.evo;
const evoType = MONSTER_TYPES.find(t => t.name === evoName);
if (!evoType) return;
let cost = Math.floor(100 * Math.pow(2, evoType.tier) * Math.pow(state.upgrades.mutagen ? 0.9 : 1, state.upgrades.mutagen.level));
if (state.souls < cost) {
showToast(`Need ${cost} Souls!`);
return;
}
state.souls -= cost;
m.type = evoType;
m.level = 1;
m.xp = 0;
m.xpToLevel = 20;
m.maxHp = evoType.hp;
m.hp = evoType.hp;
m.atk = evoType.atk;
m.def = evoType.def;
m.spd = evoType.spd;
m.prod = evoType.prod;
m.sprite = generateMonsterSprite(evoType);
log(`${m.type.name} has EVOLVED!`);
showToast(`EVOLVED: ${evoType.name}!`);
spawnParticles(window.innerWidth/2, window.innerHeight/2, evoType.palette[0]);
closeModal();
render();
}
function sacrificeMonster(m) {
const soulGain = Math.floor(m.level * m.type.tier * 5 + 1);
state.souls += soulGain;
state.monsters = state.monsters.filter(x => x.id !== m.id);
log(`Sacrificed ${m.type.name} for ${soulGain} Souls.`);
showToast(`+${soulGain} Souls`);
closeModal();
render();
}
function buyUpgrade(key) {
const up = state.upgrades[key];
if (state.dread < up.cost) {
showToast("Not enough Dread!");
return;
}
state.dread -= up.cost;
up.level++;
up.cost = Math.floor(up.cost * up.costScale);
if (key === 'voidgate') {
state.zones.swamp.unlocked = true;
}
log(`Purchased: ${up.name} Lv.${up.level}`);
showToast(`${up.name} +1`);
render();
}
function enterZone(key) {
const zone = state.zones[key];
if (!zone.unlocked) {
showToast("Zone locked!");
return;
}
if (state.monsters.length === 0) {
showToast("You need monsters to farm!");
return;
}
state.activeZone = key;
state.zoneProgress = 0;
state.zoneHealth = 100;
log(`Entered ${zone.name}...`);
showToast(`Farming: ${zone.name}`);
render();
}
// ==================== PRODUCTION ====================
function gameTick() {
// Monster production
let mult = Math.pow(state.upgrades.amplifier.effect, state.upgrades.amplifier.level);
if (mult === Infinity || isNaN(mult)) mult = 1;
for (const m of state.monsters) {
const gain = m.prod * m.level * mult / 10; // per tick (100ms)
state.dread += gain;
state.totalDread += gain;
m.xp += 0.1 * m.level;
if (m.xp >= m.xpToLevel) {
m.xp = m.xpToLevel; // cap, manual level up
}
}
// Automation
if (state.upgrades.extractor.level > 0) {
state.dread += state.upgrades.extractor.level * state.upgrades.extractor.effect / 10;
state.totalDread += state.upgrades.extractor.level * state.upgrades.extractor.effect / 10;
}
if (state.upgrades.harvester.level > 0) {
state.souls += state.upgrades.harvester.level * state.upgrades.harvester.effect / 10;
}
// Zone farming
if (state.activeZone) {
const zone = state.zones[state.activeZone];
state.zoneProgress += 1;
// Danger check every 10 ticks
if (state.zoneProgress % 10 === 0) {
if (Math.random() < zone.danger) {
// Damage random monster
const target = state.monsters[Math.floor(Math.random() * state.monsters.length)];
const dmg = Math.floor(Math.random() * 10) + 5;
target.hp -= dmg;
if (target.hp <= 0) {
log(`${target.type.name} was consumed by the ${zone.name}...`);
state.monsters = state.monsters.filter(m => m.id !== target.id);
if (state.monsters.length === 0) {
state.activeZone = null;
log("All monsters lost. Retreating...");
}
}
}
}
// Rewards
state.dread += zone.dreadRate / 10;
state.totalDread += zone.dreadRate / 10;
state.souls += zone.soulRate / 10;
// Zone unlocks
if (state.activeZone === 'swamp' && state.zoneProgress > 300) {
state.zones.abyss.unlocked = true;
}
if (state.activeZone === 'abyss' && state.zoneProgress > 600) {
state.zones.nullspace.unlocked = true;
}
}
updateResources();
if (state.tab === 'monsters') renderMonsters();
if (state.tab === 'zones') renderZones();
}
function updateResources() {
document.getElementById('resDread').textContent = formatNum(state.dread);
document.getElementById('resSouls').textContent = formatNum(state.souls);
document.getElementById('resShards').textContent = formatNum(state.shards);
}
function formatNum(n) {
if (n >= 1e12) return (n/1e12).toFixed(2) + 'T';
if (n >= 1e9) return (n/1e9).toFixed(2) + 'B';
if (n >= 1e6) return (n/1e6).toFixed(2) + 'M';
if (n >= 1e3) return (n/1e3).toFixed(2) + 'K';
return Math.floor(n).toString();
}
// ==================== RENDERING ====================
function render() {
const content = document.getElementById('content');
content.innerHTML = '';
swit
Saved diffs
Original text
Open file
1
Changed text
Open file
<!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>ABYSSAL FARM — Void Breeder</title> <style> @import url('https://fonts.googleapis.com/css2?family=VT323&display=swap'); :root { --bg: #0a0a0f; --panel: #12121a; --panel2: #1a1a25; --accent: #8b0000; --accent2: #4a0080; --accent3: #1a5c1a; --text: #c0c0c0; --text-dim: #555; --border: #2a2a35; --glow-red: 0 0 8px #8b0000; --glow-purple: 0 0 8px #4a0080; --glow-green: 0 0 8px #1a5c1a; } * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; } html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: 'VT323', monospace; font-size: 18px; overflow: hidden; height: 100vh; width: 100vw; touch-action: manipulation; } /* CRT overlay */ body::before { content: ""; position: fixed; top:0; left:0; right:0; bottom:0; background: repeating-linear-gradient( 0deg, rgba(0,0,0,0.15), rgba(0,0,0,0.15) 1px, transparent 1px, transparent 2px ); pointer-events: none; z-index: 9999; } body::after { content: ""; position: fixed; top:0; left:0; right:0; bottom:0; background: radial-gradient(circle, transparent 60%, rgba(0,0,0,0.6) 100%); pointer-events: none; z-index: 9998; } #app { display: flex; flex-direction: column; height: 100vh; max-width: 600px; margin: 0 auto; position: relative; z-index: 1; } /* Header */ .header { background: var(--panel); border-bottom: 2px solid var(--border); padding: 8px 12px; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; } .header h1 { margin: 0; font-size: 1.4rem; color: var(--accent); text-shadow: var(--glow-red); letter-spacing: 2px; } .resources { display: flex; gap: 12px; font-size: 0.95rem; } .res-item { display: flex; align-items: center; gap: 4px; } .res-icon { width: 14px; height: 14px; display: inline-block; } /* Tabs */ .tabs { display: flex; background: var(--panel); border-bottom: 2px solid var(--border); flex-shrink: 0; } .tab { flex: 1; padding: 10px; text-align: center; cursor: pointer; border: none; background: transparent; color: var(--text-dim); font-family: 'VT323', monospace; font-size: 1rem; border-bottom: 3px solid transparent; transition: all 0.2s; } .tab.active { color: var(--text); border-bottom-color: var(--accent); background: var(--panel2); } .tab:hover { color: var(--text); } /* Content area */ .content { flex: 1; overflow-y: auto; padding: 12px; position: relative; } .content::-webkit-scrollbar { width: 6px; } .content::-webkit-scrollbar-thumb { background: var(--accent); } /* Monster Cards */ .monster-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; } .monster-card { background: var(--panel); border: 2px solid var(--border); border-radius: 4px; padding: 8px; text-align: center; cursor: pointer; transition: all 0.2s; position: relative; overflow: hidden; } .monster-card:hover { border-color: var(--accent); box-shadow: var(--glow-red); } .monster-card.selected { border-color: var(--accent2); box-shadow: var(--glow-purple); } .monster-canvas { width: 64px; height: 64px; image-rendering: pixelated; margin: 0 auto 6px; display: block; } .monster-name { font-size: 0.9rem; color: var(--accent); margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .monster-lvl { font-size: 0.8rem; color: var(--text-dim); } .monster-bar { width: 100%; height: 4px; background: #222; margin-top: 4px; border-radius: 2px; overflow: hidden; } .monster-bar-fill { height: 100%; background: var(--accent); transition: width 0.3s; } /* Hatch Button */ .hatch-area { text-align: center; padding: 20px; margin-bottom: 16px; } .hatch-btn { width: 120px; height: 120px; border-radius: 50%; border: 3px solid var(--accent); background: var(--panel); color: var(--accent); font-family: 'VT323', monospace; font-size: 1.2rem; cursor: pointer; position: relative; overflow: hidden; box-shadow: var(--glow-red); transition: transform 0.1s; display: inline-flex; align-items: center; justify-content: center; flex-direction: column; } .hatch-btn:active { transform: scale(0.92); } .hatch-btn .cost { font-size: 0.8rem; color: var(--text-dim); margin-top: 4px; } .hatch-btn::before { content: ""; position: absolute; width: 80%; height: 80%; border: 2px dashed var(--accent); border-radius: 50%; animation: spin 8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* Upgrades / Automation */ .upgrade-list { display: flex; flex-direction: column; gap: 8px; } .upgrade-item { background: var(--panel); border: 2px solid var(--border); border-radius: 4px; padding: 10px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; transition: all 0.2s; } .upgrade-item:hover { border-color: var(--accent2); } .upgrade-item.disabled { opacity: 0.4; cursor: not-allowed; } .upgrade-info { flex: 1; } .upgrade-name { font-size: 1rem; color: var(--accent2); } .upgrade-desc { font-size: 0.8rem; color: var(--text-dim); } .upgrade-cost { background: var(--panel2); padding: 4px 10px; border-radius: 4px; border: 1px solid var(--border); font-size: 0.9rem; white-space: nowrap; } /* Farm Zones */ .zone-list { display: flex; flex-direction: column; gap: 10px; } .zone-card { background: var(--panel); border: 2px solid var(--border); border-radius: 4px; padding: 12px; cursor: pointer; position: relative; } .zone-card:hover { border-color: var(--accent3); } .zone-name { font-size: 1.1rem; color: var(--accent3); margin-bottom: 4px; } .zone-desc { font-size: 0.85rem; color: var(--text-dim); } .zone-reward { font-size: 0.9rem; color: var(--text); margin-top: 6px; } .zone-danger { position: absolute; top: 8px; right: 8px; font-size: 0.75rem; color: var(--accent); } /* Detail Modal */ .modal-overlay { position: fixed; top:0; left:0; right:0; bottom:0; background: rgba(0,0,0,0.85); display: none; align-items: center; justify-content: center; z-index: 10000; padding: 20px; } .modal-overlay.active { display: flex; } .modal { background: var(--panel); border: 2px solid var(--accent); border-radius: 8px; padding: 20px; max-width: 400px; width: 100%; max-height: 80vh; overflow-y: auto; position: relative; } .modal-close { position: absolute; top: 8px; right: 12px; background: none; border: none; color: var(--text); font-size: 1.5rem; cursor: pointer; font-family: 'VT323', monospace; } .modal-title { font-size: 1.3rem; color: var(--accent); margin-bottom: 12px; text-align: center; } .modal-canvas { width: 96px; height: 96px; image-rendering: pixelated; margin: 0 auto 12px; display: block; } .stat-row { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid var(--border); font-size: 0.95rem; } .action-btn { width: 100%; padding: 10px; margin-top: 10px; border: 2px solid var(--accent); background: var(--panel2); color: var(--text); font-family: 'VT323', monospace; font-size: 1rem; cursor: pointer; border-radius: 4px; transition: all 0.2s; } .action-btn:hover { background: var(--accent); color: #fff; } .action-btn:disabled { opacity: 0.3; cursor: not-allowed; } /* Floating text */ .float-text { position: absolute; color: var(--accent); font-size: 1rem; pointer-events: none; animation: floatUp 1s ease-out forwards; z-index: 100; } @keyframes floatUp { 0% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-40px); } } /* Log */ .log-area { background: var(--panel); border: 2px solid var(--border); border-radius: 4px; padding: 8px; height: 120px; overflow-y: auto; font-size: 0.85rem; color: var(--text-dim); } .log-entry { margin-bottom: 2px; } .log-entry.new { color: var(--text); } /* Glitch effect on title */ .glitch { animation: glitch 3s infinite; } @keyframes glitch { 0%, 90%, 100% { text-shadow: var(--glow-red); } 92% { text-shadow: 2px 0 #0ff, -2px 0 #f0f; } 94% { text-shadow: -2px 0 #0ff, 2px 0 #f0f; } 96% { text-shadow: var(--glow-red); } } /* Notification toast */ .toast { position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%); background: var(--panel); border: 2px solid var(--accent); padding: 10px 20px; border-radius: 4px; font-size: 1rem; z-index: 10001; opacity: 0; transition: opacity 0.3s; pointer-events: none; white-space: nowrap; } .toast.show { opacity: 1; } /* Evolution tree */ .evo-tree { display: flex; justify-content: center; align-items: center; gap: 8px; margin: 12px 0; flex-wrap: wrap; } .evo-node { width: 48px; height: 48px; border: 2px solid var(--border); border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 0.7rem; color: var(--text-dim); background: var(--panel2); } .evo-node.active { border-color: var(--accent); color: var(--accent); box-shadow: var(--glow-red); } .evo-arrow { color: var(--text-dim); font-size: 1.2rem; } /* Particle canvas */ #particleCanvas { position: fixed; top: 0; left: 0; pointer-events: none; z-index: 9997; } /* Responsive */ @media (max-width: 400px) { .monster-grid { grid-template-columns: repeat(2, 1fr); } .header h1 { font-size: 1.1rem; } } </style> </head> <body> <canvas id="particleCanvas"></canvas> <div id="app"> <div class="header"> <h1 class="glitch">ABYSSAL FARM</h1> <div class="resources"> <div class="res-item"><span style="color:#8b0000">◆</span> <span id="resDread">0</span></div> <div class="res-item"><span style="color:#4a0080">◆</span> <span id="resSouls">0</span></div> <div class="res-item"><span style="color:#1a5c1a">◆</span> <span id="resShards">0</span></div> </div> </div> <div class="tabs"> <button class="tab active" data-tab="hatch">HATCH</button> <button class="tab" data-tab="monsters">MONSTERS</button> <button class="tab" data-tab="upgrades">VOID SHOP</button> <button class="tab" data-tab="zones">REALMS</button> </div> <div class="content" id="content"> <!-- Dynamic content --> </div> <div class="log-area" id="logArea"> <div class="log-entry">Welcome to the Abyss. Hatch your first abomination.</div> </div> </div> <!-- Modal --> <div class="modal-overlay" id="modalOverlay"> <div class="modal" id="modal"> <button class="modal-close" onclick="closeModal()">×</button> <div id="modalContent"></div> </div> </div> <div class="toast" id="toast"></div> <script> // ==================== GAME STATE ==================== const state = { dread: 0, souls: 0, shards: 0, totalDread: 0, monsters: [], nextId: 1, selectedMonster: null, tab: 'hatch', upgrades: { extractor: { level: 0, cost: 50, costScale: 1.6, name: "Void Extractor", desc: "Auto-generates +1 Dread/sec", effect: 1 }, harvester: { level: 0, cost: 200, costScale: 1.7, name: "Soul Harvester", desc: "Auto-generates +0.5 Souls/sec", effect: 0.5 }, incubator: { level: 0, cost: 500, costScale: 1.8, name: "Corrupt Incubator", desc: "Monsters hatch 1 tier higher base", effect: 1 }, amplifier: { level: 0, cost: 1000, costScale: 1.9, name: "Dread Amplifier", desc: "All monster production ×1.5", effect: 1.5 }, voidgate: { level: 0, cost: 5000, costScale: 2.0, name: "Void Gate", desc: "Unlocks Realm farming", effect: 1 }, mutagen: { level: 0, cost: 10000, costScale: 2.2, name: "Mutagen Vat", desc: "Evolution cost reduced by 10%", effect: 0.9 }, }, zones: { crypt: { unlocked: true, name: "Forgotten Crypt", desc: "Ancient burial grounds teeming with weak spirits", dreadRate: 5, soulRate: 0, danger: 0.05, reward: "+5 Dread/sec" }, swamp: { unlocked: false, name: "Rotting Swamp", desc: "Bubbling miasma corrupts everything it touches", dreadRate: 15, soulRate: 1, danger: 0.1, reward: "+15 Dread, +1 Soul/sec" }, abyss: { unlocked: false, name: "The Abyss", desc: "Stare into the void. It stares back.", dreadRate: 50, soulRate: 5, danger: 0.2, reward: "+50 Dread, +5 Souls/sec" }, nullspace: { unlocked: false, name: "Null Space", desc: "Reality unravels. Only the strongest survive.", dreadRate: 200, soulRate: 20, danger: 0.35, reward: "+200 Dread, +20 Souls/sec" }, }, activeZone: null, zoneProgress: 0, zoneHealth: 100, }; // ==================== MONSTER DATA ==================== const MONSTER_TYPES = [ // Tier 0 - Larvae { tier: 0, name: "Grubspawn", hp: 10, atk: 1, def: 0, spd: 1, prod: 0.5, evo: "Mawling", palette: [[0,80,0],[40,120,40],[20,60,20]] }, { tier: 0, name: "Boneling", hp: 8, atk: 2, def: 0, spd: 2, prod: 0.4, evo: "Ribcage", palette: [[200,200,200],[150,150,150],[100,100,100]] }, { tier: 0, name: "Sludge", hp: 15, atk: 1, def: 1, spd: 0, prod: 0.6, evo: "Muckfiend", palette: [[60,40,20],[100,80,40],[80,60,30]] }, // Tier 1 - Juveniles { tier: 1, name: "Mawling", hp: 30, atk: 4, def: 1, spd: 3, prod: 1.5, evo: "Goremaw", palette: [[120,0,0],[180,20,20],[80,0,0]] }, { tier: 1, name: "Ribcage", hp: 25, atk: 5, def: 2, spd: 4, prod: 1.2, evo: "Ossuary", palette: [[220,220,220],[180,180,180],[120,120,120]] }, { tier: 1, name: "Muckfiend", hp: 40, atk: 3, def: 3, spd: 1, prod: 1.8, evo: "Rotlord", palette: [[80,60,30],[120,100,50],[60,40,10]] }, { tier: 1, name: "Shadelet", hp: 20, atk: 6, def: 0, spd: 5, prod: 1.0, evo: "Dreadwraith", palette: [[40,0,60],[80,20,100],[20,0,40]] }, // Tier 2 - Adults { tier: 2, name: "Goremaw", hp: 80, atk: 10, def: 3, spd: 5, prod: 4.0, evo: "Fleshbeast", palette: [[160,0,0],[220,40,40],[100,0,0]] }, { tier: 2, name: "Ossuary", hp: 70, atk: 12, def: 5, spd: 6, prod: 3.5, evo: "Catacomb", palette: [[240,240,240],[200,200,200],[140,140,140]] }, { tier: 2, name: "Rotlord", hp: 100, atk: 8, def: 6, spd: 2, prod: 5.0, evo: "Plaguefather", palette: [[100,80,40],[140,120,60],[80,60,20]] }, { tier: 2, name: "Dreadwraith", hp: 60, atk: 15, def: 1, spd: 10, prod: 3.0, evo: "Voidreaper", palette: [[60,0,80],[100,30,120],[40,0,60]] }, // Tier 3 - Elders { tier: 3, name: "Fleshbeast", hp: 200, atk: 25, def: 8, spd: 6, prod: 12.0, evo: "Abomination", palette: [[180,20,20],[240,60,60],[120,0,0]] }, { tier: 3, name: "Catacomb", hp: 180, atk: 28, def: 12, spd: 8, prod: 10.0, evo: "Gravemind", palette: [[255,255,255],[220,220,220],[160,160,160]] }, { tier: 3, name: "Plaguefather", hp: 250, atk: 20, def: 15, spd: 3, prod: 15.0, evo: "Pestilence", palette: [[120,100,50],[160,140,70],[100,80,30]] }, { tier: 3, name: "Voidreaper", hp: 150, atk: 35, def: 4, spd: 15, prod: 9.0, evo: "Null Emperor", palette: [[80,0,100],[120,40,140],[60,0,80]] }, // Tier 4 - Apex { tier: 4, name: "Abomination", hp: 600, atk: 60, def: 20, spd: 8, prod: 40.0, evo: null, palette: [[200,0,0],[255,50,50],[150,0,0]] }, { tier: 4, name: "Gravemind", hp: 550, atk: 65, def: 25, spd: 10, prod: 35.0, evo: null, palette: [[255,255,255],[230,230,230],[180,180,180]] }, { tier: 4, name: "Pestilence", hp: 700, atk: 50, def: 30, spd: 4, prod: 50.0, evo: null, palette: [[140,120,60],[180,160,80],[120,100,40]] }, { tier: 4, name: "Null Emperor", hp: 500, atk: 80, def: 10, spd: 20, prod: 30.0, evo: null, palette: [[100,0,120],[140,50,160],[80,0,100]] }, ]; const HATCH_COST_BASE = 10; const HATCH_COST_SCALE = 1.15; let hatchCount = 0; // ==================== PIXEL ART GENERATOR ==================== function generateMonsterSprite(type, size = 64) { const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; const ctx = canvas.getContext('2d'); const seed = type.name.split('').reduce((a,b)=>a+b.charCodeAt(0),0); const rng = mulberry32(seed + Date.now() % 10000); const palette = type.palette; const px = size / 16; // Background glow ctx.fillStyle = `rgba(${palette[0][0]},${palette[0][1]},${palette[0][2]},0.1)`; ctx.fillRect(0,0,size,size); // Body shape based on tier const bodyColor = `rgb(${palette[0][0]},${palette[0][1]},${palette[0][2]})`; const detailColor = `rgb(${palette[1][0]},${palette[1][1]},${palette[1][2]})`; const darkColor = `rgb(${palette[2][0]},${palette[2][1]},${palette[2][2]})`; ctx.fillStyle = bodyColor; // Procedural body const cx = 8, cy = 8; const bodyW = 6 + type.tier + Math.floor(rng()*4); const bodyH = 6 + type.tier + Math.floor(rng()*4); for(let y=0; y<16; y++) { for(let x=0; x<16; x++) { const dx = x - cx; const dy = y - cy; const dist = Math.sqrt(dx*dx + dy*dy); const noise = rng(); if (dist < bodyW/2 + noise) { // Core body ctx.fillStyle = bodyColor; ctx.fillRect(x*px, y*px, px, px); } else if (dist < bodyW/2 + 2 && noise > 0.4) { // Details / limbs ctx.fillStyle = detailColor; ctx.fillRect(x*px, y*px, px, px); } else if (dist < bodyW/2 + 3 && noise > 0.7) { // Dark accents ctx.fillStyle = darkColor; ctx.fillRect(x*px, y*px, px, px); } } } // Eyes const eyeY = cy - 1 + Math.floor(rng()*3); const eyeX1 = cx - 2 - Math.floor(rng()*2); const eyeX2 = cx + 2 + Math.floor(rng()*2); ctx.fillStyle = '#ff0000'; if (rng() > 0.3) ctx.fillRect(eyeX1*px, eyeY*px, px, px); if (rng() > 0.3) ctx.fillRect(eyeX2*px, eyeY*px, px, px); // Mouth if (type.tier >= 1) { ctx.fillStyle = darkColor; for(let i=-2; i<=2; i++) { if (rng() > 0.2) ctx.fillRect((cx+i)*px, (cy+3)*px, px, px); } } // Horns / spikes for higher tiers if (type.tier >= 2) { ctx.fillStyle = detailColor; ctx.fillRect((cx-3)*px, (cy-4)*px, px, px*2); ctx.fillRect((cx+3)*px, (cy-4)*px, px, px*2); } if (type.tier >= 3) { ctx.fillStyle = '#fff'; ctx.fillRect((cx-1)*px, (cy-5)*px, px, px*2); ctx.fillRect((cx+1)*px, (cy-5)*px, px, px*2); } return canvas; } function mulberry32(a) { return function() { let t = a += 0x6D2B79F5; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; } } // ==================== CORE FUNCTIONS ==================== function getHatchCost() { return Math.floor(HATCH_COST_BASE * Math.pow(HATCH_COST_SCALE, hatchCount)); } function hatchMonster() { const cost = getHatchCost(); if (state.dread < cost) { showToast("Not enough Dread!"); return; } state.dread -= cost; hatchCount++; const bonus = state.upgrades.incubator.level; const available = MONSTER_TYPES.filter(m => m.tier <= Math.min(bonus, 4)); const type = available[Math.floor(Math.random() * available.length)]; const monster = { id: state.nextId++, type: type, level: 1, xp: 0, xpToLevel: 10, hp: type.hp, maxHp: type.hp, atk: type.atk, def: type.def, spd: type.spd, prod: type.prod, sprite: null, }; monster.sprite = generateMonsterSprite(type); state.monsters.push(monster); log(`Hatched ${type.name}!`); showToast(`Hatched: ${type.name}`); spawnParticles(window.innerWidth/2, window.innerHeight/2, type.palette[0]); render(); } function levelUpMonster(m) { const cost = Math.floor(m.xpToLevel * 0.5); if (state.dread < cost) { showToast("Not enough Dread!"); return; } state.dread -= cost; m.level++; m.xp = 0; m.xpToLevel = Math.floor(m.xpToLevel * 1.5); m.maxHp = Math.floor(m.maxHp * 1.2); m.hp = m.maxHp; m.atk = Math.floor(m.atk * 1.15); m.def = Math.floor(m.def * 1.1); m.spd = Math.floor(m.spd * 1.1); m.prod = m.prod * 1.1; log(`${m.type.name} reached level ${m.level}!`); showToast(`${m.type.name} Lv.${m.level}`); render(); } function evolveMonster(m) { if (!m.type.evo) { showToast("Cannot evolve further!"); return; } const evoName = m.type.evo; const evoType = MONSTER_TYPES.find(t => t.name === evoName); if (!evoType) return; let cost = Math.floor(100 * Math.pow(2, evoType.tier) * Math.pow(state.upgrades.mutagen ? 0.9 : 1, state.upgrades.mutagen.level)); if (state.souls < cost) { showToast(`Need ${cost} Souls!`); return; } state.souls -= cost; m.type = evoType; m.level = 1; m.xp = 0; m.xpToLevel = 20; m.maxHp = evoType.hp; m.hp = evoType.hp; m.atk = evoType.atk; m.def = evoType.def; m.spd = evoType.spd; m.prod = evoType.prod; m.sprite = generateMonsterSprite(evoType); log(`${m.type.name} has EVOLVED!`); showToast(`EVOLVED: ${evoType.name}!`); spawnParticles(window.innerWidth/2, window.innerHeight/2, evoType.palette[0]); closeModal(); render(); } function sacrificeMonster(m) { const soulGain = Math.floor(m.level * m.type.tier * 5 + 1); state.souls += soulGain; state.monsters = state.monsters.filter(x => x.id !== m.id); log(`Sacrificed ${m.type.name} for ${soulGain} Souls.`); showToast(`+${soulGain} Souls`); closeModal(); render(); } function buyUpgrade(key) { const up = state.upgrades[key]; if (state.dread < up.cost) { showToast("Not enough Dread!"); return; } state.dread -= up.cost; up.level++; up.cost = Math.floor(up.cost * up.costScale); if (key === 'voidgate') { state.zones.swamp.unlocked = true; } log(`Purchased: ${up.name} Lv.${up.level}`); showToast(`${up.name} +1`); render(); } function enterZone(key) { const zone = state.zones[key]; if (!zone.unlocked) { showToast("Zone locked!"); return; } if (state.monsters.length === 0) { showToast("You need monsters to farm!"); return; } state.activeZone = key; state.zoneProgress = 0; state.zoneHealth = 100; log(`Entered ${zone.name}...`); showToast(`Farming: ${zone.name}`); render(); } // ==================== PRODUCTION ==================== function gameTick() { // Monster production let mult = Math.pow(state.upgrades.amplifier.effect, state.upgrades.amplifier.level); if (mult === Infinity || isNaN(mult)) mult = 1; for (const m of state.monsters) { const gain = m.prod * m.level * mult / 10; // per tick (100ms) state.dread += gain; state.totalDread += gain; m.xp += 0.1 * m.level; if (m.xp >= m.xpToLevel) { m.xp = m.xpToLevel; // cap, manual level up } } // Automation if (state.upgrades.extractor.level > 0) { state.dread += state.upgrades.extractor.level * state.upgrades.extractor.effect / 10; state.totalDread += state.upgrades.extractor.level * state.upgrades.extractor.effect / 10; } if (state.upgrades.harvester.level > 0) { state.souls += state.upgrades.harvester.level * state.upgrades.harvester.effect / 10; } // Zone farming if (state.activeZone) { const zone = state.zones[state.activeZone]; state.zoneProgress += 1; // Danger check every 10 ticks if (state.zoneProgress % 10 === 0) { if (Math.random() < zone.danger) { // Damage random monster const target = state.monsters[Math.floor(Math.random() * state.monsters.length)]; const dmg = Math.floor(Math.random() * 10) + 5; target.hp -= dmg; if (target.hp <= 0) { log(`${target.type.name} was consumed by the ${zone.name}...`); state.monsters = state.monsters.filter(m => m.id !== target.id); if (state.monsters.length === 0) { state.activeZone = null; log("All monsters lost. Retreating..."); } } } } // Rewards state.dread += zone.dreadRate / 10; state.totalDread += zone.dreadRate / 10; state.souls += zone.soulRate / 10; // Zone unlocks if (state.activeZone === 'swamp' && state.zoneProgress > 300) { state.zones.abyss.unlocked = true; } if (state.activeZone === 'abyss' && state.zoneProgress > 600) { state.zones.nullspace.unlocked = true; } } updateResources(); if (state.tab === 'monsters') renderMonsters(); if (state.tab === 'zones') renderZones(); } function updateResources() { document.getElementById('resDread').textContent = formatNum(state.dread); document.getElementById('resSouls').textContent = formatNum(state.souls); document.getElementById('resShards').textContent = formatNum(state.shards); } function formatNum(n) { if (n >= 1e12) return (n/1e12).toFixed(2) + 'T'; if (n >= 1e9) return (n/1e9).toFixed(2) + 'B'; if (n >= 1e6) return (n/1e6).toFixed(2) + 'M'; if (n >= 1e3) return (n/1e3).toFixed(2) + 'K'; return Math.floor(n).toString(); } // ==================== RENDERING ==================== function render() { const content = document.getElementById('content'); content.innerHTML = ''; switch(state.tab) { case 'hatch': renderHatch(content); break; case 'monsters': renderMonsters(content); break; case 'upgrades': renderUpgrades(content); break; case 'zones': renderZones(content); break; } } function renderHatch(container) { const cost = getHatchCost(); const div = document.createElement('div'); div.className = 'hatch-area'; div.innerHTML = ` <button class="hatch-btn" id="hatchBtn"> <div>HATCH</div> <div class="cost">${formatNum(cost)} Dread</div> </button> <div style="margin-top:16px;color:var(--text-dim);font-size:0.9rem;"> Monsters hatched: ${hatchCount}<br> Active: ${state.monsters.length} </div> <div style="margin-top:12px;"> <div style="font-size:0.85rem;color:var(--text-dim);margin-bottom:6px;">Quick Sacrifice All Weaklings</div> <button class="action-btn" id="sacAllBtn" style="border-color:var(--accent2);">Sacrifice Tier 0 Monsters</button> </div> `; container.appendChild(div); document.getElementById('hatchBtn').onclick = hatchMonster; document.getElementById('sacAllBtn').onclick = () => { const victims = state.monsters.filter(m => m.type.tier === 0); let souls = 0; victims.forEach(m => souls += Math.floor(m.level * 5 + 1)); state.souls += souls; state.monsters = state.monsters.filter(m => m.type.tier !== 0); log(`Sacrificed ${victims.length} weaklings for ${souls} Souls.`); showToast(`+${souls} Souls`); render(); }; } function renderMonsters(container) { if (!container) container = document.getElementById('content'); container.innerHTML = ''; if (state.monsters.length === 0) { container.innerHTML = '<div style="text-align:center;padding:40px;color:var(--text-dim);">No monsters. Hatch some abominations first.</div>'; return; } const grid = document.createElement('div'); grid.className = 'monster-grid'; for (const m of state.monsters) { const card = document.createElement('div'); card.className = 'monster-card' + (state.selectedMonster === m.id ? ' selected' : ''); const canvas = m.sprite || generateMonsterSprite(m.type); canvas.className = 'monster-canvas'; const xpPct = Math.min(100, (m.xp / m.xpToLevel) * 100); card.innerHTML = ''; card.appendChild(canvas); const info = document.createElement('div'); info.innerHTML = ` <div class="monster-name">${m.type.name}</div> <div class="monster-lvl">Lv.${m.level} | HP:${Math.floor(m.hp)}/${m.maxHp}</div> <div class="monster-bar"><div class="monster-bar-fill" style="width:${xpPct}%"></div></div> `; card.appendChild(info); card.onclick = (e) => { state.selectedMonster = m.id; openMonsterDetail(m, e.clientX, e.clientY); }; grid.appendChild(card); } container.appendChild(grid); } function renderUpgrades(container) { const list = document.createElement('div'); list.className = 'upgrade-list'; for (const [key, up] of Object.entries(state.upgrades)) { const item = document.createElement('div'); item.className = 'upgrade-item' + (state.dread < up.cost ? ' disabled' : ''); item.innerHTML = ` <div class="upgrade-info"> <div class="upgrade-name">${up.name} <span style="color:var(--text-dim)">Lv.${up.level}</span></div> <div class="upgrade-desc">${up.desc}</div> </div> <div class="upgrade-cost">${formatNum(up.cost)} D</div> `; item.onclick = () => buyUpgrade(key); list.appendChild(item); } container.appendChild(list); } function renderZones(container) { if (!container) container = document.getElementById('content'); container.innerHTML = ''; const list = document.createElement('div'); list.className = 'zone-list'; for (const [key, zone] of Object.entries(state.zones)) { const card = document.createElement('div'); card.className = 'zone-card'; if (state.activeZone === key) { card.style.borderColor = 'var(--accent3)'; card.style.boxShadow = 'var(--glow-green)'; } const progress = state.activeZone === key ? `<div class="monster-bar" style="margin-top:8px"><div class="monster-bar-fill" style="width:${Math.min(100, state.zoneProgress/10)}%;background:var(--accent3)"></div></div>` : ''; card.innerHTML = ` <div class="zone-danger">${zone.unlocked ? 'OPEN' : 'SEALED'}</div> <div class="zone-name">${zone.name}</div> <div class="zone-desc">${zone.desc}</div> <div class="zone-reward">${zone.reward}</div> ${progress} `; if (zone.unlocked) { card.onclick = () => enterZone(key); } else { card.style.opacity = '0.4'; } list.appendChild(card); } container.appendChild(list); } // ==================== MODAL ==================== function openMonsterDetail(m, x, y) { const overlay = document.getElementById('modalOverlay'); const content = document.getElementById('modalContent'); const evoTree = []; let curr = m.type; while(curr) { evoTree.push(curr); if (curr.evo) curr = MONSTER_TYPES.find(t => t.name === curr.evo); else break; } const evoHtml = evoTree.map((t, i) => ` <div class="evo-node ${t.name === m.type.name ? 'active' : ''}">${t.name}</div> ${i < evoTree.length-1 ? '<div class="evo-arrow">→</div>' : ''} `).join(''); const levelCost = Math.floor(m.xpToLevel * 0.5); const canLevel = state.dread >= levelCost && m.xp >= m.xpToLevel; const canEvo = m.type.evo && state.souls >= Math.floor(100 * Math.pow(2, MONSTER_TYPES.find(t=>t.name===m.type.evo)?.tier || 0)); const canvas = m.sprite || generateMonsterSprite(m.type); canvas.className = 'modal-canvas'; content.innerHTML = ''; content.appendChild(canvas); const info = document.createElement('div'); info.innerHTML = ` <div class="modal-title">${m.type.name} <span style="color:var(--text-dim)">Lv.${m.level}</span></div> <div class="evo-tree">${evoHtml}</div> <div class="stat-row"><span>HP</span><span>${Math.floor(m.hp)}/${m.maxHp}</span></div> <div class="stat-row"><span>ATK</span><span>${m.atk}</span></div> <div class="stat-row"><span>DEF</span><span>${m.def}</span></div> <div class="stat-row"><span>SPD</span><span>${m.spd}</span></div> <div class="stat-row"><span>Production</span><span>${m.prod.toFixed(2)}/s</span></div> <div class="stat-row"><span>XP</span><span>${Math.floor(m.xp)}/${m.xpToLevel}</span></div> <button class="action-btn" id="btnLevel" ${!canLevel ? 'disabled' : ''}> Level Up (${formatNum(levelCost)} Dread) </button> <button class="action-btn" id="btnEvo" style="border-color:var(--accent2);" ${!canEvo ? 'disabled' : ''}> EVOLVE (${formatNum(Math.floor(100 * Math.pow(2, MONSTER_TYPES.find(t=>t.name===m.type.evo)?.tier || 0)))} Souls) </button> <button class="action-btn" id="btnSac" style="border-color:var(--accent);"> SACRIFICE (+${Math.floor(m.level * m.type.tier * 5 + 1)} Souls) </button> `; content.appendChild(info); overlay.classList.add('active'); document.getElementById('btnLevel').onclick = () => levelUpMonster(m); document.getElementById('btnEvo').onclick = () => evolveMonster(m); document.getElementById('btnSac').onclick = () => sacrificeMonster(m); } function closeModal() { document.getElementById('modalOverlay').classList.remove('active'); state.selectedMonster = null; if (state.tab === 'monsters') renderMonsters(); } // ==================== UI HELPERS ==================== function log(msg) { const area = document.getElementById('logArea'); const entry = document.createElement('div'); entry.className = 'log-entry new'; entry.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; area.insertBefore(entry, area.firstChild); if (area.children.length > 50) area.removeChild(area.lastChild); setTimeout(() => entry.classList.remove('new'), 2000); } function showToast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); setTimeout(() => t.classList.remove('show'), 1500); } function spawnParticles(x, y, color) { const canvas = document.getElementById('particleCanvas'); const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const particles = []; for(let i=0; i<20; i++) { particles.push({ x: x, y: y, vx: (Math.random()-0.5)*8, vy: (Math.random()-0.5)*8, life: 1, color: `rgb(${color[0]},${color[1]},${color[2]})` }); } function anim() { ctx.clearRect(0,0,canvas.width,canvas.height); let alive = false; for(const p of particles) { if(p.life <= 0) continue; alive = true; p.x += p.vx; p.y += p.vy; p.life -= 0.03; ctx.globalAlpha = p.life; ctx.fillStyle = p.color; ctx.fillRect(p.x, p.y, 4, 4); } ctx.globalAlpha = 1; if(alive) requestAnimationFrame(anim); else ctx.clearRect(0,0,canvas.width,canvas.height); } anim(); } // ==================== TABS ==================== document.querySelectorAll('.tab').forEach(tab => { tab.onclick = () => { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); tab.classList.add('active'); state.tab = tab.dataset.tab; render(); }; }); // ==================== INIT ==================== render(); setInterval(gameTick, 100); // Initial free monster setTimeout(() => { state.dread = 15; hatchMonster(); }, 100); </script> </body> </html>
Find difference