fuzzyworld

dm / commit

feat: persist API key in localStorage

ff6a15340c52639fa353c4a33587c9609a8c4691 · Pavel Safronov · 2026-05-27 00:09:09 -0700 · parents 8f029ca9ac31

feat: persist API key in localStorage

Pre-fills the key field on return visits so users only enter it once
per device. Shows a "Saved on this device" badge when restored, and a
"Forget saved key" link to clear it. The badge is hidden as soon as
the user edits the field. Key is saved (or overwritten) each time a
new adventure starts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
 public/index.html | 31 ++++++++++++++++++++++++++++---
 1 file changed, 28 insertions(+), 3 deletions(-)

diff --git a/public/index.html b/public/index.html
index 4a249c4..e5af3f2 100644
--- a/public/index.html
+++ b/public/index.html
@@ -749,9 +749,14 @@ body {
       <label>API Key</label>
       <input type="password" id="api-key-input" placeholder="sk-ant-api03-…" autocomplete="off" spellcheck="false">
     </div>
-    <p style="font-size:0.78rem;color:var(--text-dim);margin-top:8px;font-style:italic">
-      Your key is sent directly to Anthropic from your browser — never stored or forwarded elsewhere.
-      Get one at <a href="https://console.anthropic.com/" target="_blank" rel="noopener" style="color:var(--gold-dim)">console.anthropic.com</a>.
+    <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>
   </div>
 
@@ -988,6 +993,25 @@ function buildCreationScreen() {
   });
 
   document.getElementById('begin-btn').addEventListener('click', beginAdventure);
+
+  // Restore saved API key
+  const savedKey = 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 = '';
+  }
+
+  document.getElementById('forget-key-btn').addEventListener('click', () => {
+    localStorage.removeItem('dm_api_key');
+    document.getElementById('api-key-input').value = '';
+    document.getElementById('key-saved-badge').style.display = 'none';
+    document.getElementById('forget-key-btn').style.display = 'none';
+  });
+
+  document.getElementById('api-key-input').addEventListener('input', () => {
+    document.getElementById('key-saved-badge').style.display = 'none';
+  });
 }
 
 function checkBeginReady() {
@@ -997,6 +1021,7 @@ function checkBeginReady() {
 function beginAdventure() {
   const apiKey = document.getElementById('api-key-input').value.trim();
   if (!apiKey) { alert('Please enter your Anthropic API key.'); return; }
+  localStorage.setItem('dm_api_key', apiKey);
 
   const name = document.getElementById('char-name').value.trim() || `The ${selectedClass.name}`;
   const theme = document.getElementById('adventure-theme').value.trim();