From b91233092b3f5e708726c230e09913dbb101f09e Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Mon, 23 Mar 2026 13:29:45 -0700 Subject: [PATCH] fix(ux): move people-online badge into sub-tab header bar - Move people badge + panel from fixed position to inline in .rstack-tab-row - Badge sits right of the layer toggle icon with a subtle separator - Panel drops down from badge position instead of floating fixed - Online/Offline toggle replaces Solo/Multi labels for clarity - Badge shows "Offline" with gray dot when in offline mode - Mobile: hide text label, show dots only - Tab bar gets flex:1 + min-width:0 to share row space Co-Authored-By: Claude Opus 4.6 --- shared/components/rstack-tab-bar.ts | 2 +- website/canvas.html | 116 +++++++++++++++------------- website/public/shell.css | 2 + 3 files changed, 66 insertions(+), 54 deletions(-) diff --git a/shared/components/rstack-tab-bar.ts b/shared/components/rstack-tab-bar.ts index a6b5298..dc4b463 100644 --- a/shared/components/rstack-tab-bar.ts +++ b/shared/components/rstack-tab-bar.ts @@ -1439,7 +1439,7 @@ const ICON_FLAT = ` +
+
+ + 1 online + +
+
+
+

People Online

+ 0 +
+
+

Loading...

@@ -2268,19 +2282,6 @@
-
-
-

People Online

- 0 -
-
-
- -
- - 1 online - -
@@ -3437,26 +3438,35 @@ function renderPeopleBadge() { const totalCount = onlinePeers.size + 1; // +1 for self - peopleBadgeText.textContent = totalCount === 1 ? "1 online" : `${totalCount} online`; peopleDots.innerHTML = ""; - // Self dot - const selfDot = document.createElement("span"); - selfDot.className = "dot"; - selfDot.style.background = localColor; - peopleDots.appendChild(selfDot); - // Remote dots (up to 4) - let dotCount = 0; - for (const [, peer] of onlinePeers) { - if (dotCount >= 4) break; - const dot = document.createElement("span"); - dot.className = "dot"; - dot.style.background = peer.color || "#94a3b8"; - peopleDots.appendChild(dot); - dotCount++; + if (!isMultiplayer) { + // Offline / solo mode + peopleBadgeText.textContent = "Offline"; + const selfDot = document.createElement("span"); + selfDot.className = "dot"; + selfDot.style.background = "#64748b"; + peopleDots.appendChild(selfDot); + } else { + peopleBadgeText.textContent = totalCount === 1 ? "1 online" : `${totalCount} online`; + // Self dot + const selfDot = document.createElement("span"); + selfDot.className = "dot"; + selfDot.style.background = localColor; + peopleDots.appendChild(selfDot); + // Remote dots (up to 4) + let dotCount = 0; + for (const [, peer] of onlinePeers) { + if (dotCount >= 4) break; + const dot = document.createElement("span"); + dot.className = "dot"; + dot.style.background = peer.color || "#94a3b8"; + peopleDots.appendChild(dot); + dotCount++; + } } - peopleCount.textContent = totalCount; + peopleCount.textContent = isMultiplayer ? totalCount : "—"; // Connection status indicator - if (connState === "connected") { + if (connState === "connected" || !isMultiplayer) { peopleConnStatus.classList.remove("visible"); peopleConnStatus.innerHTML = ""; } else { @@ -3470,14 +3480,14 @@ function renderPeoplePanel() { peopleList.innerHTML = ""; - // Self row with mode toggle + // Self row with online/offline toggle const selfRow = document.createElement("div"); selfRow.className = "people-row"; - selfRow.innerHTML = ` + selfRow.innerHTML = ` ${escapeHtml(storedUsername)} (you) - - + + `; selfRow.querySelector(".mode-solo").addEventListener("click", () => setMultiplayerMode(false)); selfRow.querySelector(".mode-multi").addEventListener("click", () => setMultiplayerMode(true)); diff --git a/website/public/shell.css b/website/public/shell.css index 6e73985..761af07 100644 --- a/website/public/shell.css +++ b/website/public/shell.css @@ -154,6 +154,8 @@ body { -webkit-backdrop-filter: blur(12px); border-bottom: 1px solid var(--rs-border-subtle); background: var(--rs-glass-bg); + display: flex; + align-items: center; } /* ── Main content area ── */