diff --git a/assets/sci-aquatic-lab.jpg b/assets/sci-aquatic-lab.jpg
index 18fed6f..82454c4 100644
Binary files a/assets/sci-aquatic-lab.jpg and b/assets/sci-aquatic-lab.jpg differ
diff --git a/assets/sci-botanist.jpg b/assets/sci-botanist.jpg
index fd6c7a2..484bf55 100644
Binary files a/assets/sci-botanist.jpg and b/assets/sci-botanist.jpg differ
diff --git a/assets/sci-excavation.jpg b/assets/sci-excavation.jpg
index 8b7e11b..016e335 100644
Binary files a/assets/sci-excavation.jpg and b/assets/sci-excavation.jpg differ
diff --git a/assets/sci-globe-room.jpg b/assets/sci-globe-room.jpg
index 09d1beb..2388d4a 100644
Binary files a/assets/sci-globe-room.jpg and b/assets/sci-globe-room.jpg differ
diff --git a/assets/sci-growth-chamber.jpg b/assets/sci-growth-chamber.jpg
index ca90aa5..1c4faf4 100644
Binary files a/assets/sci-growth-chamber.jpg and b/assets/sci-growth-chamber.jpg differ
diff --git a/assets/sci-herbarium.jpg b/assets/sci-herbarium.jpg
index c907e10..d4e9ef5 100644
Binary files a/assets/sci-herbarium.jpg and b/assets/sci-herbarium.jpg differ
diff --git a/assets/sci-laboratory.jpg b/assets/sci-laboratory.jpg
index 01ed10a..dc2dc5a 100644
Binary files a/assets/sci-laboratory.jpg and b/assets/sci-laboratory.jpg differ
diff --git a/assets/sci-paleontology.jpg b/assets/sci-paleontology.jpg
index 90007b8..34fa100 100644
Binary files a/assets/sci-paleontology.jpg and b/assets/sci-paleontology.jpg differ
diff --git a/assets/sci-specimen-hall.jpg b/assets/sci-specimen-hall.jpg
index e2a6a47..8ca64c1 100644
Binary files a/assets/sci-specimen-hall.jpg and b/assets/sci-specimen-hall.jpg differ
diff --git a/assets/sci-specimen-tanks.jpg b/assets/sci-specimen-tanks.jpg
index afc3fdc..e99927c 100644
Binary files a/assets/sci-specimen-tanks.jpg and b/assets/sci-specimen-tanks.jpg differ
diff --git a/assets/sci-telescope.jpg b/assets/sci-telescope.jpg
index a39801c..978b9e3 100644
Binary files a/assets/sci-telescope.jpg and b/assets/sci-telescope.jpg differ
diff --git a/assets/sci-weather-station.jpg b/assets/sci-weather-station.jpg
index 065e8a0..e92ef6f 100644
Binary files a/assets/sci-weather-station.jpg and b/assets/sci-weather-station.jpg differ
diff --git a/game.html b/game.html
index 2c6a6f3..9001ab6 100644
--- a/game.html
+++ b/game.html
@@ -989,6 +989,16 @@
+
+
+ Chronicle updated
+
+
{
+ if (e.data === 'connected') return
+ // Don't reload while a modal is open — pick it up when they close
+ if (this.showModal) return
+ await Promise.all([this.loadGame(), this.loadItems(), this.loadKeywords()])
+ this.liveUpdate = true
+ setTimeout(() => this.liveUpdate = false, 2500)
+ }
+ es.onerror = () => {
+ es.close()
+ setTimeout(() => this.connectLive(), 5000)
+ }
+ },
+
async loadCards() {
try {
const r = await fetch('/assets/cards.json')
diff --git a/server.js b/server.js
index 11132e5..15f1679 100644
--- a/server.js
+++ b/server.js
@@ -6,6 +6,18 @@ const path = require('path')
const app = express()
const PORT = process.env.PORT || 3000
+
+// SSE clients: Map>
+const sseClients = new Map()
+
+function broadcast(slug) {
+ const bucket = sseClients.get(slug)
+ if (!bucket) return
+ const msg = `data: update\n\n`
+ for (const res of bucket) {
+ try { res.write(msg) } catch {}
+ }
+}
const DB_PATH = path.join(__dirname, 'colony.json')
function loadDB() {
@@ -59,6 +71,24 @@ function publicGame(g) {
}
}
+// --- Live updates (SSE) ---
+app.get('/api/games/:slug/live', (req, res) => {
+ res.writeHead(200, {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ 'Connection': 'keep-alive',
+ 'X-Accel-Buffering': 'no',
+ })
+ res.write('data: connected\n\n')
+ const slug = req.params.slug
+ if (!sseClients.has(slug)) sseClients.set(slug, new Set())
+ sseClients.get(slug).add(res)
+ req.on('close', () => {
+ sseClients.get(slug)?.delete(res)
+ if (sseClients.get(slug)?.size === 0) sseClients.delete(slug)
+ })
+})
+
// --- Games ---
app.get('/api/games', (req, res) => {
const db = loadDB()
@@ -120,7 +150,7 @@ app.put('/api/games/:slug', requirePassword, (req, res) => {
game.end_title = end_title || ''
game.end_description = end_description || ''
game.end_mood = end_mood || ''
- saveDB(req.db)
+ saveDB(req.db); broadcast(req.params.slug)
res.json(publicGame(game))
})
@@ -159,7 +189,7 @@ app.post('/api/games/:slug/items', requirePassword, (req, res) => {
}
req.game.items = req.game.items || []
req.game.items.push(item)
- saveDB(req.db)
+ saveDB(req.db); broadcast(req.params.slug)
res.status(201).json(item)
})
@@ -171,7 +201,7 @@ app.put('/api/games/:slug/items/:id', requirePassword, (req, res) => {
if (!title || !date) return res.status(400).json({ error: 'title, date required' })
const { env, env_layout } = req.body
items[idx] = { ...items[idx], title, description: description || '', mood: mood || null, date, end_date: end_date || null, status: status || null, discipline: discipline || null, env: env || null, env_layout: env_layout || null }
- saveDB(req.db)
+ saveDB(req.db); broadcast(req.params.slug)
res.json(items[idx])
})
@@ -180,7 +210,7 @@ app.delete('/api/games/:slug/items/:id', requirePassword, (req, res) => {
const idx = items.findIndex(i => i.id === req.params.id)
if (idx === -1) return res.status(404).json({ error: 'Item not found' })
items.splice(idx, 1)
- saveDB(req.db)
+ saveDB(req.db); broadcast(req.params.slug)
res.json({ ok: true })
})
@@ -200,12 +230,12 @@ app.post('/api/games/:slug/keywords', requirePassword, (req, res) => {
const existing = req.game.keywords.find(k => k.term.toLowerCase() === term.toLowerCase())
if (existing) {
existing.definition = definition || ''
- saveDB(req.db)
+ saveDB(req.db); broadcast(req.params.slug)
return res.json(existing)
}
const kw = { id: newId(), term, definition: definition || '', created_at: now() }
req.game.keywords.push(kw)
- saveDB(req.db)
+ saveDB(req.db); broadcast(req.params.slug)
res.status(201).json(kw)
})
@@ -215,7 +245,7 @@ app.put('/api/games/:slug/keywords/:id', requirePassword, (req, res) => {
if (idx === -1) return res.status(404).json({ error: 'Keyword not found' })
const { term, definition } = req.body
kws[idx] = { ...kws[idx], term: term || kws[idx].term, definition: definition || '' }
- saveDB(req.db)
+ saveDB(req.db); broadcast(req.params.slug)
res.json(kws[idx])
})
@@ -224,7 +254,7 @@ app.delete('/api/games/:slug/keywords/:id', requirePassword, (req, res) => {
const idx = kws.findIndex(k => k.id === req.params.id)
if (idx === -1) return res.status(404).json({ error: 'Keyword not found' })
kws.splice(idx, 1)
- saveDB(req.db)
+ saveDB(req.db); broadcast(req.params.slug)
res.json({ ok: true })
})