beacon: add collapsible completed projects + export/import save files
- Completed research projects are now collapsed by default (click to expand), preventing panel clutter in mid/late game. Toggle state persists in saves. - Export saves as JSON files with E key or Export button - Import saves from file with I key or Import button (validates before loading) - Ctrl+S keyboard shortcut for quick save - Updated keybind hints in startup log
This commit is contained in:
81
game.js
81
game.js
@@ -1729,13 +1729,21 @@ function renderProjects() {
|
||||
|
||||
let html = '';
|
||||
|
||||
// Show completed projects
|
||||
if (G.completedProjects) {
|
||||
for (const id of G.completedProjects) {
|
||||
const pDef = PDEFS.find(p => p.id === id);
|
||||
if (pDef) {
|
||||
html += `<div class="project-done">OK ${pDef.name}</div>`;
|
||||
// Collapsible completed projects section
|
||||
if (G.completedProjects && G.completedProjects.length > 0) {
|
||||
const count = G.completedProjects.length;
|
||||
const collapsed = G.projectsCollapsed !== false;
|
||||
html += `<div id="completed-header" onclick="toggleCompletedProjects()" style="cursor:pointer;font-size:9px;color:#555;padding:4px 0;border-bottom:1px solid #1a2a1a;margin-bottom:4px;user-select:none">`;
|
||||
html += `${collapsed ? '▶' : '▼'} COMPLETED (${count})</div>`;
|
||||
if (!collapsed) {
|
||||
html += `<div id="completed-list">`;
|
||||
for (const id of G.completedProjects) {
|
||||
const pDef = PDEFS.find(p => p.id === id);
|
||||
if (pDef) {
|
||||
html += `<div class="project-done">OK ${pDef.name}</div>`;
|
||||
}
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1759,6 +1767,11 @@ function renderProjects() {
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function toggleCompletedProjects() {
|
||||
G.projectsCollapsed = G.projectsCollapsed === false ? true : false;
|
||||
renderProjects();
|
||||
}
|
||||
|
||||
function renderStats() {
|
||||
const set = (id, v, raw) => {
|
||||
const el = document.getElementById(id);
|
||||
@@ -2080,6 +2093,49 @@ function dismissOfflinePopup() {
|
||||
if (el) el.style.display = 'none';
|
||||
}
|
||||
|
||||
// === EXPORT / IMPORT SAVE FILES ===
|
||||
function exportSave() {
|
||||
const raw = localStorage.getItem('the-beacon-v2');
|
||||
if (!raw) { log('No save data to export.'); return; }
|
||||
const blob = new Blob([raw], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
const ts = new Date().toISOString().slice(0, 10);
|
||||
a.download = `beacon-save-${ts}.json`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
log('Save exported to file.');
|
||||
}
|
||||
|
||||
function importSave() {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json,application/json';
|
||||
input.onchange = function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(ev) {
|
||||
try {
|
||||
const data = JSON.parse(ev.target.result);
|
||||
if (!data.code && !data.totalCode && !data.buildings) {
|
||||
log('Import failed: file does not look like a Beacon save.');
|
||||
return;
|
||||
}
|
||||
if (confirm('Import this save? Current progress will be overwritten.')) {
|
||||
localStorage.setItem('the-beacon-v2', ev.target.result);
|
||||
location.reload();
|
||||
}
|
||||
} catch (err) {
|
||||
log('Import failed: invalid JSON file.');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
// === SAVE / LOAD ===
|
||||
function showSaveToast() {
|
||||
const el = document.getElementById('save-toast');
|
||||
@@ -2124,6 +2180,7 @@ function saveGame() {
|
||||
sprintActive: G.sprintActive || false,
|
||||
sprintTimer: G.sprintTimer || 0,
|
||||
sprintCooldown: G.sprintCooldown || 0,
|
||||
projectsCollapsed: G.projectsCollapsed !== false,
|
||||
savedAt: Date.now()
|
||||
};
|
||||
|
||||
@@ -2260,7 +2317,7 @@ function initGame() {
|
||||
log('Click WRITE CODE or press SPACE to start.');
|
||||
log('Build AutoCode for passive production.');
|
||||
log('Watch for Research Projects to appear.');
|
||||
log('Keys: SPACE=Code S=Code Sprint 1=Ops->Code 2=Ops->Compute 3=Ops->Knowledge 4=Ops->Trust B=Buy x1/x10/MAX');
|
||||
log('Keys: SPACE=Code S=Sprint 1-4=Ops B=Buy x1/10/MAX E=Export I=Import Ctrl+S=Save');
|
||||
}
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
@@ -2308,4 +2365,14 @@ window.addEventListener('keydown', function (e) {
|
||||
else setBuyAmount(1);
|
||||
}
|
||||
if (e.code === 'KeyS') activateSprint();
|
||||
if (e.code === 'KeyE') exportSave();
|
||||
if (e.code === 'KeyI') importSave();
|
||||
});
|
||||
|
||||
// Ctrl+S to save (must be on keydown to preventDefault)
|
||||
window.addEventListener('keydown', function (e) {
|
||||
if ((e.ctrlKey || e.metaKey) && e.code === 'KeyS') {
|
||||
e.preventDefault();
|
||||
saveGame();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -130,7 +130,11 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
|
||||
<div id="sprint-label" style="font-size:9px;color:#666;margin-top:2px;text-align:center"></div>
|
||||
</div>
|
||||
<div id="alignment-ui" style="display:none"></div>
|
||||
<button class="save-btn" onclick="saveGame()">Save Game</button>
|
||||
<button class="save-btn" onclick="saveGame()">Save Game [Ctrl+S]</button>
|
||||
<div style="display:flex;gap:4px;margin-top:4px">
|
||||
<button class="save-btn" onclick="exportSave()" style="flex:1">Export [E]</button>
|
||||
<button class="save-btn" onclick="importSave()" style="flex:1">Import [I]</button>
|
||||
</div>
|
||||
<button class="reset-btn" onclick="if(confirm('Reset all progress?')){localStorage.removeItem('the-beacon-v2');location.reload()}">Reset Progress</button>
|
||||
<h2>BUILDINGS</h2>
|
||||
<div id="buildings"></div>
|
||||
|
||||
Reference in New Issue
Block a user