fuzzyworld

north-star / commit

added room controls

b7d4b972f5d25e622b06c8410686e0a4255a54ed · Pavel Safronov · 2026-03-14 13:24:06 -0700 · parents dcd59aa1eefe

added room controls
 src/app.js     | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/index.html |  7 ++++++
 src/style.css  | 47 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 123 insertions(+)

diff --git a/src/app.js b/src/app.js
index 14232c4..9abc076 100644
--- a/src/app.js
+++ b/src/app.js
@@ -11,6 +11,10 @@ const debugParagraph = document.getElementById('debug');
 const nameForm = document.getElementById('name-form');
 const selfNameInput = document.getElementById('self-name-input');
 const selfColorInput = document.getElementById('self-color-input');
+const roomCurrent = document.getElementById('room-current');
+const inviteRoomLink = document.getElementById('invite-room-link');
+const inviteRoomStatus = document.getElementById('invite-room-status');
+const createRoomLink = document.getElementById('create-room-link');
 const logDebug = (message) => {
     debugParagraph.innerText = message;
 }
@@ -84,6 +88,71 @@ if (!roomId) {
 }
 console.debug('Room ID:', roomId);
 
+if (roomCurrent) {
+    roomCurrent.textContent = `You are currently in room ${roomId}`;
+}
+
+const BASE_ROOM_URL = `${window.location.origin}${window.location.pathname}`;
+if (inviteRoomLink) {
+    inviteRoomLink.href = `${BASE_ROOM_URL}#room=${encodeURIComponent(roomId)}`;
+}
+if (createRoomLink) {
+    createRoomLink.href = BASE_ROOM_URL;
+}
+
+let inviteStatusTimer = null;
+const setInviteStatus = (message, isError = false) => {
+    if (!inviteRoomStatus) return;
+
+    inviteRoomStatus.textContent = message;
+    inviteRoomStatus.classList.toggle('error', isError);
+
+    if (inviteStatusTimer) {
+        clearTimeout(inviteStatusTimer);
+    }
+
+    inviteStatusTimer = setTimeout(() => {
+        inviteRoomStatus.textContent = '';
+        inviteRoomStatus.classList.remove('error');
+    }, 1800);
+};
+
+const copyTextToClipboard = async (text) => {
+    if (navigator.clipboard && window.isSecureContext) {
+        await navigator.clipboard.writeText(text);
+        return;
+    }
+
+    const tempTextArea = document.createElement('textarea');
+    tempTextArea.value = text;
+    tempTextArea.setAttribute('readonly', '');
+    tempTextArea.style.position = 'fixed';
+    tempTextArea.style.left = '-9999px';
+    document.body.appendChild(tempTextArea);
+    tempTextArea.select();
+
+    const copied = document.execCommand('copy');
+    document.body.removeChild(tempTextArea);
+
+    if (!copied) {
+        throw new Error('Copy command failed');
+    }
+};
+
+if (inviteRoomLink) {
+    inviteRoomLink.addEventListener('click', async (event) => {
+        event.preventDefault();
+
+        try {
+            await copyTextToClipboard(inviteRoomLink.href);
+            setInviteStatus('Copied!');
+        } catch (err) {
+            console.error('Failed to copy invite URL', err);
+            setInviteStatus('Copy failed', true);
+        }
+    });
+}
+
 const NAME_STORAGE_KEY = 'north-star-self-name';
 const COLOR_STORAGE_KEY = 'north-star-self-color';
 const normalizeName = (value) => (value || '').trim().slice(0, 32);
diff --git a/src/index.html b/src/index.html
index 5551059..b5bee25 100644
--- a/src/index.html
+++ b/src/index.html
@@ -11,6 +11,13 @@
 
 <body>
     <p class="app-title"><a href="https://pavelsafronov.gitlab.io/north-star/">North Star</a></p>
+    <p id="room-current" class="room-current"></p>
+    <p class="room-links">
+        <a id="invite-room-link" href="#">Invite to Room</a>
+        <span id="invite-room-status" class="room-link-status" aria-live="polite"></span>
+
+        <a id="create-room-link" href="https://pavelsafronov.gitlab.io/north-star/">Create New Room</a>
+    </p>
     <form id="name-form" class="name-form" autocomplete="off">
         <label for="self-name-input">You</label>
         <input id="self-name-input" name="selfName" type="text" maxlength="32" enterkeyhint="done" />
diff --git a/src/style.css b/src/style.css
index acd6ffa..6af8c78 100644
--- a/src/style.css
+++ b/src/style.css
@@ -35,6 +35,53 @@ a {
     text-decoration: none;
 }
 
+.room-current {
+    position: fixed;
+    top: max(50px, calc(env(safe-area-inset-top) + 50px));
+    left: 50%;
+    transform: translateX(-50%);
+    margin: 0;
+    z-index: 11;
+    font-size: clamp(12px, 1.8vmin, 16px);
+    font-weight: 600;
+    color: #f8fafc;
+    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
+}
+
+.room-links {
+    position: fixed;
+    top: max(74px, calc(env(safe-area-inset-top) + 74px));
+    left: 50%;
+    transform: translateX(-50%);
+    margin: 0;
+    z-index: 11;
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    font-size: clamp(13px, 2vmin, 18px);
+    font-weight: 600;
+}
+
+.room-links a {
+    text-decoration: underline;
+    text-underline-offset: 0.15em;
+}
+
+.room-links span {
+    color: rgba(248, 250, 252, 0.8);
+}
+
+.room-link-status {
+    min-width: 58px;
+    color: #86efac;
+    font-size: 0.9em;
+    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
+}
+
+.room-link-status.error {
+    color: #fecaca;
+}
+
 .name-form {
     position: fixed;
     left: 50%;