Diff
checker
Texto
Texto
Imagens
Documentos
Excel
Pastas
Legal
Enterprise
Aplicativo para desktop
Preços
Fazer login
Baixar o Diffchecker Desktop
Comparar texto
Encontre a diferença entre dois arquivos de texto
Ferramentas
Histórico
Editor live
Recolher inalteradas
Sem quebra de linha
Layout
Dividido
Unificado
Nível de detalhe
Inteligente
Palavra
Caractere
Realce de sintaxe
Escolher sintaxe
Ignorar
Transformar texto
Ir à primeira mudança
Editar entrada
Diffchecker Desktop
A maneira mais segura de usar o Diffchecker. Obtenha o aplicativo Diffchecker Desktop: seus diffs nunca saem do seu computador!
Obter Desktop
abyssalfarm.html
Criado
há 16 horas
O diff nunca expira
Limpar
Exportar
Compartilhar
Explicar
1 remoção
Linhas
Total
Removido
Caracteres
Total
Removido
Para continuar usando este recurso, atualize para
Diff
checker
Pro
Ver preços
1 linha
Copiar tudo
929 adições
Linhas
Total
Adicionado
Caracteres
Total
Adicionado
Para continuar usando este recurso, atualize para
Diff
checker
Pro
Ver preços
929 linhas
Copiar tudo
Copiar
Copiado
Copiar
Copiado
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
Diferenças salvas
Texto original
Abrir arquivo
1
Texto alterado
Abrir arquivo
<!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>
Encontrar Diferença