Paste your save data, then choose which slot to import into.
diff --git a/js/game.js b/js/game.js
index 5041f72..1264feb 100644
--- a/js/game.js
+++ b/js/game.js
@@ -20,6 +20,10 @@ const Game = {
totalPrestigeEssence: 0,
prestigeUpgrades: [],
+ // Save Slot System
+ activeSlot: 0, // index of the currently selected save slot (0..SLOTS-1)
+ autoSave: true, // whether the periodic autosave loop is running
+
// Golden Starbloom random event state
goldenBlooms: {
active: false, // is a bloom currently visible on screen?
@@ -34,6 +38,10 @@ const Game = {
tickRate: 100, // ms per tick (10 ticks/sec)
autoSaveInterval: 30000, // 30 seconds
costScaling: 1.15, // Cost multiplier per building level
+
+ // Save Slot System - number of independent save slots available
+ SLOTS: 3,
+
goldenBloomMinMs: 45000, // min ms between bloom spawns (45s)
goldenBloomMaxMs: 90000, // max ms between bloom spawns (90s)
goldenBloomDuration: 12, // seconds a visible bloom stays before vanishing
@@ -59,6 +67,9 @@ const Game = {
* Setup event listeners
*/
setupEventListeners() {
+ // Track which slot the user has selected for importing.
+ this.importSlot = 0;
+
// Harvest button - support both click and touch
const harvestBtn = document.getElementById('harvest-btn');
@@ -100,9 +111,12 @@ const Game = {
document.getElementById('confirm-import').addEventListener('click', () => {
const data = document.getElementById('import-data').value;
if (data) {
- this.loadFromData(data);
+ const ok = this.importSlotFromData(data, this.importSlot);
UI.hideImportModal();
- UI.showNotification('Save imported!', 'success');
+ if (ok) {
+ // If the import landed in the active slot, switchSlot already re-rendered.
+ UI.update();
+ }
}
});
@@ -116,6 +130,37 @@ const Game = {
}
});
+ // Save Slot System: slot selection buttons.
+ document.querySelectorAll('.slot-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ if (this.state.activeSlot === parseInt(btn.dataset.slot, 10)) {
+ UI.showNotification(`Already on Slot ${parseInt(btn.dataset.slot, 10) + 1}`, 'info');
+ return;
+ }
+ if (confirm(`Switch to Slot ${parseInt(btn.dataset.slot, 10) + 1}?\nUnsaved changes will be saved first.`)) {
+ this.save(); // Persist current slot before switching.
+ this.switchSlot(parseInt(btn.dataset.slot, 10));
+ }
+ });
+ });
+
+ // Autosave toggle button.
+ document.getElementById('autosave-btn').addEventListener('click', () => {
+ this.state.autoSave = !this.state.autoSave;
+ const btn = document.getElementById('autosave-btn');
+ if (this.state.autoSave) {
+ btn.classList.add('on');
+ btn.textContent = 'ON';
+ UI.showNotification('Autosave enabled', 'success');
+ } else {
+ btn.classList.remove('on');
+ btn.textContent = 'OFF';
+ UI.showNotification('Autosave disabled', 'info');
+ }
+ // Persist the toggle preference immediately.
+ localStorage.setItem('cosmicGardenAutoSave', String(this.state.autoSave));
+ });
+
// Tab switching
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
@@ -126,6 +171,14 @@ const Game = {
document.getElementById(`${tab}-tab`).classList.add('active');
});
});
+
+ // Import slot selector buttons.
+ document.querySelectorAll('[data-import-slot]').forEach(btn => {
+ btn.addEventListener('click', () => {
+ this.importSlot = parseInt(btn.dataset.importSlot, 10);
+ UI.showNotification(`Importing into Slot ${this.importSlot + 1}`, 'info');
+ });
+ });
},
/**
@@ -480,6 +533,8 @@ const Game = {
*/
startAutoSave() {
setInterval(() => {
+ // Only autosave when the player has enabled it (default on).
+ if (!this.state.autoSave) return;
this.save();
document.getElementById('last-saved').textContent =
`Last saved: ${new Date().toLocaleTimeString()}`;
@@ -487,22 +542,46 @@ const Game = {
},
/**
- * Save game state
+ * localStorage key for a given slot's save data.
+ */
+ getSaveKey(slot) {
+ return `cosmicGardenSave-${slot}`;
+ },
+
+ /**
+ * The key holding the selected slot index across page loads.
+ */
+ getActiveSlotKey() {
+ return 'cosmicGardenActiveSlot';
+ },
+
+ /**
+ * Save game state to the currently active slot.
*/
save() {
this.state.lastSaveTime = Date.now();
- localStorage.setItem('cosmicGardenSave', JSON.stringify(this.state));
+ localStorage.setItem(this.getSaveKey(this.state.activeSlot), JSON.stringify(this.state));
},
/**
* Load game state
*/
loadState() {
- const saved = localStorage.getItem('cosmicGardenSave');
+ // Restore the persisted selected-slot index before reading its data.
+ const persistedSlot = localStorage.getItem(this.getActiveSlotKey());
+ if (persistedSlot !== null) {
+ this.state.activeSlot = parseInt(persistedSlot, 10) || 0;
+ }
+
+ const saved = localStorage.getItem(this.getSaveKey(this.state.activeSlot));
if (saved) {
try {
const parsed = JSON.parse(saved);
this.state = { ...this.state, ...parsed };
+ // The explicitly-persisted slot selection always wins.
+ if (persistedSlot !== null) {
+ this.state.activeSlot = parseInt(persistedSlot, 10) || 0;
+ }
// Calculate offline progress
const offlineTime = (Date.now() - this.state.lastSaveTime) / 1000;
@@ -543,6 +622,117 @@ const Game = {
}
},
+ /**
+ * Load the data stored in a specific slot index (without changing it).
+ * Returns the parsed state, or null if that slot is empty.
+ */
+ loadSlot(slot) {
+ const raw = localStorage.getItem(this.getSaveKey(slot));
+ if (!raw) return null;
+ try {
+ return JSON.parse(raw);
+ } catch (e) {
+ console.error(`Failed to read slot ${slot}:`, e);
+ return null;
+ }
+ },
+
+ /**
+ * Switch the active save slot. Persists the selection and reloads that
+ * slot's data from localStorage into the live game state, preserving
+ * prestige-related counters that should carry across slots only when
+ * present in the target slot.
+ */
+ switchSlot(slot) {
+ slot = ((parseInt(slot, 10) || 0) + this.config.SLOTS) % this.config.SLOTS;
+
+ // Persist the selection first so loadState() reads it back.
+ localStorage.setItem(this.getActiveSlotKey(), String(slot));
+ this.state.activeSlot = slot;
+
+ const data = this.loadSlot(slot);
+ if (data) {
+ try {
+ const parsed = JSON.parse(JSON.stringify(data));
+ this.state = { ...this.state, ...parsed };
+ // Ensure the slot index stays consistent.
+ this.state.activeSlot = slot;
+
+ UI.showNotification(
+ `Saved to Slot ${slot + 1}!`, 'success'
+ );
+ } catch (e) {
+ console.error('Failed to load selected slot:', e);
+ UI.showNotification('Could not load that slot!', 'warning');
+ }
+ } else {
+ // Target slot is empty: reset to a fresh start but keep essence.
+ this.state = {
+ stardust: 0,
+ totalStardust: 0,
+ totalClicks: 0,
+ clickPower: 1,
+ startTime: Date.now(),
+ lastSaveTime: Date.now(),
+ buildings: {},
+ upgrades: [],
+ achievements: [],
+ stellarEssence: this.state.stellarEssence || 0,
+ prestigeCount: this.state.prestigeCount || 0,
+ totalPrestigeEssence: this.state.totalPrestigeEssence || 0,
+ prestigeUpgrades: this.state.prestigeUpgrades || [],
+ goldenBlooms: { active: false, timer: 0, lastSpawn: Date.now(), totalClaimed: 0 },
+ activeSlot: slot,
+ autoSave: this.state.autoSave
+ };
+ UI.showNotification(
+ `Empty Slot ${slot + 1} - new game started!`, 'success'
+ );
+ }
+
+ // Re-render dynamic UI and refresh the golden-bloom badge.
+ UI.init();
+ UI.update();
+ },
+
+ /**
+ * Export a single slot's save as base64 (for backup or transfer).
+ */
+ exportSlotToBase64(slot) {
+ const data = this.loadSlot(slot);
+ if (!data) {
+ UI.showNotification(`Slot ${slot + 1} is empty!`, 'warning');
+ return null;
+ }
+ const encoded = btoa(JSON.stringify(data));
+ navigator.clipboard.writeText(encoded).then(() => {
+ UI.showNotification(`Slot ${slot + 1} copied to clipboard!`, 'success');
+ });
+ return encoded;
+ },
+
+ /**
+ * Import base64 save data into a chosen slot.
+ */
+ importSlotFromData(data, slot) {
+ try {
+ const parsed = JSON.parse(atob(data));
+ localStorage.setItem(this.getSaveKey(slot), JSON.stringify(parsed));
+ UI.showNotification(`Imported into Slot ${slot + 1}!`, 'success');
+
+ // If we imported into the active slot, reload it.
+ if (parseInt(slot, 10) === this.state.activeSlot) {
+ this.switchSlot(slot);
+ } else {
+ UI.update();
+ }
+ return true;
+ } catch (e) {
+ UI.showNotification('Invalid save data!', 'warning');
+ return false;
+ }
+ },
+
/**
* Export save as base64
*/
@@ -572,7 +762,10 @@ const Game = {
stellarEssence: 0,
prestigeCount: 0,
totalPrestigeEssence: 0,
- prestigeUpgrades: []
+ prestigeUpgrades: [],
+ goldenBlooms: { active: false, timer: 0, lastSpawn: Date.now(), totalClaimed: 0 },
+ activeSlot: this.state.activeSlot,
+ autoSave: this.state.autoSave
};
UI.update();
UI.showNotification('Game reset!', 'warning');
diff --git a/js/stats.js b/js/stats.js
index ca8627e..2c29b89 100644
--- a/js/stats.js
+++ b/js/stats.js
@@ -75,6 +75,16 @@ const Stats = {