dm / commit
feat: separate API key entry into setup modal shown before game UI
.gitignore | 1 + public/index.html | 456 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 264 insertions(+), 193 deletions(-) diff --git a/.gitignore b/.gitignore index d273ca6..079df7e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ *.log .DS_Store .playwright-mcp/ +screenshots/ diff --git a/public/index.html b/public/index.html index 9c0eded..55e3c79 100644 --- a/public/index.html +++ b/public/index.html @@ -729,7 +729,7 @@ body { display: none; position: fixed; inset: 0; - z-index: 200; + z-index: 400; background: rgba(0,0,0,0.75); align-items: center; justify-content: center; @@ -823,6 +823,50 @@ body { } .share-link-trigger:hover { border-color: var(--gold-dim); color: var(--gold); } +/* ── SETUP MODAL ────────────────────────────────── */ + +#setup-modal { + display: none; + position: fixed; + inset: 0; + z-index: 300; + background: rgba(0,0,0,0.92); + align-items: center; + justify-content: center; + padding: 20px; +} +#setup-modal.open { display: flex; } + +.setup-inner { + background: linear-gradient(135deg, #14140a, #1e1c0a); + border: 1px solid var(--gold-dim); + border-radius: 12px; + padding: 36px 40px; + width: 480px; + max-width: 100%; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 0 80px rgba(201,168,76,0.15), 0 20px 60px rgba(0,0,0,0.8); +} + +.setup-logo { + font-family: 'Cinzel', serif; + font-size: 1.4rem; + font-weight: 700; + color: var(--gold-light); + text-align: center; + letter-spacing: 0.06em; + margin-bottom: 6px; +} + +.setup-sub { + font-style: italic; + color: var(--text-dim); + font-size: 0.95rem; + text-align: center; + margin-bottom: 24px; +} + /* ── REPORT MODAL ───────────────────────────────── */ #report-modal { @@ -906,43 +950,10 @@ body { <body> <!-- ── CHARACTER CREATION ───────────────────── --> -<div id="creation-screen"> +<div id="creation-screen" style="display:none"> <div class="creation-title"><img src="dice.png" alt="" style="height:0.85em;vertical-align:middle;margin-right:0.2em;filter:invert(1) sepia(1) saturate(2) hue-rotate(5deg) brightness(0.9)"> Solo Dungeon Master</div> <div class="creation-sub">Powered by Claude — your story begins here</div> - <div class="creation-section"> - <!-- Shown when no encrypted key in URL (developer / own key) --> - <div id="own-key-section"> - <div class="section-label">Anthropic API Key</div> - <div class="field-group"> - <label>API Key</label> - <input type="password" id="api-key-input" placeholder="sk-ant-api03-…" autocomplete="off" spellcheck="false"> - </div> - <div style="display:flex;justify-content:space-between;align-items:center;margin-top:6px;min-height:18px"> - <span id="key-saved-badge" style="font-size:0.75rem;color:var(--hp-full);display:none">✓ Saved on this device</span> - <button id="forget-key-btn" style="font-size:0.72rem;color:var(--text-dim);background:none;border:none;cursor:pointer;text-decoration:underline;text-underline-offset:2px;padding:0;display:none" type="button">Forget saved key</button> - </div> - <p style="font-size:0.78rem;color:var(--text-dim);margin-top:6px;font-style:italic"> - Sent directly to Anthropic — never forwarded elsewhere. Saved to this device so you only enter it once. - Get one free at <a href="https://console.anthropic.com/" target="_blank" rel="noopener" style="color:var(--gold-dim)">console.anthropic.com</a> - — pay-as-you-go, a typical session costs a few cents. - </p> - <button class="share-link-trigger" id="open-share-modal">🔗 Generate share link</button> - </div> - - <!-- Shown when #k=... is present in the URL (shared link recipient) --> - <div id="access-code-section" style="display:none"> - <div class="section-label">Access Code</div> - <div class="field-group"> - <label class="access-code-label">Enter the code you received</label> - <input type="password" id="access-code-input" placeholder="your passphrase" autocomplete="off" spellcheck="false"> - </div> - <p style="font-size:0.78rem;color:var(--text-dim);margin-top:8px;font-style:italic"> - You received a special link to this app. Enter the access code to unlock it. - </p> - </div> - </div> - <div class="creation-section"> <div class="section-label">Choose Your Race</div> <div class="selection-grid" id="race-grid"></div> @@ -969,6 +980,10 @@ body { <button class="begin-btn" id="begin-btn" disabled>Begin Adventure</button> + <p style="margin-top:12px;text-align:center"> + <button id="change-key-btn" style="font-size:0.72rem;color:var(--text-dim);background:none;border:none;cursor:pointer;text-decoration:underline;text-underline-offset:2px;padding:0" type="button">⚙ Change API key</button> + </p> + <p class="attribution"> <a href="https://www.flaticon.com/authors/icongeek26" target="_blank" rel="noopener">Dice icon by Icongeek26</a> from <a href="https://www.flaticon.com/" target="_blank" rel="noopener">Flaticon</a> @@ -1076,41 +1091,6 @@ body { </div> </div> -<!-- Share link modal --> -<div id="share-modal"> - <div class="share-inner"> - <div class="share-title">🔗 Generate Share Link</div> - - <p id="share-http-warning" style="display:none;font-size:0.78rem;color:var(--hp-low);background:rgba(231,76,60,0.08);border:1px solid rgba(231,76,60,0.25);border-radius:4px;padding:8px 12px;margin-bottom:14px"> - ⚠️ Running on HTTP — using a simplified cipher. For stronger encryption, generate links from your deployed HTTPS site. - </p> - - <div class="field-group" style="margin-bottom:14px"> - <label>Your API Key</label> - <input type="password" id="share-key-input" placeholder="sk-ant-api03-…" autocomplete="off" spellcheck="false" style="width:100%;background:var(--bg-input);border:1px solid var(--border);border-radius:4px;color:var(--text-bright);font-family:'Crimson Text',serif;font-size:1rem;padding:10px 14px;outline:none"> - </div> - - <div class="field-group" style="margin-bottom:16px"> - <label>Access Code <span style="color:var(--text-dim);font-weight:400;text-transform:none;letter-spacing:0">(passphrase your users will enter)</span></label> - <input type="text" id="share-pass-input" placeholder="e.g. goblin-treasure-42" autocomplete="off" spellcheck="false" style="width:100%;background:var(--bg-input);border:1px solid var(--border);border-radius:4px;color:var(--text-bright);font-family:'Crimson Text',serif;font-size:1rem;padding:10px 14px;outline:none"> - </div> - - <div class="share-row"> - <button class="share-btn share-btn-primary" id="share-generate-btn">Generate</button> - <button class="share-btn share-btn-secondary" id="share-close-btn">Cancel</button> - </div> - - <div id="share-result" style="display:none;margin-top:16px"> - <div style="font-family:'Cinzel',serif;font-size:0.65rem;letter-spacing:0.12em;color:var(--gold);text-transform:uppercase;margin-bottom:6px">Share this URL</div> - <div class="share-url-box" id="share-url-display"></div> - <div class="share-row" style="margin-bottom:8px"> - <button class="share-btn share-btn-primary" id="share-copy-btn">Copy URL</button> - </div> - <p class="share-note">⚠️ Share the access code <em>separately</em> from this link — by text, verbally, etc. The link alone is not enough to access the app.</p> - </div> - </div> -</div> - <!-- Report Issue modal --> <div id="report-modal"> <div class="report-inner"> @@ -1137,6 +1117,75 @@ body { </div> </div> +<!-- Setup / key entry modal (shown before game on first visit) --> +<div id="setup-modal"> + <div class="setup-inner"> + + <!-- Own-key panel --> + <div id="setup-own-panel"> + <div class="setup-logo"><img src="dice.png" alt="" style="height:1em;vertical-align:middle;margin-right:0.3em;filter:invert(1) sepia(1) saturate(2) hue-rotate(5deg) brightness(0.9)"> Solo Dungeon Master</div> + <p class="setup-sub">Powered by Claude — your story begins here</p> + + <div style="font-family:'Cinzel',serif;font-size:0.75rem;letter-spacing:0.12em;color:var(--gold);text-transform:uppercase;margin-bottom:8px">Anthropic API Key</div> + <div class="field-group"> + <input type="password" id="setup-key-input" placeholder="sk-ant-api03-…" autocomplete="off" spellcheck="false"> + </div> + <div style="display:flex;justify-content:space-between;align-items:center;margin-top:6px;min-height:18px"> + <span id="setup-key-saved-badge" style="font-size:0.75rem;color:var(--hp-full);display:none">✓ Saved on this device</span> + <button id="setup-forget-btn" style="font-size:0.72rem;color:var(--text-dim);background:none;border:none;cursor:pointer;text-decoration:underline;text-underline-offset:2px;padding:0;display:none" type="button">Forget saved key</button> + </div> + <p style="font-size:0.78rem;color:var(--text-dim);margin-top:6px;font-style:italic"> + Sent directly to Anthropic — never forwarded elsewhere. Saved to this device so you only enter it once. + Get one free at <a href="https://console.anthropic.com/" target="_blank" rel="noopener" style="color:var(--gold-dim)">console.anthropic.com</a> + — pay-as-you-go, a typical session costs a few cents. + </p> + + <div style="margin-top:18px;border-top:1px solid var(--border);padding-top:16px"> + <button id="setup-share-toggle" class="share-link-trigger" style="margin-top:0">🔗 Share this app (optional)</button> + <div id="setup-share-panel" style="display:none;margin-top:14px"> + <p id="setup-share-http-warning" style="display:none;font-size:0.78rem;color:var(--hp-low);background:rgba(231,76,60,0.08);border:1px solid rgba(231,76,60,0.25);border-radius:4px;padding:8px 12px;margin-bottom:12px"> + ⚠️ Running on HTTP — using a simplified cipher. For stronger encryption, generate links from the deployed HTTPS site. + </p> + <div class="field-group" style="margin-bottom:12px"> + <label>Access Code <span style="color:var(--text-dim);font-weight:400;text-transform:none;letter-spacing:0">(passphrase your users will enter)</span></label> + <input type="text" id="setup-pass-input" placeholder="e.g. goblin-treasure-42" autocomplete="off" spellcheck="false" style="width:100%;background:var(--bg-input);border:1px solid var(--border);border-radius:4px;color:var(--text-bright);font-family:'Crimson Text',serif;font-size:1rem;padding:10px 14px;outline:none"> + </div> + <div class="share-row"> + <button class="share-btn share-btn-primary" id="setup-share-generate-btn">Generate</button> + </div> + <div id="setup-share-result" style="display:none;margin-top:12px"> + <div style="font-family:'Cinzel',serif;font-size:0.65rem;letter-spacing:0.12em;color:var(--gold);text-transform:uppercase;margin-bottom:6px">Share this URL</div> + <div class="share-url-box" id="setup-share-url-display"></div> + <div class="share-row" style="margin-bottom:6px"> + <button class="share-btn share-btn-primary" id="setup-share-copy-btn">Copy URL</button> + </div> + <p class="share-note">⚠️ Share the access code <em>separately</em> — by text, verbally, etc. The link alone is not enough.</p> + </div> + </div> + </div> + + <button class="begin-btn" id="setup-continue-btn" disabled style="margin-top:24px;width:100%">Continue →</button> + </div> + + <!-- Shared-key panel --> + <div id="setup-shared-panel" style="display:none"> + <div class="setup-logo"><img src="dice.png" alt="" style="height:1em;vertical-align:middle;margin-right:0.3em;filter:invert(1) sepia(1) saturate(2) hue-rotate(5deg) brightness(0.9)"> Solo Dungeon Master</div> + <p class="setup-sub">You received a special link to this app.<br>Enter your access code to continue.</p> + + <div style="font-family:'Cinzel',serif;font-size:0.75rem;letter-spacing:0.12em;color:var(--gold);text-transform:uppercase;margin:20px 0 8px">Access Code</div> + <div class="field-group"> + <input type="password" id="setup-passphrase-input" placeholder="your passphrase" autocomplete="off" spellcheck="false"> + </div> + <p style="font-size:0.78rem;color:var(--text-dim);margin-top:6px;font-style:italic"> + Enter the access code you received alongside this link. + </p> + + <button class="begin-btn" id="setup-unlock-btn" disabled style="margin-top:24px;width:100%">Unlock →</button> + </div> + + </div> +</div> + <!-- Level up toast --> <div id="levelup-toast"> <div class="toast-icon">⭐</div> @@ -1379,6 +1428,7 @@ let selectedClass = null; let gameState = null; let conversationHistory = []; let isBusy = false; +let currentApiKey = null; // ── IDs ─────────────────────────────────────────────── // player_id persists across games; game_id is fresh each adventure. @@ -1460,20 +1510,14 @@ function saveSession() { } async function tryResumeSession() { - if (getSharedBlob()) return false; + if (!currentApiKey) return false; const sessionRaw = localStorage.getItem('dm_session'); if (!sessionRaw) return false; - const keyRaw = localStorage.getItem('dm_api_key'); - if (!keyRaw) return false; - let session, apiKey; - try { - session = JSON.parse(sessionRaw); - apiKey = await decryptFromStorage(keyRaw); - if (!apiKey) return false; - } catch { return false; } + let session; + try { session = JSON.parse(sessionRaw); } catch { return false; } - gameState = { ...session.gameState, apiKey }; + gameState = { ...session.gameState, apiKey: currentApiKey }; conversationHistory = session.conversationHistory || []; document.getElementById('creation-screen').style.display = 'none'; @@ -1528,94 +1572,7 @@ async function buildCreationScreen() { }); document.getElementById('begin-btn').addEventListener('click', () => beginAdventure()); - - const sharedBlob = getSharedBlob(); - - if (sharedBlob) { - // Shared-link mode: show access code input, hide own-key section - document.getElementById('own-key-section').style.display = 'none'; - document.getElementById('access-code-section').style.display = ''; - } else { - // Own-key mode: restore from localStorage (stored encrypted) - let keyFromStorage = false; - const savedKey = await decryptFromStorage(localStorage.getItem('dm_api_key')); - if (savedKey) { - document.getElementById('api-key-input').value = savedKey; - document.getElementById('key-saved-badge').style.display = ''; - document.getElementById('forget-key-btn').style.display = ''; - keyFromStorage = true; - } - - // Share button is only useful when the key was typed manually, not auto-loaded - function syncShareBtn() { - const hasManualKey = !keyFromStorage && document.getElementById('api-key-input').value.trim().length > 0; - document.getElementById('open-share-modal').style.display = hasManualKey ? '' : 'none'; - } - syncShareBtn(); - - document.getElementById('forget-key-btn').addEventListener('click', () => { - localStorage.removeItem('dm_api_key'); - localStorage.removeItem('dm_session'); - document.getElementById('api-key-input').value = ''; - document.getElementById('key-saved-badge').style.display = 'none'; - document.getElementById('forget-key-btn').style.display = 'none'; - keyFromStorage = false; - syncShareBtn(); - }); - - document.getElementById('api-key-input').addEventListener('input', () => { - document.getElementById('key-saved-badge').style.display = 'none'; - keyFromStorage = false; - syncShareBtn(); - }); - - // Share modal - document.getElementById('open-share-modal').addEventListener('click', () => { - const cur = document.getElementById('api-key-input').value.trim(); - if (cur) document.getElementById('share-key-input').value = cur; - document.getElementById('share-result').style.display = 'none'; - document.getElementById('share-http-warning').style.display = - window.isSecureContext ? 'none' : ''; - document.getElementById('share-modal').classList.add('open'); - }); - - document.getElementById('share-close-btn').addEventListener('click', () => { - document.getElementById('share-modal').classList.remove('open'); - }); - - document.getElementById('share-modal').addEventListener('click', e => { - if (e.target === document.getElementById('share-modal')) - document.getElementById('share-modal').classList.remove('open'); - }); - - document.getElementById('share-generate-btn').addEventListener('click', async () => { - const apiKey = document.getElementById('share-key-input').value.trim(); - const passphrase = document.getElementById('share-pass-input').value.trim(); - if (!apiKey) { alert('Enter the API key to encrypt.'); return; } - if (!passphrase) { alert('Enter an access code / passphrase.'); return; } - const btn = document.getElementById('share-generate-btn'); - btn.disabled = true; btn.textContent = 'Encrypting…'; - try { - const blob = await encryptApiKey(apiKey, passphrase); - const url = `${location.origin}${location.pathname}#k=${blob}`; - document.getElementById('share-url-display').textContent = url; - document.getElementById('share-result').style.display = ''; - } catch (err) { - alert(`Encryption failed: ${err.message}`); - } finally { - btn.disabled = false; btn.textContent = 'Generate'; - } - }); - - document.getElementById('share-copy-btn').addEventListener('click', () => { - const url = document.getElementById('share-url-display').textContent; - navigator.clipboard.writeText(url).then(() => { - const btn = document.getElementById('share-copy-btn'); - btn.textContent = '✓ Copied!'; - setTimeout(() => btn.textContent = 'Copy URL', 2000); - }); - }); - } + document.getElementById('change-key-btn').addEventListener('click', () => openSetupModal(getSharedBlob() ? 'shared' : 'own')); } function checkBeginReady() { @@ -1623,27 +1580,8 @@ function checkBeginReady() { } async function beginAdventure() { - let apiKey; - const sharedBlob = getSharedBlob(); - - if (sharedBlob) { - const passphrase = document.getElementById('access-code-input').value.trim(); - if (!passphrase) { alert('Please enter your access code.'); return; } - const btn = document.getElementById('begin-btn'); - btn.disabled = true; btn.textContent = 'Unlocking…'; - try { - apiKey = await decryptApiKey(sharedBlob, passphrase); - } catch (err) { - alert(err.message.includes('HTTPS') ? err.message : 'Invalid access code. Please check and try again.'); - btn.disabled = false; btn.textContent = 'Begin Adventure'; - return; - } - btn.textContent = 'Begin Adventure'; - } else { - apiKey = document.getElementById('api-key-input').value.trim(); - if (!apiKey) { alert('Please enter your Anthropic API key.'); return; } - localStorage.setItem('dm_api_key', await encryptForStorage(apiKey)); - } + if (!currentApiKey) { openSetupModal(getSharedBlob() ? 'shared' : 'own'); return; } + const apiKey = currentApiKey; const name = document.getElementById('char-name').value.trim() || `The ${selectedClass.name}`; const theme = document.getElementById('adventure-theme').value.trim(); @@ -2153,7 +2091,7 @@ document.getElementById('quick-actions').addEventListener('click', e => { if (btn && !isBusy) sendMessage(btn.dataset.action, true); }); -document.getElementById('new-game-btn').addEventListener('click', () => { +document.getElementById('new-game-btn').addEventListener('click', async () => { if (!confirm('Start a new game? Your current adventure will be lost.')) return; document.getElementById('game-screen').classList.remove('visible'); document.getElementById('creation-screen').style.display = ''; @@ -2165,9 +2103,129 @@ document.getElementById('new-game-btn').addEventListener('click', () => { isBusy = false; document.querySelectorAll('.sel-card').forEach(c => c.classList.remove('selected')); localStorage.removeItem('dm_session'); + currentApiKey = (await decryptFromStorage(localStorage.getItem('dm_api_key'))) || null; + if (!currentApiKey) { openSetupModal('own'); return; } checkBeginReady(); }); +// ── SETUP MODAL ─────────────────────────────────────── + +function openSetupModal(mode) { + const isShared = mode === 'shared'; + document.getElementById('setup-own-panel').style.display = isShared ? 'none' : ''; + document.getElementById('setup-shared-panel').style.display = isShared ? '' : 'none'; + if (!isShared) { + // Pre-fill with currentApiKey if available (editing) + if (currentApiKey) { + document.getElementById('setup-key-input').value = currentApiKey; + document.getElementById('setup-key-saved-badge').style.display = localStorage.getItem('dm_api_key') ? '' : 'none'; + document.getElementById('setup-forget-btn').style.display = localStorage.getItem('dm_api_key') ? '' : 'none'; + } + syncSetupContinueBtn(); + } + document.getElementById('setup-modal').classList.add('open'); +} + +function closeSetupModal() { + document.getElementById('setup-modal').classList.remove('open'); +} + +function syncSetupContinueBtn() { + const hasKey = document.getElementById('setup-key-input').value.trim().length > 0; + document.getElementById('setup-continue-btn').disabled = !hasKey; +} + +// Own-key panel +document.getElementById('setup-key-input').addEventListener('input', () => { + document.getElementById('setup-key-saved-badge').style.display = 'none'; + syncSetupContinueBtn(); +}); + +document.getElementById('setup-forget-btn').addEventListener('click', () => { + localStorage.removeItem('dm_api_key'); + localStorage.removeItem('dm_session'); + currentApiKey = null; + document.getElementById('setup-key-input').value = ''; + document.getElementById('setup-key-saved-badge').style.display = 'none'; + document.getElementById('setup-forget-btn').style.display = 'none'; + syncSetupContinueBtn(); +}); + +document.getElementById('setup-continue-btn').addEventListener('click', async () => { + const key = document.getElementById('setup-key-input').value.trim(); + if (!key) return; + currentApiKey = key; + localStorage.setItem('dm_api_key', await encryptForStorage(key)); + closeSetupModal(); + if (!(await tryResumeSession())) { + document.getElementById('creation-screen').style.display = ''; + } +}); + +// Share link inside setup modal +document.getElementById('setup-share-toggle').addEventListener('click', () => { + const panel = document.getElementById('setup-share-panel'); + const isOpen = panel.style.display !== 'none'; + panel.style.display = isOpen ? 'none' : ''; + if (!isOpen) { + document.getElementById('setup-share-http-warning').style.display = + window.isSecureContext ? 'none' : ''; + document.getElementById('setup-share-result').style.display = 'none'; + } +}); + +document.getElementById('setup-share-generate-btn').addEventListener('click', async () => { + const apiKey = document.getElementById('setup-key-input').value.trim(); + const passphrase = document.getElementById('setup-pass-input').value.trim(); + if (!apiKey) { alert('Enter your API key first.'); return; } + if (!passphrase) { alert('Enter an access code / passphrase.'); return; } + const btn = document.getElementById('setup-share-generate-btn'); + btn.disabled = true; btn.textContent = 'Encrypting…'; + try { + const blob = await encryptApiKey(apiKey, passphrase); + const url = `${location.origin}${location.pathname}#k=${blob}`; + document.getElementById('setup-share-url-display').textContent = url; + document.getElementById('setup-share-result').style.display = ''; + } catch (err) { + alert(`Encryption failed: ${err.message}`); + } finally { + btn.disabled = false; btn.textContent = 'Generate'; + } +}); + +document.getElementById('setup-share-copy-btn').addEventListener('click', () => { + const url = document.getElementById('setup-share-url-display').textContent; + navigator.clipboard.writeText(url).then(() => { + const btn = document.getElementById('setup-share-copy-btn'); + btn.textContent = '✓ Copied!'; + setTimeout(() => btn.textContent = 'Copy URL', 2000); + }); +}); + +// Shared-key (passphrase) panel +document.getElementById('setup-passphrase-input').addEventListener('input', () => { + const hasPass = document.getElementById('setup-passphrase-input').value.trim().length > 0; + document.getElementById('setup-unlock-btn').disabled = !hasPass; +}); + +document.getElementById('setup-unlock-btn').addEventListener('click', async () => { + const passphrase = document.getElementById('setup-passphrase-input').value.trim(); + if (!passphrase) return; + const btn = document.getElementById('setup-unlock-btn'); + btn.disabled = true; btn.textContent = 'Unlocking…'; + try { + currentApiKey = await decryptApiKey(getSharedBlob(), passphrase); + } catch (err) { + alert(err.message.includes('HTTPS') ? err.message : 'Invalid access code. Please check and try again.'); + btn.disabled = false; btn.textContent = 'Unlock →'; + return; + } + closeSetupModal(); + if (!(await tryResumeSession())) { + document.getElementById('creation-screen').style.display = ''; + } +}); + // ── REPORT MODAL HANDLERS ───────────────────────────── const SERVICE_DESK_EMAIL = 'contact-project+pavelsafronov-dm-82624720-issue-@incoming.gitlab.com'; @@ -2221,10 +2279,22 @@ document.getElementById('report-copy-btn').addEventListener('click', () => { // ── INIT ────────────────────────────────────────────── (async () => { - // Always build the creation screen so grids/listeners are ready for "New Game" await buildCreationScreen(); - // Attempt resume — overwrites the game/creation screen state if a session exists - await tryResumeSession(); + + // Load own key from storage if available + const keyRaw = localStorage.getItem('dm_api_key'); + if (keyRaw) currentApiKey = await decryptFromStorage(keyRaw) || null; + + if (currentApiKey) { + // Key known — resume session or show creation screen + if (!(await tryResumeSession())) { + document.getElementById('creation-screen').style.display = ''; + } + } else if (getSharedBlob()) { + openSetupModal('shared'); + } else { + openSetupModal('own'); + } })().catch(console.error); </script> </body>