openvibers/openvibe.live Commits

Watching live on a phone: one family of action buttons, and a chat that folds away (#694)

OP OpenVibers committed 5 days ago · 3af110c090 · 3 files, +69 −3

The owner's phone screenshots: the row under the stream was ugly and cut off (Share, a greyed Unfollow and Ban in
three different shapes, the streamer's name squeezed to "W…"), and scrolling past the chat felt wrong.
- Share, Ask your agent, Follow, Still and Ban are pills of one height (36 px), one type size and one icon size.
  Follow is the solid one and comes first; following, it reads "Following" (quiet; red on hover, titled Unfollow)
  instead of a greyed "Unfollow"; Ban is red-tinted. The category is a quiet chip, not a second red button.
- On a phone the streamer's line (picture, name, title) has a row of its own and the buttons the next; more buttons
  than fit slide sideways instead of squeezing the name.
- The chat's header has a chevron that folds the chat away: the stream and its line stay on top, the channel's
  sections follow, and a Chat pill (bottom right) brings it back. The choice is kept (ov_ch_chat_hidden).

Changes

ddiff --git a/public/css/features/channel.css b/public/css/features/channel.css
iindex ec1a0487..8f082e12 100644
---- a/public/css/features/channel.css
++++ b/public/css/features/channel.css
@@@ -1792,3 +1792,66 @@ html[data-ov-motion="reduced"] #page-channel #ch-tabs { transition: none; }
#page-channel .ch-tab-panel .stream-grid > p.muted { grid-column: auto; }
}
@media (prefers-reduced-motion: reduce) { #page-channel .ch-tab-panel { scroll-margin-top: calc(var(--ovnav-h, 56px) + 56px); } }
++
++/* The stream's actions are one family (owner 11-08, phone screenshot: "ugly and cut off … make it consistent and nice
++ looking"): Share, Ask your agent, Follow, Still and Ban are pills of one height, one type size and one icon size.
++ Follow is the solid one; Following is quiet (it read as a disabled grey button); Ban is a red-tinted one. */
++#page-channel .ch-info-bar-right { gap: 8px; }
++#page-channel .ch-info-bar-right :is(#ch-share-slot, #ch-ask-slot) { display: contents; }
++#page-channel .ch-info-bar-right :is(#ch-btn-share, .ovk-ask, #ch-btn-follow, #ch-btn-ban, #btn-cam) {
++ box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: 7px; flex: none;
++ height: 36px; min-height: 36px; margin: 0; padding: 0 14px; border: 0; border-radius: 999px; box-shadow: none;
++ font: inherit; font-size: 14px; font-weight: 650; line-height: 1; white-space: nowrap; text-decoration: none; cursor: pointer;
++ transition: background-color .15s ease, color .15s ease;
++}
++#page-channel .ch-info-bar-right :is(#ch-btn-share, .ovk-ask, #ch-btn-follow, #ch-btn-ban, #btn-cam) :is(svg, i) { flex: none; width: 16px; height: 16px; font-size: 14px; line-height: 16px; text-align: center; }
++#page-channel .ch-info-bar-right :is(#ch-btn-share, .ovk-ask, #btn-cam) { background: color-mix(in srgb, var(--text-primary) 9%, transparent); color: var(--text-primary); }
++#page-channel .ch-info-bar-right :is(#ch-btn-share, .ovk-ask, #btn-cam):hover { background: color-mix(in srgb, var(--text-primary) 15%, transparent); }
++#page-channel .ch-info-bar-right #ch-btn-follow { background: var(--live-red, #ef4444); color: #fff; }
++#page-channel .ch-info-bar-right #ch-btn-follow:hover { filter: brightness(1.08); }
++#page-channel .ch-info-bar-right #ch-btn-follow.following { background: color-mix(in srgb, var(--text-primary) 9%, transparent); color: var(--text-primary); filter: none; }
++#page-channel .ch-info-bar-right #ch-btn-follow.following:hover { background: color-mix(in srgb, var(--danger, #ef4444) 20%, transparent); color: var(--danger, #ef4444); }
++#page-channel .ch-info-bar-right #ch-btn-ban { background: color-mix(in srgb, var(--danger, #ef4444) 15%, transparent); color: var(--danger, #f87171); }
++#page-channel .ch-info-bar-right #ch-btn-ban:hover { background: color-mix(in srgb, var(--danger, #ef4444) 26%, transparent); }
++#page-channel .ch-info-bar-right :is(#ch-btn-share, .ovk-ask, #ch-btn-follow, #ch-btn-ban, #btn-cam):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
++
++@media (max-width: 768px) and (orientation: portrait) {
++ /* A phone watching live: who is streaming on one line of its own (never under the buttons), the buttons on the
++ next, Follow first, sliding sideways when a moderator has more of them than fit. */
++ #page-channel.ch-is-live .ch-info-bar { flex-wrap: wrap; align-items: center; row-gap: 10px; padding: 10px 12px; }
++ #page-channel.ch-is-live .ch-info-bar-left { flex: 1 1 100%; align-items: center; gap: 10px; }
++ #page-channel.ch-is-live .ch-info-bar .ch-avatar { width: 40px; height: 40px; }
++ #page-channel.ch-is-live .ch-info-bar-top h2 { font-size: 15px; font-weight: 750; }
++ #page-channel.ch-is-live .ch-stream-title { font-size: 13px; color: var(--text-secondary); }
++ #page-channel.ch-is-live .ch-info-bar-right { flex: 1 1 100%; width: 100%; justify-content: flex-start; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; padding-bottom: 1px; }
++ #page-channel.ch-is-live .ch-info-bar-right::-webkit-scrollbar { display: none; }
++ #page-channel.ch-is-live .ch-info-bar-right #ch-btn-follow { order: -1; }
++ /* The category is a quiet chip beside the name, not a second red button competing with Follow. */
++ #page-channel.ch-is-live .ch-info-bar-top .ch-category-badge { flex: none; padding: 2px 8px; border-radius: 999px; font-size: 11.5px; font-weight: 650;
++ background: color-mix(in srgb, var(--text-primary) 9%, transparent); color: var(--text-secondary); border: 0; }
++}
++
++/* A phone watching live (owner 11-08: "the chat should be collapsible … togglable like it used to be; scrolling past
++ chat feels weird"): the chat fills the screen under the stream as before, and its header's chevron folds it away.
++ Folded, the stream and its line stay on top and the channel's sections follow right under them; a Chat pill at the
++ bottom right brings it back. The choice is kept (toggleChannelChat: ov_ch_chat_hidden). */
++@media (max-width: 768px) and (orientation: portrait) {
++ #page-channel.ch-is-live .chat-header .ch-chat-collapse { display: inline-flex !important; align-items: center; justify-content: center; order: 3; margin: 0 0 0 2px; }
++ #page-channel.ch-is-live .chat-header .ch-chat-collapse i { transform: rotate(90deg); }
++ #page-channel.ch-is-live .chat-sidebar .chat-header { justify-content: flex-start; }
++ #page-channel.ch-is-live .chat-sidebar .chat-header .chat-header-actions { margin-left: auto; }
++ #page-channel.ch-is-live.chat-collapsed .stream-layout { height: auto; }
++ #page-channel.ch-is-live.chat-collapsed .stream-layout > .chat-sidebar { display: none !important; }
++ #page-channel.ch-is-live.chat-revealing .stream-layout > .chat-sidebar { animation: chChatRise var(--ov-dur-3, .32s) var(--ov-ease-out, cubic-bezier(.22,1,.36,1)); }
++ body:has(#page-channel.active.ch-is-live) .ch-chat-reopen:not([hidden]) {
++ display: inline-flex !important; top: auto; transform: none;
++ right: max(16px, env(safe-area-inset-right, 0px)); bottom: calc(16px + env(safe-area-inset-bottom, 0px));
++ height: 44px; padding: 0 18px; gap: 8px; border: 0; border-radius: 999px;
++ background: var(--accent-strong, var(--accent)); color: var(--on-accent-strong, #fff); font: 700 15px/1 inherit;
++ box-shadow: 0 10px 24px -10px rgba(0, 0, 0, .7);
++ }
++ body:has(#page-channel.active.ch-is-live) .ch-chat-reopen .fa-angles-left { display: none; }
++ body:has(#page-channel.active.ch-is-live) .ch-chat-reopen .fa-comments { color: inherit; }
++}
++@keyframes chChatRise { from { opacity: 0; transform: translateY(24px); } to { opacity: 1; transform: none; } }
++@media (prefers-reduced-motion: reduce) { #page-channel.chat-revealing .stream-layout > .chat-sidebar { animation: none !important; } }
ddiff --git a/public/js/app-channel.js b/public/js/app-channel.js
iindex 221352cd..9cef7a0f 100644
---- a/public/js/app-channel.js
++++ b/public/js/app-channel.js
@@@ -846,8 +846,9 @@ async function loadChannelPage(username, managedStreamRef = null, legacySessionI
btn.style.display = '';
btn.classList.toggle('following', ch.is_following);
btn.innerHTML = ch.is_following
-- ? '<i class="fa-solid fa-heart-crack"></i> Unfollow'
++ ? '<i class="fa-solid fa-check"></i> Following'
: '<i class="fa-solid fa-heart"></i> Follow';
++ btn.title = ch.is_following ? 'Unfollow' : 'Follow';
btn.onclick = () => toggleChannelFollow(username);
}
};
@@@ -3765,8 +3766,9 @@ async function toggleChannelFollow(username) {
if (!btn) return;
btn.classList.toggle('following', data.following);
btn.innerHTML = data.following
-- ? '<i class="fa-solid fa-heart-crack"></i> Unfollow'
++ ? '<i class="fa-solid fa-check"></i> Following'
: '<i class="fa-solid fa-heart"></i> Follow';
++ btn.title = data.following ? 'Unfollow' : 'Follow';
});
['ch-follower-count', 'ch-follower-count-offline'].forEach(id => {
const el = document.getElementById(id);
ddiff --git a/public/js/app.js b/public/js/app.js
iindex 323529b9..90a32aff 100644
---- a/public/js/app.js
++++ b/public/js/app.js
@@@ -1945,8 +1945,9 @@ async function toggleFollow() {
const btn = document.getElementById('btn-follow');
btn.classList.toggle('following', data.following);
btn.innerHTML = data.following
-- ? '<i class="fa-solid fa-heart-crack"></i> Unfollow'
++ ? '<i class="fa-solid fa-check"></i> Following'
: '<i class="fa-solid fa-heart"></i> Follow';
++ btn.title = data.following ? 'Unfollow' : 'Follow';
toast(data.following ? 'Followed!' : 'Unfollowed', 'info');
} catch (e) { toast(e.message, 'error'); }
}