🌾 Granja Épica - Animales permanentes
by RapidFalcon681921 lines76.0 KB
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🌾 Granja Épica - Animales permanentes</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
user-select: none;
}
body {
background: linear-gradient(145deg, #1f3a1f, #0f2a0f);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
}
#app {
max-width: 1200px;
width: 100%;
background: #4a6b3a;
background-image: radial-gradient(circle at 20% 30%, #5f824a, #2f4a22);
border-radius: 48px 48px 32px 32px;
padding: 20px 24px 24px 24px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.8), inset 0 2px 4px rgba(255, 255, 200, 0.2);
border: 2px solid #8aa87a;
}
header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
background: rgba(20, 30, 15, 0.8);
backdrop-filter: blur(4px);
padding: 10px 18px;
border-radius: 60px;
margin-bottom: 16px;
border: 1px solid #b8d0a8;
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.5);
}
.stats {
display: flex;
gap: 10px;
flex-wrap: wrap;
font-weight: 700;
font-size: 0.9rem;
color: #f9f3d9;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.stats span {
display: flex;
align-items: center;
gap: 4px;
background: rgba(0, 0, 0, 0.3);
padding: 4px 12px 4px 8px;
border-radius: 40px;
border: 1px solid #b8d0a8;
box-shadow: inset 0 1px 3px rgba(255, 255, 200, 0.15);
font-size: 0.85rem;
transition: 0.2s;
}
.stats span:hover {
background: rgba(0, 0, 0, 0.5);
transform: scale(1.02);
}
.selected-seed {
display: flex;
align-items: center;
gap: 10px;
background: rgba(0, 0, 0, 0.35);
padding: 4px 18px 4px 14px;
border-radius: 40px;
color: #f9f3d9;
font-weight: 600;
font-size: 0.95rem;
border: 1px solid #d4e8c4;
box-shadow: inset 0 1px 4px rgba(255, 255, 200, 0.15);
cursor: pointer;
transition: 0.2s;
}
.selected-seed:hover {
background: rgba(0, 0, 0, 0.5);
border-color: #f5d742;
}
.selected-seed #selectedSeedDisplay {
background: #2d3d1f;
padding: 2px 16px 2px 12px;
border-radius: 30px;
font-size: 0.95rem;
min-width: 80px;
text-align: center;
border: 1px solid #b8d0a8;
}
.tabs {
display: flex;
gap: 8px;
margin-bottom: 14px;
flex-wrap: wrap;
}
.tab-btn {
background: rgba(30, 40, 20, 0.6);
border: 2px solid #6d8f5a;
color: #f9f3d9;
font-weight: 700;
padding: 8px 22px;
border-radius: 40px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.2s;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
flex: 1 0 auto;
text-align: center;
}
.tab-btn:hover {
background: rgba(60, 80, 40, 0.7);
border-color: #b8d0a8;
transform: translateY(-2px);
}
.tab-btn.active {
background: #4d7a3a;
border-color: #f5d742;
box-shadow: 0 0 20px #f5d74244, inset 0 -4px 0 #b8921a;
transform: scale(1.02);
}
.tab-content {
display: none;
background: rgba(20, 30, 15, 0.5);
backdrop-filter: blur(2px);
padding: 16px 18px;
border-radius: 32px;
border: 1px solid #8aa87a;
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.3);
margin-top: 4px;
animation: fadeIn 0.3s ease;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
#farm {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
max-width: 600px;
width: 100%;
margin: 0 auto;
}
.plot {
aspect-ratio: 1 / 1;
background: #6b4f34;
background-image: radial-gradient(circle at 30% 30%, #8b6b4a, #4d351f);
border-radius: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 2.6rem;
cursor: pointer;
transition: transform 0.1s, box-shadow 0.3s, border-color 0.3s;
border: 3px solid #4d351f;
box-shadow: inset 0 -6px 0 #2f1f0e, 0 6px 10px rgba(0, 0, 0, 0.4);
position: relative;
min-height: 90px;
color: #f9f3d9;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.6);
}
.plot:active {
transform: scale(0.93);
}
.plot.empty {
background: #7a5d3e;
background-image: radial-gradient(circle at 30% 30%, #9a7a56, #4d351f);
color: #b8a080;
font-size: 2.2rem;
}
.plot.growing {
border-color: #7daa6a;
box-shadow: inset 0 -6px 0 #2f5a1f, 0 0 12px #7daa6a88;
animation: grow-pulse 2s infinite;
}
@keyframes grow-pulse {
0%,
100% {
box-shadow: inset 0 -6px 0 #2f5a1f, 0 0 12px #7daa6a88;
}
50% {
box-shadow: inset 0 -6px 0 #2f5a1f, 0 0 20px #7daa6abb;
}
}
.plot.ready {
border-color: #f5d742;
box-shadow: inset 0 -6px 0 #b8921a, 0 0 20px #f5d742bb, 0 0 40px #f5d74255;
animation: pulse-gold 1.2s infinite alternate;
}
.plot.plagued {
border-color: #aa4444;
box-shadow: inset 0 -6px 0 #6a2222, 0 0 20px #aa444488;
animation: pulse-red 0.8s infinite alternate;
}
@keyframes pulse-gold {
0% {
box-shadow: inset 0 -6px 0 #b8921a, 0 0 20px #f5d742bb, 0 0 40px #f5d74244;
}
100% {
box-shadow: inset 0 -6px 0 #b8921a, 0 0 30px #f5d742ee, 0 0 70px #f5d74277;
}
}
@keyframes pulse-red {
0% {
box-shadow: inset 0 -6px 0 #6a2222, 0 0 20px #aa444488;
}
100% {
box-shadow: inset 0 -6px 0 #6a2222, 0 0 40px #aa4444bb;
}
}
.plot .emoji {
line-height: 1;
margin-bottom: 2px;
font-size: 2.8rem;
transition: transform 0.2s;
}
.plot.ready .emoji {
animation: bounce-emoji 0.8s infinite alternate;
}
@keyframes bounce-emoji {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-10px);
}
}
.plot .progress-wrap {
width: 80%;
height: 6px;
background: rgba(0, 0, 0, 0.5);
border-radius: 10px;
overflow: hidden;
margin-top: 2px;
border: 1px solid #2f1f0e;
}
.plot .progress-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #8bc34a, #cddc39);
border-radius: 10px;
transition: width 0.3s;
}
.plot .time-left {
font-size: 0.6rem;
font-weight: 700;
background: rgba(0, 0, 0, 0.5);
padding: 0 8px;
border-radius: 12px;
margin-top: 2px;
color: #eee;
letter-spacing: 0.5px;
}
.plot .harvest-icon {
font-size: 1.2rem;
margin-top: 2px;
background: rgba(0, 0, 0, 0.3);
padding: 0 8px;
border-radius: 12px;
}
.plot .event-badge {
position: absolute;
top: -8px;
right: -8px;
font-size: 1.2rem;
background: rgba(0, 0, 0, 0.7);
border-radius: 50%;
padding: 2px;
line-height: 1;
border: 1px solid gold;
}
#shop-container {
display: flex;
flex-wrap: wrap;
gap: 24px 32px;
justify-content: center;
}
#shop-container>div {
flex: 1 1 220px;
min-width: 180px;
}
#shop-container h4 {
color: #f9f3d9;
font-size: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
border-bottom: 2px solid #b8d0a8;
padding-bottom: 6px;
margin-bottom: 12px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
}
.shop-item {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(0, 0, 0, 0.25);
padding: 6px 12px 6px 10px;
border-radius: 40px;
margin-bottom: 8px;
border: 1px solid #6d8f5a;
transition: all 0.15s;
cursor: pointer;
gap: 4px;
flex-wrap: wrap;
}
.shop-item:hover {
background: rgba(0, 0, 0, 0.4);
border-color: #b8d0a8;
transform: scale(1.02);
}
.shop-item.active {
border-color: #f5d742;
background: rgba(245, 215, 66, 0.2);
box-shadow: 0 0 16px #f5d74244;
}
.shop-item .info {
display: flex;
align-items: center;
gap: 6px;
color: #f9f3d9;
font-weight: 600;
font-size: 0.85rem;
flex: 1;
}
.shop-item .info .emoji {
font-size: 1.6rem;
}
.shop-item .info .count {
background: rgba(0, 0, 0, 0.4);
padding: 0 10px;
border-radius: 30px;
font-size: 0.7rem;
}
.shop-item .actions {
display: flex;
gap: 6px;
align-items: center;
}
.shop-item .actions button {
background: #3d5a2e;
border: none;
color: #f9f3d9;
font-weight: 700;
padding: 4px 14px;
border-radius: 40px;
cursor: pointer;
font-size: 0.7rem;
transition: 0.1s;
border: 1px solid #6d8f5a;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
white-space: nowrap;
}
.shop-item .actions button:active {
transform: scale(0.92);
}
.shop-item .actions button.buy {
background: #4d7a3a;
border-color: #8bc34a;
}
.shop-item .actions button.buy:hover {
background: #5d9a4a;
}
.shop-item .actions button.select {
background: #5a6a4a;
border-color: #b8d0a8;
}
.shop-item .actions button.select:hover {
background: #7a9a6a;
}
.shop-item .actions button.owned {
background: #2a3a2a;
border-color: #6d8f5a;
color: #aac09a;
cursor: default;
}
.shop-item .actions button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.mission-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.mission-item {
background: rgba(0, 0, 0, 0.35);
border-radius: 30px;
padding: 14px 18px;
display: flex;
align-items: center;
justify-content: space-between;
border-left: 6px solid #f5d742;
flex-wrap: wrap;
gap: 8px;
transition: 0.2s;
}
.mission-item.completed {
border-left-color: #8bc34a;
opacity: 0.7;
}
.mission-item .desc {
color: #f9f3d9;
font-weight: 600;
font-size: 0.9rem;
flex: 2;
}
.mission-item .progress {
color: #b8d0a8;
font-size: 0.8rem;
background: rgba(0, 0, 0, 0.3);
padding: 2px 14px;
border-radius: 30px;
min-width: 60px;
text-align: center;
}
.mission-item .reward {
color: #f5d742;
font-weight: 700;
font-size: 0.85rem;
padding: 2px 10px;
background: rgba(0, 0, 0, 0.2);
border-radius: 20px;
}
.mission-item .status {
font-size: 1.2rem;
min-width: 30px;
text-align: center;
}
.animal-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 12px;
margin-top: 8px;
}
.animal-card {
background: rgba(0, 0, 0, 0.3);
border-radius: 20px;
padding: 12px;
text-align: center;
border: 2px solid #6d8f5a;
transition: 0.2s;
}
.animal-card .animal-emoji {
font-size: 2.8rem;
}
.animal-card .animal-name {
color: #f9f3d9;
font-weight: 600;
font-size: 0.9rem;
}
.animal-card .animal-prod {
color: #b8d0a8;
font-size: 0.8rem;
font-weight: 600;
}
.animal-card .animal-prod span {
color: #f5d742;
}
.animal-card button {
margin-top: 6px;
background: #4d7a3a;
border: none;
color: #f9f3d9;
font-weight: 700;
padding: 4px 16px;
border-radius: 40px;
cursor: pointer;
font-size: 0.7rem;
border: 1px solid #8bc34a;
transition: 0.1s;
}
.animal-card button:active {
transform: scale(0.92);
}
.animal-card button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.animal-card .animal-timer {
font-size: 0.7rem;
color: #f9f3d9;
background: rgba(0, 0, 0, 0.3);
padding: 2px 8px;
border-radius: 20px;
display: inline-block;
margin-top: 4px;
}
.achievement-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 12px;
}
.achievement-card {
background: rgba(0, 0, 0, 0.3);
border-radius: 20px;
padding: 12px;
text-align: center;
border: 2px solid #4d6a3a;
transition: 0.2s;
}
.achievement-card.unlocked {
border-color: #f5d742;
background: rgba(245, 215, 66, 0.1);
box-shadow: 0 0 20px #f5d74233;
}
.achievement-card .ach-emoji {
font-size: 2.5rem;
}
.achievement-card .ach-name {
color: #f9f3d9;
font-weight: 600;
font-size: 0.85rem;
}
.achievement-card .ach-desc {
color: #b8d0a8;
font-size: 0.65rem;
}
.achievement-card .ach-status {
font-size: 0.75rem;
margin-top: 4px;
}
.event-banner {
background: rgba(0, 0, 0, 0.5);
border-radius: 40px;
padding: 8px 20px;
text-align: center;
color: #f9f3d9;
font-weight: 700;
font-size: 0.9rem;
border: 2px solid #f5d742;
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
animation: glow-banner 1.5s infinite alternate;
}
@keyframes glow-banner {
0% {
box-shadow: 0 0 10px #f5d74244;
}
100% {
box-shadow: 0 0 30px #f5d74288;
}
}
.event-banner .timer {
background: rgba(0, 0, 0, 0.4);
padding: 2px 14px;
border-radius: 40px;
font-family: monospace;
font-size: 1rem;
}
.prestige-section {
margin-top: 16px;
padding: 14px;
background: rgba(0, 0, 0, 0.4);
border-radius: 30px;
text-align: center;
border: 2px solid #f5d742;
transition: 0.3s;
}
.prestige-section:hover {
box-shadow: 0 0 30px #f5d74233;
}
.prestige-info {
color: #f9f3d9;
font-weight: 600;
margin-bottom: 8px;
font-size: 0.9rem;
}
.prestige-info .highlight {
color: #f5d742;
}
.prestige-progress {
width: 100%;
height: 8px;
background: rgba(0, 0, 0, 0.4);
border-radius: 10px;
overflow: hidden;
margin: 6px 0 10px 0;
border: 1px solid #6d8f5a;
}
.prestige-progress .bar {
height: 100%;
background: linear-gradient(90deg, #f5d742, #f5a742);
border-radius: 10px;
transition: width 0.3s;
width: 0%;
}
.prestige-section button {
background: #7a4a3a;
border: none;
color: #f9f3d9;
font-weight: 700;
padding: 10px 30px;
border-radius: 40px;
cursor: pointer;
font-size: 1rem;
border: 2px solid #b8921a;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.4);
transition: 0.15s;
}
.prestige-section button:active {
transform: scale(0.94);
}
.prestige-section button:not(:disabled):hover {
background: #9a5a4a;
border-color: #f5d742;
box-shadow: 0 0 30px #f5d74255;
}
#notification {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(20, 30, 10, 0.95);
backdrop-filter: blur(6px);
padding: 12px 28px;
border-radius: 60px;
color: #f9f3d9;
font-weight: 700;
font-size: 1.1rem;
border: 2px solid #f5d742;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8);
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease, transform 0.3s ease;
transform: translateX(-50%) translateY(-20px);
z-index: 999;
white-space: nowrap;
}
#notification.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
@media (max-width: 750px) {
#app {
padding: 12px;
}
header {
flex-direction: column;
align-items: stretch;
gap: 8px;
padding: 10px;
}
.stats {
justify-content: center;
font-size: 0.8rem;
gap: 6px;
}
.selected-seed {
justify-content: center;
font-size: 0.85rem;
}
#farm {
gap: 8px;
padding: 8px;
}
.plot {
font-size: 2rem;
min-height: 70px;
}
.plot .emoji {
font-size: 2.2rem;
}
.tab-btn {
font-size: 0.75rem;
padding: 6px 12px;
flex: 1;
}
#shop-container {
flex-direction: column;
gap: 12px;
}
#shop-container>div {
flex: 1 1 auto;
}
.shop-item {
padding: 4px 8px;
font-size: 0.75rem;
}
.mission-item {
padding: 8px 12px;
font-size: 0.75rem;
}
#notification {
font-size: 0.9rem;
padding: 8px 16px;
top: 8px;
white-space: normal;
max-width: 90%;
}
.animal-grid {
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
}
}
@media (max-width: 450px) {
#farm {
gap: 5px;
padding: 4px;
}
.plot {
min-height: 55px;
font-size: 1.6rem;
border-radius: 14px;
}
.plot .emoji {
font-size: 1.8rem;
}
.plot .time-left {
font-size: 0.5rem;
}
.achievement-grid {
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
}
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.2);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #6d8f5a;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #8aa87a;
}
</style>
</head>
<body>
<div id="notification"></div>
<div id="app">
<header>
<div class="stats">
<span>🪙 <span id="moneyDisplay">100</span></span>
<span>⭐ Nivel <span id="levelDisplay">1</span></span>
<span>🌾 <span id="harvestDisplay">0</span></span>
<span>🏆 <span id="achCountDisplay">0</span></span>
<span>🔁 Prestigio <span id="prestigeDisplay">0</span></span>
<span>📈 x<span id="multiplierDisplay">1.00</span></span>
</div>
<div class="selected-seed" id="selectedSeedContainer">
🌱 Semilla:
<span id="selectedSeedDisplay">Ninguna</span>
</div>
</header>
<div class="tabs">
<button class="tab-btn active" data-tab="tab-farm">🌾 Granja</button>
<button class="tab-btn" data-tab="tab-shop">🛒 Tienda</button>
<button class="tab-btn" data-tab="tab-animals">🐄 Animales</button>
<button class="tab-btn" data-tab="tab-missions">📜 Misiones</button>
<button class="tab-btn" data-tab="tab-achievements">🏅 Logros</button>
</div>
<div id="tab-farm" class="tab-content active">
<div id="eventBanner" class="event-banner" style="display:none;"></div>
<div id="farm"></div>
<div class="prestige-section">
<div class="prestige-info">
🔁 <span class="highlight">Prestigio</span> · Requiere nivel <span id="prestigeReqLevel">8</span> y <span id="prestigeReqMoney">800</span>🪙 disponibles (actual: nivel <span id="prestigeCurLevel">1</span>, <span id="prestigeCurMoney">100</span>🪙)
<div class="prestige-progress"><div class="bar" id="prestigeProgressBar" style="width:0%"></div></div>
</div>
<button id="prestigeBtn">✨ Reiniciar con prestigio (+10% ventas)</button>
</div>
</div>
<div id="tab-shop" class="tab-content">
<div id="shop-container">
<div class="seeds-section">
<h4>🌿 Semillas</h4>
<div id="seedShop"></div>
</div>
<div class="upgrades-section">
<h4>⚡ Mejoras</h4>
<div id="upgradeShop"></div>
</div>
<div class="special-section">
<h4>🌟 Especiales</h4>
<div id="specialShop"></div>
</div>
</div>
</div>
<div id="tab-animals" class="tab-content">
<h4 style="color:#f9f3d9; margin-bottom:12px;">🐄 Animales de granja (ingresos pasivos)</h4>
<div id="animalShop" class="animal-grid"></div>
<div style="margin-top:16px; color:#b8d0a8; font-size:0.8rem; text-align:center;">Los animales generan ingresos cada cierto tiempo. ¡Recógelos!</div>
</div>
<div id="tab-missions" class="tab-content">
<div id="missionList" class="mission-list"></div>
</div>
<div id="tab-achievements" class="tab-content">
<div id="achievementGrid" class="achievement-grid"></div>
</div>
</div>
<script>
// ============================================================
// ESTADO DEL JUEGO (v6.2 - Animales permanentes)
// ============================================================
const SEED_TYPES = ['carrot', 'tomato', 'wheat', 'corn', 'strawberry', 'pumpkin'];
const state = {
money: 100,
level: 1,
harvestCount: 0,
totalEarned: 0,
prestigeLevel: 0,
selectedSeed: null,
seeds: {
carrot: { name: 'Zanahoria', cost: 5, growTime: 4, sellPrice: 8, emoji: '🥕', count: 0 },
tomato: { name: 'Tomate', cost: 15, growTime: 7, sellPrice: 22, emoji: '🍅', count: 0 },
wheat: { name: 'Trigo', cost: 35, growTime: 14, sellPrice: 55, emoji: '🌾', count: 0 },
corn: { name: 'Maíz', cost: 80, growTime: 20, sellPrice: 120, emoji: '🌽', count: 0 },
strawberry: { name: 'Fresa', cost: 25, growTime: 6, sellPrice: 38, emoji: '🍓', count: 0 },
pumpkin: { name: 'Calabaza', cost: 70, growTime: 18, sellPrice: 110, emoji: '🎃', count: 0 }
},
upgrades: {
irrigation: { name: 'Riego Automático', cost: 60, owned: false, desc: '⏱️ -20% tiempo' },
fertilizer: { name: 'Fertilizante', cost: 120, owned: false, desc: '💰 +30% venta' },
harvester: { name: 'Cosechadora', cost: 220, owned: false, desc: '🎲 20% doble' },
scarecrow: { name: 'Espantapájaros', cost: 150, owned: false, desc: '🛡️ -50% plagas' },
greenhouse: { name: 'Invernadero', cost: 350, owned: false, desc: '🌱 +20% velocidad' }
},
specialUpgrades: {
goldenShovel: { name: 'Pala Dorada', cost: 500, owned: false, desc: '✨ +10% cosecha extra' },
seedMaker: { name: 'Fábrica de Semillas', cost: 800, owned: false, desc: '🌱 +1 semilla por compra' },
luckyClover: { name: 'Trébol de la Suerte', cost: 1200, owned: false, desc: '🍀 +5% doble cosecha' }
},
animals: {
cow: { name: 'Vaca', emoji: '🐄', cost: 300, income: 45, interval: 8, count: 0, timer: 0,
produces: '🥛' },
chicken: { name: 'Gallina', emoji: '🐔', cost: 150, income: 25, interval: 6, count: 0, timer: 0,
produces: '🥚' },
sheep: { name: 'Oveja', emoji: '🐑', cost: 500, income: 70, interval: 12, count: 0, timer: 0,
produces: '🧶' }
},
plots: Array(16).fill().map(() => ({
state: 'empty',
seedType: null,
timeLeft: 0,
totalTime: 0,
plagued: false
})),
activeEvent: null,
missions: [],
achievements: {
firstHarvest: { name: 'Primera Cosecha', emoji: '🌱', desc: 'Cosecha tu primer cultivo', unlocked: false },
harvest100: { name: 'Cosechador', emoji: '🧑🌾', desc: 'Cosecha 100 cultivos', unlocked: false },
harvest1000: { name: 'Super Cosechador', emoji: '🚜', desc: 'Cosecha 1000 cultivos', unlocked: false },
money1000: { name: 'Rico', emoji: '💰', desc: 'Gana 1000 monedas en total', unlocked: false },
allSeeds: { name: 'Coleccionista', emoji: '🌿', desc: 'Compra al menos 1 de cada semilla', unlocked: false },
fullUpgrades: { name: 'Granjero Pro', emoji: '🏆', desc: 'Compra todas las mejoras básicas', unlocked: false },
level10: { name: 'Experto', emoji: '⭐', desc: 'Alcanza el nivel 10', unlocked: false },
prestige1: { name: 'Renacido', emoji: '🔁', desc: 'Realiza tu primer prestigio', unlocked: false },
specialUpgrades: { name: 'Leyenda', emoji: '🌟', desc: 'Compra todas las mejoras especiales', unlocked: false },
money10000: { name: 'Magnate', emoji: '💎', desc: 'Gana 10000 monedas en total', unlocked: false },
firstAnimal: { name: 'Granjero Animal', emoji: '🐄', desc: 'Compra tu primer animal', unlocked: false },
allAnimals: { name: 'Zoológico', emoji: '🐾', desc: 'Tiene al menos 1 de cada animal', unlocked: false }
},
seedsBought: new Set(),
totalPlanted: 0,
totalHarvested: 0,
totalMoneyEarned: 0,
missionStats: {
totalHarvests: 0,
totalPlants: 0,
totalMoney: 0,
totalUpgrades: 0,
totalAnimals: 0,
maxHarvestInOne: 0
}
};
// ============================================================
// REFERENCIAS DOM
// ============================================================
const farmEl = document.getElementById('farm');
const moneyDisplay = document.getElementById('moneyDisplay');
const levelDisplay = document.getElementById('levelDisplay');
const harvestDisplay = document.getElementById('harvestDisplay');
const achCountDisplay = document.getElementById('achCountDisplay');
const prestigeDisplay = document.getElementById('prestigeDisplay');
const multiplierDisplay = document.getElementById('multiplierDisplay');
const selectedSeedDisplay = document.getElementById('selectedSeedDisplay');
const seedShopEl = document.getElementById('seedShop');
const upgradeShopEl = document.getElementById('upgradeShop');
const specialShopEl = document.getElementById('specialShop');
const animalShopEl = document.getElementById('animalShop');
const missionListEl = document.getElementById('missionList');
const achievementGridEl = document.getElementById('achievementGrid');
const eventBannerEl = document.getElementById('eventBanner');
const notificationEl = document.getElementById('notification');
const prestigeBtn = document.getElementById('prestigeBtn');
const prestigeReqLevel = document.getElementById('prestigeReqLevel');
const prestigeReqMoney = document.getElementById('prestigeReqMoney');
const prestigeCurLevel = document.getElementById('prestigeCurLevel');
const prestigeCurMoney = document.getElementById('prestigeCurMoney');
const prestigeProgressBar = document.getElementById('prestigeProgressBar');
let notifTimeout = null;
// ============================================================
// REQUISITOS DE PRESTIGIO
// ============================================================
function getPrestigeRequirements() {
const level = 8 + state.prestigeLevel * 4;
const money = 800 + state.prestigeLevel * 800;
return { level, money };
}
// ============================================================
// FUNCIONES AUXILIARES
// ============================================================
function notify(msg, duration = 1800) {
notificationEl.textContent = msg;
notificationEl.classList.add('show');
clearTimeout(notifTimeout);
notifTimeout = setTimeout(() => {
notificationEl.classList.remove('show');
}, duration);
}
function getGrowTimeMultiplier() {
let m = 1;
if (state.upgrades.irrigation.owned) m *= 0.8;
if (state.upgrades.greenhouse.owned) m *= 0.8;
if (state.activeEvent && state.activeEvent.type === 'rain') m *= 0.5;
return Math.max(0.2, m);
}
function getSellPriceMultiplier() {
let m = 1;
if (state.upgrades.fertilizer.owned) m *= 1.3;
m *= (1 + (state.level - 1) * 0.08);
m *= (1 + state.prestigeLevel * 0.10);
if (state.activeEvent && state.activeEvent.type === 'market') m *= 1.4;
return m;
}
function getDoubleChance() {
let chance = state.upgrades.harvester.owned ? 0.20 : 0.05;
if (state.specialUpgrades.luckyClover.owned) chance += 0.05;
return chance;
}
function getExtraHarvestBonus() {
return state.specialUpgrades.goldenShovel.owned ? 0.10 : 0;
}
function getPlagueChance() {
let base = 0.10;
if (state.upgrades.scarecrow.owned) base *= 0.5;
if (state.activeEvent && state.activeEvent.type === 'plague') base += 0.4;
return Math.min(base, 0.6);
}
function getSeedCount(seedType) { return state.seeds[seedType].count; }
function addSeed(seedType, n = 1) {
state.seeds[seedType].count += n;
if (state.specialUpgrades.seedMaker.owned && n > 0) {
state.seeds[seedType].count += 1;
}
}
function removeSeed(seedType, n = 1) {
if (state.seeds[seedType].count >= n) {
state.seeds[seedType].count -= n;
return true;
}
return false;
}
function getEffectiveGrowTime(seedType) {
return Math.max(1, Math.ceil(state.seeds[seedType].growTime * getGrowTimeMultiplier()));
}
function getEffectiveSellPrice(seedType) {
return Math.floor(state.seeds[seedType].sellPrice * getSellPriceMultiplier());
}
// ============================================================
// LÓGICA DEL JUEGO
// ============================================================
function plantSeed(plotIndex) {
const plot = state.plots[plotIndex];
if (plot.state !== 'empty') return false;
if (!state.selectedSeed) { notify('🌱 Selecciona una semilla primero'); return false; }
const seedType = state.selectedSeed;
if (getSeedCount(seedType) <= 0) { notify('❌ No tienes semillas de ' + state.seeds[seedType].name); return false; }
if (!removeSeed(seedType, 1)) return false;
let plagued = Math.random() < getPlagueChance();
plot.state = 'growing';
plot.seedType = seedType;
plot.totalTime = getEffectiveGrowTime(seedType);
plot.timeLeft = plot.totalTime;
plot.plagued = plagued;
state.totalPlanted += 1;
state.missionStats.totalPlants += 1;
renderFarm();
renderShop();
notify('🌱 Plantaste ' + state.seeds[seedType].name + (plagued ? ' ⚠️ (con plaga)' : ''));
updateMissions('plant', 1);
return true;
}
function harvestPlot(plotIndex) {
const plot = state.plots[plotIndex];
if (plot.state !== 'ready') return false;
const seedType = plot.seedType;
if (plot.plagued) {
plot.state = 'empty';
plot.seedType = null;
plot.timeLeft = 0;
plot.totalTime = 0;
plot.plagued = false;
notify('🐛 ¡La plaga arruinó la cosecha!', 1500);
renderFarm();
return false;
}
let price = getEffectiveSellPrice(seedType);
let doubleHarvest = false;
if (Math.random() < getDoubleChance()) {
price *= 2;
doubleHarvest = true;
}
let extra = 0;
if (Math.random() < getExtraHarvestBonus()) {
extra = Math.floor(price * 0.3);
price += extra;
}
state.money += price;
state.totalEarned += price;
state.harvestCount += 1;
state.totalHarvested += 1;
state.totalMoneyEarned += price;
state.missionStats.totalHarvests += 1;
state.missionStats.totalMoney += price;
if (price > state.missionStats.maxHarvestInOne) {
state.missionStats.maxHarvestInOne = price;
}
const newLevel = Math.floor(state.harvestCount / 8) + 1;
if (newLevel > state.level) {
state.level = newLevel;
notify('🎉 ¡Subiste al nivel ' + state.level + '! +8% de venta');
checkAchievements();
updatePrestigeInfo();
}
plot.state = 'empty';
plot.seedType = null;
plot.timeLeft = 0;
plot.totalTime = 0;
plot.plagued = false;
let msg = '💰 Cosechaste ' + price + ' 🪙';
if (doubleHarvest) msg += ' (¡DOBLE!)';
if (extra > 0) msg += ' (+' + extra + ' extra)';
notify(msg);
renderFarm();
renderShop();
updateStats();
checkAchievements();
updateMissions('harvest', 1, seedType);
updateMissions('money', price);
return true;
}
// ============================================================
// ANIMALES
// ============================================================
function buyAnimal(animalKey) {
const animal = state.animals[animalKey];
if (state.money < animal.cost) { notify('❌ Dinero insuficiente'); return; }
state.money -= animal.cost;
animal.count += 1;
animal.timer = animal.interval;
state.missionStats.totalAnimals += 1;
updateMissions('animal', 1);
renderAnimals();
renderShop();
updateStats();
checkAchievements();
notify('🐄 Compraste un ' + animal.name);
saveGame();
}
function tickAnimals() {
for (const key of Object.keys(state.animals)) {
const animal = state.animals[key];
if (animal.count > 0) {
animal.timer -= 1;
if (animal.timer <= 0) {
const income = Math.floor(animal.income * animal.count * (1 + state.prestigeLevel * 0.05));
state.money += income;
state.totalEarned += income;
state.totalMoneyEarned += income;
state.missionStats.totalMoney += income;
animal.timer = animal.interval;
notify('🐄 ' + animal.name + ' produjo ' + income + '🪙', 1200);
renderAnimals();
updateStats();
updateMissions('money', income);
checkAchievements();
}
}
}
renderAnimals();
}
function renderAnimals() {
animalShopEl.innerHTML = '';
for (const [key, animal] of Object.entries(state.animals)) {
const card = document.createElement('div');
card.className = 'animal-card';
const canBuy = state.money >= animal.cost;
const timerText = animal.count > 0 ? `⏱️ ${animal.timer}s` : '—';
const totalIncome = animal.count * animal.income;
card.innerHTML = `
<div class="animal-emoji">${animal.emoji}</div>
<div class="animal-name">${animal.name}</div>
<div class="animal-prod">${animal.produces} <span>+${animal.income}🪙</span> c/${animal.interval}s</div>
<div style="color:#f9f3d9;font-weight:700;">x${animal.count}</div>
<div class="animal-timer">${timerText}</div>
<button ${!canBuy ? 'disabled' : ''} data-action="buyAnimal" data-key="${key}">Comprar (${animal.cost}🪙)</button>
`;
animalShopEl.appendChild(card);
}
document.querySelectorAll('#animalShop .animal-card button').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const key = btn.dataset.key;
if (key) buyAnimal(key);
});
});
}
// ============================================================
// MISIONES
// ============================================================
function generateMissions() {
const templates = [
{ type: 'harvest', target: 8, seed: 'carrot', reward: 60, desc: 'Cosecha 8 zanahorias' },
{ type: 'harvest', target: 5, seed: 'tomato', reward: 75, desc: 'Cosecha 5 tomates' },
{ type: 'harvest', target: 4, seed: 'wheat', reward: 100, desc: 'Cosecha 4 trigos' },
{ type: 'harvest', target: 3, seed: 'corn', reward: 130, desc: 'Cosecha 3 maíces' },
{ type: 'harvest', target: 4, seed: 'strawberry', reward: 110, desc: 'Cosecha 4 fresas' },
{ type: 'harvest', target: 3, seed: 'pumpkin', reward: 150, desc: 'Cosecha 3 calabazas' },
{ type: 'money', target: 400, reward: 60, desc: 'Gana 400 monedas' },
{ type: 'money', target: 1000, reward: 120, desc: 'Gana 1000 monedas' },
{ type: 'money', target: 3000, reward: 250, desc: 'Gana 3000 monedas' },
{ type: 'plant', target: 15, reward: 50, desc: 'Planta 15 semillas' },
{ type: 'plant', target: 30, reward: 100, desc: 'Planta 30 semillas' },
{ type: 'animal', target: 2, reward: 80, desc: 'Compra 2 animales' },
{ type: 'animal', target: 5, reward: 180, desc: 'Compra 5 animales' },
{ type: 'upgrade', target: 3, reward: 100, desc: 'Compra 3 mejoras' },
{ type: 'upgrade', target: 6, reward: 200, desc: 'Compra 6 mejoras' },
{ type: 'totalHarvest', target: 40, reward: 80, desc: 'Cosecha 40 cultivos en total' },
{ type: 'totalHarvest', target: 100, reward: 160, desc: 'Cosecha 100 cultivos en total' },
{ type: 'bigHarvest', target: 200, reward: 150, desc: 'Obtén 200🪙 en una sola cosecha' },
{ type: 'bigHarvest', target: 500, reward: 300, desc: 'Obtén 500🪙 en una sola cosecha' }
];
const shuffled = templates.sort(() => Math.random() - 0.5);
const selected = shuffled.slice(0, 4).map(m => {
const bonus = 1 + state.prestigeLevel * 0.25;
return {
...m,
reward: Math.floor(m.reward * bonus),
progress: 0,
completed: false,
claimed: false
};
});
state.missions = selected;
renderMissions();
}
function updateMissions(action, amount, seedType = null) {
if (!state.missions || state.missions.length === 0) return;
let changed = false;
state.missions.forEach(mission => {
if (mission.completed || mission.claimed) return;
let progress = false;
if (mission.type === 'harvest' && action === 'harvest' && mission.seed === seedType) {
mission.progress += amount;
progress = true;
} else if (mission.type === 'money' && action === 'money') {
mission.progress += amount;
progress = true;
} else if (mission.type === 'plant' && action === 'plant') {
mission.progress += amount;
progress = true;
} else if (mission.type === 'animal' && action === 'animal') {
mission.progress += amount;
progress = true;
} else if (mission.type === 'upgrade' && action === 'upgrade') {
mission.progress += amount;
progress = true;
} else if (mission.type === 'totalHarvest' && action === 'harvest') {
mission.progress += amount;
progress = true;
} else if (mission.type === 'bigHarvest' && action === 'harvest') {
if (state.missionStats.maxHarvestInOne >= mission.target) {
mission.progress = mission.target;
progress = true;
}
}
if (progress && mission.progress >= mission.target) {
mission.completed = true;
mission.claimed = true;
state.money += mission.reward;
notify('✅ Misión completada: ' + mission.desc + ' (+' + mission.reward + '🪙)', 2000);
changed = true;
}
});
if (changed) {
renderMissions();
updateStats();
renderShop();
const allDone = state.missions.every(m => m.claimed);
if (allDone) {
setTimeout(() => {
generateMissions();
notify('📜 Nuevas misiones disponibles', 1500);
}, 2000);
}
}
}
function renderMissions() {
missionListEl.innerHTML = '';
if (!state.missions || state.missions.length === 0) {
missionListEl.innerHTML = '<p style="color:#b8d0a8;text-align:center;">No hay misiones activas</p>';
return;
}
state.missions.forEach(m => {
const div = document.createElement('div');
div.className = 'mission-item' + (m.claimed ? ' completed' : '');
let progressText = '';
if (m.type === 'bigHarvest') {
progressText = '🪙 ' + state.missionStats.maxHarvestInOne + '/' + m.target;
} else {
progressText = m.progress + '/' + m.target;
}
div.innerHTML = `
<span class="desc">${m.desc}</span>
<span class="progress">${progressText}</span>
<span class="reward">+${m.reward}🪙</span>
<span class="status">${m.claimed ? '✅' : '⏳'}</span>
`;
missionListEl.appendChild(div);
});
}
// ============================================================
// EVENTOS ALEATORIOS
// ============================================================
function triggerRandomEvent() {
if (state.activeEvent) return;
const events = [
{ type: 'rain', description: '🌧️ ¡Lluvia! Crecimiento -50%', duration: 12, effect: 'growth' },
{ type: 'market', description: '📈 ¡Mercado alcista! +40% precio', duration: 15, effect: 'price' },
{ type: 'plague', description: '🐛 ¡Plaga! +40% probabilidad de infección', duration: 10,
effect: 'plague' },
{ type: 'sun', description: '☀️ Día soleado: +20% velocidad', duration: 18, effect: 'growth' },
{ type: 'bonus', description: '🎁 ¡Lluvia de monedas! +100🪙', duration: 0, effect: 'bonus' }
];
const evt = events[Math.floor(Math.random() * events.length)];
if (evt.type === 'bonus') {
state.money += 100;
notify('🎁 ¡Bonificación! +100 monedas', 1500);
updateStats();
return;
}
state.activeEvent = {
type: evt.type,
description: evt.description,
timer: evt.duration,
effect: evt.effect
};
if (evt.type === 'plague') {
for (let i = 0; i < state.plots.length; i++) {
const p = state.plots[i];
if (p.state === 'growing' && Math.random() < 0.4) {
p.plagued = true;
}
}
renderFarm();
}
updateEventBanner();
notify('⚡ ' + evt.description, 2500);
}
function updateEventBanner() {
if (state.activeEvent) {
eventBannerEl.style.display = 'flex';
eventBannerEl.innerHTML = `
<span>${state.activeEvent.description}</span>
<span class="timer">⏱️ ${state.activeEvent.timer}s</span>
`;
} else {
eventBannerEl.style.display = 'none';
}
}
function tickEvents() {
if (state.activeEvent) {
state.activeEvent.timer -= 1;
if (state.activeEvent.timer <= 0) {
state.activeEvent = null;
updateEventBanner();
notify('⏳ El evento ha terminado', 1200);
renderFarm();
} else {
updateEventBanner();
}
} else {
if (Math.random() < 0.02) {
triggerRandomEvent();
}
}
}
// ============================================================
// PRESTIGIO (CON ANIMALES PERMANENTES)
// ============================================================
function updatePrestigeInfo() {
const req = getPrestigeRequirements();
prestigeCurLevel.textContent = state.level;
prestigeCurMoney.textContent = state.money;
prestigeReqLevel.textContent = req.level;
prestigeReqMoney.textContent = req.money;
const levelProgress = Math.min(100, (state.level / req.level) * 100);
const moneyProgress = Math.min(100, (state.money / req.money) * 100);
const avgProgress = Math.min(100, (levelProgress + moneyProgress) / 2);
prestigeProgressBar.style.width = avgProgress + '%';
}
window.doPrestige = function() {
console.log('Prestigio ejecutado. Nivel:', state.level, 'Dinero:', state.money);
const req = getPrestigeRequirements();
if (state.level < req.level) {
notify(`❌ Necesitas nivel ${req.level} (tienes ${state.level})`);
return;
}
if (state.money < req.money) {
notify(`❌ Necesitas ${req.money}🪙 disponibles (tienes ${state.money})`);
return;
}
// Guardar animales antes de resetear
const savedAnimals = JSON.parse(JSON.stringify(state.animals));
// Ejecutar prestigio
state.prestigeLevel += 1;
const savedAchievements = JSON.parse(JSON.stringify(state.achievements));
const savedSeedsBought = new Set(state.seedsBought);
state.money = 200;
state.level = 1;
state.harvestCount = 0;
state.totalEarned = 0;
state.totalPlanted = 0;
state.totalHarvested = 0;
state.totalMoneyEarned = 0;
state.selectedSeed = null;
for (const key of SEED_TYPES) {
state.seeds[key].count = 0;
}
for (const key of Object.keys(state.upgrades)) {
state.upgrades[key].owned = false;
}
for (const key of Object.keys(state.specialUpgrades)) {
state.specialUpgrades[key].owned = false;
}
// NO resetear animales: restaurar los guardados
state.animals = savedAnimals;
state.seedsBought = new Set();
for (let i = 0; i < state.plots.length; i++) {
state.plots[i] = { state: 'empty', seedType: null, timeLeft: 0, totalTime: 0, plagued: false };
}
state.activeEvent = null;
state.achievements = savedAchievements;
state.achievements.prestige1.unlocked = true;
state.seedsBought = savedSeedsBought;
state.missionStats = {
totalHarvests: 0,
totalPlants: 0,
totalMoney: 0,
totalUpgrades: 0,
totalAnimals: 0,
maxHarvestInOne: 0
};
addSeed('carrot', 12);
addSeed('tomato', 8);
addSeed('wheat', 5);
state.selectedSeed = 'carrot';
generateMissions();
renderFarm();
renderShop();
renderAnimals();
renderMissions();
renderAchievements();
updateStats();
updateSelectedDisplay();
updateEventBanner();
updatePrestigeInfo();
notify('🔁 ¡Prestigio completado! Nivel de prestigio: ' + state.prestigeLevel + ' (+' + (state.prestigeLevel * 10) +
'% venta)\nPróximo requisito: nivel ' + (8 + state.prestigeLevel * 4) + ' y ' + (800 + state.prestigeLevel *
800) + '🪙 disponibles', 4000);
saveGame();
};
// ============================================================
// LOGROS
// ============================================================
function checkAchievements() {
const a = state.achievements;
if (!a.firstHarvest.unlocked && state.harvestCount >= 1) { a.firstHarvest.unlocked = true;
notify('🏅 Logro: Primera Cosecha', 1500); }
if (!a.harvest100.unlocked && state.harvestCount >= 100) { a.harvest100.unlocked = true;
notify('🏅 Logro: Cosechador', 1500); }
if (!a.harvest1000.unlocked && state.harvestCount >= 1000) { a.harvest1000.unlocked = true;
notify('🏅 Logro: Super Cosechador', 1500); }
if (!a.money1000.unlocked && state.totalEarned >= 1000) { a.money1000.unlocked = true;
notify('🏅 Logro: Rico', 1500); }
if (!a.money10000.unlocked && state.totalEarned >= 10000) { a.money10000.unlocked = true;
notify('🏅 Logro: Magnate', 1500); }
const allSeedsBought = SEED_TYPES.every(t => state.seedsBought.has(t));
if (!a.allSeeds.unlocked && allSeedsBought) { a.allSeeds.unlocked = true;
notify('🏅 Logro: Coleccionista', 1500); }
const allUpgrades = Object.values(state.upgrades).every(u => u.owned);
if (!a.fullUpgrades.unlocked && allUpgrades) { a.fullUpgrades.unlocked = true;
notify('🏅 Logro: Granjero Pro', 1500); }
const allSpecial = Object.values(state.specialUpgrades).every(u => u.owned);
if (!a.specialUpgrades.unlocked && allSpecial) { a.specialUpgrades.unlocked = true;
notify('🏅 Logro: Leyenda', 1500); }
if (!a.level10.unlocked && state.level >= 10) { a.level10.unlocked = true;
notify('🏅 Logro: Experto', 1500); }
if (!a.prestige1.unlocked && state.prestigeLevel >= 1) { a.prestige1.unlocked = true;
notify('🏅 Logro: Renacido', 1500); }
let hasAnimal = false;
for (const key of Object.keys(state.animals)) {
if (state.animals[key].count > 0) hasAnimal = true;
}
if (!a.firstAnimal.unlocked && hasAnimal) { a.firstAnimal.unlocked = true;
notify('🏅 Logro: Granjero Animal', 1500); }
const allAnimals = Object.values(state.animals).every(a => a.count > 0);
if (!a.allAnimals.unlocked && allAnimals) { a.allAnimals.unlocked = true;
notify('🏅 Logro: Zoológico', 1500); }
renderAchievements();
updateStats();
}
function renderAchievements() {
achievementGridEl.innerHTML = '';
for (const [key, ach] of Object.entries(state.achievements)) {
const div = document.createElement('div');
div.className = 'achievement-card' + (ach.unlocked ? ' unlocked' : '');
div.innerHTML = `
<div class="ach-emoji">${ach.emoji}</div>
<div class="ach-name">${ach.name}</div>
<div class="ach-desc">${ach.desc}</div>
<div class="ach-status">${ach.unlocked ? '✅ Desbloqueado' : '🔒 Bloqueado'}</div>
`;
achievementGridEl.appendChild(div);
}
const count = Object.values(state.achievements).filter(a => a.unlocked).length;
achCountDisplay.textContent = count;
}
// ============================================================
// COMPRAS
// ============================================================
function buySeed(seedType) {
const seed = state.seeds[seedType];
if (state.money < seed.cost) { notify('❌ Dinero insuficiente'); return; }
state.money -= seed.cost;
addSeed(seedType, 1);
state.seedsBought.add(seedType);
if (!state.selectedSeed) state.selectedSeed = seedType;
renderShop();
updateStats();
updateSelectedDisplay();
checkAchievements();
notify('✅ Compraste 1 ' + seed.name);
}
function buyUpgrade(upgradeKey) {
const up = state.upgrades[upgradeKey];
if (up.owned) { notify('⚠️ Ya tienes esta mejora'); return; }
if (state.money < up.cost) { notify('❌ Dinero insuficiente'); return; }
state.money -= up.cost;
up.owned = true;
state.missionStats.totalUpgrades += 1;
updateMissions('upgrade', 1);
renderShop();
updateStats();
checkAchievements();
notify('⚡ Mejora "' + up.name + '" activada');
saveGame();
}
function buySpecialUpgrade(upgradeKey) {
const up = state.specialUpgrades[upgradeKey];
if (up.owned) { notify('⚠️ Ya tienes esta mejora'); return; }
if (state.money < up.cost) { notify('❌ Dinero insuficiente'); return; }
state.money -= up.cost;
up.owned = true;
state.missionStats.totalUpgrades += 1;
updateMissions('upgrade', 1);
renderShop();
updateStats();
checkAchievements();
notify('🌟 Mejora especial "' + up.name + '" activada');
saveGame();
}
function selectSeed(seedType) {
if (getSeedCount(seedType) > 0) {
state.selectedSeed = seedType;
updateSelectedDisplay();
renderShop();
notify('🔀 Semilla seleccionada: ' + state.seeds[seedType].name);
} else {
notify('❌ No tienes semillas de ' + state.seeds[seedType].name);
}
}
// ============================================================
// RENDERIZADO
// ============================================================
function renderFarm() {
farmEl.innerHTML = '';
state.plots.forEach((plot, index) => {
const div = document.createElement('div');
let cls = 'plot ' + plot.state;
if (plot.plagued && plot.state !== 'empty') cls += ' plagued';
div.className = cls;
div.dataset.index = index;
if (plot.state === 'empty') {
div.innerHTML = `<span class="emoji">🟫</span>`;
} else if (plot.state === 'growing') {
const emoji = state.seeds[plot.seedType].emoji;
const progress = (plot.totalTime > 0) ? ((plot.totalTime - plot.timeLeft) / plot.totalTime * 100) : 0;
div.innerHTML = `
<span class="emoji">${emoji}</span>
<div class="progress-wrap"><div class="progress-bar" style="width:${Math.min(100, progress)}%"></div></div>
<span class="time-left">⏱️ ${Math.ceil(plot.timeLeft)}s</span>
${plot.plagued ? '<span class="event-badge">🐛</span>' : ''}
`;
} else if (plot.state === 'ready') {
const emoji = state.seeds[plot.seedType].emoji;
div.innerHTML = `
<span class="emoji">${emoji}</span>
<span class="harvest-icon">✅</span>
${plot.plagued ? '<span class="event-badge">🐛</span>' : ''}
`;
}
div.addEventListener('click', () => {
if (plot.state === 'empty') {
plantSeed(index);
} else if (plot.state === 'ready') {
harvestPlot(index);
} else if (plot.state === 'growing') {
notify('⏳ Creciendo... ' + Math.ceil(plot.timeLeft) + 's restantes');
}
});
farmEl.appendChild(div);
});
}
function renderShop() {
seedShopEl.innerHTML = '';
SEED_TYPES.forEach(key => {
const seed = state.seeds[key];
const count = seed.count;
const canBuy = state.money >= seed.cost;
const isActive = (state.selectedSeed === key);
const item = document.createElement('div');
item.className = 'shop-item' + (isActive ? ' active' : '');
item.innerHTML = `
<div class="info">
<span class="emoji">${seed.emoji}</span>
<span>${seed.name}</span>
<span class="count">x${count}</span>
<span style="font-size:0.7rem;color:#b8d0a8;">💰${seed.cost}</span>
</div>
<div class="actions">
<button class="buy" ${!canBuy ? 'disabled' : ''} data-action="buySeed" data-key="${key}">Comprar</button>
<button class="select" ${count <= 0 ? 'disabled' : ''} data-action="selectSeed" data-key="${key}">Seleccionar</button>
</div>
`;
seedShopEl.appendChild(item);
});
upgradeShopEl.innerHTML = '';
for (const [key, up] of Object.entries(state.upgrades)) {
const canBuy = !up.owned && state.money >= up.cost;
const item = document.createElement('div');
item.className = 'shop-item' + (up.owned ? ' active' : '');
item.innerHTML = `
<div class="info">
<span>${up.name}</span>
<span style="font-size:0.7rem;color:#b8d0a8;">${up.desc}</span>
<span style="font-size:0.7rem;color:#f5d742;">💰${up.cost}</span>
</div>
<div class="actions">
${up.owned ? '<button class="owned" disabled>✅ Activada</button>' :
`<button class="buy" ${!canBuy ? 'disabled' : ''} data-action="buyUpgrade" data-key="${key}">Mejorar</button>`}
</div>
`;
upgradeShopEl.appendChild(item);
}
specialShopEl.innerHTML = '';
for (const [key, up] of Object.entries(state.specialUpgrades)) {
const canBuy = !up.owned && state.money >= up.cost;
const item = document.createElement('div');
item.className = 'shop-item' + (up.owned ? ' active' : '');
item.innerHTML = `
<div class="info">
<span>${up.name}</span>
<span style="font-size:0.7rem;color:#b8d0a8;">${up.desc}</span>
<span style="font-size:0.7rem;color:#f5d742;">💰${up.cost}</span>
</div>
<div class="actions">
${up.owned ? '<button class="owned" disabled>✅ Activada</button>' :
`<button class="buy" ${!canBuy ? 'disabled' : ''} data-action="buySpecial" data-key="${key}">Mejorar</button>`}
</div>
`;
specialShopEl.appendChild(item);
}
document.querySelectorAll('#seedShop .shop-item .actions button').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const action = btn.dataset.action;
const key = btn.dataset.key;
if (action === 'buySeed') buySeed(key);
else if (action === 'selectSeed') selectSeed(key);
});
});
document.querySelectorAll('#upgradeShop .shop-item .actions button').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const action = btn.dataset.action;
const key = btn.dataset.key;
if (action === 'buyUpgrade') buyUpgrade(key);
});
});
document.querySelectorAll('#specialShop .shop-item .actions button').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const action = btn.dataset.action;
const key = btn.dataset.key;
if (action === 'buySpecial') buySpecialUpgrade(key);
});
});
document.querySelectorAll('#seedShop .shop-item').forEach(item => {
item.addEventListener('click', (e) => {
if (e.target.tagName === 'BUTTON') return;
const key = item.querySelector('.buy')?.dataset.key;
if (key && getSeedCount(key) > 0) selectSeed(key);
else if (key) notify('❌ No tienes semillas de ' + state.seeds[key].name);
});
});
}
function updateStats() {
moneyDisplay.textContent = state.money;
levelDisplay.textContent = state.level;
harvestDisplay.textContent = state.harvestCount;
const unlocked = Object.values(state.achievements).filter(a => a.unlocked).length;
achCountDisplay.textContent = unlocked;
prestigeDisplay.textContent = state.prestigeLevel;
const mult = getSellPriceMultiplier();
multiplierDisplay.textContent = mult.toFixed(2);
updatePrestigeInfo();
}
function updateSelectedDisplay() {
if (state.selectedSeed && getSeedCount(state.selectedSeed) > 0) {
const seed = state.seeds[state.selectedSeed];
selectedSeedDisplay.textContent = seed.emoji + ' ' + seed.name;
} else {
selectedSeedDisplay.textContent = 'Ninguna';
if (state.selectedSeed) {
if (getSeedCount(state.selectedSeed) <= 0) state.selectedSeed = null;
}
}
}
// ============================================================
// PESTAÑAS
// ============================================================
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const tabId = btn.dataset.tab;
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
document.getElementById(tabId).classList.add('active');
});
});
// ============================================================
// GUARDADO / CARGA
// ============================================================
function saveGame() {
try {
const data = {
money: state.money,
level: state.level,
harvestCount: state.harvestCount,
totalEarned: state.totalEarned,
prestigeLevel: state.prestigeLevel,
selectedSeed: state.selectedSeed,
seeds: state.seeds,
upgrades: state.upgrades,
specialUpgrades: state.specialUpgrades,
animals: state.animals,
plots: state.plots,
achievements: state.achievements,
seedsBought: [...state.seedsBought],
missions: state.missions,
activeEvent: state.activeEvent,
totalPlanted: state.totalPlanted,
totalHarvested: state.totalHarvested,
totalMoneyEarned: state.totalMoneyEarned,
missionStats: state.missionStats
};
localStorage.setItem('farmGameSave_v6', JSON.stringify(data));
} catch (e) { /* ignore */ }
}
function loadGame() {
try {
const raw = localStorage.getItem('farmGameSave_v6');
if (!raw) return false;
const data = JSON.parse(raw);
state.money = data.money ?? 100;
state.level = data.level ?? 1;
state.harvestCount = data.harvestCount ?? 0;
state.totalEarned = data.totalEarned ?? 0;
state.prestigeLevel = data.prestigeLevel ?? 0;
state.selectedSeed = data.selectedSeed ?? null;
if (data.seeds) {
for (const key of SEED_TYPES) {
if (data.seeds[key]) state.seeds[key].count = data.seeds[key].count ?? 0;
}
}
if (data.upgrades) {
for (const key of Object.keys(state.upgrades)) {
if (data.upgrades[key]) state.upgrades[key].owned = data.upgrades[key].owned ?? false;
}
}
if (data.specialUpgrades) {
for (const key of Object.keys(state.specialUpgrades)) {
if (data.specialUpgrades[key]) state.specialUpgrades[key].owned = data.specialUpgrades[key].owned ??
false;
}
}
if (data.animals) {
for (const key of Object.keys(state.animals)) {
if (data.animals[key]) {
state.animals[key].count = data.animals[key].count ?? 0;
state.animals[key].timer = data.animals[key].timer ?? 0;
}
}
}
if (data.plots && data.plots.length === 16) {
for (let i = 0; i < 16; i++) {
const p = data.plots[i];
state.plots[i].state = p.state ?? 'empty';
state.plots[i].seedType = p.seedType ?? null;
state.plots[i].timeLeft = p.timeLeft ?? 0;
state.plots[i].totalTime = p.totalTime ?? 0;
state.plots[i].plagued = p.plagued ?? false;
}
}
if (data.achievements) {
for (const key of Object.keys(state.achievements)) {
if (data.achievements[key]) state.achievements[key].unlocked = data.achievements[key].unlocked ??
false;
}
}
if (data.seedsBought) state.seedsBought = new Set(data.seedsBought);
if (data.missions) state.missions = data.missions;
else generateMissions();
if (data.activeEvent) state.activeEvent = data.activeEvent;
if (data.totalPlanted) state.totalPlanted = data.totalPlanted;
if (data.totalHarvested) state.totalHarvested = data.totalHarvested;
if (data.totalMoneyEarned) state.totalMoneyEarned = data.totalMoneyEarned;
if (data.missionStats) state.missionStats = data.missionStats;
return true;
} catch (e) { return false; }
}
setInterval(saveGame, 8000);
window.addEventListener('beforeunload', saveGame);
// ============================================================
// INICIO
// ============================================================
function init() {
const loaded = loadGame();
if (!loaded) {
addSeed('carrot', 8);
addSeed('tomato', 5);
addSeed('wheat', 3);
state.selectedSeed = 'carrot';
generateMissions();
notify('🌾 ¡Bienvenido a la Granja Épica!', 3000);
} else {
notify('📂 Partida cargada', 1200);
if (!state.missions || state.missions.length === 0) generateMissions();
renderFarm();
}
renderFarm();
renderShop();
renderAnimals();
renderMissions();
renderAchievements();
updateStats();
updateSelectedDisplay();
updateEventBanner();
updatePrestigeInfo();
prestigeBtn.disabled = false;
prestigeBtn.addEventListener('click', window.doPrestige);
setInterval(() => {
tickEvents();
tickAnimals();
let changed = false;
for (let i = 0; i < state.plots.length; i++) {
const plot = state.plots[i];
if (plot.state === 'growing') {
plot.timeLeft -= 1;
if (plot.timeLeft <= 0) {
plot.timeLeft = 0;
if (plot.plagued) {
plot.state = 'empty';
plot.seedType = null;
plot.plagued = false;
notify('🐛 ¡La plaga arruinó un cultivo!', 1200);
} else {
plot.state = 'ready';
}
changed = true;
}
}
}
if (changed) renderFarm();
updateStats();
updateSelectedDisplay();
updatePrestigeInfo();
}, 1000);
setInterval(() => {
renderShop();
renderAnimals();
renderMissions();
renderAchievements();
}, 5000);
}
init();
</script>
</body>
</html>Game Source: 🌾 Granja Épica - Animales permanentes
Creator: RapidFalcon68
Libraries: none
Complexity: complex (1921 lines, 76.0 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: granja-pica-definitiva-rapidfalcon68" to link back to the original. Then publish at arcadelab.ai/publish.