dm / commit
feat: version mismatch modal with adventure log export
public/index.html | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 3 deletions(-) diff --git a/public/index.html b/public/index.html index 7f28f9f..963dc01 100644 --- a/public/index.html +++ b/public/index.html @@ -955,6 +955,43 @@ body { margin-bottom: 24px; } +/* ── VERSION MISMATCH MODAL ─────────────────────── */ + +#version-modal { + display: none; + position: fixed; + inset: 0; + z-index: 500; + background: rgba(0,0,0,0.85); + align-items: center; + justify-content: center; +} +#version-modal.open { display: flex; } +.version-inner { + background: linear-gradient(135deg, #0e0e1a, #141420); + border: 2px solid #d97706; + border-radius: 10px; + padding: 32px; + width: 480px; + max-width: 95vw; + box-shadow: 0 0 60px rgba(217,119,6,0.2), 0 20px 60px rgba(0,0,0,0.8); +} +.version-title { + font-family: 'Cinzel', serif; + font-size: 1rem; + font-weight: 600; + color: #f59e0b; + margin-bottom: 12px; + letter-spacing: 0.06em; +} +.version-body { + font-size: 0.9rem; + color: var(--text-dim); + line-height: 1.6; + margin-bottom: 24px; +} +.version-actions { display: flex; gap: 10px; flex-wrap: wrap; } + /* ── REPORT MODAL ───────────────────────────────── */ #report-modal { @@ -1194,6 +1231,21 @@ body { </div> </div> +<!-- Version mismatch modal --> +<div id="version-modal"> + <div class="version-inner"> + <div class="version-title">📜 Game Format Updated</div> + <p class="version-body"> + The app has been updated and your saved adventure uses an older format that can no longer be loaded. + You can download your adventure log as a Markdown file before it's cleared, or start fresh right away. + </p> + <div class="version-actions"> + <button class="wn-save-btn" style="width:auto;padding:10px 20px" id="version-download-btn">⬇ Download adventure log</button> + <button class="share-btn share-btn-secondary" id="version-discard-btn">Start fresh</button> + </div> + </div> +</div> + <!-- Report Issue modal --> <div id="report-modal"> <div class="report-inner"> @@ -1627,9 +1679,9 @@ async function tryResumeSession() { try { session = JSON.parse(sessionRaw); } catch { return false; } if (session.version !== SAVE_VERSION) { - console.log(`Save version mismatch (saved ${session.version ?? 'none'}, current ${SAVE_VERSION}) — discarding old session.`); - localStorage.removeItem('dm_session'); - return false; + console.log(`Save version mismatch (saved ${session.version ?? 'none'}, current ${SAVE_VERSION}) — offering export.`); + showVersionMismatchModal(session); + return true; // returning true suppresses the creation screen until the modal is dismissed } gameState = { ...session.gameState, apiKey: currentApiKey }; @@ -1657,6 +1709,59 @@ async function tryResumeSession() { return true; } +// ── VERSION MISMATCH ────────────────────────────── + +function generateAdventureMarkdown(session) { + const c = session.gameState?.character; + const lines = ['# Solo Dungeon Master — Adventure Log', '']; + + if (c) { + lines.push(`**Character:** ${c.name} — Level ${c.level} ${c.race} ${c.class}`); + lines.push(`**HP:** ${c.hp}/${c.maxHp} · **XP:** ${c.xp} · **Gold:** ${c.gold} GP`); + if (c.inventory?.length) lines.push(`**Inventory:** ${c.inventory.join(', ')}`); + } + if (session.sessionCost) { + lines.push(`**Session cost:** $${session.sessionCost.toFixed(4)}`); + } + if (session.worldNotes) { + lines.push('', '## World Notes', '', session.worldNotes); + } + + lines.push('', '---', '', '## Adventure'); + + for (const msg of (session.conversationHistory || [])) { + if (!msg.content || typeof msg.content !== 'string') continue; + const speaker = msg.role === 'user' ? `**${c?.name || 'You'}**` : '**DM**'; + lines.push('', `${speaker}: ${msg.content}`); + } + + return lines.join('\n'); +} + +function showVersionMismatchModal(session) { + const modal = document.getElementById('version-modal'); + modal.classList.add('open'); + + document.getElementById('version-download-btn').onclick = () => { + const md = generateAdventureMarkdown(session); + const blob = new Blob([md], { type: 'text/markdown' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + const charName = session.gameState?.character?.name?.replace(/\s+/g, '-') || 'adventure'; + a.href = url; + a.download = `${charName}-adventure-log.md`; + a.click(); + URL.revokeObjectURL(url); + }; + + const dismiss = () => { + modal.classList.remove('open'); + localStorage.removeItem('dm_session'); + document.getElementById('creation-screen').style.display = ''; + }; + document.getElementById('version-discard-btn').onclick = dismiss; +} + // ── CREATION SCREEN ─────────────────────────────────── async function buildCreationScreen() {