Diff
checker
Text
Text
Bilder
Dokumente
Excel
Ordner
Legal
Enterprise
Desktop-App
Preise
Einloggen
Diffchecker Desktop herunterladen
Texte vergleichen
Finde den Unterschied zwischen zwei Textdateien
Werkzeuge
Verlauf
Live-Editor
Gleiches ausblenden
Zeilenumbruch aus
Ansicht
Zweispaltig
Einspaltig
Vergleichsgenauigkeit
Intelligent
Wort
Zeichen
Syntaxhervorhebung
Syntax auswählen
Ignorieren
Text umwandeln
Zur ersten Änderung
Eingabe bearbeiten
Diffchecker Desktop
Der sicherste Weg, Diffchecker zu nutzen. Hol dir die Desktop-App: Deine Diffs verlassen nie deinen Computer!
Desktop holen
abyssalfarm.html
Erstellt
vor 16 Stunden
Diff läuft nie ab
Löschen
Exportieren
Teilen
Erklären
1 Entfernung
Zeilen
Gesamt
Entfernt
Zeichen
Gesamt
Entfernt
Um diese Funktion weiterhin zu nutzen, aktualisiere auf
Diff
checker
Pro
Preise anzeigen
1 Zeile
Kopieren
929 Hinzufügungen
Zeilen
Gesamt
Hinzugefügt
Zeichen
Gesamt
Hinzugefügt
Um diese Funktion weiterhin zu nutzen, aktualisiere auf
Diff
checker
Pro
Preise anzeigen
929 Zeilen
Kopieren
Kopieren
Kopiert
Kopieren
Kopiert
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
Gespeicherte Diffs
Originaltext
Datei öffnen
1
Bearbeitung
Datei öffnen
<!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>
Unterschied finden