openvibers/openvibe.live Commits

Channel: the offline header shows the streamer's picture in a circle (it only ever got the first letter, unstyled); one DOM helper for both headers (#190)

OP OpenVibers committed 27 days ago · 382cde0d8b · 2 files, +25 −10

Changes

ddiff --git a/public/css/features/channel.css b/public/css/features/channel.css
iindex d3286a42..45ac464d 100644
---- a/public/css/features/channel.css
++++ b/public/css/features/channel.css
@@@ -1070,6 +1070,12 @@
padding: 14px clamp(12px, 2vw, 20px); background: var(--bg-card); border-bottom: 1px solid var(--border);
}
.ch-avatar-lg { width: 64px; height: 64px; font-size: 1.6rem; }
++/* The offline header's picture (its size is .ch-avatar-lg's): a circle like the live bar's, the letter on the accent. */
++.ch-offline-header .ch-avatar {
++ border-radius: 50%; overflow: hidden; flex-shrink: 0;
++ background: var(--primary, var(--accent)); color: var(--bg-main, #000);
++ display: flex; align-items: center; justify-content: center; font-weight: 700;
++}
.ch-offline-header .ch-username { margin-bottom: 2px; }
.ch-offline-header .ch-description:empty { display: none; }
.ch-offline-header .ch-actions { display: flex; gap: 6px; margin-left: auto; }
@@@ -1127,7 +1133,7 @@
#page-channel .stream-layout { height: auto; }
}
.ch-info-bar .ch-avatar { width: 52px; height: 52px; overflow: hidden; cursor: pointer; }
--.ch-info-bar .ch-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }
++.ch-info-bar .ch-avatar img, .ch-offline-header .ch-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }
.ch-info-bar-top h2 a:hover { text-decoration: underline !important; }
.ch-stream-title { font-size: 1.02rem; color: var(--text); white-space: normal; margin-top: 4px; }
.ch-stream-description { font-size: 0.9rem; }
ddiff --git a/public/js/app-channel.js b/public/js/app-channel.js
iindex 00ee10ff..451f21a7 100644
---- a/public/js/app-channel.js
++++ b/public/js/app-channel.js
@@@ -158,6 +158,22 @@ async function renderChannelVodsSection(username, liveStreams, vods, meta = {})
// Can the current user manage a channel's content (its owner, or any admin)?
// OpenVibe staff badge for a user (admin/owner -> Staff · Admin, mod -> Staff · Mod).
++// The streamer's picture in a channel header (the live info bar and the offline header), or the first letter of the
++// name when there is none or it fails to load. `path` makes it a link (the live bar); the offline header has none.
++function _fillChannelAvatar(el, ch, path) {
++ if (!el) return;
++ const letter = ((ch.username || '?')[0] || '?').toUpperCase();
++ el.textContent = letter;
++ if (ch.avatar_url) {
++ const img = document.createElement('img');
++ img.alt = '';
++ img.onerror = () => { el.textContent = letter; };
++ img.src = ch.avatar_url;
++ el.replaceChildren(img);
++ }
++ if (path) { el.style.cursor = 'pointer'; el.onclick = () => navigate(path); }
++}
++
function _staffBadge(role, isOwner) {
if (role === 'admin' || isOwner) return '<span class="staff-badge staff-badge-admin" data-tip="Staff - Admin"><i class="fa-solid fa-shield-halved"></i></span>';
if (role === 'global_mod') return '<span class="staff-badge staff-badge-mod" data-tip="Staff - Mod"><i class="fa-solid fa-shield"></i></span>';
@@@ -732,14 +748,7 @@ async function loadChannelPage(username, managedStreamRef = null, legacySessionI
// Populate streamer info bar (below video). Avatar image (letter fallback)
// + display name both link to the channel; the @handle is dropped.
const _chPath = channelPath(ch.username);
-- const _chAvatar = document.getElementById('ch-avatar');
-- if (_chAvatar) {
-- _chAvatar.innerHTML = ch.avatar_url
-- ? `<img src="${esc(ch.avatar_url)}" alt="" onerror="this.style.display='none';this.parentNode.textContent='${((ch.username || '?')[0] || '?').toUpperCase()}'">`
-- : ((ch.username || '?')[0] || '?').toUpperCase();
-- _chAvatar.style.cursor = 'pointer';
-- _chAvatar.onclick = () => navigate(_chPath);
-- }
++ _fillChannelAvatar(document.getElementById('ch-avatar'), ch, _chPath);
const _chName = document.getElementById('ch-display-name');
if (_chName) {
// Name only — the h2 clips with ellipsis, so the staff badge lives in
@@@ -810,7 +819,7 @@ async function loadChannelPage(username, managedStreamRef = null, legacySessionI
_placeBelowFold();
// Populate offline header
-- document.getElementById('ch-avatar-offline').textContent = (ch.username || '?')[0].toUpperCase();
++ _fillChannelAvatar(document.getElementById('ch-avatar-offline'), ch);
document.getElementById('ch-display-name-offline').innerHTML = `${esc(ch.display_name || ch.username)} ${_staffBadge(ch.role, ch.is_owner)}`;
_activeChannelIsOwnerRank = !!ch.is_owner;
document.getElementById('ch-username-offline').textContent = '@' + ch.username;