fuzzyworld

word-waltz / commit

added notification button

42caeb8510d0f9049084dffbad9006afdd551bbe ยท Pavel Safronov ยท 2026-03-25 22:31:14 -0700 ยท parents 51cb47e73673

added notification button
 cspell.json         |  2 ++
 src/index.html      |  2 ++
 src/main.ts         | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/styles/main.css | 25 +++++++++++++++++++
 4 files changed, 99 insertions(+)

diff --git a/cspell.json b/cspell.json
index f2f9255..71a9877 100644
--- a/cspell.json
+++ b/cspell.json
@@ -3,6 +3,8 @@
     "version": "0.2",
     "language": "en",
     "words": [
+        "notif",
+        "Notif",
         "ASDFGHJKL",
         "ciphertext",
         "ENTERZCVBNM",
diff --git a/src/index.html b/src/index.html
index c02748b..df57278 100644
--- a/src/index.html
+++ b/src/index.html
@@ -19,6 +19,8 @@
             <span class="header__title">Word Waltz</span>
         </a>
         <nav class="header__links">
+            <button id="notif-btn" class="header__notif-btn" type="button" aria-label="Notification settings"
+                title="Enable notifications">๐Ÿ””</button>
             <a href="https://www.flaticon.com/search?author_id=296&style_id=1001&type=standard" target="_blank"
                 rel="noopener" title="Icons by Freepik on Flaticon">Icons</a>
             <a href="https://gitlab.com/PavelSafronov/word-waltz" target="_blank" rel="noopener"
diff --git a/src/main.ts b/src/main.ts
index c99d796..4087c2b 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -794,3 +794,73 @@ bootstrap().catch((err: unknown) => {
     if (app) showError(app, 'Something went wrong loading the game. Please refresh.');
 });
 
+// ---------------------------------------------------------------------------
+// Notification status button โ€” always visible in the header
+// ---------------------------------------------------------------------------
+
+function initNotifButton(): void {
+    const btn = document.getElementById('notif-btn') as HTMLButtonElement | null;
+    if (!btn) return;
+
+    if (!('Notification' in window)) {
+        btn.hidden = true;
+        return;
+    }
+
+    function updateBtn(): void {
+        const perm = Notification.permission;
+        if (perm === 'granted') {
+            btn!.textContent = '๐Ÿ””';
+            btn!.title = 'Notifications enabled โ€” click to test';
+            btn!.classList.add('header__notif-btn--on');
+            btn!.classList.remove('header__notif-btn--blocked');
+        } else if (perm === 'denied') {
+            btn!.textContent = '๐Ÿ”•';
+            btn!.title = 'Notifications blocked';
+            btn!.classList.remove('header__notif-btn--on');
+            btn!.classList.add('header__notif-btn--blocked');
+        } else {
+            btn!.textContent = '๐Ÿ””';
+            btn!.title = 'Enable notifications';
+            btn!.classList.remove('header__notif-btn--on', 'header__notif-btn--blocked');
+        }
+    }
+
+    async function showTestNotification(): Promise<void> {
+        const reg = await registerServiceWorker();
+        if (reg) {
+            await reg.showNotification('Word Waltz', {
+                body: "Notifications are working! You'll be alerted when it's your turn.",
+                icon: `${import.meta.env.BASE_URL}apple-touch-icon.png`,
+                tag: 'word-waltz-test',
+            });
+        }
+    }
+
+    updateBtn();
+
+    btn.addEventListener('click', async () => {
+        const perm = Notification.permission;
+
+        if (perm === 'denied') {
+            alert("Notifications are blocked. To enable them, click the lock/info icon in your browser's address bar, find Notifications, and set it to Allow.");
+            return;
+        }
+
+        if (perm === 'granted') {
+            await showTestNotification();
+            return;
+        }
+
+        // 'default' โ€” request permission
+        await registerServiceWorker();
+        const result = await Notification.requestPermission();
+        updateBtn();
+        if (result === 'granted') {
+            await showTestNotification();
+        }
+    });
+}
+
+initNotifButton();
+
diff --git a/src/styles/main.css b/src/styles/main.css
index 0da06f2..615a19a 100644
--- a/src/styles/main.css
+++ b/src/styles/main.css
@@ -109,6 +109,31 @@ header {
     text-decoration: underline;
 }
 
+.header__notif-btn {
+    background: none;
+    border: none;
+    cursor: pointer;
+    font-size: 1rem;
+    padding: 2px 4px;
+    border-radius: 4px;
+    line-height: 1;
+    opacity: 0.35;
+    transition: opacity 0.15s;
+}
+
+.header__notif-btn:hover {
+    opacity: 0.8;
+}
+
+.header__notif-btn--on {
+    opacity: 1;
+}
+
+.header__notif-btn--blocked {
+    opacity: 0.3;
+    cursor: default;
+}
+
 /* --- App container ---------------------------------------- */
 #app {
     flex: 1;