From 36cd3b81f59f1e75d1d0cf47bd3f1ece899ce68d Mon Sep 17 00:00:00 2001 From: Olov Johansson Date: Sat, 8 Aug 2026 19:54:06 +0200 Subject: [PATCH] events linked to projects; truths; rules audit --- game.html | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++---- server.js | 9 +++-- 2 files changed, 109 insertions(+), 10 deletions(-) diff --git a/game.html b/game.html index 8bd1300..8c8b9ff 100644 --- a/game.html +++ b/game.html @@ -626,6 +626,30 @@ } .tl-chooser-btn:hover { background: rgba(184,138,67,.12); color: var(--paper); } + /* Truths */ + .truth-list { display:flex; flex-direction:column; gap:.6rem; margin-top:.8rem; } + .truth-item { + display:flex; align-items:flex-start; gap:1rem; + padding:.8rem 1rem; border-left:3px solid var(--brass); + background:rgba(184,138,67,.05); + font:italic 1rem/1.6 var(--serif); color:var(--paper); + } + .truth-item-text { flex:1; } + .project-events { + margin-top:.8rem; padding-top:.8rem; border-top:1px solid var(--line); + } + .project-events-label { + font:700 .62rem/1 var(--mono); letter-spacing:.12em; text-transform:uppercase; + color:var(--muted); margin-bottom:.5rem; + } + .project-events-list { display:flex; flex-direction:column; gap:.3rem; } + .project-event-link { + font:.85rem/1.4 var(--sans); color:var(--muted); text-decoration:none; + display:flex; align-items:baseline; gap:.5rem; + } + .project-event-link:hover { color:var(--paper); } + .project-event-date { font:.62rem/1 var(--mono); color:var(--muted); flex-shrink:0; } + /* Autocomplete dropdown */ .ac-wrap { position: relative; } .ac-dropdown { @@ -747,6 +771,24 @@ + +
+
+ + +
+
+ +
+
+ No truths established. Add the absolute rules of your setting in Edit Session Details. +
+
+
Colony Chronicle // Timeline
@@ -846,6 +888,7 @@
// + // // //
@@ -895,6 +938,17 @@
+
+
Events in this project
+
+ +
+
+
+
Truths
+

Absolute rules of the setting. Nothing created during play can contradict them.

+
+ +
+
+ + +
+
+
- -
- - + +
+
+ + +
+
+ + +
@@ -1427,6 +1511,8 @@ showCard: false, // Timeline click chooser tlChooser: { show: false, x: 0, y: 0, date: '' }, + // Truths + newTruth: '', // Autocomplete acMode: null, acQuery: '', @@ -1989,6 +2075,15 @@ return result.sort((a, b) => a.term.localeCompare(b.term)) }, + eventsForProject(projectId) { + return this.events().filter(e => e.project_id === projectId) + }, + + projectForEvent(item) { + if (!item.project_id) return null + return this.projects().find(p => p.id === item.project_id) || null + }, + castUsages(name) { const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') const re = new RegExp(`@"${escaped}"`, 'i') @@ -2113,7 +2208,7 @@ openAddItem(type) { const env = this.randomEnv(null) - this.activeForm = { type, title: '', description: '', mood: '', date: '', end_date: '', status: 'active', discipline: '', env, env_layout: env ? this.randomEnvLayout() : 'after' } + this.activeForm = { type, title: '', description: '', mood: '', date: '', end_date: '', status: 'active', discipline: '', env, env_layout: env ? this.randomEnvLayout() : 'after', project_id: '' } this.editingId = null this.formError = null this.modalType = 'item' @@ -2121,7 +2216,7 @@ }, openEditItem(item) { - this.activeForm = Object.assign({ env: '', env_layout: 'after' }, item) + this.activeForm = Object.assign({ env: '', env_layout: 'after', project_id: '' }, item) this.editingId = item.id this.formError = null this.modalType = 'item' @@ -2138,7 +2233,8 @@ }, openEditGame() { - this.activeForm = Object.assign({ deck_id: '' }, this.game) + this.activeForm = Object.assign({ deck_id: '', truths: [] }, this.game) + this.activeForm.truths = [...(this.game.truths || [])] this.formError = null this.modalType = 'editGame' this.showModal = true diff --git a/server.js b/server.js index e910128..0491851 100644 --- a/server.js +++ b/server.js @@ -90,6 +90,7 @@ function publicGame(g) { start_title: g.start_title || '', start_description: g.start_description || '', start_mood: g.start_mood || '', end_title: g.end_title || '', end_description: g.end_description || '', end_mood: g.end_mood || '', deck_id: g.deck_id || '', + truths: g.truths || [], created_at: g.created_at } } @@ -183,6 +184,7 @@ app.put('/api/games/:slug', requirePassword, (req, res) => { game.end_description = end_description || '' game.end_mood = end_mood || '' game.deck_id = req.body.deck_id || '' + game.truths = req.body.truths || [] saveDB(req.db); broadcast(req.params.slug) res.json(publicGame(game)) }) @@ -209,7 +211,7 @@ app.get('/api/games/:slug/items', (req, res) => { }) app.post('/api/games/:slug/items', requirePassword, (req, res) => { - const { type, title, description, mood, date, end_date, status, discipline, env } = req.body + const { type, title, description, mood, date, end_date, status, discipline, env, project_id } = req.body if (!type || !title || !date) return res.status(400).json({ error: 'type, title, date required' }) if (!['event', 'project', 'person'].includes(type)) { return res.status(400).json({ error: 'type must be event, project, or person' }) @@ -218,7 +220,8 @@ app.post('/api/games/:slug/items', requirePassword, (req, res) => { id: newId(), type, title, description: description || '', mood: mood || null, date, end_date: end_date || null, status: status || null, discipline: discipline || null, - env: env || null, env_layout: req.body.env_layout || null, created_at: now() + env: env || null, env_layout: req.body.env_layout || null, + project_id: project_id || null, created_at: now() } req.game.items = req.game.items || [] req.game.items.push(item) @@ -233,7 +236,7 @@ app.put('/api/games/:slug/items/:id', requirePassword, (req, res) => { const { title, description, mood, date, end_date, status, discipline } = req.body 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 } + 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, project_id: req.body.project_id || null } saveDB(req.db); broadcast(req.params.slug) res.json(items[idx]) })