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)
Changes
@@@ -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; }@@@ -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');
@@ -1491,38 +1491,6 @@
@@ -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). */
/* 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); }
.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). */
/* 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 { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.elsewhere-title { font-weight: 600; font-size: 0.9rem; margin-bottom: 8px; }
.elsewhere-title { font-weight: 600; font-size: 0.9rem; margin-bottom: 8px; }
@@ -1,11 +1,10 @@
@@ -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
* node test/channel-phone-chat.test.js
*/
*/
@@ -14,20 +13,16 @@ const assert = require('assert');
@@ -14,20 +13,16 @@ const assert = require('assert');
const fs = require('fs');
const fs = require('fs');
const path = require('path');
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');
console.log('channel phone chat: all checks passed');