openvibers/openvibe.live Commits

Channel page: the chat no longer runs off the right edge at some widths (at 1181px+ the main column stopped scrolling on its own, and as a flex item it then refused to shrink below the one-row tabs; min-width 0). The chat can be hidden on desktop: a button at the left of the chat header slides it away so the stream or the offline page takes the full width, and a Chat tab at the middle of the window edge brings it back (it slides in). Remembered per browser; phones keep their own chat toggle.

OP OpenVibers committed 29 Sept 2026 · 85ec90c2e1 · 3 files, +49 −1


Changes

ddiff --git a/public/css/features/channel.css b/public/css/features/channel.css
iindex 9d80bd0c..4f7fe21e 100644
---- a/public/css/features/channel.css
++++ b/public/css/features/channel.css
@@@ -73,7 +73,9 @@
height: calc(100vh - var(--navbar-h) - 44px); /* account for live tabs bar when visible */
}
.stream-main {
-- flex: 1; overflow-y: auto;
++ /* min-width 0: at 1181px+ it stops scrolling on its own (overflow visible), and a flex item then refuses to
++ shrink below its content (the one-row tabs), pushing the chat off the right edge. */
++ flex: 1; min-width: 0; overflow-y: auto;
display: flex; flex-direction: column;
}
@@@ -1458,3 +1460,25 @@ body.controls-docked .messenger-toggle { z-index: 1100; }
.ch-social-row input[data-f=url] { grid-column: 1 / -1; }
.ch-socials-grid { grid-template-columns: 1fr; }
}
++
++/* ── Hide / show the chat (desktop) ─────────────────────────── */
++.chat-header .ch-chat-collapse { margin-right: 2px; }
++@media (min-width: 769px) {
++ #page-channel.chat-collapsed .stream-layout > .chat-sidebar,
++ #page-channel.chat-collapsed .chat-resize-handle { display: none !important; }
++ #page-channel.chat-revealing .stream-layout > .chat-sidebar { animation: chChatReveal .32s cubic-bezier(.22,1,.36,1); }
++}
++@keyframes chChatReveal { from { opacity: 0; transform: translateX(36px); } to { opacity: 1; transform: none; } }
++.ch-chat-reopen {
++ position: fixed; right: 0; top: 50%; transform: translateY(-50%); z-index: 60; /* mid-edge: clear of the header's buttons */
++ display: inline-flex; align-items: center; gap: 7px; padding: 9px 12px 9px 12px;
++ border: 1px solid var(--border); border-right: 0; border-radius: 12px 0 0 12px;
++ background: color-mix(in srgb, var(--bg-card, var(--bg-secondary)) 90%, transparent); backdrop-filter: blur(10px);
++ color: var(--text-secondary); font: 700 0.8rem/1 inherit; cursor: pointer; box-shadow: var(--shadow);
++ transition: color .15s, border-color .15s, padding-right .18s ease;
++}
++.ch-chat-reopen:hover { color: var(--text-primary); border-color: var(--accent); padding-right: 16px; }
++.ch-chat-reopen .fa-comments { color: var(--accent); }
++.ch-chat-reopen[hidden] { display: none; }
++@media (max-width: 768px) { .ch-chat-collapse, .ch-chat-reopen { display: none !important; } }
++@media (prefers-reduced-motion: reduce) { #page-channel.chat-revealing .stream-layout > .chat-sidebar { animation: none; } }
ddiff --git a/public/fragments/channel.html b/public/fragments/channel.html
iindex 57971631..d516d31b 100644
---- a/public/fragments/channel.html
++++ b/public/fragments/channel.html
@@@ -1,4 +1,6 @@
<!-- #page-channel markup, loaded on first visit to the route (public/features.json). The server inlines it for direct visits. -->
++ <!-- Chat hidden (desktop): the stream or offline page takes the full width; this tab at the window edge brings it back. -->
++ <button type="button" class="ch-chat-reopen" id="ch-chat-reopen" onclick="toggleChannelChat(false)" title="Show chat" hidden><i class="fa-solid fa-angles-left"></i><i class="fa-solid fa-comments"></i><span>Chat</span></button>
<!-- Live Stream Tabs (this channel's concurrent streams) -->
<div class="live-stream-tabs" id="live-stream-tabs" style="display:none" role="tablist" aria-label="Live streams">
<div class="live-tabs-scroll" id="live-tabs-scroll"></div>
@@@ -144,6 +146,7 @@
<!-- Chat Sidebar -->
<div class="chat-sidebar" id="chat-sidebar">
<div class="chat-header">
++ <button type="button" class="chat-settings-btn ch-chat-collapse" onclick="toggleChannelChat(true)" title="Hide chat" aria-label="Hide chat"><i class="fa-solid fa-angles-right"></i></button>
<span>STREAM CHAT</span>
<div class="chat-header-actions">
<button class="chat-settings-btn" onclick="toggleChatUsers(this)" title="Users">
@@@ -268,6 +271,7 @@
<!-- Streamer's persistent chat (works while offline) — sidebar, mirrors live layout -->
<div class="offline-global-chat chat-sidebar" id="offline-global-chat">
<div class="chat-header">
++ <button type="button" class="chat-settings-btn ch-chat-collapse" onclick="toggleChannelChat(true)" title="Hide chat" aria-label="Hide chat"><i class="fa-solid fa-angles-right"></i></button>
<span><i class="fa-solid fa-comments"></i> STREAM CHAT</span>
<div class="chat-header-actions">
<button class="chat-settings-btn" onclick="toggleChatUsers(this)" title="Users">
ddiff --git a/public/js/app-channel.js b/public/js/app-channel.js
iindex a6803457..e24e09cb 100644
---- a/public/js/app-channel.js
++++ b/public/js/app-channel.js
@@@ -563,7 +563,27 @@ async function setChannelClipsOfPage(page) {
if (top) top.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
++/** Hide or show the channel page's chat column on desktop (the stream or offline page then takes the full width). Remembered per browser. */
++function toggleChannelChat(hide) {
++ const page = document.getElementById('page-channel');
++ if (!page) return;
++ const next = typeof hide === 'boolean' ? hide : !page.classList.contains('chat-collapsed');
++ const was = page.classList.contains('chat-collapsed');
++ page.classList.toggle('chat-collapsed', next);
++ if (was && !next) { page.classList.add('chat-revealing'); setTimeout(() => page.classList.remove('chat-revealing'), 400); }
++ const re = document.getElementById('ch-chat-reopen');
++ if (re) re.hidden = !next;
++ try { localStorage.setItem('ov_ch_chat_hidden', next ? '1' : '0'); } catch { /* private mode */ }
++ if (was !== next) window.dispatchEvent(new Event('resize')); // the player and the diagrams re-measure
++}
++function _restoreChannelChat() {
++ let hidden = false;
++ try { hidden = localStorage.getItem('ov_ch_chat_hidden') === '1'; } catch { /* */ }
++ toggleChannelChat(hidden);
++}
++
async function loadChannelPage(username, managedStreamRef = null, legacySessionId = null) {
++ _restoreChannelChat();
try {
const isNewChannel = currentChannelUsername !== username;
currentChannelUsername = username;