Add Statistics Panel feature
- Recreated full game foundation from previous session
- Added Statistics tab with detailed game metrics:
* Total Stardust Earned
* Current Production Rate (✨/sec)
* Total Clicks
* Click Power
* Time Played (auto-formatted)
* Buildings Owned
* Upgrades Purchased
* Achievements Unlocked
* Game Progress bar
- Fixed global production bonus calculation in game.js
- Added responsive CSS for stats panel
Files added: js/stats.js, dev-journal.md, README.md, JOURNAL.md
This commit is contained in:
+44
@@ -0,0 +1,44 @@
|
|||||||
|
# Cosmic Garden - Development Journal
|
||||||
|
|
||||||
|
This file tracks all development progress for the Cosmic Garden idle game.
|
||||||
|
|
||||||
|
## Session Log
|
||||||
|
|
||||||
|
### 2026-08-19 - Statistics Panel Feature
|
||||||
|
|
||||||
|
**What was worked on:**
|
||||||
|
- Recreated the full game foundation (workspace was cleared between sessions)
|
||||||
|
- Added a new Statistics tab to the side panel
|
||||||
|
- Fixed global production bonus calculation bug in game.js
|
||||||
|
|
||||||
|
**New Feature Details:**
|
||||||
|
The Statistics panel provides comprehensive game metrics:
|
||||||
|
- Total Stardust Earned
|
||||||
|
- Current Production Rate (✨/sec)
|
||||||
|
- Total Clicks
|
||||||
|
- Click Power
|
||||||
|
- Time Played (auto-formatted as HH:MM:SS)
|
||||||
|
- Buildings Owned count
|
||||||
|
- Upgrades Purchased (with total available)
|
||||||
|
- Achievements Unlocked (with total available)
|
||||||
|
- Game Progress bar (percentage based on stardust milestones)
|
||||||
|
|
||||||
|
**Files Changed:**
|
||||||
|
- `index.html` - Added Stats tab UI
|
||||||
|
- `js/stats.js` - New statistics tracking module
|
||||||
|
- `js/game.js` - Fixed global bonus calculation
|
||||||
|
- `css/game.css` - Added stats panel styling
|
||||||
|
|
||||||
|
**Testing:**
|
||||||
|
- Verified stats update correctly during gameplay
|
||||||
|
- Confirmed time formatting works properly
|
||||||
|
- Checked progress bar calculation
|
||||||
|
|
||||||
|
**Next Session Goals:**
|
||||||
|
- Add particle effects on click
|
||||||
|
- Implement mobile touch optimizations
|
||||||
|
- Consider adding prestige system
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last updated: 2026-08-19*
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Cosmic Garden 🌌🌱
|
||||||
|
|
||||||
|
A space gardening idle game built with vanilla HTML, CSS, and JavaScript.
|
||||||
|
|
||||||
|
## How to Play
|
||||||
|
|
||||||
|
1. Open `index.html` in your browser
|
||||||
|
2. Click the "Collect Stardust" button to earn stardust
|
||||||
|
3. Buy plants to auto-generate stardust
|
||||||
|
4. Purchase upgrades to boost production
|
||||||
|
5. Unlock achievements as you progress
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **12 Unique Plants** across 4 tiers (Star Sprout → Void Fern)
|
||||||
|
- **15+ Upgrades** with requirements and bonuses
|
||||||
|
- **20 Achievements** to unlock
|
||||||
|
- **Core Mechanics**: Click harvesting, auto-production, cost scaling (1.15x per level)
|
||||||
|
- **Save System**: Auto-save every 30s, offline progress (50% efficiency), export/import
|
||||||
|
- **Statistics Panel**: Track your progress with detailed metrics
|
||||||
|
- **UI**: Dark cosmic theme with purple/gold accents, responsive layout, animations
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
This project is being developed incrementally. Check `dev-journal.md` for the latest updates.
|
||||||
|
|
||||||
|
### Current Version
|
||||||
|
- Statistics panel added (2026-08-19)
|
||||||
|
|
||||||
|
### Planned Features
|
||||||
|
- Visual effects (particle effects on click)
|
||||||
|
- Mobile touch optimizations
|
||||||
|
- Prestige/reset system
|
||||||
|
- Sound effects and music
|
||||||
|
- Save game slot system
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Private project - owned by lullu
|
||||||
+602
@@ -0,0 +1,602 @@
|
|||||||
|
/* ============================================
|
||||||
|
COSMIC GARDEN - Main Stylesheet
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg-dark: #0a0a1a;
|
||||||
|
--bg-panel: #12122a;
|
||||||
|
--bg-card: #1a1a3e;
|
||||||
|
--bg-hover: #252550;
|
||||||
|
--accent-gold: #ffd700;
|
||||||
|
--accent-purple: #9b59b6;
|
||||||
|
--accent-blue: #3498db;
|
||||||
|
--accent-green: #2ecc71;
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: #a0a0c0;
|
||||||
|
--text-muted: #606080;
|
||||||
|
--border-color: #2a2a5a;
|
||||||
|
--success: #2ecc71;
|
||||||
|
--danger: #e74c3c;
|
||||||
|
--warning: #f39c12;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: var(--bg-dark);
|
||||||
|
color: var(--text-primary);
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
#game-header {
|
||||||
|
background: linear-gradient(135deg, var(--bg-panel) 0%, var(--bg-card) 100%);
|
||||||
|
padding: 20px 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid var(--border-color);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
#game-header h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
background: linear-gradient(90deg, var(--accent-gold), var(--accent-purple));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
#currency-display {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
#per-second {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Layout */
|
||||||
|
#main-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 280px 1fr 300px;
|
||||||
|
gap: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panels */
|
||||||
|
section, aside {
|
||||||
|
background: var(--bg-panel);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: var(--accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Click Panel */
|
||||||
|
#click-area {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#harvest-btn {
|
||||||
|
background: linear-gradient(135deg, var(--accent-purple) 0%, #7d3c98 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 150px;
|
||||||
|
height: 150px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.1s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-shadow: 0 4px 20px rgba(155, 89, 182, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#harvest-btn:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
box-shadow: 0 6px 30px rgba(155, 89, 182, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#harvest-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-top: 5px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#click-info, #total-clicks {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buildings Panel */
|
||||||
|
#buildings-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.building-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.building-card:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
border-color: var(--accent-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
.building-card.locked {
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.building-info h4 {
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.building-info .building-desc {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.building-info .building-production {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--accent-green);
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.building-stats {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.building-cost {
|
||||||
|
color: var(--accent-gold);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.building-count {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--accent-purple);
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-btn {
|
||||||
|
background: var(--accent-green);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-btn:hover:not(:disabled) {
|
||||||
|
background: #27ae60;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.buy-btn:disabled {
|
||||||
|
background: var(--text-muted);
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Side Panel Tabs */
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn {
|
||||||
|
flex: 1;
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn.active {
|
||||||
|
background: var(--accent-purple);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Upgrades */
|
||||||
|
#upgrades-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-card:hover:not(.purchased):not(.locked) {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
border-color: var(--accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-card.purchased {
|
||||||
|
opacity: 0.6;
|
||||||
|
border-color: var(--accent-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-card.locked {
|
||||||
|
opacity: 0.4;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-name {
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-desc {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upgrade-cost {
|
||||||
|
color: var(--accent-gold);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Achievements */
|
||||||
|
#achievements-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achievement {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achievement.unlocked {
|
||||||
|
border-color: var(--accent-gold);
|
||||||
|
background: linear-gradient(135deg, var(--bg-card) 0%, #2a2a5e 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.achievement.locked {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achievement-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.achievement-name {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stats Panel */
|
||||||
|
#stats-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar {
|
||||||
|
height: 8px;
|
||||||
|
background: var(--bg-dark);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--accent-purple), var(--accent-gold));
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
#game-footer {
|
||||||
|
background: var(--bg-panel);
|
||||||
|
padding: 15px 40px;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
border-top: 2px solid var(--border-color);
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#game-footer button {
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#game-footer button:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
border-color: var(--accent-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
#reset-btn:hover {
|
||||||
|
border-color: var(--danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
#last-saved {
|
||||||
|
margin-left: auto;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notifications */
|
||||||
|
#notifications {
|
||||||
|
position: fixed;
|
||||||
|
top: 80px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1000;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-left: 4px solid var(--accent-purple);
|
||||||
|
animation: slideIn 0.3s ease, fadeOut 0.3s ease 2.7s;
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.success {
|
||||||
|
border-left-color: var(--accent-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.warning {
|
||||||
|
border-left-color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.achievement {
|
||||||
|
border-left-color: var(--accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeOut {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal */
|
||||||
|
.modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--bg-panel);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 30px;
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
border: 2px solid var(--accent-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h3 {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 150px;
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons button {
|
||||||
|
flex: 1;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#confirm-import {
|
||||||
|
background: var(--accent-green);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#cancel-import {
|
||||||
|
background: var(--bg-card);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating Text Animation */
|
||||||
|
.float-text {
|
||||||
|
position: fixed;
|
||||||
|
pointer-events: none;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--accent-gold);
|
||||||
|
animation: floatUp 1s ease-out forwards;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes floatUp {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar Styling */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--accent-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
#main-layout {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#side-panel {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#main-layout {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#side-panel {
|
||||||
|
grid-column: span 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#game-header {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Dev Journal - Cosmic Garden
|
||||||
|
|
||||||
|
## 2026-08-19 - Statistics Panel Added
|
||||||
|
|
||||||
|
### What was done:
|
||||||
|
- Recreated the full Cosmic Garden game foundation (workspace was cleared between sessions)
|
||||||
|
- Added a new **Statistics tab** to the side panel with detailed game metrics
|
||||||
|
- Fixed global production bonus calculation in `game.js`
|
||||||
|
|
||||||
|
### New Feature: Statistics Panel
|
||||||
|
The stats panel tracks and displays:
|
||||||
|
- Total Stardust Earned
|
||||||
|
- Current Production Rate (✨/sec)
|
||||||
|
- Total Clicks
|
||||||
|
- Click Power
|
||||||
|
- Time Played (formatted as HH:MM:SS)
|
||||||
|
- Buildings Owned
|
||||||
|
- Upgrades Purchased (with total count)
|
||||||
|
- Achievements Unlocked (with total count)
|
||||||
|
- Game Progress bar (percentage based on total stardust milestones)
|
||||||
|
|
||||||
|
### Files Modified/Created:
|
||||||
|
- `index.html` - Added Stats tab button and content area
|
||||||
|
- `js/stats.js` - New file for statistics tracking and rendering
|
||||||
|
- `js/game.js` - Fixed global bonus calculation in `getProductionPerSecond()`
|
||||||
|
- `css/game.css` - Added styles for stats panel elements
|
||||||
|
|
||||||
|
### Next Up:
|
||||||
|
- Visual effects (particle effects on click)
|
||||||
|
- Mobile touch optimizations
|
||||||
|
- Prestige/reset system
|
||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Cosmic Garden</title>
|
||||||
|
<link rel="stylesheet" href="css/game.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="game-container">
|
||||||
|
<!-- Header -->
|
||||||
|
<header id="game-header">
|
||||||
|
<h1>🌌 Cosmic Garden</h1>
|
||||||
|
<div id="currency-display">
|
||||||
|
<span id="stardust-count">0</span> ✨ Stardust
|
||||||
|
</div>
|
||||||
|
<div id="per-second">
|
||||||
|
<span id="stardust-ps">0</span> ✨/sec
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Layout -->
|
||||||
|
<div id="main-layout">
|
||||||
|
<!-- Left Panel: Click Area -->
|
||||||
|
<section id="click-panel">
|
||||||
|
<h2>Harvest</h2>
|
||||||
|
<div id="click-area">
|
||||||
|
<button id="harvest-btn" title="Click to collect stardust!">
|
||||||
|
<span class="btn-icon">✨</span>
|
||||||
|
<span class="btn-text">Collect Stardust</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="click-info">
|
||||||
|
<p>Click power: <span id="click-power">1</span> ✨</p>
|
||||||
|
</div>
|
||||||
|
<div id="total-clicks">
|
||||||
|
Total clicks: <span id="total-click-count">0</span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Center Panel: Buildings -->
|
||||||
|
<section id="buildings-panel">
|
||||||
|
<h2>Plants</h2>
|
||||||
|
<div id="buildings-list"></div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Right Panel: Upgrades & Achievements -->
|
||||||
|
<aside id="side-panel">
|
||||||
|
<div class="tabs">
|
||||||
|
<button class="tab-btn active" data-tab="upgrades">Upgrades</button>
|
||||||
|
<button class="tab-btn" data-tab="achievements">Achievements</button>
|
||||||
|
<button class="tab-btn" data-tab="stats">Stats</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="upgrades-tab" class="tab-content active">
|
||||||
|
<h3>Upgrades</h3>
|
||||||
|
<div id="upgrades-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="achievements-tab" class="tab-content">
|
||||||
|
<h3>Achievements (0/20)</h3>
|
||||||
|
<div id="achievements-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="stats-tab" class="tab-content">
|
||||||
|
<h3>Statistics</h3>
|
||||||
|
<div id="stats-content"></div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer: Controls -->
|
||||||
|
<footer id="game-footer">
|
||||||
|
<button id="save-btn">💾 Save</button>
|
||||||
|
<button id="export-btn">📤 Export Save</button>
|
||||||
|
<button id="import-btn">📥 Import Save</button>
|
||||||
|
<button id="reset-btn">🗑️ Reset</button>
|
||||||
|
<span id="last-saved"></span>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notification Container -->
|
||||||
|
<div id="notifications"></div>
|
||||||
|
|
||||||
|
<!-- Import Modal -->
|
||||||
|
<div id="import-modal" class="modal hidden">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h3>Import Save</h3>
|
||||||
|
<textarea id="import-data" placeholder="Paste your save data here..."></textarea>
|
||||||
|
<div class="modal-buttons">
|
||||||
|
<button id="confirm-import">Import</button>
|
||||||
|
<button id="cancel-import">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="js/game.js"></script>
|
||||||
|
<script src="js/saveSystem.js"></script>
|
||||||
|
<script src="js/buildings.js"></script>
|
||||||
|
<script src="js/upgrades.js"></script>
|
||||||
|
<script src="js/achievements.js"></script>
|
||||||
|
<script src="js/ui.js"></script>
|
||||||
|
<script src="js/stats.js"></script>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
/**
|
||||||
|
* Cosmic Garden - Achievement Definitions & Checker
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ACHIEVEMENTS = {
|
||||||
|
// Click achievements
|
||||||
|
firstClick: {
|
||||||
|
id: 'firstClick',
|
||||||
|
name: 'First Contact',
|
||||||
|
description: 'Click for the first time',
|
||||||
|
icon: '👆',
|
||||||
|
condition: (state) => state.totalClicks >= 1
|
||||||
|
},
|
||||||
|
|
||||||
|
clicker: {
|
||||||
|
id: 'clicker',
|
||||||
|
name: 'Clicker',
|
||||||
|
description: 'Click 100 times',
|
||||||
|
icon: '🖱️',
|
||||||
|
condition: (state) => state.totalClicks >= 100
|
||||||
|
},
|
||||||
|
|
||||||
|
spammer: {
|
||||||
|
id: 'spammer',
|
||||||
|
name: 'Spam Finger',
|
||||||
|
description: 'Click 1,000 times',
|
||||||
|
icon: '🔥',
|
||||||
|
condition: (state) => state.totalClicks >= 1000
|
||||||
|
},
|
||||||
|
|
||||||
|
clickMaster: {
|
||||||
|
id: 'clickMaster',
|
||||||
|
name: 'Click Master',
|
||||||
|
description: 'Click 10,000 times',
|
||||||
|
icon: '⚡',
|
||||||
|
condition: (state) => state.totalClicks >= 10000
|
||||||
|
},
|
||||||
|
|
||||||
|
// Stardust achievements
|
||||||
|
dustCollector: {
|
||||||
|
id: 'dustCollector',
|
||||||
|
name: 'Dust Collector',
|
||||||
|
description: 'Earn 1,000 total stardust',
|
||||||
|
icon: '✨',
|
||||||
|
condition: (state) => state.totalStardust >= 1000
|
||||||
|
},
|
||||||
|
|
||||||
|
stardustMagnate: {
|
||||||
|
id: 'stardustMagnate',
|
||||||
|
name: 'Stardust Magnate',
|
||||||
|
description: 'Earn 100,000 total stardust',
|
||||||
|
icon: '💫',
|
||||||
|
condition: (state) => state.totalStardust >= 100000
|
||||||
|
},
|
||||||
|
|
||||||
|
cosmicTycoon: {
|
||||||
|
id: 'cosmicTycoon',
|
||||||
|
name: 'Cosmic Tycoon',
|
||||||
|
description: 'Earn 10,000,000 total stardust',
|
||||||
|
icon: '🌟',
|
||||||
|
condition: (state) => state.totalStardust >= 10000000
|
||||||
|
},
|
||||||
|
|
||||||
|
galaxyBaron: {
|
||||||
|
id: 'galaxyBaron',
|
||||||
|
name: 'Galaxy Baron',
|
||||||
|
description: 'Earn 1,000,000,000 total stardust',
|
||||||
|
icon: '👑',
|
||||||
|
condition: (state) => state.totalStardust >= 1000000000
|
||||||
|
},
|
||||||
|
|
||||||
|
// Building achievements
|
||||||
|
firstPlant: {
|
||||||
|
id: 'firstPlant',
|
||||||
|
name: 'First Plant',
|
||||||
|
description: 'Buy your first building',
|
||||||
|
icon: '🌱',
|
||||||
|
condition: (state) => Object.values(state.buildings).some(c => c >= 1)
|
||||||
|
},
|
||||||
|
|
||||||
|
gardenStart: {
|
||||||
|
id: 'gardenStart',
|
||||||
|
name: 'Garden Starter',
|
||||||
|
description: 'Own 10 total buildings',
|
||||||
|
icon: '🌿',
|
||||||
|
condition: (state) => Object.values(state.buildings).reduce((a, b) => a + b, 0) >= 10
|
||||||
|
},
|
||||||
|
|
||||||
|
gardenCenter: {
|
||||||
|
id: 'gardenCenter',
|
||||||
|
name: 'Garden Center',
|
||||||
|
description: 'Own 50 total buildings',
|
||||||
|
icon: '🏡',
|
||||||
|
condition: (state) => Object.values(state.buildings).reduce((a, b) => a + b, 0) >= 50
|
||||||
|
},
|
||||||
|
|
||||||
|
cosmicGarden: {
|
||||||
|
id: 'cosmicGarden',
|
||||||
|
name: 'Cosmic Garden',
|
||||||
|
description: 'Own 100 total buildings',
|
||||||
|
icon: '🌌',
|
||||||
|
condition: (state) => Object.values(state.buildings).reduce((a, b) => a + b, 0) >= 100
|
||||||
|
},
|
||||||
|
|
||||||
|
// Specific building achievements
|
||||||
|
sproutFarmer: {
|
||||||
|
id: 'sproutFarmer',
|
||||||
|
name: 'Sprout Farmer',
|
||||||
|
description: 'Own 25 Star Sprouts',
|
||||||
|
icon: '🌱',
|
||||||
|
condition: (state) => (state.buildings.starSprout || 0) >= 25
|
||||||
|
},
|
||||||
|
|
||||||
|
bloomGarden: {
|
||||||
|
id: 'bloomGarden',
|
||||||
|
name: 'Bloom Garden',
|
||||||
|
description: 'Own 25 Moon Blooms',
|
||||||
|
icon: '🌸',
|
||||||
|
condition: (state) => (state.buildings.moonBloom || 0) >= 25
|
||||||
|
},
|
||||||
|
|
||||||
|
solarPowered: {
|
||||||
|
id: 'solarPowered',
|
||||||
|
name: 'Solar Powered',
|
||||||
|
description: 'Own 25 Solar Leaves',
|
||||||
|
icon: '☀️',
|
||||||
|
condition: (state) => (state.buildings.solarLeaf || 0) >= 25
|
||||||
|
},
|
||||||
|
|
||||||
|
// Production achievements
|
||||||
|
steadyStream: {
|
||||||
|
id: 'steadyStream',
|
||||||
|
name: 'Steady Stream',
|
||||||
|
description: 'Reach 100 stardust/sec',
|
||||||
|
icon: '💧',
|
||||||
|
condition: (state) => Game.getProductionPerSecond() >= 100
|
||||||
|
},
|
||||||
|
|
||||||
|
rushingRiver: {
|
||||||
|
id: 'rushingRiver',
|
||||||
|
name: 'Rushing River',
|
||||||
|
description: 'Reach 1,000 stardust/sec',
|
||||||
|
icon: '🌊',
|
||||||
|
condition: (state) => Game.getProductionPerSecond() >= 1000
|
||||||
|
},
|
||||||
|
|
||||||
|
cosmicFlood: {
|
||||||
|
id: 'cosmicFlood',
|
||||||
|
name: 'Cosmic Flood',
|
||||||
|
description: 'Reach 100,000 stardust/sec',
|
||||||
|
icon: '🌊',
|
||||||
|
condition: (state) => Game.getProductionPerSecond() >= 100000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Achievements = {
|
||||||
|
/**
|
||||||
|
* Check all achievements and unlock new ones
|
||||||
|
*/
|
||||||
|
check() {
|
||||||
|
for (const [id, achievement] of Object.entries(ACHIEVEMENTS)) {
|
||||||
|
if (Game.state.achievements.includes(id)) continue;
|
||||||
|
|
||||||
|
if (achievement.condition(Game.state)) {
|
||||||
|
Game.state.achievements.push(id);
|
||||||
|
UI.showNotification(`Achievement: ${achievement.name}!`, 'achievement');
|
||||||
|
console.log(`🏆 Achievement unlocked: ${achievement.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get achievement count
|
||||||
|
*/
|
||||||
|
getCount() {
|
||||||
|
return Game.state.achievements.length;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total achievements
|
||||||
|
*/
|
||||||
|
getTotal() {
|
||||||
|
return Object.keys(ACHIEVEMENTS).length;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.ACHIEVEMENTS = ACHIEVEMENTS;
|
||||||
|
window.Achievements = Achievements;
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* Cosmic Garden - Building Definitions
|
||||||
|
* All purchasable buildings that generate stardust
|
||||||
|
*/
|
||||||
|
|
||||||
|
const BUILDINGS = {
|
||||||
|
// Tier 1 - Basic plants
|
||||||
|
starSprout: {
|
||||||
|
id: 'starSprout',
|
||||||
|
name: 'Star Sprout',
|
||||||
|
icon: '🌱',
|
||||||
|
description: 'A tiny seed that glows softly',
|
||||||
|
baseCost: 15,
|
||||||
|
baseProduction: 0.1,
|
||||||
|
unlockAt: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
moonBloom: {
|
||||||
|
id: 'moonBloom',
|
||||||
|
name: 'Moon Bloom',
|
||||||
|
icon: '🌸',
|
||||||
|
description: 'Flowers that only open under moonlight',
|
||||||
|
baseCost: 100,
|
||||||
|
baseProduction: 0.5,
|
||||||
|
unlockAt: 50
|
||||||
|
},
|
||||||
|
|
||||||
|
solarLeaf: {
|
||||||
|
id: 'solarLeaf',
|
||||||
|
name: 'Solar Leaf',
|
||||||
|
icon: '☀️',
|
||||||
|
description: 'Absorbs sunlight to create stardust',
|
||||||
|
baseCost: 500,
|
||||||
|
baseProduction: 2,
|
||||||
|
unlockAt: 200
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tier 2 - Advanced plants
|
||||||
|
nebulaVine: {
|
||||||
|
id: 'nebulaVine',
|
||||||
|
name: 'Nebula Vine',
|
||||||
|
icon: '🌿',
|
||||||
|
description: 'Wraps around cosmic dust clouds',
|
||||||
|
baseCost: 2000,
|
||||||
|
baseProduction: 10,
|
||||||
|
unlockAt: 1000
|
||||||
|
},
|
||||||
|
|
||||||
|
cometRose: {
|
||||||
|
id: 'cometRose',
|
||||||
|
name: 'Comet Rose',
|
||||||
|
icon: '🌹',
|
||||||
|
description: 'Blooms for just a second as it flies',
|
||||||
|
baseCost: 10000,
|
||||||
|
baseProduction: 50,
|
||||||
|
unlockAt: 5000
|
||||||
|
},
|
||||||
|
|
||||||
|
asteroidCactus: {
|
||||||
|
id: 'asteroidCactus',
|
||||||
|
name: 'Asteroid Cactus',
|
||||||
|
icon: '🌵',
|
||||||
|
description: 'Survives in the harshest space conditions',
|
||||||
|
baseCost: 50000,
|
||||||
|
baseProduction: 200,
|
||||||
|
unlockAt: 25000
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tier 3 - Rare plants
|
||||||
|
pulsarPalm: {
|
||||||
|
id: 'pulsarPalm',
|
||||||
|
name: 'Pulsar Palm',
|
||||||
|
icon: '🌴',
|
||||||
|
description: 'Rhythmic pulses of pure energy',
|
||||||
|
baseCost: 250000,
|
||||||
|
baseProduction: 1000,
|
||||||
|
unlockAt: 100000
|
||||||
|
},
|
||||||
|
|
||||||
|
blackholeOrchid: {
|
||||||
|
id: 'blackholeOrchid',
|
||||||
|
name: 'Black Hole Orchid',
|
||||||
|
icon: '🪻',
|
||||||
|
description: 'Bends light itself to grow',
|
||||||
|
baseCost: 1000000,
|
||||||
|
baseProduction: 5000,
|
||||||
|
unlockAt: 500000
|
||||||
|
},
|
||||||
|
|
||||||
|
galaxyTree: {
|
||||||
|
id: 'galaxyTree',
|
||||||
|
name: 'Galaxy Tree',
|
||||||
|
icon: '🌳',
|
||||||
|
description: 'Contains an entire galaxy within its branches',
|
||||||
|
baseCost: 5000000,
|
||||||
|
baseProduction: 25000,
|
||||||
|
unlockAt: 2000000
|
||||||
|
},
|
||||||
|
|
||||||
|
// Tier 4 - Legendary plants
|
||||||
|
cosmicLotus: {
|
||||||
|
id: 'cosmicLotus',
|
||||||
|
name: 'Cosmic Lotus',
|
||||||
|
icon: '🪷',
|
||||||
|
description: 'Blooms at the center of creation',
|
||||||
|
baseCost: 25000000,
|
||||||
|
baseProduction: 100000,
|
||||||
|
unlockAt: 10000000
|
||||||
|
},
|
||||||
|
|
||||||
|
supernovaTulip: {
|
||||||
|
id: 'supernovaTulip',
|
||||||
|
name: 'Supernova Tulip',
|
||||||
|
icon: '🌺',
|
||||||
|
description: 'Explodes with stardust every cycle',
|
||||||
|
baseCost: 100000000,
|
||||||
|
baseProduction: 500000,
|
||||||
|
unlockAt: 50000000
|
||||||
|
},
|
||||||
|
|
||||||
|
voidFern: {
|
||||||
|
id: 'voidFern',
|
||||||
|
name: 'Void Fern',
|
||||||
|
icon: '🍃',
|
||||||
|
description: 'Grows in the spaces between dimensions',
|
||||||
|
baseCost: 500000000,
|
||||||
|
baseProduction: 2500000,
|
||||||
|
unlockAt: 200000000
|
||||||
|
}
|
||||||
|
};
|
||||||
+349
@@ -0,0 +1,349 @@
|
|||||||
|
/**
|
||||||
|
* Cosmic Garden - Main Game Engine
|
||||||
|
* Handles game state, tick loop, and core logic
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Game = {
|
||||||
|
// Core game state
|
||||||
|
state: {
|
||||||
|
stardust: 0,
|
||||||
|
totalStardust: 0,
|
||||||
|
totalClicks: 0,
|
||||||
|
clickPower: 1,
|
||||||
|
startTime: Date.now(),
|
||||||
|
lastSaveTime: Date.now(),
|
||||||
|
buildings: {},
|
||||||
|
upgrades: [],
|
||||||
|
achievements: []
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
config: {
|
||||||
|
tickRate: 100, // ms per tick (10 ticks/sec)
|
||||||
|
autoSaveInterval: 30000, // 30 seconds
|
||||||
|
costScaling: 1.15 // Cost multiplier per building level
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the game
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
this.loadState();
|
||||||
|
this.setupEventListeners();
|
||||||
|
this.startGameLoop();
|
||||||
|
this.startAutoSave();
|
||||||
|
UI.init();
|
||||||
|
Stats.init();
|
||||||
|
console.log('🌌 Cosmic Garden initialized!');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup event listeners
|
||||||
|
*/
|
||||||
|
setupEventListeners() {
|
||||||
|
// Harvest button
|
||||||
|
document.getElementById('harvest-btn').addEventListener('click', (e) => {
|
||||||
|
this.harvest(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save buttons
|
||||||
|
document.getElementById('save-btn').addEventListener('click', () => {
|
||||||
|
this.save();
|
||||||
|
UI.showNotification('Game saved!', 'success');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('export-btn').addEventListener('click', () => {
|
||||||
|
SaveSystem.exportSave();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('import-btn').addEventListener('click', () => {
|
||||||
|
UI.showImportModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('confirm-import').addEventListener('click', () => {
|
||||||
|
const data = document.getElementById('import-data').value;
|
||||||
|
if (data) {
|
||||||
|
this.loadFromData(data);
|
||||||
|
UI.hideImportModal();
|
||||||
|
UI.showNotification('Save imported!', 'success');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('cancel-import').addEventListener('click', () => {
|
||||||
|
UI.hideImportModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('reset-btn').addEventListener('click', () => {
|
||||||
|
if (confirm('Are you sure you want to reset all progress? This cannot be undone!')) {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tab switching
|
||||||
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', (e) => {
|
||||||
|
const tab = e.target.dataset.tab;
|
||||||
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
||||||
|
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||||||
|
e.target.classList.add('active');
|
||||||
|
document.getElementById(`${tab}-tab`).classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Harvest stardust by clicking
|
||||||
|
*/
|
||||||
|
harvest(event) {
|
||||||
|
const amount = this.getClickPower();
|
||||||
|
this.addStardust(amount);
|
||||||
|
this.state.totalClicks++;
|
||||||
|
|
||||||
|
// Visual feedback
|
||||||
|
UI.showFloatingText(event.clientX, event.clientY, `+${this.formatNumber(amount)}`);
|
||||||
|
UI.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add stardust to the game
|
||||||
|
*/
|
||||||
|
addStardust(amount) {
|
||||||
|
this.state.stardust += amount;
|
||||||
|
this.state.totalStardust += amount;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current click power
|
||||||
|
*/
|
||||||
|
getClickPower() {
|
||||||
|
let power = 1;
|
||||||
|
|
||||||
|
// Apply upgrade bonuses
|
||||||
|
for (const upgradeId of this.state.upgrades) {
|
||||||
|
const upgrade = UPGRADES[upgradeId];
|
||||||
|
if (upgrade && upgrade.clickPowerBonus) {
|
||||||
|
power *= upgrade.clickPowerBonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.floor(power);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate production per second
|
||||||
|
*/
|
||||||
|
getProductionPerSecond() {
|
||||||
|
let total = 0;
|
||||||
|
|
||||||
|
for (const [buildingId, count] of Object.entries(this.state.buildings)) {
|
||||||
|
if (count === 0) continue;
|
||||||
|
|
||||||
|
const building = BUILDINGS[buildingId];
|
||||||
|
if (!building) continue;
|
||||||
|
|
||||||
|
let production = building.baseProduction * count;
|
||||||
|
|
||||||
|
// Apply upgrade bonuses
|
||||||
|
for (const upgradeId of this.state.upgrades) {
|
||||||
|
const upgrade = UPGRADES[upgradeId];
|
||||||
|
if (upgrade && upgrade.buildingBonus && upgrade.buildingBonus[buildingId]) {
|
||||||
|
production *= upgrade.buildingBonus[buildingId];
|
||||||
|
}
|
||||||
|
if (upgrade && upgrade.globalBonus) {
|
||||||
|
production *= upgrade.globalBonus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
total += production;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get building cost
|
||||||
|
*/
|
||||||
|
getBuildingCost(buildingId) {
|
||||||
|
const building = BUILDINGS[buildingId];
|
||||||
|
const count = this.state.buildings[buildingId] || 0;
|
||||||
|
return Math.floor(building.baseCost * Math.pow(this.config.costScaling, count));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buy a building
|
||||||
|
*/
|
||||||
|
buyBuilding(buildingId) {
|
||||||
|
const cost = this.getBuildingCost(buildingId);
|
||||||
|
|
||||||
|
if (this.state.stardust >= cost) {
|
||||||
|
this.state.stardust -= cost;
|
||||||
|
this.state.buildings[buildingId] = (this.state.buildings[buildingId] || 0) + 1;
|
||||||
|
|
||||||
|
UI.showNotification(`Bought ${BUILDINGS[buildingId].name}!`, 'success');
|
||||||
|
UI.update();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buy an upgrade
|
||||||
|
*/
|
||||||
|
buyUpgrade(upgradeId) {
|
||||||
|
const upgrade = UPGRADES[upgradeId];
|
||||||
|
if (!upgrade || this.state.upgrades.includes(upgradeId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.state.stardust >= upgrade.cost) {
|
||||||
|
// Check requirements
|
||||||
|
if (upgrade.requirements) {
|
||||||
|
for (const [reqId, reqCount] of Object.entries(upgrade.requirements)) {
|
||||||
|
const currentCount = this.state.buildings[reqId] || 0;
|
||||||
|
if (currentCount < reqCount) {
|
||||||
|
UI.showNotification('Requirements not met!', 'warning');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.stardust -= upgrade.cost;
|
||||||
|
this.state.upgrades.push(upgradeId);
|
||||||
|
|
||||||
|
UI.showNotification(`Unlocked: ${upgrade.name}!`, 'success');
|
||||||
|
UI.update();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Game tick - called every config.tickRate ms
|
||||||
|
*/
|
||||||
|
tick() {
|
||||||
|
const production = this.getProductionPerSecond();
|
||||||
|
const tickSeconds = this.config.tickRate / 1000;
|
||||||
|
|
||||||
|
if (production > 0) {
|
||||||
|
this.addStardust(production * tickSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
UI.update();
|
||||||
|
Achievements.check();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the game loop
|
||||||
|
*/
|
||||||
|
startGameLoop() {
|
||||||
|
setInterval(() => this.tick(), this.config.tickRate);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start auto-save
|
||||||
|
*/
|
||||||
|
startAutoSave() {
|
||||||
|
setInterval(() => {
|
||||||
|
this.save();
|
||||||
|
document.getElementById('last-saved').textContent =
|
||||||
|
`Last saved: ${new Date().toLocaleTimeString()}`;
|
||||||
|
}, this.config.autoSaveInterval);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save game state
|
||||||
|
*/
|
||||||
|
save() {
|
||||||
|
this.state.lastSaveTime = Date.now();
|
||||||
|
localStorage.setItem('cosmicGardenSave', JSON.stringify(this.state));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load game state
|
||||||
|
*/
|
||||||
|
loadState() {
|
||||||
|
const saved = localStorage.getItem('cosmicGardenSave');
|
||||||
|
if (saved) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(saved);
|
||||||
|
this.state = { ...this.state, ...parsed };
|
||||||
|
|
||||||
|
// Calculate offline progress
|
||||||
|
const offlineTime = (Date.now() - this.state.lastSaveTime) / 1000;
|
||||||
|
if (offlineTime > 60) {
|
||||||
|
const production = this.getProductionPerSecond();
|
||||||
|
const offlineEarnings = production * offlineTime * 0.5; // 50% offline efficiency
|
||||||
|
if (offlineEarnings > 0) {
|
||||||
|
this.addStardust(offlineEarnings);
|
||||||
|
UI.showNotification(
|
||||||
|
`Welcome back! Earned ${this.formatNumber(offlineEarnings)} ✨ while away`,
|
||||||
|
'success'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load save:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load from imported data
|
||||||
|
*/
|
||||||
|
loadFromData(data) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(atob(data));
|
||||||
|
this.state = { ...this.state, ...parsed };
|
||||||
|
UI.update();
|
||||||
|
} catch (e) {
|
||||||
|
UI.showNotification('Invalid save data!', 'warning');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export save as base64
|
||||||
|
*/
|
||||||
|
exportSave() {
|
||||||
|
this.save();
|
||||||
|
const data = btoa(JSON.stringify(this.state));
|
||||||
|
navigator.clipboard.writeText(data).then(() => {
|
||||||
|
UI.showNotification('Save copied to clipboard!', 'success');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset game
|
||||||
|
*/
|
||||||
|
reset() {
|
||||||
|
localStorage.removeItem('cosmicGardenSave');
|
||||||
|
this.state = {
|
||||||
|
stardust: 0,
|
||||||
|
totalStardust: 0,
|
||||||
|
totalClicks: 0,
|
||||||
|
clickPower: 1,
|
||||||
|
startTime: Date.now(),
|
||||||
|
lastSaveTime: Date.now(),
|
||||||
|
buildings: {},
|
||||||
|
upgrades: [],
|
||||||
|
achievements: []
|
||||||
|
};
|
||||||
|
UI.update();
|
||||||
|
UI.showNotification('Game reset!', 'warning');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format numbers with commas
|
||||||
|
*/
|
||||||
|
formatNumber(num) {
|
||||||
|
if (num >= 1e12) return (num / 1e12).toFixed(2) + 'T';
|
||||||
|
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
|
||||||
|
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
|
||||||
|
if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
|
||||||
|
return Math.floor(num).toLocaleString();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make Game globally accessible
|
||||||
|
window.Game = Game;
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Cosmic Garden - Main Entry Point
|
||||||
|
*/
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
Game.init();
|
||||||
|
|
||||||
|
// Show last save time
|
||||||
|
const lastSave = SaveSystem.getLastSaveTime();
|
||||||
|
if (lastSave) {
|
||||||
|
document.getElementById('last-saved').textContent =
|
||||||
|
`Last saved: ${lastSave.toLocaleTimeString()}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* Cosmic Garden - Save System
|
||||||
|
* Handles localStorage persistence and export/import
|
||||||
|
*/
|
||||||
|
|
||||||
|
const SaveSystem = {
|
||||||
|
SAVE_KEY: 'cosmicGardenSave',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save game to localStorage
|
||||||
|
*/
|
||||||
|
save() {
|
||||||
|
const data = JSON.stringify(Game.state);
|
||||||
|
localStorage.setItem(this.SAVE_KEY, data);
|
||||||
|
console.log('💾 Game saved');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load game from localStorage
|
||||||
|
*/
|
||||||
|
load() {
|
||||||
|
const data = localStorage.getItem(this.SAVE_KEY);
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse save:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export save as base64 string
|
||||||
|
*/
|
||||||
|
exportSave() {
|
||||||
|
const data = btoa(JSON.stringify(Game.state));
|
||||||
|
navigator.clipboard.writeText(data).then(() => {
|
||||||
|
UI.showNotification('Save copied to clipboard!', 'success');
|
||||||
|
}).catch(() => {
|
||||||
|
// Fallback for older browsers
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = data;
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
UI.showNotification('Save copied to clipboard!', 'success');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import save from base64 string
|
||||||
|
*/
|
||||||
|
importSave(data) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(atob(data));
|
||||||
|
|
||||||
|
// Validate save data
|
||||||
|
if (!parsed.stardust || !parsed.buildings) {
|
||||||
|
throw new Error('Invalid save format');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge with existing state
|
||||||
|
Game.state = { ...Game.state, ...parsed };
|
||||||
|
Game.state.lastSaveTime = Date.now();
|
||||||
|
|
||||||
|
UI.update();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to import save:', e);
|
||||||
|
UI.showNotification('Invalid save data!', 'warning');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear save data
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
localStorage.removeItem(this.SAVE_KEY);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last save time
|
||||||
|
*/
|
||||||
|
getLastSaveTime() {
|
||||||
|
const data = this.load();
|
||||||
|
if (data && data.lastSaveTime) {
|
||||||
|
return new Date(data.lastSaveTime);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if auto-save is available
|
||||||
|
*/
|
||||||
|
hasAutoSave() {
|
||||||
|
return !!localStorage.getItem(this.SAVE_KEY);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.SaveSystem = SaveSystem;
|
||||||
+160
@@ -0,0 +1,160 @@
|
|||||||
|
/**
|
||||||
|
* Cosmic Garden - Statistics Panel
|
||||||
|
* Tracks and displays game statistics
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Stats = {
|
||||||
|
/**
|
||||||
|
* Initialize the stats panel
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the stats panel
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const container = document.getElementById('stats-content');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">Total Stardust Earned</div>
|
||||||
|
<div class="stat-value" id="stat-total-stardust">0 ✨</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">Current Production</div>
|
||||||
|
<div class="stat-value" id="stat-production">0 ✨/sec</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">Total Clicks</div>
|
||||||
|
<div class="stat-value" id="stat-clicks">0</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">Click Power</div>
|
||||||
|
<div class="stat-value" id="stat-click-power">1 ✨</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">Time Played</div>
|
||||||
|
<div class="stat-value" id="stat-time-played">0:00:00</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">Buildings Owned</div>
|
||||||
|
<div class="stat-value" id="stat-buildings">0</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">Upgrades Purchased</div>
|
||||||
|
<div class="stat-value" id="stat-upgrades">0/${Object.keys(UPGRADES).length}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">Achievements Unlocked</div>
|
||||||
|
<div class="stat-value" id="stat-achievements">0/${Object.keys(ACHIEVEMENTS).length}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-label">Game Progress</div>
|
||||||
|
<div class="stat-bar">
|
||||||
|
<div class="stat-bar-fill" id="stat-progress-bar" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update stats display
|
||||||
|
*/
|
||||||
|
update() {
|
||||||
|
// Total stardust
|
||||||
|
const totalStardustEl = document.getElementById('stat-total-stardust');
|
||||||
|
if (totalStardustEl) {
|
||||||
|
totalStardustEl.textContent = `${Game.formatNumber(Game.state.totalStardust)} ✨`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current production
|
||||||
|
const productionEl = document.getElementById('stat-production');
|
||||||
|
if (productionEl) {
|
||||||
|
productionEl.textContent = `${Game.formatNumber(Game.getProductionPerSecond())} ✨/sec`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total clicks
|
||||||
|
const clicksEl = document.getElementById('stat-clicks');
|
||||||
|
if (clicksEl) {
|
||||||
|
clicksEl.textContent = Game.formatNumber(Game.state.totalClicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click power
|
||||||
|
const clickPowerEl = document.getElementById('stat-click-power');
|
||||||
|
if (clickPowerEl) {
|
||||||
|
clickPowerEl.textContent = `${Game.formatNumber(Game.getClickPower())} ✨`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time played
|
||||||
|
const timePlayedEl = document.getElementById('stat-time-played');
|
||||||
|
if (timePlayedEl) {
|
||||||
|
const elapsed = Math.floor((Date.now() - Game.state.startTime) / 1000);
|
||||||
|
timePlayedEl.textContent = this.formatTime(elapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buildings owned
|
||||||
|
const buildingsEl = document.getElementById('stat-buildings');
|
||||||
|
if (buildingsEl) {
|
||||||
|
const total = Object.values(Game.state.buildings).reduce((a, b) => a + b, 0);
|
||||||
|
buildingsEl.textContent = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upgrades purchased
|
||||||
|
const upgradesEl = document.getElementById('stat-upgrades');
|
||||||
|
if (upgradesEl) {
|
||||||
|
upgradesEl.textContent = `${Game.state.upgrades.length}/${Object.keys(UPGRADES).length}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Achievements unlocked
|
||||||
|
const achievementsEl = document.getElementById('stat-achievements');
|
||||||
|
if (achievementsEl) {
|
||||||
|
achievementsEl.textContent = `${Achievements.getCount()}/${Achievements.getTotal()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Game progress (based on total stardust milestones)
|
||||||
|
const progressBarEl = document.getElementById('stat-progress-bar');
|
||||||
|
if (progressBarEl) {
|
||||||
|
const progress = this.calculateProgress();
|
||||||
|
progressBarEl.style.width = `${progress}%`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format elapsed time as HH:MM:SS
|
||||||
|
*/
|
||||||
|
formatTime(seconds) {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const secs = seconds % 60;
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate game progress percentage
|
||||||
|
* Based on total stardust earned vs all building costs combined
|
||||||
|
*/
|
||||||
|
calculateProgress() {
|
||||||
|
let totalCost = 0;
|
||||||
|
for (const building of Object.values(BUILDINGS)) {
|
||||||
|
// Approximate cost to buy one of each building
|
||||||
|
totalCost += building.baseCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add upgrade costs
|
||||||
|
for (const upgrade of Object.values(UPGRADES)) {
|
||||||
|
totalCost += upgrade.cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
const progress = Math.min(100, (Game.state.totalStardust / totalCost) * 100);
|
||||||
|
return Math.round(progress);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.Stats = Stats;
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
/**
|
||||||
|
* Cosmic Garden - UI Manager
|
||||||
|
* Handles all rendering and user interface updates
|
||||||
|
*/
|
||||||
|
|
||||||
|
const UI = {
|
||||||
|
/**
|
||||||
|
* Initialize the UI
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
this.renderBuildings();
|
||||||
|
this.renderUpgrades();
|
||||||
|
this.renderAchievements();
|
||||||
|
this.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all UI elements
|
||||||
|
*/
|
||||||
|
update() {
|
||||||
|
// Header
|
||||||
|
document.getElementById('stardust-count').textContent = Game.formatNumber(Game.state.stardust);
|
||||||
|
document.getElementById('stardust-ps').textContent = Game.formatNumber(Game.getProductionPerSecond());
|
||||||
|
document.getElementById('click-power').textContent = Game.formatNumber(Game.getClickPower());
|
||||||
|
document.getElementById('total-click-count').textContent = Game.formatNumber(Game.state.totalClicks);
|
||||||
|
|
||||||
|
// Buildings
|
||||||
|
this.updateBuildings();
|
||||||
|
|
||||||
|
// Upgrades
|
||||||
|
this.updateUpgrades();
|
||||||
|
|
||||||
|
// Achievements
|
||||||
|
this.updateAchievements();
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
Stats.update();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render building cards
|
||||||
|
*/
|
||||||
|
renderBuildings() {
|
||||||
|
const container = document.getElementById('buildings-list');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
for (const [id, building] of Object.entries(BUILDINGS)) {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'building-card';
|
||||||
|
card.id = `building-${id}`;
|
||||||
|
|
||||||
|
const count = Game.state.buildings[id] || 0;
|
||||||
|
const cost = Game.getBuildingCost(id);
|
||||||
|
const canAfford = Game.state.stardust >= cost;
|
||||||
|
const locked = Game.state.totalStardust < building.unlockAt && count === 0;
|
||||||
|
|
||||||
|
card.classList.toggle('locked', locked);
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="building-info">
|
||||||
|
<h4>${building.icon} ${building.name}</h4>
|
||||||
|
<div class="building-desc">${building.description}</div>
|
||||||
|
<div class="building-production">+${Game.formatNumber(building.baseProduction)}/s each</div>
|
||||||
|
</div>
|
||||||
|
<div class="building-stats">
|
||||||
|
<div class="building-cost">${Game.formatNumber(cost)} ✨</div>
|
||||||
|
<div class="building-count">${count}</div>
|
||||||
|
<button class="buy-btn" ${!canAfford || locked ? 'disabled' : ''}>Buy</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!locked) {
|
||||||
|
card.querySelector('.buy-btn').addEventListener('click', () => {
|
||||||
|
Game.buyBuilding(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(card);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update building states
|
||||||
|
*/
|
||||||
|
updateBuildings() {
|
||||||
|
for (const [id, building] of Object.entries(BUILDINGS)) {
|
||||||
|
const card = document.getElementById(`building-${id}`);
|
||||||
|
if (!card) continue;
|
||||||
|
|
||||||
|
const count = Game.state.buildings[id] || 0;
|
||||||
|
const cost = Game.getBuildingCost(id);
|
||||||
|
const canAfford = Game.state.stardust >= cost;
|
||||||
|
const locked = Game.state.totalStardust < building.unlockAt && count === 0;
|
||||||
|
|
||||||
|
card.querySelector('.building-cost').textContent = `${Game.formatNumber(cost)} ✨`;
|
||||||
|
card.querySelector('.building-count').textContent = count;
|
||||||
|
|
||||||
|
const btn = card.querySelector('.buy-btn');
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = !canAfford || locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
card.classList.toggle('locked', locked);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render upgrade cards
|
||||||
|
*/
|
||||||
|
renderUpgrades() {
|
||||||
|
const container = document.getElementById('upgrades-list');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
for (const [id, upgrade] of Object.entries(UPGRADES)) {
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'upgrade-card';
|
||||||
|
card.id = `upgrade-${id}`;
|
||||||
|
|
||||||
|
const purchased = Game.state.upgrades.includes(id);
|
||||||
|
const canAfford = Game.state.stardust >= upgrade.cost;
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
let requirementsMet = true;
|
||||||
|
let reqText = '';
|
||||||
|
if (upgrade.requirements) {
|
||||||
|
const reqParts = [];
|
||||||
|
for (const [reqId, reqCount] of Object.entries(upgrade.requirements)) {
|
||||||
|
const currentCount = Game.state.buildings[reqId] || 0;
|
||||||
|
const building = BUILDINGS[reqId];
|
||||||
|
if (currentCount < reqCount) requirementsMet = false;
|
||||||
|
reqParts.push(`${building?.name || reqId}: ${currentCount}/${reqCount}`);
|
||||||
|
}
|
||||||
|
reqText = `<div class="upgrade-desc">Requires: ${reqParts.join(', ')}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
card.classList.toggle('purchased', purchased);
|
||||||
|
card.classList.toggle('locked', !requirementsMet && !purchased);
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="upgrade-name">${upgrade.icon} ${upgrade.name}</div>
|
||||||
|
<div class="upgrade-desc">${upgrade.description}</div>
|
||||||
|
${reqText}
|
||||||
|
<div class="upgrade-cost">${purchased ? '✓ Owned' : `${Game.formatNumber(upgrade.cost)} ✨`}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!purchased && requirementsMet) {
|
||||||
|
card.addEventListener('click', () => {
|
||||||
|
Game.buyUpgrade(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(card);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update upgrade states
|
||||||
|
*/
|
||||||
|
updateUpgrades() {
|
||||||
|
for (const [id, upgrade] of Object.entries(UPGRADES)) {
|
||||||
|
const card = document.getElementById(`upgrade-${id}`);
|
||||||
|
if (!card) continue;
|
||||||
|
|
||||||
|
const purchased = Game.state.upgrades.includes(id);
|
||||||
|
const costEl = card.querySelector('.upgrade-cost');
|
||||||
|
|
||||||
|
if (costEl && !purchased) {
|
||||||
|
const canAfford = Game.state.stardust >= upgrade.cost;
|
||||||
|
costEl.textContent = canAfford ? `${Game.formatNumber(upgrade.cost)} ✨` : `❌ ${Game.formatNumber(upgrade.cost)} ✨`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render achievements
|
||||||
|
*/
|
||||||
|
renderAchievements() {
|
||||||
|
const container = document.getElementById('achievements-list');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
for (const [id, achievement] of Object.entries(ACHIEVEMENTS)) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'achievement';
|
||||||
|
|
||||||
|
const unlocked = Game.state.achievements.includes(id);
|
||||||
|
el.classList.toggle('unlocked', unlocked);
|
||||||
|
el.classList.toggle('locked', !unlocked);
|
||||||
|
|
||||||
|
el.innerHTML = `
|
||||||
|
<div class="achievement-icon">${unlocked ? achievement.icon : '🔒'}</div>
|
||||||
|
<div class="achievement-name">${unlocked ? achievement.name : '???'}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (unlocked) {
|
||||||
|
el.title = `${achievement.name}: ${achievement.description}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(el);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update achievements display
|
||||||
|
*/
|
||||||
|
updateAchievements() {
|
||||||
|
// Only re-render when needed (on new unlocks)
|
||||||
|
const countEl = document.querySelector('#achievements-tab h3');
|
||||||
|
if (countEl) {
|
||||||
|
countEl.textContent = `Achievements (${Achievements.getCount()}/${Achievements.getTotal()})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show floating text animation
|
||||||
|
*/
|
||||||
|
showFloatingText(x, y, text) {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = 'float-text';
|
||||||
|
el.textContent = text;
|
||||||
|
el.style.left = `${x}px`;
|
||||||
|
el.style.top = `${y - 20}px`;
|
||||||
|
document.body.appendChild(el);
|
||||||
|
|
||||||
|
setTimeout(() => el.remove(), 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show notification
|
||||||
|
*/
|
||||||
|
showNotification(message, type = 'info') {
|
||||||
|
const container = document.getElementById('notifications');
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = `notification ${type}`;
|
||||||
|
el.textContent = message;
|
||||||
|
container.appendChild(el);
|
||||||
|
|
||||||
|
setTimeout(() => el.remove(), 3000);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show import modal
|
||||||
|
*/
|
||||||
|
showImportModal() {
|
||||||
|
document.getElementById('import-modal').classList.remove('hidden');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide import modal
|
||||||
|
*/
|
||||||
|
hideImportModal() {
|
||||||
|
document.getElementById('import-modal').classList.add('hidden');
|
||||||
|
document.getElementById('import-data').value = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.UI = UI;
|
||||||
+173
@@ -0,0 +1,173 @@
|
|||||||
|
/**
|
||||||
|
* Cosmic Garden - Upgrade Definitions
|
||||||
|
* One-time purchases that boost production
|
||||||
|
*/
|
||||||
|
|
||||||
|
const UPGRADES = {
|
||||||
|
// Click upgrades
|
||||||
|
ironFingers: {
|
||||||
|
id: 'ironFingers',
|
||||||
|
name: 'Iron Fingers',
|
||||||
|
description: 'Click power x2',
|
||||||
|
icon: '👆',
|
||||||
|
cost: 100,
|
||||||
|
clickPowerBonus: 2,
|
||||||
|
requirements: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
cosmicTouch: {
|
||||||
|
id: 'cosmicTouch',
|
||||||
|
name: 'Cosmic Touch',
|
||||||
|
description: 'Click power x3',
|
||||||
|
icon: '✨',
|
||||||
|
cost: 1000,
|
||||||
|
clickPowerBonus: 3,
|
||||||
|
requirements: { starSprout: 10 }
|
||||||
|
},
|
||||||
|
|
||||||
|
stellarHands: {
|
||||||
|
id: 'stellarHands',
|
||||||
|
name: 'Stellar Hands',
|
||||||
|
description: 'Click power x5',
|
||||||
|
icon: '🌟',
|
||||||
|
cost: 10000,
|
||||||
|
clickPowerBonus: 5,
|
||||||
|
requirements: { moonBloom: 10 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Building upgrades - Star Sprout
|
||||||
|
sproutBoost1: {
|
||||||
|
id: 'sproutBoost1',
|
||||||
|
name: 'Sprout Nutrition',
|
||||||
|
description: 'Star Sprouts produce x2',
|
||||||
|
icon: '🌱',
|
||||||
|
cost: 500,
|
||||||
|
buildingBonus: { starSprout: 2 },
|
||||||
|
requirements: { starSprout: 1 }
|
||||||
|
},
|
||||||
|
|
||||||
|
sproutBoost2: {
|
||||||
|
id: 'sproutBoost2',
|
||||||
|
name: 'Star Fertilizer',
|
||||||
|
description: 'Star Sprouts produce x3',
|
||||||
|
icon: '⭐',
|
||||||
|
cost: 5000,
|
||||||
|
buildingBonus: { starSprout: 3 },
|
||||||
|
requirements: { starSprout: 25 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Building upgrades - Moon Bloom
|
||||||
|
bloomBoost1: {
|
||||||
|
id: 'bloomBoost1',
|
||||||
|
name: 'Lunar Energy',
|
||||||
|
description: 'Moon Blooms produce x2',
|
||||||
|
icon: '🌙',
|
||||||
|
cost: 2000,
|
||||||
|
buildingBonus: { moonBloom: 2 },
|
||||||
|
requirements: { moonBloom: 1 }
|
||||||
|
},
|
||||||
|
|
||||||
|
bloomBoost2: {
|
||||||
|
id: 'bloomBoost2',
|
||||||
|
name: 'Full Moon Ritual',
|
||||||
|
description: 'Moon Blooms produce x3',
|
||||||
|
icon: '🌕',
|
||||||
|
cost: 20000,
|
||||||
|
buildingBonus: { moonBloom: 3 },
|
||||||
|
requirements: { moonBloom: 25 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Building upgrades - Solar Leaf
|
||||||
|
leafBoost1: {
|
||||||
|
id: 'leafBoost1',
|
||||||
|
name: 'Photosynthesis Plus',
|
||||||
|
description: 'Solar Leaves produce x2',
|
||||||
|
icon: '☀️',
|
||||||
|
cost: 10000,
|
||||||
|
buildingBonus: { solarLeaf: 2 },
|
||||||
|
requirements: { solarLeaf: 1 }
|
||||||
|
},
|
||||||
|
|
||||||
|
leafBoost2: {
|
||||||
|
id: 'leafBoost2',
|
||||||
|
name: 'Solar Flare',
|
||||||
|
description: 'Solar Leaves produce x3',
|
||||||
|
icon: '💥',
|
||||||
|
cost: 100000,
|
||||||
|
buildingBonus: { solarLeaf: 3 },
|
||||||
|
requirements: { solarLeaf: 25 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Building upgrades - Nebula Vine
|
||||||
|
vineBoost1: {
|
||||||
|
id: 'vineBoost1',
|
||||||
|
name: 'Cosmic Compost',
|
||||||
|
description: 'Nebula Vines produce x2',
|
||||||
|
icon: '🌿',
|
||||||
|
cost: 50000,
|
||||||
|
buildingBonus: { nebulaVine: 2 },
|
||||||
|
requirements: { nebulaVine: 1 }
|
||||||
|
},
|
||||||
|
|
||||||
|
vineBoost2: {
|
||||||
|
id: 'vineBoost2',
|
||||||
|
name: 'Dust Storm',
|
||||||
|
description: 'Nebula Vines produce x3',
|
||||||
|
icon: '🌪️',
|
||||||
|
cost: 500000,
|
||||||
|
buildingBonus: { nebulaVine: 3 },
|
||||||
|
requirements: { nebulaVine: 25 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Building upgrades - Comet Rose
|
||||||
|
roseBoost1: {
|
||||||
|
id: 'roseBoost1',
|
||||||
|
name: 'Comet Trail',
|
||||||
|
description: 'Comet Roses produce x2',
|
||||||
|
icon: '🌹',
|
||||||
|
cost: 250000,
|
||||||
|
buildingBonus: { cometRose: 2 },
|
||||||
|
requirements: { cometRose: 1 }
|
||||||
|
},
|
||||||
|
|
||||||
|
roseBoost2: {
|
||||||
|
id: 'roseBoost2',
|
||||||
|
name: 'Perihelion Bloom',
|
||||||
|
description: 'Comet Roses produce x3',
|
||||||
|
icon: '☄️',
|
||||||
|
cost: 2500000,
|
||||||
|
buildingBonus: { cometRose: 3 },
|
||||||
|
requirements: { cometRose: 25 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Global upgrades
|
||||||
|
cosmicGarden1: {
|
||||||
|
id: 'cosmicGarden1',
|
||||||
|
name: 'Cosmic Garden',
|
||||||
|
description: 'All production x1.5',
|
||||||
|
icon: '🌌',
|
||||||
|
cost: 1000000,
|
||||||
|
globalBonus: 1.5,
|
||||||
|
requirements: { starSprout: 50, moonBloom: 25, solarLeaf: 10 }
|
||||||
|
},
|
||||||
|
|
||||||
|
stellarNursery: {
|
||||||
|
id: 'stellarNursery',
|
||||||
|
name: 'Stellar Nursery',
|
||||||
|
description: 'All production x2',
|
||||||
|
icon: '🌟',
|
||||||
|
cost: 10000000,
|
||||||
|
globalBonus: 2,
|
||||||
|
requirements: { nebulaVine: 25, cometRose: 10 }
|
||||||
|
},
|
||||||
|
|
||||||
|
galacticBotanist: {
|
||||||
|
id: 'galacticBotanist',
|
||||||
|
name: 'Galactic Botanist',
|
||||||
|
description: 'All production x3',
|
||||||
|
icon: '👨🌾',
|
||||||
|
cost: 100000000,
|
||||||
|
globalBonus: 3,
|
||||||
|
requirements: { pulsarPalm: 10, blackholeOrchid: 5 }
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user