fuzzyworld

word-waltz / commit

waiting notifier

5d462205cdac0f556e28c7252fee71f889d88745 · Pavel Safronov · 2026-03-23 23:43:13 -0700 · parents e86661e4d73d

waiting notifier
 .vscode/settings.json |  9 ++++++++-
 src/main.ts           | 29 ++++++++++++++++++++---------
 src/styles/main.css   | 22 ++++++++++++++++++++++
 3 files changed, 50 insertions(+), 10 deletions(-)

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 9e26dfe..9a49189 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1 +1,8 @@
-{}
\ No newline at end of file
+{
+    "chat.tools.terminal.autoApprove": {
+        "/^npx tsc --noEmit 2>&1 && echo \"TS OK\"$/": {
+            "approve": true,
+            "matchCommandLine": true
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
index 88d1055..90d6af8 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -18,6 +18,23 @@ import { mountShareDialog } from './components/ShareDialog.js';
 import { notifyMyTurn, clearNotification } from './utils/notifications.js';
 import type { GameState, PlayerId, Round } from './types.js';
 
+// ---------------------------------------------------------------------------
+// Waiting status helper
+// ---------------------------------------------------------------------------
+
+function createWaitingEl(text: string): HTMLElement {
+    const wrap = document.createElement('div');
+    wrap.className = 'status status--waiting';
+    const dot = document.createElement('span');
+    dot.className = 'status__live-dot';
+    dot.setAttribute('aria-hidden', 'true');
+    const msg = document.createElement('span');
+    msg.textContent = text;
+    wrap.appendChild(dot);
+    wrap.appendChild(msg);
+    return wrap;
+}
+
 // ---------------------------------------------------------------------------
 // Player identity — stored in localStorage, keyed to the gameId.
 //
@@ -123,10 +140,7 @@ function render(
         const shareEl = document.createElement('div');
         mainEl.appendChild(shareEl);
         cleanupMain = mountShareDialog(shareEl, { shareUrl });
-        const waiting = document.createElement('p');
-        waiting.className = 'status';
-        waiting.textContent = 'Share the link above with your opponent, then wait for them to join…';
-        mainEl.appendChild(waiting);
+        mainEl.appendChild(createWaitingEl('Share the link above with your opponent, then wait for them to join…'));
     } else if (state.turn === 'pick') {
         const challenger = nextChallenger(state);
 
@@ -150,7 +164,7 @@ function render(
                 },
             });
         } else {
-            mainEl.innerHTML = '<p class="status">Waiting for your opponent to pick a word…</p>';
+            mainEl.appendChild(createWaitingEl('Waiting for your opponent to pick a word…'));
         }
     } else {
         // turn === 'solve'
@@ -188,10 +202,7 @@ function render(
             });
         } else {
             // Challenger waits while their opponent solves
-            const waiting = document.createElement('p');
-            waiting.className = 'status';
-            waiting.textContent = 'Waiting for your opponent to solve the word…';
-            mainEl.appendChild(waiting);
+            mainEl.appendChild(createWaitingEl('Waiting for your opponent to solve the word…'));
         }
     }
 
diff --git a/src/styles/main.css b/src/styles/main.css
index f9e5a66..d39fed4 100644
--- a/src/styles/main.css
+++ b/src/styles/main.css
@@ -132,6 +132,28 @@ header {
     padding: 12px 0;
 }
 
+.status--waiting {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+}
+
+.status__live-dot {
+    display: inline-block;
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    background: var(--color-correct);
+    flex-shrink: 0;
+    animation: live-pulse 1.8s ease-in-out infinite;
+}
+
+@keyframes live-pulse {
+    0%, 100% { opacity: 1; transform: scale(1); }
+    50%       { opacity: 0.3; transform: scale(0.6); }
+}
+
 .error {
     color: var(--color-error);
     text-align: center;