diff --git a/game.js b/game.js index 920a823..bc3c51e 100644 --- a/game.js +++ b/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 += `
OK ${pDef.name}
`; + // Collapsible completed projects section + if (G.completedProjects && G.completedProjects.length > 0) { + const count = G.completedProjects.length; + const collapsed = G.projectsCollapsed !== false; + html += `
`; + html += `${collapsed ? '▶' : '▼'} COMPLETED (${count})
`; + if (!collapsed) { + html += `
`; + for (const id of G.completedProjects) { + const pDef = PDEFS.find(p => p.id === id); + if (pDef) { + html += `
OK ${pDef.name}
`; + } } + html += `
`; } } @@ -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(); + } }); diff --git a/index.html b/index.html index 549edac..4a190b7 100644 --- a/index.html +++ b/index.html @@ -130,7 +130,11 @@ body{background:var(--bg);color:var(--text);font-family:'SF Mono','Cascadia Code
- + +
+ + +

BUILDINGS