Diff
checker
Texte
Texte
Images
Documents
Excel
Dossiers
Legal
Enterprise
Application de bureau
Prix
Se connecter
Télécharger Diffchecker Desktop
Comparer le texte
Trouver la différence entre deux fichiers texte
Outils
Historique
Éditeur live
Cacher identiques
Sans retour à la ligne
Vue
Divisé
Unifié
Niveau de précision
Intelligent
Mot
Caractère
Coloration syntaxique
Choisir la syntaxe
Ignorer
Transformer le texte
Aller au premier écart
Modifier l'entrée
Diffchecker Desktop
La façon la plus sécurisée d'utiliser Diffchecker. Obtenez l'application Diffchecker Desktop : vos diffs ne quittent jamais votre ordinateur !
Obtenir Desktop
abyssalfarm.html
Créé
il y a 16 heures
Le diff n'expire jamais
Effacer
Exporter
Partager
Expliquer
1 suppression
Lignes
Total
Supprimé
Caractères
Total
Supprimé
Pour continuer à utiliser cette fonctionnalité, passez à
Diff
checker
Pro
Voir les prix
1 ligne
Copier tout
929 ajouts
Lignes
Total
Ajouté
Caractères
Total
Ajouté
Pour continuer à utiliser cette fonctionnalité, passez à
Diff
checker
Pro
Voir les prix
929 lignes
Copier tout
Copier
Copié
Copier
Copié
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
Différences enregistrées
Texte d'origine
Ouvrir un fichier
1
Texte modifié
Ouvrir un fichier
<!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>
Trouver la différence