forked from clawdia/der_letzte_auftrag
Quests & Tracker spoilerfrei im Spielmodus + Live-Sync der Schlucke
- Quests- und Tracker-Tab auch im Spieler-Modus sichtbar; Spoiler (NPC/Komplize, Belohnungen, Beschreibungen, Details, Notizen) per .gm-only nur im Spielleiter-Modus - pollState() gleicht alle 3s Schlucke/Wuerfel/Quests ueber offene Instanzen ab; Notizen werden beim Tippen nicht ueberschrieben - Start: Quests erst nach Server-State bauen (Erledigt-Status korrekt) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+59
-20
@@ -895,6 +895,9 @@
|
||||
body:not(.gm-mode) .nav-tab.gm-tab { display: none; }
|
||||
body.gm-mode .nav-tab.player-tab { display: none; }
|
||||
|
||||
/* Spoiler-Inhalte nur im Spielleiter-Modus zeigen */
|
||||
body:not(.gm-mode) .gm-only { display: none !important; }
|
||||
|
||||
/* Dezenter GM-Hinweis-Streifen */
|
||||
.gm-banner {
|
||||
display: none;
|
||||
@@ -945,10 +948,10 @@
|
||||
<button class="nav-tab player-tab" data-tab="ablauf-spieler" onclick="showTab('ablauf-spieler', this)">Ablauf</button>
|
||||
<!-- Spielleiter-Tabs (passwortgeschützt) -->
|
||||
<button class="nav-tab gm-tab" data-tab="intro" onclick="showTab('intro', this)">Intro</button>
|
||||
<button class="nav-tab gm-tab" data-tab="quests" onclick="showTab('quests', this)">Quests</button>
|
||||
<button class="nav-tab" data-tab="quests" onclick="showTab('quests', this)">Quests</button>
|
||||
<button class="nav-tab gm-tab" data-tab="tabletop" onclick="showTab('tabletop', this)">Tabletop</button>
|
||||
<button class="nav-tab gm-tab" data-tab="werwolf" onclick="showTab('werwolf', this)">Werwolf</button>
|
||||
<button class="nav-tab gm-tab" data-tab="tracker" onclick="showTab('tracker', this)">Tracker</button>
|
||||
<button class="nav-tab" data-tab="tracker" onclick="showTab('tracker', this)">Tracker</button>
|
||||
<button class="nav-tab gm-tab" data-tab="spieler" onclick="showTab('spieler', this)">Spieler</button>
|
||||
<button class="nav-tab gm-tab" data-tab="ablauf" onclick="showTab('ablauf', this)">Ablauf (SL)</button>
|
||||
</div>
|
||||
@@ -1112,9 +1115,11 @@
|
||||
<div id="tab-quests" class="tab-content">
|
||||
<div class="parchment">
|
||||
<div class="section-title">Die Quests</div>
|
||||
<div class="section-subtitle">Aufgaben der NPCs — Klicken zum Aufklappen</div>
|
||||
<div class="section-subtitle">Die Prüfungen für Wendl</div>
|
||||
|
||||
<div class="rules-box">
|
||||
<p class="gm-only" style="text-align:center;font-style:italic;color:#5a3a10;margin-bottom:0.5rem;">Im Spielleiter-Modus mit allen Details — Spieler sehen nur die Quest-Namen & den Fortschritt.</p>
|
||||
|
||||
<div class="rules-box gm-only">
|
||||
<div class="rules-title">Farbcode</div>
|
||||
<ul class="rules-list" style="columns:2; gap:1rem;">
|
||||
<li><span class="quest-badge badge-easy">AKT 1</span> Leichte Prüfungen</li>
|
||||
@@ -1365,7 +1370,7 @@
|
||||
<div class="drink-count" id="questCount" style="color: var(--green);">0/6</div>
|
||||
<div class="drink-label">Würfel 🎲</div>
|
||||
<div style="margin-top:1rem; font-family: 'Cinzel', serif; font-size:0.75rem; color:#8a6520; letter-spacing:0.1em;">
|
||||
Ab 6 → Kampf frei · im Intro-Tab verwalten
|
||||
Ab 6 → Kampf frei<span class="gm-only"> · im Intro-Tab verwalten</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1375,14 +1380,14 @@
|
||||
<div id="questStatusList" style="display:flex; flex-direction:column; gap:0.4rem; margin-top:0.5rem;"></div>
|
||||
</div>
|
||||
|
||||
<div class="rules-box" style="margin-top:1rem;">
|
||||
<div class="rules-box gm-only" style="margin-top:1rem;">
|
||||
<div class="rules-title">Notizen</div>
|
||||
<textarea id="notes" placeholder="Hier Notizen eingeben..."
|
||||
<textarea id="notes" placeholder="Hier Notizen eingeben..."
|
||||
style="width:100%; background:rgba(138,101,32,0.05); border:1px solid #c9a22740; border-radius:4px; padding:0.6rem; font-family:'Crimson Pro',serif; font-size:0.95rem; color:var(--ink-light); resize:vertical; min-height:80px; outline:none;"
|
||||
oninput="saveNotes()"></textarea>
|
||||
</div>
|
||||
|
||||
<div style="text-align:center; margin-top:1.5rem;">
|
||||
<div class="gm-only" style="text-align:center; margin-top:1.5rem;">
|
||||
<button class="btn-primary" onclick="resetAll()">↺ Neustart</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1561,7 +1566,39 @@ async function syncState() {
|
||||
if (s.questDone) s.questDone.forEach((v, i) => { if (i < questDone.length) questDone[i] = v; });
|
||||
document.getElementById('drinkCount').textContent = drinks;
|
||||
const ta = document.getElementById('notes');
|
||||
if (ta && typeof s.notesText === 'string') ta.value = s.notesText;
|
||||
// Notizen nicht überschreiben, während jemand tippt
|
||||
if (ta && document.activeElement !== ta && typeof s.notesText === 'string') ta.value = s.notesText;
|
||||
}
|
||||
|
||||
// Live-Sync: zieht regelmäßig den Server-State und gleicht offene Instanzen ab
|
||||
// (Schlucke, Würfel, Quests synchron über mehrere Geräte hinweg).
|
||||
let pollTimer = null;
|
||||
async function pollState() {
|
||||
const data = await apiGet('/game');
|
||||
if (!data || !data.state) return;
|
||||
const s = data.state;
|
||||
let itemsChanged = false, questsChanged = false;
|
||||
if (s.drinks !== undefined && s.drinks !== drinks) {
|
||||
drinks = s.drinks;
|
||||
const dc = document.getElementById('drinkCount');
|
||||
if (dc) dc.textContent = drinks;
|
||||
}
|
||||
if (s.itemCollected) s.itemCollected.forEach((v, i) => {
|
||||
if (i < itemCollected.length && itemCollected[i] !== v) { itemCollected[i] = v; itemsChanged = true; }
|
||||
});
|
||||
if (s.questDone) s.questDone.forEach((v, i) => {
|
||||
if (i < questDone.length && questDone[i] !== v) { questDone[i] = v; questsChanged = true; }
|
||||
});
|
||||
if (itemsChanged) {
|
||||
for (let i = 0; i < itemCollected.length; i++) {
|
||||
const slot = document.getElementById('inv-' + i);
|
||||
if (slot) slot.classList.toggle('collected', itemCollected[i]);
|
||||
}
|
||||
}
|
||||
if (questsChanged) buildQuests();
|
||||
if (itemsChanged || questsChanged) updateQuestStatus();
|
||||
const ta = document.getElementById('notes');
|
||||
if (ta && document.activeElement !== ta && typeof s.notesText === 'string' && ta.value !== s.notesText) ta.value = s.notesText;
|
||||
}
|
||||
|
||||
// State zum Server speichern
|
||||
@@ -1704,14 +1741,16 @@ function toggleMode() {
|
||||
}
|
||||
}
|
||||
|
||||
function startApp() {
|
||||
async function startApp() {
|
||||
document.getElementById('cover').style.display = 'none';
|
||||
document.getElementById('app').style.display = 'block';
|
||||
buildQuests();
|
||||
buildDice();
|
||||
syncState();
|
||||
syncSpieler();
|
||||
updateQuestStatus();
|
||||
await syncState(); // erst Server-State holen …
|
||||
await syncSpieler();
|
||||
buildQuests(); // … dann Quests mit korrektem Erledigt-Status bauen
|
||||
refreshUI();
|
||||
// Live-Sync starten (nur einmal, auch wenn startApp doppelt läuft)
|
||||
if (!pollTimer) pollTimer = setInterval(pollState, 3000);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════
|
||||
@@ -1728,20 +1767,20 @@ function buildQuests() {
|
||||
card.innerHTML = `
|
||||
<span class="quest-badge badge-${q.badge}">${q.badgeLabel}</span>
|
||||
<div class="quest-name">${q.name}</div>
|
||||
<div class="quest-holder">NPC: ${q.holder}</div>
|
||||
<div class="quest-desc">${q.desc}</div>
|
||||
<div class="quest-rewards">
|
||||
<div class="quest-holder gm-only">NPC: ${q.holder}</div>
|
||||
<div class="quest-desc gm-only">${q.desc}</div>
|
||||
<div class="quest-rewards gm-only">
|
||||
<span class="reward-success">${q.success}</span>
|
||||
<span class="reward-fail">${q.fail}</span>
|
||||
</div>
|
||||
<div class="quest-expand">${q.details}<br><br>
|
||||
<div class="quest-expand gm-only">${q.details}<br><br>
|
||||
<button onclick="event.stopPropagation(); toggleDone(${i})"
|
||||
style="font-family:'Cinzel',serif;font-size:0.75rem;background:transparent;border:1px solid #8a6520;
|
||||
padding:0.3rem 0.8rem;cursor:pointer;color:#8a6520;border-radius:3px;">
|
||||
${questDone[i] ? '↩ Als offen markieren' : '✓ Als erledigt markieren'}
|
||||
</button>
|
||||
</div>
|
||||
<div class="expand-hint">▼ Aufklappen für Details</div>`;
|
||||
<div class="expand-hint gm-only">▼ Aufklappen für Details</div>`;
|
||||
card.addEventListener('click', () => toggleExpand(i));
|
||||
grid.appendChild(card);
|
||||
});
|
||||
@@ -1798,7 +1837,7 @@ function updateQuestStatus() {
|
||||
color:${questDone[i]?'#2a5a1a':'#5a3a10'};">
|
||||
<span>${questDone[i] ? '✓' : '○'}</span>
|
||||
<span>${q.name}</span>
|
||||
<span style="margin-left:auto;font-size:0.8rem;opacity:0.7">${q.item}</span>
|
||||
<span class="gm-only" style="margin-left:auto;font-size:0.8rem;opacity:0.7">${q.item}</span>
|
||||
</div>`).join('');
|
||||
}
|
||||
// ═══════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user