icons
This commit is contained in:
1 parent
ec02a6ddaf
commit
aab7931142
3 files changed
+286
-90
No files matched your search
+101
-18
@@ -34,6 +34,7 @@ window.__dbg = S; // POC debug handle
|
||||
|
||||
const $ = (sel) => document.querySelector(sel);
|
||||
const els = {
|
||||
voiceView: $('#voice-view'),
|
||||
stage: $('#stage'), screens: $('#screens'),
|
||||
spatial: $('#spatial'), status: $('#voice-status'), statusDot: $('#status-dot'),
|
||||
statusRow: $('#status-row'), nick: $('#nick'),
|
||||
@@ -50,6 +51,13 @@ const shareButtons = [...document.querySelectorAll('#btn-share, .mirror-share')]
|
||||
const statusTextEls = [...document.querySelectorAll('#voice-status, #mobile-status-text')];
|
||||
const statusDotEls = [...document.querySelectorAll('#status-dot, #mobile-status-dot')];
|
||||
|
||||
function mIcon(name, extraClass = '') {
|
||||
const span = document.createElement('span');
|
||||
span.className = `material-symbols-outlined${extraClass ? ` ${extraClass}` : ''}`;
|
||||
span.textContent = name;
|
||||
return span;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -784,9 +792,27 @@ function renderTiles() {
|
||||
|
||||
const meBadges = document.createElement('span');
|
||||
meBadges.className = 'user-line-badges';
|
||||
if (S.muted) meBadges.innerHTML += '<span class="badge-mute" title="Muted">🎤️‍❌</span>';
|
||||
if (S.deafened) meBadges.innerHTML += '<span class="badge-deafen" title="Deafened">👂❌</span>';
|
||||
if (S.sharing) meBadges.innerHTML += '<span class="badge-share" title="Sharing screen">🖥️</span>';
|
||||
if (S.muted) {
|
||||
const b = document.createElement('span');
|
||||
b.className = 'badge-mute';
|
||||
b.title = 'Muted';
|
||||
b.appendChild(mIcon('mic_off'));
|
||||
meBadges.appendChild(b);
|
||||
}
|
||||
if (S.deafened) {
|
||||
const b = document.createElement('span');
|
||||
b.className = 'badge-deafen';
|
||||
b.title = 'Deafened';
|
||||
b.appendChild(mIcon('headset_off'));
|
||||
meBadges.appendChild(b);
|
||||
}
|
||||
if (S.sharing) {
|
||||
const b = document.createElement('span');
|
||||
b.className = 'badge-share';
|
||||
b.title = 'Sharing screen';
|
||||
b.appendChild(mIcon('present_to_all'));
|
||||
meBadges.appendChild(b);
|
||||
}
|
||||
|
||||
meItem.append(meAv, meName, meBadges);
|
||||
roster.appendChild(meItem);
|
||||
@@ -808,8 +834,20 @@ function renderTiles() {
|
||||
if (!peer.pc || peer.pc.connectionState !== 'connected') {
|
||||
badges.innerHTML += '<span class="badge-conn" title="Connecting...">…</span>';
|
||||
}
|
||||
if (peer.state.muted) badges.innerHTML += '<span class="badge-mute" title="Muted">🎤️‍❌</span>';
|
||||
if (peer.state.sharing) badges.innerHTML += '<span class="badge-share" title="Sharing screen">🖥️</span>';
|
||||
if (peer.state.muted) {
|
||||
const b = document.createElement('span');
|
||||
b.className = 'badge-mute';
|
||||
b.title = 'Muted';
|
||||
b.appendChild(mIcon('mic_off'));
|
||||
badges.appendChild(b);
|
||||
}
|
||||
if (peer.state.sharing) {
|
||||
const b = document.createElement('span');
|
||||
b.className = 'badge-share';
|
||||
b.title = 'Sharing screen';
|
||||
b.appendChild(mIcon('present_to_all'));
|
||||
badges.appendChild(b);
|
||||
}
|
||||
|
||||
// Volume hover popup
|
||||
const volPop = document.createElement('div');
|
||||
@@ -858,8 +896,20 @@ function renderTiles() {
|
||||
|
||||
const badges = document.createElement('span');
|
||||
badges.className = 'user-line-badges';
|
||||
if (user.state?.muted) badges.innerHTML += '<span class="badge-mute" title="Muted">🎤️‍❌</span>';
|
||||
if (user.state?.sharing) badges.innerHTML += '<span class="badge-share" title="Sharing screen">🖥️</span>';
|
||||
if (user.state?.muted) {
|
||||
const b = document.createElement('span');
|
||||
b.className = 'badge-mute';
|
||||
b.title = 'Muted';
|
||||
b.appendChild(mIcon('mic_off'));
|
||||
badges.appendChild(b);
|
||||
}
|
||||
if (user.state?.sharing) {
|
||||
const b = document.createElement('span');
|
||||
b.className = 'badge-share';
|
||||
b.title = 'Sharing screen';
|
||||
b.appendChild(mIcon('present_to_all'));
|
||||
badges.appendChild(b);
|
||||
}
|
||||
|
||||
item.append(av, name, badges);
|
||||
roster.appendChild(item);
|
||||
@@ -878,7 +928,9 @@ function activeShares() {
|
||||
const shares = [];
|
||||
if (S.sharing && S.screenStream) shares.push({ key: 'me', name: `${S.name} (you)`, stream: S.screenStream });
|
||||
for (const peer of S.peers.values()) {
|
||||
if (peer.screenStream) shares.push({ key: peer.id, name: peer.name, stream: peer.screenStream });
|
||||
if (peer.state.sharing && peer.screenStream) {
|
||||
shares.push({ key: peer.id, name: peer.name, stream: peer.screenStream });
|
||||
}
|
||||
}
|
||||
return shares;
|
||||
}
|
||||
@@ -893,6 +945,8 @@ function renderScreens() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!els.screens) return;
|
||||
|
||||
els.screens.innerHTML = '';
|
||||
els.screens.classList.toggle('multi', activeShares().length > 1);
|
||||
for (const share of activeShares()) {
|
||||
@@ -902,7 +956,8 @@ function renderScreens() {
|
||||
ph.className = 'screen-card popped';
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'pop-btn';
|
||||
btn.textContent = '⧉ re-embed';
|
||||
btn.appendChild(mIcon('open_in_new'));
|
||||
btn.appendChild(document.createTextNode(' re-embed'));
|
||||
btn.onclick = () => closePopout(share.key);
|
||||
const label = document.createElement('div');
|
||||
label.className = 'label';
|
||||
@@ -926,11 +981,12 @@ function renderScreens() {
|
||||
label.appendChild(document.createTextNode(share.name));
|
||||
const micBadge = document.createElement('span');
|
||||
micBadge.className = 'tile-mic-badge';
|
||||
micBadge.appendChild(mIcon('mic_off'));
|
||||
label.appendChild(micBadge);
|
||||
const pop = document.createElement('button');
|
||||
pop.className = 'pop-btn';
|
||||
pop.title = 'Pop out in new window';
|
||||
pop.textContent = '⧉';
|
||||
pop.appendChild(mIcon('open_in_new'));
|
||||
pop.onclick = () => popOutShare(share);
|
||||
card.append(video, label, pop);
|
||||
els.screens.appendChild(card);
|
||||
@@ -1428,8 +1484,24 @@ function mxRenderTimeline() {
|
||||
els.mxMessages.scrollTop = els.mxMessages.scrollHeight;
|
||||
}
|
||||
|
||||
function mxShowPanel() {
|
||||
function switchToChatView() {
|
||||
if (els.voiceView) els.voiceView.classList.add('hidden');
|
||||
if (els.mxPanel) els.mxPanel.classList.remove('hidden');
|
||||
document.querySelectorAll('.channel.text').forEach((c) => c.classList.add('active'));
|
||||
if (els.mxMessages) els.mxMessages.scrollTop = els.mxMessages.scrollHeight;
|
||||
if (els.mxInput) els.mxInput.focus();
|
||||
}
|
||||
|
||||
function switchToVoiceView() {
|
||||
if (els.mxPanel) els.mxPanel.classList.add('hidden');
|
||||
if (els.voiceView) els.voiceView.classList.remove('hidden');
|
||||
document.querySelectorAll('.channel.text').forEach((c) => c.classList.remove('active'));
|
||||
// Trigger Cytoscape redraw/resize because voice-view was unhidden
|
||||
if (typeof cy !== 'undefined') cy.resize();
|
||||
}
|
||||
|
||||
function mxShowPanel() {
|
||||
switchToChatView();
|
||||
}
|
||||
|
||||
function mxSend(text) {
|
||||
@@ -1462,12 +1534,13 @@ mxInit();
|
||||
|
||||
document.querySelectorAll('.channel.text').forEach((el) => {
|
||||
el.onclick = () => {
|
||||
if (Mx.roomId) { mxShowPanel(); return; }
|
||||
closeSidebar();
|
||||
if (Mx.roomId) { switchToChatView(); return; }
|
||||
if (Mx.client) { toast('Connecting to Matrix…'); return; }
|
||||
mxStartLogin();
|
||||
};
|
||||
});
|
||||
$('#mx-close')?.addEventListener('click', () => els.mxPanel.classList.add('hidden'));
|
||||
$('#mx-close')?.addEventListener('click', () => switchToVoiceView());
|
||||
$('#mx-send')?.addEventListener('click', () => {
|
||||
mxSend(els.mxInput.value);
|
||||
els.mxInput.value = '';
|
||||
@@ -1486,12 +1559,15 @@ function updateMicButton() {
|
||||
const off = S.muted || !S.micStream;
|
||||
for (const btn of micButtons) {
|
||||
btn.classList.toggle('off', off);
|
||||
btn.textContent = off ? '\u{1F507}' : '\u{1F3A4}'; // 🔇 muted/no-mic vs 🎤 live
|
||||
btn.replaceChildren(mIcon(off ? 'mic_off' : 'mic'));
|
||||
btn.disabled = !S.micStream;
|
||||
}
|
||||
}
|
||||
function updateDeafenButton() {
|
||||
for (const btn of deafenButtons) btn.classList.toggle('off', S.deafened);
|
||||
for (const btn of deafenButtons) {
|
||||
btn.classList.toggle('off', S.deafened);
|
||||
btn.replaceChildren(mIcon(S.deafened ? 'headset_off' : 'headphones'));
|
||||
}
|
||||
}
|
||||
function sendState() {
|
||||
send({ type: 'state', state: { muted: S.muted, deafened: S.deafened, sharing: S.sharing } });
|
||||
@@ -1524,9 +1600,15 @@ $('#btn-settings').onclick = openSettings;
|
||||
|
||||
els.nick.value = S.name;
|
||||
els.nick.addEventListener('change', () => {
|
||||
const trimmed = els.nick.value.trim();
|
||||
if (!trimmed) { els.nick.value = S.name; return; } // nickname can't be blanked out
|
||||
S.name = trimmed;
|
||||
const v = els.nick.value.trim();
|
||||
if (v) S.name = v;
|
||||
els.nick.value = S.name;
|
||||
localStorage.setItem(LS_NAME, S.name);
|
||||
setAvatar(els.meAvatar, S.name, S.myId || 'me');
|
||||
if (S.ws && S.ws.readyState === 1) send({ type: 'rename', name: S.name });
|
||||
renderTiles();
|
||||
});
|
||||
els.nick.addEventListener('blur', () => {
|
||||
els.nick.value = S.name;
|
||||
localStorage.setItem(LS_NAME, S.name);
|
||||
setAvatar(els.meAvatar, S.name, S.myId || 'me');
|
||||
@@ -1536,6 +1618,7 @@ setAvatar(els.meAvatar, S.name, 'me');
|
||||
document.querySelectorAll('.channel.voice').forEach((el) => {
|
||||
el.onclick = () => {
|
||||
closeSidebar();
|
||||
switchToVoiceView();
|
||||
if (S.room === el.dataset.room) return;
|
||||
cancelReconnect();
|
||||
document.querySelectorAll('.channel.voice').forEach((c) => c.classList.remove('active'));
|
||||
|
||||
+48
-39
@@ -5,6 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>gibbz voice — POC</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" href="/favicon.ico" sizes="any" />
|
||||
<link rel="icon" type="image/svg+xml" href="/icons/icon.svg" />
|
||||
@@ -28,7 +29,7 @@
|
||||
<body>
|
||||
<!-- mobile top bar: hamburger opens the channel drawer, shows live status -->
|
||||
<header id="mobile-topbar">
|
||||
<button id="btn-menu" title="Channels">☰</button>
|
||||
<button id="btn-menu" title="Channels"><span class="material-symbols-outlined">menu</span></button>
|
||||
<img id="mobile-logo" src="/icons/icon.svg" alt="" width="24" height="24" />
|
||||
<span id="mobile-title">gibbz voice</span>
|
||||
<span class="status-row" id="mobile-status-row">
|
||||
@@ -46,12 +47,12 @@
|
||||
gibbz voice <span class="poc-tag">POC</span>
|
||||
</header>
|
||||
<div class="channel-group-label">Text channels</div>
|
||||
<div class="channel text" data-mx-room="gibbz-general">#
|
||||
<div class="channel text" data-mx-room="gibbz-general"><span class="material-symbols-outlined channel-icon">tag</span>
|
||||
general <span class="hint">matrix</span></div>
|
||||
<div class="channel-group-label">Voice channels</div>
|
||||
<div class="channel voice" data-room="general">🔊 General <span class="count" data-room-count="general"></span></div>
|
||||
<div class="channel voice" data-room="gaming">🔊 Gaming <span class="count" data-room-count="gaming"></span></div>
|
||||
<div class="channel voice" data-room="lounge">🔊 Lounge <span class="count" data-room-count="lounge"></span></div>
|
||||
<div class="channel voice" data-room="general"><span class="material-symbols-outlined channel-icon">volume_up</span> General <span class="count" data-room-count="general"></span></div>
|
||||
<div class="channel voice" data-room="gaming"><span class="material-symbols-outlined channel-icon">volume_up</span> Gaming <span class="count" data-room-count="gaming"></span></div>
|
||||
<div class="channel voice" data-room="lounge"><span class="material-symbols-outlined channel-icon">volume_up</span> Lounge <span class="count" data-room-count="lounge"></span></div>
|
||||
|
||||
<footer id="user-panel">
|
||||
<div class="me">
|
||||
@@ -65,42 +66,62 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-buttons">
|
||||
<button id="btn-mic" class="ctl" title="Toggle mic (M)">🎤</button>
|
||||
<button id="btn-deafen" class="ctl" title="Toggle deafen (D)">👀</button>
|
||||
<button id="btn-share" class="ctl" title="Share your screen (S)">🖥️</button>
|
||||
<button id="btn-settings" class="ctl" title="Voice & ICE settings">⚙️</button>
|
||||
<button id="btn-help" class="ctl" title="How to use gibbz voice">❓</button>
|
||||
<button id="btn-mic" class="ctl" title="Toggle mic (M)"><span class="material-symbols-outlined">mic</span></button>
|
||||
<button id="btn-deafen" class="ctl" title="Toggle deafen (D)"><span class="material-symbols-outlined">headphones</span></button>
|
||||
<button id="btn-share" class="ctl" title="Share your screen (S)"><span class="material-symbols-outlined">present_to_all</span></button>
|
||||
<button id="btn-settings" class="ctl" title="Voice & ICE settings"><span class="material-symbols-outlined">settings</span></button>
|
||||
<button id="btn-help" class="ctl" title="How to use gibbz voice"><span class="material-symbols-outlined">help</span></button>
|
||||
</div>
|
||||
</footer>
|
||||
</aside>
|
||||
|
||||
<!-- main stage -->
|
||||
<!-- main content area -->
|
||||
<main id="main">
|
||||
<div id="stage">
|
||||
<div id="screens"></div>
|
||||
<div id="stage-empty">
|
||||
<div class="big-icon">🖥️</div>
|
||||
<h2>No active screen shares</h2>
|
||||
<p>When someone shares their screen, it will appear here. Click the screen button below or press <b>S</b> to share.</p>
|
||||
<!-- voice stage and spatial audio view -->
|
||||
<div id="voice-view">
|
||||
<div id="stage">
|
||||
<div id="screens"></div>
|
||||
<div id="stage-empty">
|
||||
<div class="big-icon"><span class="material-symbols-outlined">present_to_all</span></div>
|
||||
<h2>No active screen shares</h2>
|
||||
<p>When someone shares their screen, it will appear here. Click the screen button below or press <b>S</b> to share.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="spatial-wrap">
|
||||
<div class="spatial-label">
|
||||
<span>SPATIAL AUDIO</span>
|
||||
<span class="hint">drag your avatar to move; drag peers for local positioning • scroll to zoom</span>
|
||||
<button id="btn-spatial-fit" class="spatial-action-btn" title="Fit all users in view"><span class="material-symbols-outlined">fit_screen</span> Fit View</button>
|
||||
</div>
|
||||
<div id="spatial"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="spatial-wrap">
|
||||
<div class="spatial-label">
|
||||
<span>SPATIAL AUDIO</span>
|
||||
<span class="hint">drag your avatar to move; drag peers for local positioning • scroll to zoom</span>
|
||||
<button id="btn-spatial-fit" class="spatial-action-btn" title="Fit all users in view">⛶ Fit View</button>
|
||||
<!-- matrix text chat view -->
|
||||
<div id="mx-panel" class="hidden">
|
||||
<div class="mx-header">
|
||||
<div class="mx-header-info">
|
||||
<span class="material-symbols-outlined channel-icon">tag</span>
|
||||
<span class="mx-header-title">general</span>
|
||||
<span class="poc-tag">matrix · experimental</span>
|
||||
</div>
|
||||
<button id="mx-close" class="ghost" title="Back to Voice Stage"><span class="material-symbols-outlined">close</span></button>
|
||||
</div>
|
||||
<div id="mx-messages"></div>
|
||||
<div class="mx-composer">
|
||||
<textarea id="mx-input" rows="1" placeholder="Message #general (Enter to send)"></textarea>
|
||||
<button id="mx-send">Send</button>
|
||||
</div>
|
||||
<div id="spatial"></div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- mobile bottom bar: always-reachable call controls, mirror the sidebar's -->
|
||||
<div id="mobile-controls">
|
||||
<button class="ctl mirror-mic" title="Toggle mic (M)">🎤</button>
|
||||
<button class="ctl mirror-deafen" title="Toggle deafen (D)">👀</button>
|
||||
<button class="ctl mirror-share" title="Share your screen (S)">🖥️</button>
|
||||
<button class="ctl mirror-mic" title="Toggle mic (M)"><span class="material-symbols-outlined">mic</span></button>
|
||||
<button class="ctl mirror-deafen" title="Toggle deafen (D)"><span class="material-symbols-outlined">headphones</span></button>
|
||||
<button class="ctl mirror-share" title="Share your screen (S)"><span class="material-symbols-outlined">present_to_all</span></button>
|
||||
</div>
|
||||
|
||||
<!-- settings modal -->
|
||||
@@ -138,7 +159,7 @@
|
||||
<li>Click a <b>voice channel</b> in the sidebar to join it — click another to switch.</li>
|
||||
<li><b>Mic</b>, <b>Deafen</b> and <b>Share screen</b> are the buttons bottom-left, or press <b>M</b> / <b>D</b> / <b>S</b>.</li>
|
||||
<li>On the <b>spatial audio</b> map, drag your dot around — you hear people relative to where they stand.</li>
|
||||
<li>Screen shares show up at the top of the main area; pop one into its own window with <b>⧦</b>.</li>
|
||||
<li>Screen shares show up at the top of the main area; pop one into its own window with <span class="material-symbols-outlined inline-icon">open_in_new</span>.</li>
|
||||
</ul>
|
||||
|
||||
<div class="modal-actions">
|
||||
@@ -148,18 +169,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mx-panel" class="hidden">
|
||||
<div class="mx-header">
|
||||
<span>#general <span class="poc-tag">matrix · experimental</span></span>
|
||||
<button id="mx-close" class="ghost" title="Close">×</button>
|
||||
</div>
|
||||
<div id="mx-messages"></div>
|
||||
<div class="mx-composer">
|
||||
<textarea id="mx-input" rows="1" placeholder="Message #general (Enter to send)"></textarea>
|
||||
<button id="mx-send">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="reconnect-banner" class="hidden" role="alert">
|
||||
<div class="reconnect-spinner"></div>
|
||||
<span id="reconnect-text">Connection lost</span>
|
||||
|
||||
+137
-33
@@ -22,6 +22,41 @@ body {
|
||||
color: var(--text-0);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Material Symbols font configuration */
|
||||
.material-symbols-outlined {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
font-feature-settings: 'liga';
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.inline-icon {
|
||||
font-size: 16px;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
.channel-icon {
|
||||
font-size: 18px;
|
||||
color: var(--text-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.channel:hover .channel-icon,
|
||||
.channel.active .channel-icon {
|
||||
color: var(--text-0);
|
||||
}
|
||||
|
||||
#app { display: flex; height: 100vh; }
|
||||
|
||||
/* sidebar */
|
||||
@@ -46,12 +81,14 @@ body {
|
||||
.channel {
|
||||
margin: 1px 8px; padding: 7px 8px; border-radius: 6px; color: var(--text-1);
|
||||
cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.channel:hover { background: var(--bg-3); color: var(--text-0); }
|
||||
.channel.text { color: var(--text-2); }
|
||||
.channel.text.active { background: rgba(88,101,242,.25); color: var(--text-0); }
|
||||
.channel.text .hint { font-size: 10px; margin-left: 4px; }
|
||||
.channel.voice.active { background: rgba(88,101,242,.25); color: var(--text-0); }
|
||||
.channel .count { float: right; color: var(--text-2); font-size: 12px; }
|
||||
.channel .count { margin-left: auto; color: var(--text-2); font-size: 12px; }
|
||||
|
||||
#user-panel {
|
||||
margin-top: auto; background: var(--bg-0); padding: 8px;
|
||||
@@ -85,17 +122,16 @@ body {
|
||||
width: 28px; height: 28px; border-radius: 6px; cursor: pointer; position: relative;
|
||||
display: grid; place-items: center; padding: 0;
|
||||
}
|
||||
.ctl .material-symbols-outlined { font-size: 20px; }
|
||||
.ctl:hover { background: var(--bg-3); color: var(--text-0); }
|
||||
.ctl.off { color: var(--red); }
|
||||
.ctl.off::after {
|
||||
content: ""; position: absolute; left: 5px; top: 13px; width: 18px; height: 2px;
|
||||
background: var(--red); transform: rotate(-40deg); border-radius: 1px;
|
||||
}
|
||||
.ctl:disabled { opacity: .35; cursor: not-allowed; }
|
||||
.ctl.active { color: var(--green); }
|
||||
|
||||
/* main */
|
||||
#main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
||||
#main { flex: 1; display: flex; flex-direction: column; min-width: 0; position: relative; height: 100%; }
|
||||
#voice-view { flex: 1; display: flex; flex-direction: column; min-width: 0; height: 100%; }
|
||||
#voice-view.hidden { display: none; }
|
||||
#stage {
|
||||
flex: 1; display: flex; flex-direction: column; padding: 16px; gap: 16px;
|
||||
overflow: auto; position: relative;
|
||||
@@ -105,6 +141,7 @@ body {
|
||||
text-align: center; gap: 8px; color: var(--text-1); padding: 24px;
|
||||
}
|
||||
#stage-empty .big-icon { font-size: 56px; }
|
||||
#stage-empty .big-icon .material-symbols-outlined { font-size: 64px; opacity: 0.6; }
|
||||
#stage-empty h2 { font-size: 20px; color: var(--text-0); }
|
||||
#stage-empty p { max-width: 420px; color: var(--text-2); }
|
||||
#stage.empty #stage-empty { display: grid; }
|
||||
@@ -120,8 +157,10 @@ body {
|
||||
.pop-btn {
|
||||
position: absolute; right: 8px; top: 8px; z-index: 2;
|
||||
background: rgba(0,0,0,.65); color: var(--text-0); border: none; cursor: pointer;
|
||||
border-radius: 5px; padding: 3px 8px; font-size: 13px; font-weight: 600;
|
||||
border-radius: 5px; padding: 4px 8px; font-size: 12px; font-weight: 600;
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
}
|
||||
.pop-btn .material-symbols-outlined { font-size: 16px; }
|
||||
.pop-btn:hover { background: var(--brand); }
|
||||
.screen-card {
|
||||
position: relative; background: #000; border-radius: var(--radius); overflow: hidden;
|
||||
@@ -131,12 +170,12 @@ body {
|
||||
.screen-card .label {
|
||||
position: absolute; left: 8px; bottom: 8px; background: rgba(0,0,0,.65);
|
||||
padding: 3px 8px; border-radius: 5px; font-size: 12px; font-weight: 600;
|
||||
display: inline-flex; align-items: center;
|
||||
}
|
||||
.screen-card .label .live { color: var(--red); font-weight: 800; margin-right: 5px; }
|
||||
.screen-card.speaking { box-shadow: 0 0 0 3px var(--green), 0 8px 24px rgba(0,0,0,.4); }
|
||||
.tile-mic-badge { margin-left: 6px; }
|
||||
.tile-mic-badge.muted::before { content: "\1F507"; } /* 🔇 */
|
||||
.tile-mic-badge:not(.muted)::before { content: ""; }
|
||||
.tile-mic-badge { margin-left: 6px; display: inline-flex; align-items: center; }
|
||||
.tile-mic-badge .material-symbols-outlined { font-size: 15px; color: var(--red); }
|
||||
|
||||
/* channel user lines */
|
||||
.channel-users {
|
||||
@@ -180,15 +219,27 @@ body {
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.user-line-badges .material-symbols-outlined {
|
||||
font-size: 15px;
|
||||
}
|
||||
.badge-conn {
|
||||
color: var(--yellow);
|
||||
font-weight: bold;
|
||||
}
|
||||
.badge-mute {
|
||||
font-size: 11px;
|
||||
color: var(--red);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.badge-deafen {
|
||||
color: var(--red);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.badge-share {
|
||||
font-size: 11px;
|
||||
color: var(--brand);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* hover volume popup */
|
||||
@@ -327,39 +378,94 @@ body {
|
||||
.howto-list { display: flex; flex-direction: column; gap: 8px; padding-left: 18px; font-size: 13px; color: var(--text-1); }
|
||||
.howto-list li { line-height: 1.4; }
|
||||
|
||||
/* matrix text chat panel */
|
||||
/* matrix text chat panel (main view) */
|
||||
#mx-panel {
|
||||
position: fixed; top: 0; right: 0; bottom: 0; width: 340px; max-width: 90vw;
|
||||
background: var(--bg-1); border-left: 1px solid rgba(0,0,0,.35);
|
||||
display: flex; flex-direction: column; z-index: 40;
|
||||
box-shadow: -8px 0 24px rgba(0,0,0,.45);
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg-2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
#mx-panel.hidden { display: none; }
|
||||
.mx-header {
|
||||
padding: 12px 14px; font-weight: 700; font-size: 14px;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
border-bottom: 1px solid rgba(0,0,0,.25); flex-shrink: 0;
|
||||
padding: 14px 16px;
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid rgba(0,0,0,.25);
|
||||
box-shadow: 0 1px 0 rgba(0,0,0,.06);
|
||||
flex-shrink: 0;
|
||||
background: var(--bg-2);
|
||||
}
|
||||
.mx-header-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.mx-header-title {
|
||||
color: var(--text-0);
|
||||
font-weight: 700;
|
||||
}
|
||||
.mx-header button.ghost {
|
||||
background: transparent; border: none; color: var(--text-1);
|
||||
font-size: 20px; line-height: 1; cursor: pointer; padding: 0 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-1);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
.mx-header button.ghost:hover { color: var(--text-0); }
|
||||
#mx-messages { flex: 1; overflow: auto; padding: 10px 14px; display: flex; flex-direction: column; gap: 8px; }
|
||||
.mx-msg { font-size: 13px; line-height: 1.4; }
|
||||
.mx-sender { font-weight: 700; color: var(--text-0); margin-right: 6px; }
|
||||
.mx-header button.ghost:hover {
|
||||
background: var(--bg-3);
|
||||
color: var(--text-0);
|
||||
}
|
||||
#mx-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.mx-msg { font-size: 14px; line-height: 1.45; }
|
||||
.mx-sender { font-weight: 700; color: var(--text-0); margin-right: 8px; }
|
||||
.mx-body { color: var(--text-1); white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.mx-composer {
|
||||
display: flex; gap: 8px; padding: 10px 14px; border-top: 1px solid rgba(0,0,0,.25); flex-shrink: 0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 14px 20px;
|
||||
border-top: 1px solid rgba(0,0,0,.2);
|
||||
background: var(--bg-2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.mx-composer textarea {
|
||||
flex: 1; resize: none; background: var(--bg-0); border: 1px solid var(--bg-4);
|
||||
border-radius: 6px; color: var(--text-0); padding: 8px 10px; font: inherit; max-height: 100px;
|
||||
flex: 1;
|
||||
resize: none;
|
||||
background: var(--bg-0);
|
||||
border: 1px solid var(--bg-4);
|
||||
border-radius: 8px;
|
||||
color: var(--text-0);
|
||||
padding: 10px 14px;
|
||||
font: inherit;
|
||||
max-height: 120px;
|
||||
}
|
||||
.mx-composer textarea:focus { outline: none; border-color: var(--brand); }
|
||||
.mx-composer button {
|
||||
background: var(--brand); border: none; color: #fff; border-radius: 6px;
|
||||
padding: 0 14px; font-weight: 600; cursor: pointer;
|
||||
background: var(--brand);
|
||||
border: none;
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 0 18px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* reconnect banner */
|
||||
@@ -467,7 +573,5 @@ code { font-family: ui-monospace, monospace; font-size: 12px; }
|
||||
/* modals already clamp to 92vw; just tighten padding a bit */
|
||||
#settings-modal, #welcome-modal { padding: 16px; }
|
||||
|
||||
#mx-panel { width: 100vw; }
|
||||
|
||||
#toasts { left: 12px; right: 12px; max-width: none; }
|
||||
}
|
||||
Reference in new issue
Block a user