openvibers/openvibe.live Commits

The player: one button size, a real LIVE pill, glass chips; reconnecting is a pill at the top (#724)

OP OpenVibers committed 4 days ago · c85bc29b00 · 2 files, +43 −13

The owner, 11-09: the bar's buttons and icons "better sized and centred… better tags", and the freeze notice wrapped
into a narrow column over "Waiting for video". The bar's buttons are 40 px (44 on a touch screen) with centred 18 px
icons; LIVE is a red pill with a breathing dot (a glass pill when behind); the viewer count and uptime are glass chips
at 12.5 px, the uptime stepping aside on a phone. The freeze notice is a glass pill at the top of the picture: a
turning ring, "Reconnecting…" with the step under it, and a copy icon button (its word the tooltip).

Changes

ddiff --git a/public/css/features/channel.css b/public/css/features/channel.css
iindex 6fe873cd..0ad69c89 100644
---- a/public/css/features/channel.css
++++ b/public/css/features/channel.css
@@@ -1864,3 +1864,22 @@ html[data-ov-motion="reduced"] #page-channel #ch-tabs { transition: none; }
}
@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; } }
++
++/* ── The player's bar, 11-09 (the owner: "the buttons and the icons… better sized and centred… better tags"). One button
++ size (40 px, 44 on a touch screen) with the icon centred at 18 px; LIVE is a red pill with a breathing dot (a grey
++ glass pill when you are behind, "Go live"); the viewer count and the uptime are glass chips at a readable size;
++ on a phone the uptime steps aside before anything else. ── */
++.video-controls { min-height: 52px; padding: 4px 10px 6px; gap: 4px; }
++.video-controls .vc-btn { width: 40px; height: 40px; min-width: 40px; min-height: 40px; padding: 0; border-radius: 12px; font-size: 18px; line-height: 1; }
++.video-controls .vc-btn > i { width: 20px; text-align: center; }
++@media (pointer: coarse) { .video-controls .vc-btn { width: 44px; height: 44px; min-width: 44px; min-height: 44px; } }
++.video-controls .dvr-live-btn { width: auto !important; height: 28px !important; min-height: 28px !important; padding: 0 11px 0 9px !important; gap: 7px;
++ background: var(--live-red, #e5484d) !important; font-size: 12px !important; letter-spacing: .08em; box-shadow: 0 2px 10px rgba(229, 72, 77, .35); }
++.video-controls .dvr-live-btn .live-dot { width: 7px; height: 7px; background: #fff; box-shadow: 0 0 0 0 rgba(255, 255, 255, .6); animation: vc-live-breathe 1.8s ease-in-out infinite; }
++.video-controls .dvr-live-btn.dvr-behind { background: rgba(255, 255, 255, .16) !important; box-shadow: none; }
++@keyframes vc-live-breathe { 0%, 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, .55); } 50% { box-shadow: 0 0 0 4px rgba(255, 255, 255, 0); } }
++html[data-ov-motion=reduced] .video-controls .dvr-live-btn .live-dot { animation-duration: 3.6s; }
++.video-controls .viewer-badge, .video-controls .uptime-badge { height: 28px; min-height: 28px; padding: 0 10px; border-radius: 999px; gap: 6px; font-size: 12.5px !important; font-weight: 600;
++ background: rgba(255, 255, 255, .12); -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); color: #fff; }
++.video-controls .viewer-badge i, .video-controls .uptime-badge i { font-size: 12px; color: rgba(255, 255, 255, .75); }
++@media (max-width: 480px) { .video-controls .uptime-badge { display: none; } .video-controls .viewer-badge { padding: 0 9px; } }
ddiff --git a/public/js/ov-viewer-health.js b/public/js/ov-viewer-health.js
iindex 758cdcf3..808ac1bc 100644
---- a/public/js/ov-viewer-health.js
++++ b/public/js/ov-viewer-health.js
@@@ -217,17 +217,25 @@
styleEl = document.createElement('style');
styleEl.id = 'ov-viewer-health-style';
styleEl.textContent = [
-- '.ov-freeze-notice{position:absolute;left:50%;bottom:14px;transform:translateX(-50%);z-index:40;',
-- 'display:flex;align-items:center;gap:10px;max-width:min(92%,420px);padding:9px 12px;border-radius:10px;',
-- 'background:rgba(12,12,16,.86);color:#fff;font:13px/1.35 system-ui,-apple-system,Segoe UI,sans-serif;',
-- 'box-shadow:0 6px 20px rgba(0,0,0,.35)}',
++ '.ov-freeze-notice{position:absolute;left:50%;top:12px;transform:translateX(-50%);z-index:40;box-sizing:border-box;',
++ 'display:flex;align-items:center;gap:10px;width:max-content;max-width:calc(100% - 24px);min-height:44px;padding:6px 6px 6px 12px;border-radius:999px;',
++ 'background:rgba(10,12,18,.72);-webkit-backdrop-filter:blur(14px) saturate(1.3);backdrop-filter:blur(14px) saturate(1.3);color:#fff;',
++ 'font:500 13px/1.25 Inter,system-ui,-apple-system,Segoe UI,sans-serif;letter-spacing:0;text-align:left;',
++ 'box-shadow:0 8px 28px rgba(0,0,0,.4),inset 0 0 0 1px rgba(255,255,255,.1);animation:ov-freeze-in .32s cubic-bezier(.2,.9,.3,1) both}',
'.ov-freeze-notice[hidden]{display:none}',
-- '.ov-freeze-notice-text{flex:1 1 auto;min-width:0}',
-- '.ov-freeze-notice b{display:block;font-weight:600}',
-- '.ov-freeze-notice small{opacity:.75;font-size:11px}',
-- '.ov-freeze-notice button{flex:0 0 auto;border:1px solid rgba(255,255,255,.35);background:rgba(255,255,255,.1);',
-- 'color:#fff;border-radius:7px;padding:5px 9px;font:inherit;font-size:12px;cursor:pointer}',
-- '.ov-freeze-notice button:hover{background:rgba(255,255,255,.2)}',
++ '.ov-freeze-notice::before{content:"";flex:none;width:16px;height:16px;border-radius:50%;border:2.5px solid rgba(255,255,255,.22);',
++ 'border-top-color:#4f7cff;border-right-color:#8b5cf6;animation:ov-freeze-turn .9s linear infinite}',
++ '.ov-freeze-notice-text{flex:0 1 auto;min-width:0;display:grid;gap:1px}',
++ '.ov-freeze-notice b{display:block;font-size:13px;font-weight:650;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
++ '.ov-freeze-notice small{display:block;font-size:11.5px;opacity:.72;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
++ '.ov-freeze-notice small:empty{display:none}',
++ '.ov-freeze-notice button{flex:none;display:inline-grid;place-items:center;width:32px;height:32px;padding:0;border:0;border-radius:50%;',
++ 'background:rgba(255,255,255,.12);color:#fff;cursor:pointer}',
++ '.ov-freeze-notice button:hover,.ov-freeze-notice button:focus-visible{background:rgba(255,255,255,.24);outline:none}',
++ '.ov-freeze-notice button svg{width:16px;height:16px}',
++ '@keyframes ov-freeze-in{from{opacity:0;transform:translate(-50%,-6px) scale(.96)}}',
++ '@keyframes ov-freeze-turn{to{transform:rotate(360deg)}}',
++ 'html[data-ov-motion=reduced] .ov-freeze-notice::before{animation-duration:1.8s}',
].join('');
(document.head || document.documentElement).appendChild(styleEl);
}
@@@ -256,7 +264,10 @@
text.appendChild(small);
const btn = document.createElement('button');
btn.type = 'button';
-- btn.textContent = 'Copy debug info';
++ // An icon button; its word is the tooltip and the accessible name.
++ btn.setAttribute('aria-label', 'Copy details for support');
++ btn.title = 'Copy details for support';
++ btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 9h10v10H9zM5 15V5h10"/></svg>';
btn.addEventListener('click', () => { try { onCopy && onCopy(); } catch (e) { /* nothing to do */ } });
noticeEl.appendChild(text);
noticeEl.appendChild(btn);
@@@ -418,8 +429,8 @@
if (!opts.notice) return;
showFreezeNotice({
container: opts.notice,
-- title: 'The picture froze. Reconnecting…',
-- detail: `Step: ${ladder.stageLabel}`,
++ title: 'Reconnecting…',
++ detail: `The picture froze: ${ladder.stageLabel}`,
onCopy: () => {
const text = buildDebugReport({
streamId: opts.streamId,