openvibers/openvibe.live Commits

Channel on a phone: the chat button is back in the corner (with its unread count) and opens the chat as a sheet under the video; the docked chat left one line between a vertical stream and the goal and pinned banners (owner, 2026-10-17) (#235)

OP OpenVibers committed 27 days ago · 68bd0892d5 · 2 files, +16 −53

Changes

ddiff --git a/public/css/features/channel.css b/public/css/features/channel.css
iindex bbdd7238..ee5f20dc 100644
---- a/public/css/features/channel.css
++++ b/public/css/features/channel.css
@@@ -1491,38 +1491,6 @@
/* Tabs that run past the edge fade out there, so it reads as "scroll for more" (class set by app-channel.js). */
.ch-tabs.is-clipped { -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent); mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent); }
--/* A live channel on a phone held upright, the way phone streaming apps lay it out: the video on top, the chat right
-- under it filling the rest of the screen (its messages scroll inside it, the message box at its foot), and the
-- stream info after the chat. The stream area used to fill the screen while the chat waited behind a button
-- (2026-10-16); docked under the info with `height: auto`, the chat then grew with its history (19,000 px on a busy
-- channel, 2026-10-17) and its message box was a long scroll away. `display: contents` lets the video and the info,
-- children of .stream-main, take their own places around the chat. */
--@media (max-width: 768px) and (orientation: portrait) {
-- #page-channel #ch-live-area .stream-main { display: contents; }
-- #page-channel #ch-live-area .stream-main > * { order: 5; }
-- #page-channel #ch-live-area .stream-main > #video-container { order: 1; }
-- #page-channel #ch-live-area .stream-main > #ch-bot-section { order: 2; }
-- #page-channel #ch-live-area .stream-main > #ch-captions { order: 3; }
-- #page-channel #ch-live-area .stream-layout > .chat-sidebar {
-- order: 4; position: relative; inset: auto; transform: none; transition: none; z-index: auto;
-- flex: none; width: 100%; min-height: 0; max-height: none;
-- /* The screen under the navbar, less a 16:9 video (56.25vw); a tall video leaves the chat its 260 px. */
-- height: max(260px, calc(100dvh - var(--navbar-h) - 56.25vw));
-- border-radius: 0; box-shadow: none; border-top: 1px solid var(--border);
-- }
-- #page-channel #ch-live-area .stream-layout:has(.video-container.is-vertical) > .chat-sidebar { height: max(260px, calc(100dvh - var(--navbar-h) - 75dvh)); }
-- #page-channel #ch-live-area .stream-layout > .chat-sidebar .chat-messages { flex: 1 1 0; min-height: 0; overflow-y: auto; }
-- #page-channel #ch-live-area .stream-layout > .chat-sidebar .mobile-chat-close,
-- #page-channel #ch-live-area .stream-layout > .chat-sidebar > .chat-grip { display: none; }
-- /* Docked, the chat's messages get the room: a slim header, the tools beside nothing else. */
-- #page-channel #ch-live-area .stream-layout > .chat-sidebar .chat-header { padding: 6px 12px; min-height: 0; }
-- #page-channel #ch-live-area .stream-layout > .chat-sidebar .chat-input-area { padding-top: 6px; padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); }
-- /* The chat is on screen, so its button goes; the DM bubble steps aside as it does for the open chat sheet
-- (it covered the chat's emoji button). */
-- body:has(#page-channel.active #ch-live-area .stream-layout > .chat-sidebar) .mobile-chat-toggle,
-- body:has(#page-channel.active #ch-live-area .stream-layout > .chat-sidebar) #messenger-toggle { display: none !important; }
--}
--
/* The donate and subscribe modals: the channel's VIP tiers and other ways to support it (vibes.js _elsewhereBlock). */
.elsewhere { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.elsewhere-title { font-weight: 600; font-size: 0.9rem; margin-bottom: 8px; }
ddiff --git a/test/channel-phone-chat.test.js b/test/channel-phone-chat.test.js
iindex af961d1a..715f022c 100644
---- a/test/channel-phone-chat.test.js
++++ b/test/channel-phone-chat.test.js
@@@ -1,11 +1,10 @@
/**
-- * A live channel on a phone held upright: the video on top, the chat under it filling the rest of the screen, the stream
-- * info after the chat (public/css/features/channel.css). Before, the stream area filled the screen while the chat waited
-- * behind a floating button (2026-10-16); then the docked chat had `height: auto` and grew with its history, 19,000 px on
-- * a busy channel, so its message box was a long scroll away (2026-10-17). Docked, the chat's button and the DM bubble
-- * (which covered the chat's emoji button) step aside.
++ * A live channel on a phone held upright: the chat waits behind the round chat button in the corner (with its unread
++ * count) and opens as a sheet over the page's lower part, the video staying in view above it. The owner asked for the
++ * button back on 2026-10-17: Live#163/#177 had docked the chat under the video, and on a vertical stream (the video
++ * most of the screen, the goal and pinned banners on top of the chat) one chat line was left between them.
*
-- * A real browser pass at phone width is in the PR (local Live, a live test stream, a signed-out phone viewer).
++ * Proof at phone width on production (412×915, a live channel, a real tap on the button) is in the PR.
*
* node test/channel-phone-chat.test.js
*/
@@@ -14,20 +13,16 @@ const assert = require('assert');
const fs = require('fs');
const path = require('path');
--const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'features', 'channel.css'), 'utf8');
--const at = css.lastIndexOf('@media (max-width: 768px) and (orientation: portrait) {\n #page-channel #ch-live-area .stream-main');
--assert.ok(at > 0, 'the portrait-phone block for a live channel exists');
--let depth = 0, end = css.indexOf('{', at);
--for (; end < css.length; end++) { if (css[end] === '{') depth++; else if (css[end] === '}' && --depth === 0) break; }
--const block = css.slice(at, end + 1);
++const channel = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'features', 'channel.css'), 'utf8');
++const style = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'style.css'), 'utf8');
--assert.match(block, /#ch-live-area \.stream-main \{ display: contents; \}/, 'the video and the info take their own places');
--assert.match(block, /> #video-container \{ order: 1; \}/, 'the video first');
--assert.match(block, /\.stream-layout > \.chat-sidebar \{[^}]*order: 4;[^}]*position: relative;[^}]*transform: none;[^}]*flex: none;/, 'the chat under it');
--assert.match(block, /height: max\(260px, calc\(100dvh - var\(--navbar-h\) - 56\.25vw\)\);/, 'sized to the rest of the screen, never by its history');
--assert.match(block, /\.stream-main > \* \{ order: 5; \}/, 'the stream info after the chat');
--assert.match(block, /\.chat-sidebar \.chat-messages \{ flex: 1 1 0; min-height: 0; overflow-y: auto; \}/, 'the messages scroll inside the chat');
--assert.ok(!/height: auto;/.test(block.slice(block.indexOf('.chat-sidebar {'), block.indexOf('}', block.indexOf('.chat-sidebar {')))), 'no growing height');
--assert.match(block, /\.chat-sidebar > \.chat-grip \{ display: none; \}|> \.chat-grip \{ display: none; \}/, 'no bottom-sheet grip on the docked chat');
--assert.match(block, /\.mobile-chat-toggle,\s*body:has\([^)]*\) #messenger-toggle \{ display: none !important; \}/, 'its button and the DM bubble step aside');
++// No portrait-phone rule docks the chat into the page or hides its button.
++assert.ok(!/#ch-live-area \.stream-main \{ display: contents; \}/.test(channel), 'the chat is not docked under the video');
++assert.ok(!/body:has\(#page-channel\.active #ch-live-area \.stream-layout > \.chat-sidebar\) \.mobile-chat-toggle/.test(channel), 'nothing hides the chat button on a live channel');
++
++// Phones: the chat is a sheet that the button opens, and the button shows while it is closed.
++const phone = style.slice(style.indexOf('.chat-sidebar.mobile-chat-open'), style.indexOf('.chat-sidebar.mobile-chat-open') + 2000);
++assert.match(phone, /\.chat-sidebar\.mobile-chat-open \{\s*transform: translateY\(0\);/, 'the open sheet slides into view');
++assert.match(phone, /\.mobile-chat-toggle \{\s*display: flex;/, 'the chat button shows on phones');
++assert.match(phone, /body\.mobile-chat-visible \.mobile-chat-toggle \{ display: none !important; \}/, 'and steps aside while the sheet is open');
console.log('channel phone chat: all checks passed');