fuzzyworld

dm / commit

Deploy to apps.fuzzyworld.net with fuzzyworld-tools

b6c5244137dfc374e392cc8ec627ad90e8b38b1d · Pavel Safronov · 2026-10-26 18:51:28 -0700 · parents 34071afe9129

Deploy to apps.fuzzyworld.net with fuzzyworld-tools

Adds a package.json with the fuzzyworld section and a deploy script (npx fuzzyworld-tools), and points gitlab.io / gitlab.com links at apps.fuzzyworld.net and code.fuzzyworld.net.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 README.md         |  8 ++++----
 package.json      | 10 +++++++++-
 public/index.html | 24 +++++++-----------------
 3 files changed, 20 insertions(+), 22 deletions(-)

diff --git a/README.md b/README.md
index fa03744..e489b10 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 An AI-powered solo tabletop RPG app that runs entirely in your browser, using the **Cypher System** rules. Your API key never leaves your device — all requests go directly from your browser to the Anthropic API.
 
-**Live:** [pavelsafronov.gitlab.io/dm/](https://pavelsafronov.gitlab.io/dm/)
+**Live:** [apps.fuzzyworld.net/dm/](https://apps.fuzzyworld.net/dm/)
 
 ---
 
@@ -26,7 +26,7 @@ The game is powered by `claude-haiku-4-5-20251001` and uses Claude's tool-use AP
 - **Dice roller** — d4 through d100, configurable count
 - **World Notes** — orange-bordered GM meta-context panel for providing background lore, world physics, and house rules that Claude treats as absolute truth
 - **Share links** — generate an encrypted `#k=…` URL so someone else can use the app with your API key; they enter a passphrase you share separately; the key is never in the URL
-- **Report Issue** — in-game button that collects debug info (character state, recent errors, session cost) and opens a pre-filled GitLab issue or Service Desk email
+- **Report Issue** — in-game button that collects debug info (character state, recent errors, session cost) and opens a pre-filled bug-report email
 
 ## Running locally
 
@@ -45,7 +45,7 @@ Requires Node.js. No build step — the entire app is a single `public/index.htm
 | Share links   | AES-GCM encrypted blob in the URL hash (`#k=`); hash is never sent to the server               |
 | Session state | `localStorage` — character sheet, conversation history, rendered HTML                          |
 | Analytics     | `metadata.user_id` in each API request carries `p_<playerId>:g_<gameId>` for Anthropic Console |
-| Deployment    | GitLab Pages — push to `main`, CI deploys `public/` automatically                              |
+| Deployment    | apps.fuzzyworld.net — `npm run deploy` publishes `public/`                                     |
 
 ### Why a single file?
 
@@ -55,7 +55,7 @@ The tradeoffs are real — no type safety, no module splitting, global state eve
 
 ## Reporting issues
 
-Use the **🐛 Report** button in the top bar — it collects debug info automatically. Or open an issue directly at [gitlab.com/PavelSafronov/dm/-/issues](https://gitlab.com/PavelSafronov/dm/-/issues).
+Use the **🐛 Report** button in the top bar — it collects debug info automatically.
 
 ## License
 
diff --git a/package.json b/package.json
index 5999907..08d628c 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,14 @@
   "version": "1.0.0",
   "description": "AI-powered solo D&D app powered by Claude",
   "scripts": {
-    "dev": "npx serve public"
+    "dev": "npx serve public",
+    "deploy": "npx --yes -p git+https://code.fuzzyworld.net/fuzzyworld-tools.git#semver:^1.0.0 fw deploy"
+  },
+  "fuzzyworld": {
+    "app": "dm",
+    "title": "Solo Dungeon Master",
+    "checks": [],
+    "build": "true",
+    "out": "public"
   }
 }
diff --git a/public/index.html b/public/index.html
index 8508ccf..5865efc 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1270,7 +1270,7 @@ body {
   <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>
   <p style="font-size:0.85rem;color:var(--text-dim);max-width:560px;margin:10px auto 0;line-height:1.6;text-align:center">
-    An AI-powered solo tabletop RPG running entirely in your browser. Build your character below, then Claude becomes your Game Master — narration, rules, and consequences — for a full <a href="https://cyphersrd.quest" target="_blank" rel="noopener" style="color:var(--gold-dim)">Cypher System</a> adventure. Your API key never leaves your device. <a href="https://gitlab.com/PavelSafronov/dm/-/blob/main/README.md" target="_blank" rel="noopener" style="color:var(--gold-dim)">README →</a>
+    An AI-powered solo tabletop RPG running entirely in your browser. Build your character below, then Claude becomes your Game Master — narration, rules, and consequences — for a full <a href="https://cyphersrd.quest" target="_blank" rel="noopener" style="color:var(--gold-dim)">Cypher System</a> adventure. Your API key never leaves your device. <a href="https://code.fuzzyworld.net/dm/-/blob/main/README.md" target="_blank" rel="noopener" style="color:var(--gold-dim)">README →</a>
   </p>
 
   <p class="char-sentence" id="char-sentence">I am a <em>___</em> <em>___</em> who <em>___</em></p>
@@ -1327,7 +1327,7 @@ body {
   </p>
 
   <p class="attribution">
-    <a href="https://gitlab.com/PavelSafronov/dm" target="_blank" rel="noopener">Source &amp; README</a> ·
+    <a href="https://code.fuzzyworld.net/dm/" target="_blank" rel="noopener">Source &amp; README</a> ·
     <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>
   </p>
@@ -1474,7 +1474,7 @@ body {
       <div id="dice-result"></div>
       <div id="dice-result-detail"></div>
       <p class="attribution" style="margin-top:10px">
-        <a href="https://gitlab.com/PavelSafronov/dm" target="_blank" rel="noopener">Source &amp; README</a> ·
+        <a href="https://code.fuzzyworld.net/dm/" target="_blank" rel="noopener">Source &amp; README</a> ·
         <a href="https://www.flaticon.com/authors/icongeek26" target="_blank" rel="noopener">Dice icon by Icongeek26</a> /
         <a href="https://www.flaticon.com/" target="_blank" rel="noopener">Flaticon</a>
       </p>
@@ -1539,12 +1539,11 @@ body {
 
     <div class="report-actions">
       <button class="share-btn share-btn-primary" id="report-email-btn">📧 Email — no account needed</button>
-      <button class="share-btn share-btn-secondary" id="report-gitlab-btn">🔗 GitLab Issue</button>
       <button class="share-btn share-btn-secondary" id="report-copy-btn">📋 Copy info</button>
       <button class="share-btn share-btn-secondary" id="report-close-btn">Cancel</button>
     </div>
     <p style="font-size:0.72rem;color:var(--text-dim);margin-top:10px;font-style:italic">
-      Email creates a support ticket with no account required. GitLab Issue requires a free GitLab account.
+      Email needs no account. Copy info if you would rather send it another way.
     </p>
   </div>
 </div>
@@ -1558,7 +1557,7 @@ body {
       <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>
       <p style="font-size:0.8rem;color:var(--text-dim);margin:0 0 18px;line-height:1.6">
-        An AI-powered solo RPG in your browser. Claude acts as your Game Master for a full <a href="https://cyphersrd.quest" target="_blank" rel="noopener" style="color:var(--gold-dim)">Cypher System</a> adventure — narration, rules, and consequences all generated live. <a href="https://gitlab.com/PavelSafronov/dm/-/blob/main/README.md" target="_blank" rel="noopener" style="color:var(--gold-dim)">README →</a>
+        An AI-powered solo RPG in your browser. Claude acts as your Game Master for a full <a href="https://cyphersrd.quest" target="_blank" rel="noopener" style="color:var(--gold-dim)">Cypher System</a> adventure — narration, rules, and consequences all generated live. <a href="https://code.fuzzyworld.net/dm/-/blob/main/README.md" target="_blank" rel="noopener" style="color:var(--gold-dim)">README →</a>
       </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>
@@ -1779,7 +1778,7 @@ async function decryptApiKey(blob, passphrase) {
   // 'A:' prefix or legacy (no prefix) — both use AES-GCM
   const b = blob.startsWith('A:') ? blob : 'A:' + blob;
   if (!window.isSecureContext)
-    throw new Error('This link uses strong encryption and requires HTTPS to open. Use your GitLab Pages URL.');
+    throw new Error('This link uses strong encryption and requires HTTPS to open. Open it from https://apps.fuzzyworld.net/dm/.');
   return aesDecrypt(b, passphrase);
 }
 
@@ -3511,8 +3510,7 @@ document.getElementById('setup-unlock-btn').addEventListener('click', async () =
 
 // ── REPORT MODAL HANDLERS ─────────────────────────────
 
-const SERVICE_DESK_EMAIL = 'contact-project+pavelsafronov-dm-82624720-issue-@incoming.gitlab.com';
-const GITLAB_ISSUES_URL  = 'https://gitlab.com/PavelSafronov/dm/-/issues/new';
+const SERVICE_DESK_EMAIL = 'bugs-k7q4+dm@fuzzyworld.net';
 
 function openReportModal() {
   document.getElementById('report-desc').value = '';
@@ -3542,14 +3540,6 @@ document.getElementById('report-email-btn').addEventListener('click', () => {
   window.open(`mailto:${SERVICE_DESK_EMAIL}?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`);
 });
 
-document.getElementById('report-gitlab-btn').addEventListener('click', () => {
-  const desc  = document.getElementById('report-desc').value.trim();
-  const body  = buildDebugInfo(desc);
-  const title = desc ? desc.split('\n')[0].slice(0, 80) : 'Bug report';
-  const params = new URLSearchParams({ 'issue[title]': title, 'issue[description]': body });
-  window.open(`${GITLAB_ISSUES_URL}?${params}`);
-});
-
 document.getElementById('report-copy-btn').addEventListener('click', () => {
   const desc = document.getElementById('report-desc').value.trim();
   navigator.clipboard.writeText(buildDebugInfo(desc)).then(() => {