openvibers/openvibe.live Commits
Live: AI recaps look like reports on the feed, not videos; Game leaves the navbar. (#49)
Changes
@@@ -387,6 +387,51 @@ a.cf-chan:hover .cf-name { color: var(--text-primary); text-decoration: underlin.cf-grade--b { color: #60a5fa; }.cf-grade--c { color: #94a3b8; }++/* ── AI recap: a report card, not a video card (content-feed.js recapCard) ── */++.cf-card--recap { background: linear-gradient(180deg, color-mix(in srgb, var(--cf-ai) 7%, var(--bg-card)), var(--bg-card) 55%); }++.cf-recap-band {++ position: relative;++ display: flex;++ align-items: center;++ gap: 14px;++ min-height: 104px;++ padding: 16px;++ overflow: hidden;++ color: inherit;++ text-decoration: none;++ isolation: isolate;++ border-bottom: 1px dashed color-mix(in srgb, var(--cf-ai) 35%, var(--border));++ background:++ radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--cf-ai) 34%, transparent), transparent 62%),++ radial-gradient(90% 120% at 100% 100%, color-mix(in srgb, var(--cf-ai-2) 22%, transparent), transparent 60%),++ var(--bg-secondary);++}++.cf-recap-bg { position: absolute; inset: -12px; width: calc(100% + 24px); height: calc(100% + 24px); object-fit: cover; filter: blur(14px) saturate(1.2); opacity: .28; z-index: -1; }++.cf-recap-grade {++ flex: 0 0 auto;++ width: 64px;++ height: 64px;++ display: grid;++ place-items: center;++ border-radius: 18px;++ font-weight: 900;++ font-size: 2rem;++ line-height: 1;++ background: rgba(8, 12, 22, 0.72);++ border: 2px solid currentColor;++ box-shadow: 0 8px 24px color-mix(in srgb, currentColor 22%, transparent);++}++.cf-recap-grade--none { color: var(--cf-ai); font-size: 1.5rem; }++.cf-recap-head { display: flex; flex-direction: column; gap: 3px; min-width: 0; }++.cf-recap-label { font-size: .78rem; font-weight: 800; letter-spacing: .6px; text-transform: uppercase; color: var(--cf-ai); }++.cf-recap-sub { font-size: .92rem; font-weight: 700; color: var(--text-primary); }++.cf-recap-stats { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: .82rem; color: var(--text-secondary); }++.cf-recap-stats b { color: var(--text-primary); font-variant-numeric: tabular-nums; }++a.cf-recap-band:hover .cf-recap-sub { text-decoration: underline; text-underline-offset: 3px; }++.cf-excerpt--recap { border-left: 2px solid color-mix(in srgb, var(--cf-ai) 45%, transparent); padding-left: 10px; font-style: italic; }++.cf-recap-read { display: inline-flex; align-items: center; gap: 6px; margin-top: 8px; font-size: .84rem; font-weight: 700; color: var(--cf-ai); text-decoration: none; }++.cf-recap-read:hover { text-decoration: underline; }++/* staff bulk-select wrapper (app.js _selWrap) around a feed card */.cf-col > .sel-card-wrap { position: relative; }.cf-col > .sel-card-wrap > .sel-card-check { z-index: 4; }@@@ -168,17 +168,28 @@return el('article', { class: `cf-card cf-card--paste${it.ai ? ' cf-card--ai' : ''}` }, media, body);}++ // A recap is a report about a stream, not a video: a short report band (the grade, "AI recap", the stream's numbers)++ // over the stream's thumbnail, blurred and tinted, instead of a 16:9 player frame with a duration pill.function recapCard(it) {-- const media = spa(it.href, { class: 'cf-media cf-media--recap', 'aria-label': `Read the recap: ${it.title}` },-- it.thumbnail_url ? thumb(it.thumbnail_url, 'fa-clipboard-list') : el('span', { class: 'cf-thumb cf-thumb--ph' }, icon('fa-clipboard-list')),-- aiBadge());-- if (it.grade) media.append(el('span', { class: `cf-grade cf-grade--${it.grade.toLowerCase()}`, title: `Grade ${it.grade}`, text: it.grade }));-- if (it.duration_seconds) media.append(el('span', { class: 'cf-pill cf-dur', text: duration(it.duration_seconds) }));++ const band = spa(it.href, { class: 'cf-recap-band', 'aria-label': `Read the recap: ${it.title}` });++ if (it.thumbnail_url) {++ const bg = el('img', { class: 'cf-recap-bg', src: it.thumbnail_url, alt: '', loading: 'lazy', decoding: 'async' });++ bg.addEventListener('error', () => bg.remove(), { once: true });++ band.append(bg);++ }++ band.append(it.grade++ ? el('span', { class: `cf-recap-grade cf-grade--${it.grade.toLowerCase()}`, title: `Grade ${it.grade}`, text: it.grade })++ : el('span', { class: 'cf-recap-grade cf-recap-grade--none' }, icon('fa-clipboard-list')));++ const stats = el('span', { class: 'cf-recap-stats' });++ if (it.duration_seconds) stats.append(el('span', null, el('b', { text: duration(it.duration_seconds) }), ' live'));++ if (it.views) stats.append(el('span', null, el('b', { text: compact(it.views) }), ' peak'));++ band.append(el('span', { class: 'cf-recap-head' }, el('span', { class: 'cf-recap-label' }, icon('fa-wand-magic-sparkles'), ' AI recap'), el('span', { class: 'cf-recap-sub', text: 'After-show report' }), stats));const body = el('div', { class: 'cf-body' }, spa(it.href, { class: 'cf-title', text: it.title }));if (it.stream_title && it.stream_title !== it.title) body.append(el('div', { class: 'cf-streamtitle', text: it.stream_title }));body.append(meta(it));-- if (it.excerpt) body.append(el('p', { class: 'cf-excerpt', text: it.excerpt }));-- return el('article', { class: 'cf-card cf-card--recap cf-card--ai' }, media, body);++ if (it.excerpt) body.append(el('p', { class: 'cf-excerpt cf-excerpt--recap', text: it.excerpt }));++ body.append(spa(it.href, { class: 'cf-recap-read' }, 'Read the recap ', icon('fa-arrow-right')));++ return el('article', { class: 'cf-card cf-card--recap cf-card--ai' }, band, body);}const BUILD = { vod: vodCard, clip: clipCard, paste: pasteCard, recap: recapCard };@@@ -26,7 +26,6 @@{ id: 'search', page: 'search', label: 'Search', href: '/search', icon: 'fa-magnifying-glass' },{ id: 'chat', page: 'chat', label: 'Chat', href: '/chat', icon: 'fa-comments' },{ id: 'arena', page: 'arena', label: 'Arena', href: '/arena', icon: 'fa-hand-fist' },-- { id: 'game', label: 'Game', href: 'https://openvibe.games', icon: 'fa-gamepad' },{ id: 'broadcast', page: 'broadcast', elId: 'nav-broadcast', label: 'Go Live', href: '/broadcast', icon: 'fa-tower-broadcast', dot: false, dotId: 'nav-live-dot',onClick: (e) => call('goLiveNav', e),children: user ? [{ id: 'dashboard', label: 'Dashboard', href: '/dashboard', icon: 'fa-gauge-high', onClick: (e) => call('dashNav', e) }] : [] },
@@ -387,6 +387,51 @@ a.cf-chan:hover .cf-name { color: var(--text-primary); text-decoration: underlin
@@ -387,6 +387,51 @@ a.cf-chan:hover .cf-name { color: var(--text-primary); text-decoration: underlin
.cf-grade--b { color: #60a5fa; }
.cf-grade--b { color: #60a5fa; }
.cf-grade--c { color: #94a3b8; }
.cf-grade--c { color: #94a3b8; }
+/* ── AI recap: a report card, not a video card (content-feed.js recapCard) ── */
+.cf-card--recap { background: linear-gradient(180deg, color-mix(in srgb, var(--cf-ai) 7%, var(--bg-card)), var(--bg-card) 55%); }
+.cf-recap-band {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ min-height: 104px;
+ padding: 16px;
+ overflow: hidden;
+ color: inherit;
+ text-decoration: none;
+ isolation: isolate;
+ border-bottom: 1px dashed color-mix(in srgb, var(--cf-ai) 35%, var(--border));
+ background:
+ radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--cf-ai) 34%, transparent), transparent 62%),
+ radial-gradient(90% 120% at 100% 100%, color-mix(in srgb, var(--cf-ai-2) 22%, transparent), transparent 60%),
+ var(--bg-secondary);
+}
+.cf-recap-bg { position: absolute; inset: -12px; width: calc(100% + 24px); height: calc(100% + 24px); object-fit: cover; filter: blur(14px) saturate(1.2); opacity: .28; z-index: -1; }
+.cf-recap-grade {
+ flex: 0 0 auto;
+ width: 64px;
+ height: 64px;
+ display: grid;
+ place-items: center;
+ border-radius: 18px;
+ font-weight: 900;
+ font-size: 2rem;
+ line-height: 1;
+ background: rgba(8, 12, 22, 0.72);
+ border: 2px solid currentColor;
+ box-shadow: 0 8px 24px color-mix(in srgb, currentColor 22%, transparent);
+}
+.cf-recap-grade--none { color: var(--cf-ai); font-size: 1.5rem; }
+.cf-recap-head { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
+.cf-recap-label { font-size: .78rem; font-weight: 800; letter-spacing: .6px; text-transform: uppercase; color: var(--cf-ai); }
+.cf-recap-sub { font-size: .92rem; font-weight: 700; color: var(--text-primary); }
+.cf-recap-stats { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: .82rem; color: var(--text-secondary); }
+.cf-recap-stats b { color: var(--text-primary); font-variant-numeric: tabular-nums; }
+a.cf-recap-band:hover .cf-recap-sub { text-decoration: underline; text-underline-offset: 3px; }
+.cf-excerpt--recap { border-left: 2px solid color-mix(in srgb, var(--cf-ai) 45%, transparent); padding-left: 10px; font-style: italic; }
+.cf-recap-read { display: inline-flex; align-items: center; gap: 6px; margin-top: 8px; font-size: .84rem; font-weight: 700; color: var(--cf-ai); text-decoration: none; }
+.cf-recap-read:hover { text-decoration: underline; }
+
/* staff bulk-select wrapper (app.js _selWrap) around a feed card */
/* staff bulk-select wrapper (app.js _selWrap) around a feed card */
.cf-col > .sel-card-wrap { position: relative; }
.cf-col > .sel-card-wrap { position: relative; }
.cf-col > .sel-card-wrap > .sel-card-check { z-index: 4; }
.cf-col > .sel-card-wrap > .sel-card-check { z-index: 4; }
@@ -168,17 +168,28 @@
@@ -168,17 +168,28 @@
return el('article', { class: `cf-card cf-card--paste${it.ai ? ' cf-card--ai' : ''}` }, media, body);
return el('article', { class: `cf-card cf-card--paste${it.ai ? ' cf-card--ai' : ''}` }, media, body);
}
}
+ // A recap is a report about a stream, not a video: a short report band (the grade, "AI recap", the stream's numbers)
+ // over the stream's thumbnail, blurred and tinted, instead of a 16:9 player frame with a duration pill.
function recapCard(it) {
function recapCard(it) {
- const media = spa(it.href, { class: 'cf-media cf-media--recap', 'aria-label': `Read the recap: ${it.title}` },
- it.thumbnail_url ? thumb(it.thumbnail_url, 'fa-clipboard-list') : el('span', { class: 'cf-thumb cf-thumb--ph' }, icon('fa-clipboard-list')),
- aiBadge());
- if (it.grade) media.append(el('span', { class: `cf-grade cf-grade--${it.grade.toLowerCase()}`, title: `Grade ${it.grade}`, text: it.grade }));
- if (it.duration_seconds) media.append(el('span', { class: 'cf-pill cf-dur', text: duration(it.duration_seconds) }));
+ const band = spa(it.href, { class: 'cf-recap-band', 'aria-label': `Read the recap: ${it.title}` });
+ if (it.thumbnail_url) {
+ const bg = el('img', { class: 'cf-recap-bg', src: it.thumbnail_url, alt: '', loading: 'lazy', decoding: 'async' });
+ bg.addEventListener('error', () => bg.remove(), { once: true });
+ band.append(bg);
+ }
+ band.append(it.grade
+ ? el('span', { class: `cf-recap-grade cf-grade--${it.grade.toLowerCase()}`, title: `Grade ${it.grade}`, text: it.grade })
+ : el('span', { class: 'cf-recap-grade cf-recap-grade--none' }, icon('fa-clipboard-list')));
+ const stats = el('span', { class: 'cf-recap-stats' });
+ if (it.duration_seconds) stats.append(el('span', null, el('b', { text: duration(it.duration_seconds) }), ' live'));
+ if (it.views) stats.append(el('span', null, el('b', { text: compact(it.views) }), ' peak'));
+ band.append(el('span', { class: 'cf-recap-head' }, el('span', { class: 'cf-recap-label' }, icon('fa-wand-magic-sparkles'), ' AI recap'), el('span', { class: 'cf-recap-sub', text: 'After-show report' }), stats));
const body = el('div', { class: 'cf-body' }, spa(it.href, { class: 'cf-title', text: it.title }));
const body = el('div', { class: 'cf-body' }, spa(it.href, { class: 'cf-title', text: it.title }));
if (it.stream_title && it.stream_title !== it.title) body.append(el('div', { class: 'cf-streamtitle', text: it.stream_title }));
if (it.stream_title && it.stream_title !== it.title) body.append(el('div', { class: 'cf-streamtitle', text: it.stream_title }));
body.append(meta(it));
body.append(meta(it));
- if (it.excerpt) body.append(el('p', { class: 'cf-excerpt', text: it.excerpt }));
- return el('article', { class: 'cf-card cf-card--recap cf-card--ai' }, media, body);
+ if (it.excerpt) body.append(el('p', { class: 'cf-excerpt cf-excerpt--recap', text: it.excerpt }));
+ body.append(spa(it.href, { class: 'cf-recap-read' }, 'Read the recap ', icon('fa-arrow-right')));
+ return el('article', { class: 'cf-card cf-card--recap cf-card--ai' }, band, body);
}
}
const BUILD = { vod: vodCard, clip: clipCard, paste: pasteCard, recap: recapCard };
const BUILD = { vod: vodCard, clip: clipCard, paste: pasteCard, recap: recapCard };
@@ -26,7 +26,6 @@
@@ -26,7 +26,6 @@
{ id: 'search', page: 'search', label: 'Search', href: '/search', icon: 'fa-magnifying-glass' },
{ id: 'search', page: 'search', label: 'Search', href: '/search', icon: 'fa-magnifying-glass' },
{ id: 'chat', page: 'chat', label: 'Chat', href: '/chat', icon: 'fa-comments' },
{ id: 'chat', page: 'chat', label: 'Chat', href: '/chat', icon: 'fa-comments' },
{ id: 'arena', page: 'arena', label: 'Arena', href: '/arena', icon: 'fa-hand-fist' },
{ id: 'arena', page: 'arena', label: 'Arena', href: '/arena', icon: 'fa-hand-fist' },
- { id: 'game', label: 'Game', href: 'https://openvibe.games', icon: 'fa-gamepad' },
{ id: 'broadcast', page: 'broadcast', elId: 'nav-broadcast', label: 'Go Live', href: '/broadcast', icon: 'fa-tower-broadcast', dot: false, dotId: 'nav-live-dot',
{ id: 'broadcast', page: 'broadcast', elId: 'nav-broadcast', label: 'Go Live', href: '/broadcast', icon: 'fa-tower-broadcast', dot: false, dotId: 'nav-live-dot',
onClick: (e) => call('goLiveNav', e),
onClick: (e) => call('goLiveNav', e),
children: user ? [{ id: 'dashboard', label: 'Dashboard', href: '/dashboard', icon: 'fa-gauge-high', onClick: (e) => call('dashNav', e) }] : [] },
children: user ? [{ id: 'dashboard', label: 'Dashboard', href: '/dashboard', icon: 'fa-gauge-high', onClick: (e) => call('dashNav', e) }] : [] },