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:
parent
d458a00550
commit
b91233092b
|
|
@ -1439,7 +1439,7 @@ const ICON_FLAT = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" s
|
||||||
// ── Styles ──
|
// ── Styles ──
|
||||||
|
|
||||||
const STYLES = `
|
const STYLES = `
|
||||||
:host { display: block; }
|
:host { display: block; flex: 1; min-width: 0; }
|
||||||
|
|
||||||
/* ── Tab bar (flat mode) ── */
|
/* ── Tab bar (flat mode) ── */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,7 @@
|
||||||
html.rspace-embedded .rstack-tab-row { display: none !important; }
|
html.rspace-embedded .rstack-tab-row { display: none !important; }
|
||||||
html.rspace-embedded #toolbar { top: 16px !important; }
|
html.rspace-embedded #toolbar { top: 16px !important; }
|
||||||
html.rspace-embedded #community-info { display: none !important; }
|
html.rspace-embedded #community-info { display: none !important; }
|
||||||
html.rspace-embedded #people-online-badge { top: 12px !important; }
|
</style>
|
||||||
html.rspace-embedded #people-panel { top: 48px !important; }
|
|
||||||
</style>
|
|
||||||
<script>if (window.self !== window.parent) document.documentElement.classList.add('rspace-embedded');</script>
|
<script>if (window.self !== window.parent) document.documentElement.classList.add('rspace-embedded');</script>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
|
|
@ -963,26 +961,25 @@
|
||||||
|
|
||||||
/* ── People Online badge ── */
|
/* ── People Online badge ── */
|
||||||
#people-online-badge {
|
#people-online-badge {
|
||||||
position: fixed;
|
padding: 4px 10px;
|
||||||
top: 68px;
|
border-radius: 6px;
|
||||||
right: 16px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
background: var(--rs-bg-surface);
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: var(--rs-shadow-sm);
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--rs-text-muted);
|
color: var(--rs-text-muted);
|
||||||
z-index: 1000;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
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 {
|
#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 {
|
#people-conn-status {
|
||||||
|
|
@ -1024,17 +1021,18 @@
|
||||||
|
|
||||||
/* ── People Panel ── */
|
/* ── People Panel ── */
|
||||||
#people-panel {
|
#people-panel {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
top: 104px;
|
top: 100%;
|
||||||
right: 16px;
|
right: 0;
|
||||||
width: 280px;
|
width: 280px;
|
||||||
max-height: calc(100vh - 120px);
|
max-height: calc(100vh - 120px);
|
||||||
background: var(--rs-bg-surface);
|
background: var(--rs-bg-surface);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: var(--rs-shadow-lg);
|
box-shadow: var(--rs-shadow-lg);
|
||||||
z-index: 1001;
|
z-index: 10001;
|
||||||
display: none;
|
display: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#people-panel.open {
|
#people-panel.open {
|
||||||
|
|
@ -1269,11 +1267,15 @@
|
||||||
/* ── People panel mobile ── */
|
/* ── People panel mobile ── */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
#people-online-badge {
|
#people-online-badge {
|
||||||
right: 12px;
|
margin-left: 2px;
|
||||||
top: 64px;
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
#people-badge-text {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
#people-panel {
|
#people-panel {
|
||||||
max-width: calc(100vw - 32px);
|
max-width: calc(100vw - 32px);
|
||||||
|
right: -8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1995,6 +1997,18 @@
|
||||||
<rstack-history-panel type="canvas"></rstack-history-panel>
|
<rstack-history-panel type="canvas"></rstack-history-panel>
|
||||||
<div class="rstack-tab-row" data-theme="dark">
|
<div class="rstack-tab-row" data-theme="dark">
|
||||||
<rstack-tab-bar space="" active="" view-mode="flat"></rstack-tab-bar>
|
<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>
|
||||||
<div id="community-info">
|
<div id="community-info">
|
||||||
<h2 id="community-name">Loading...</h2>
|
<h2 id="community-name">Loading...</h2>
|
||||||
|
|
@ -2268,19 +2282,6 @@
|
||||||
<div id="memory-list"></div>
|
<div id="memory-list"></div>
|
||||||
</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">
|
<div id="mp-notify">
|
||||||
<span id="mp-notify-text"></span>
|
<span id="mp-notify-text"></span>
|
||||||
|
|
@ -3437,26 +3438,35 @@
|
||||||
|
|
||||||
function renderPeopleBadge() {
|
function renderPeopleBadge() {
|
||||||
const totalCount = onlinePeers.size + 1; // +1 for self
|
const totalCount = onlinePeers.size + 1; // +1 for self
|
||||||
peopleBadgeText.textContent = totalCount === 1 ? "1 online" : `${totalCount} online`;
|
|
||||||
peopleDots.innerHTML = "";
|
peopleDots.innerHTML = "";
|
||||||
// Self dot
|
if (!isMultiplayer) {
|
||||||
const selfDot = document.createElement("span");
|
// Offline / solo mode
|
||||||
selfDot.className = "dot";
|
peopleBadgeText.textContent = "Offline";
|
||||||
selfDot.style.background = localColor;
|
const selfDot = document.createElement("span");
|
||||||
peopleDots.appendChild(selfDot);
|
selfDot.className = "dot";
|
||||||
// Remote dots (up to 4)
|
selfDot.style.background = "#64748b";
|
||||||
let dotCount = 0;
|
peopleDots.appendChild(selfDot);
|
||||||
for (const [, peer] of onlinePeers) {
|
} else {
|
||||||
if (dotCount >= 4) break;
|
peopleBadgeText.textContent = totalCount === 1 ? "1 online" : `${totalCount} online`;
|
||||||
const dot = document.createElement("span");
|
// Self dot
|
||||||
dot.className = "dot";
|
const selfDot = document.createElement("span");
|
||||||
dot.style.background = peer.color || "#94a3b8";
|
selfDot.className = "dot";
|
||||||
peopleDots.appendChild(dot);
|
selfDot.style.background = localColor;
|
||||||
dotCount++;
|
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
|
// Connection status indicator
|
||||||
if (connState === "connected") {
|
if (connState === "connected" || !isMultiplayer) {
|
||||||
peopleConnStatus.classList.remove("visible");
|
peopleConnStatus.classList.remove("visible");
|
||||||
peopleConnStatus.innerHTML = "";
|
peopleConnStatus.innerHTML = "";
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3470,14 +3480,14 @@
|
||||||
|
|
||||||
function renderPeoplePanel() {
|
function renderPeoplePanel() {
|
||||||
peopleList.innerHTML = "";
|
peopleList.innerHTML = "";
|
||||||
// Self row with mode toggle
|
// Self row with online/offline toggle
|
||||||
const selfRow = document.createElement("div");
|
const selfRow = document.createElement("div");
|
||||||
selfRow.className = "people-row";
|
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="name">${escapeHtml(storedUsername)} <span class="you-tag">(you)</span></span>
|
||||||
<span class="mode-toggle">
|
<span class="mode-toggle">
|
||||||
<button class="mode-solo ${isMultiplayer ? '' : 'active'}">Solo</button>
|
<button class="mode-solo ${isMultiplayer ? '' : 'active'}">Offline</button>
|
||||||
<button class="mode-multi ${isMultiplayer ? 'active' : ''}">Multi</button>
|
<button class="mode-multi ${isMultiplayer ? 'active' : ''}">Online</button>
|
||||||
</span>`;
|
</span>`;
|
||||||
selfRow.querySelector(".mode-solo").addEventListener("click", () => setMultiplayerMode(false));
|
selfRow.querySelector(".mode-solo").addEventListener("click", () => setMultiplayerMode(false));
|
||||||
selfRow.querySelector(".mode-multi").addEventListener("click", () => setMultiplayerMode(true));
|
selfRow.querySelector(".mode-multi").addEventListener("click", () => setMultiplayerMode(true));
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,8 @@ body {
|
||||||
-webkit-backdrop-filter: blur(12px);
|
-webkit-backdrop-filter: blur(12px);
|
||||||
border-bottom: 1px solid var(--rs-border-subtle);
|
border-bottom: 1px solid var(--rs-border-subtle);
|
||||||
background: var(--rs-glass-bg);
|
background: var(--rs-glass-bg);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Main content area ── */
|
/* ── Main content area ── */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue