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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-23 13:29:45 -07:00
parent d458a00550
commit b91233092b
3 changed files with 66 additions and 54 deletions

View File

@ -1439,7 +1439,7 @@ const ICON_FLAT = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" s
// ── Styles ──
const STYLES = `
:host { display: block; }
:host { display: block; flex: 1; min-width: 0; }
/* ── Tab bar (flat mode) ── */

View File

@ -23,9 +23,7 @@
html.rspace-embedded .rstack-tab-row { display: none !important; }
html.rspace-embedded #toolbar { top: 16px !important; }
html.rspace-embedded #community-info { display: none !important; }
html.rspace-embedded #people-online-badge { top: 12px !important; }
html.rspace-embedded #people-panel { top: 48px !important; }
</style>
</style>
<script>if (window.self !== window.parent) document.documentElement.classList.add('rspace-embedded');</script>
<style>
* {
@ -963,26 +961,25 @@
/* ── People Online badge ── */
#people-online-badge {
position: fixed;
top: 68px;
right: 16px;
padding: 6px 12px;
background: var(--rs-bg-surface);
border-radius: 8px;
box-shadow: var(--rs-shadow-sm);
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
color: var(--rs-text-muted);
z-index: 1000;
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
user-select: none;
transition: box-shadow 0.15s;
transition: background 0.15s;
flex-shrink: 0;
margin: 0 8px 0 4px;
position: relative;
border-left: 1px solid var(--rs-border-subtle, rgba(255,255,255,0.08));
padding-left: 12px;
}
#people-online-badge:hover {
box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);
background: var(--rs-bg-hover, rgba(255,255,255,0.08));
}
#people-conn-status {
@ -1024,17 +1021,18 @@
/* ── People Panel ── */
#people-panel {
position: fixed;
top: 104px;
right: 16px;
position: absolute;
top: 100%;
right: 0;
width: 280px;
max-height: calc(100vh - 120px);
background: var(--rs-bg-surface);
border-radius: 12px;
box-shadow: var(--rs-shadow-lg);
z-index: 1001;
z-index: 10001;
display: none;
overflow: hidden;
margin-top: 4px;
}
#people-panel.open {
@ -1269,11 +1267,15 @@
/* ── People panel mobile ── */
@media (max-width: 640px) {
#people-online-badge {
right: 12px;
top: 64px;
margin-left: 2px;
padding: 4px 6px;
}
#people-badge-text {
display: none;
}
#people-panel {
max-width: calc(100vw - 32px);
right: -8px;
}
}
@ -1995,6 +1997,18 @@
<rstack-history-panel type="canvas"></rstack-history-panel>
<div class="rstack-tab-row" data-theme="dark">
<rstack-tab-bar space="" active="" view-mode="flat"></rstack-tab-bar>
<div id="people-online-badge">
<span class="dots" id="people-dots"></span>
<span id="people-badge-text">1 online</span>
<span id="people-conn-status"></span>
</div>
<div id="people-panel">
<div id="people-panel-header">
<h3>People Online</h3>
<span class="count" id="people-count">0</span>
</div>
<div id="people-list"></div>
</div>
</div>
<div id="community-info">
<h2 id="community-name">Loading...</h2>
@ -2268,19 +2282,6 @@
<div id="memory-list"></div>
</div>
<div id="people-panel">
<div id="people-panel-header">
<h3>People Online</h3>
<span class="count" id="people-count">0</span>
</div>
<div id="people-list"></div>
</div>
<div id="people-online-badge">
<span class="dots" id="people-dots"></span>
<span id="people-badge-text">1 online</span>
<span id="people-conn-status"></span>
</div>
<div id="mp-notify">
<span id="mp-notify-text"></span>
@ -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 = `<span class="dot" style="background:${escapeHtml(localColor)}"></span>
selfRow.innerHTML = `<span class="dot" style="background:${isMultiplayer ? escapeHtml(localColor) : '#64748b'}"></span>
<span class="name">${escapeHtml(storedUsername)} <span class="you-tag">(you)</span></span>
<span class="mode-toggle">
<button class="mode-solo ${isMultiplayer ? '' : 'active'}">Solo</button>
<button class="mode-multi ${isMultiplayer ? 'active' : ''}">Multi</button>
<button class="mode-solo ${isMultiplayer ? '' : 'active'}">Offline</button>
<button class="mode-multi ${isMultiplayer ? 'active' : ''}">Online</button>
</span>`;
selfRow.querySelector(".mode-solo").addEventListener("click", () => setMultiplayerMode(false));
selfRow.querySelector(".mode-multi").addEventListener("click", () => setMultiplayerMode(true));

View File

@ -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 ── */