openvibers/openvibe.live Commits

Stream cards wait in their streamer's colour, not a grey box (#501)

OP OpenVibers committed 12 days ago · b027011133 · 3 files, +37 −2

Plan L52 step 6 (the owner, 11-01: "placeholder elements while it loads, a fake thumbnail… so it is not jarring").
Until a card's picture arrives, its 16:9 box is drawn in the streamer's profile colour (or one drawn from their name),
a soft light from the top left, so a grid fills in colour. Only a plain #rrggbb is used.

Changes

ddiff --git a/public/css/style.css b/public/css/style.css
iindex e74a0301..1fbf3369 100644
---- a/public/css/style.css
++++ b/public/css/style.css
@@@ -811,7 +811,8 @@ body.ov-nav-overlay .navbar.ovnav-top {
}
.stream-card-thumb {
aspect-ratio: 16/9;
-- background: var(--bg-tertiary);
++ /* Until the picture arrives, its streamer's colour (app-home.js _thumbTint), not a grey box. */
++ background: radial-gradient(120% 95% at 28% 18%, color-mix(in srgb, var(--thumb-tint, var(--bg-tertiary)) 58%, var(--bg-tertiary)), var(--bg-tertiary));
position: relative;
display: flex; align-items: center; justify-content: center;
overflow: hidden;
ddiff --git a/public/js/app-home.js b/public/js/app-home.js
iindex a71efe10..c31ffce7 100644
---- a/public/js/app-home.js
++++ b/public/js/app-home.js
@@@ -1745,6 +1745,18 @@ async function loadHomePastes(opts) {
// Markup for a single stream card (live or recent). Extracted so the home page
// can reconcile the live grid in place for real-time updates.
++/**
++ * The colour a card's picture sits on until it arrives (plan L52: "a fake thumbnail… so it is not jarring"): the
++ * streamer's own profile colour, or one drawn from their name, so a grid fills in colour rather than grey boxes.
++ */
++function _thumbTint(s) {
++ if (s && /^#[0-9a-f]{6}$/i.test(String(s.profile_color || ''))) return s.profile_color;
++ const name = String((s && s.username) || '');
++ let h = 0;
++ for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) % 360;
++ return `hsl(${h} 45% 34%)`;
++}
++
function streamCardHTML(s, isLive) {
let navUrl;
if (isLive && s.id) {
@@@ -1760,7 +1772,7 @@ function streamCardHTML(s, isLive) {
const endedAgo = !isLive && s.ended_at ? `<span class="stream-card-ago">${timeAgo(s.ended_at)}</span>` : '';
return `
<a class="stream-card" data-stream-id="${esc(String(s.id || ''))}" href="${esc(navUrl)}" onclick="return handleLinkClick(event, '${esc(navUrl)}')">
-- <div class="stream-card-thumb${s.is_nsfw ? ' stream-card-nsfw-blur' : ''}">
++ <div class="stream-card-thumb${s.is_nsfw ? ' stream-card-nsfw-blur' : ''}" style="--thumb-tint:${esc(_thumbTint(s))}">
${thumbImg(thumb, 'fa-circle-nodes', s.title, !isLive && s.vod_id ? `/api/thumbnails/generate/vod/${s.vod_id}` : null)}
${streamTypeBadge(s.browser_mode, s.streaming_method)}
${s.is_nsfw ? '<span class="stream-card-nsfw">18+</span>' : ''}
ddiff --git a/test/thumb-tint.test.js b/test/thumb-tint.test.js
nnew file mode 100644
iindex 00000000..3642c83c
---- /dev/null
++++ b/test/thumb-tint.test.js
@@@ -0,0 +1,22 @@
++'use strict';
++// A card's picture sits on its streamer's colour until it arrives (plan L52; the owner, 11-01: "placeholder elements
++// while it loads, a fake thumbnail… so it is not jarring"). The function is public/js/app-home.js's own, run here.
++// node test/thumb-tint.test.js
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++
++const src = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'app-home.js'), 'utf8');
++const body = /function _thumbTint\(s\) \{[\s\S]*?\n\}\n/.exec(src);
++assert.ok(body, 'app-home.js has _thumbTint');
++const _thumbTint = new Function(`${body[0]}; return _thumbTint;`)();
++
++assert.strictEqual(_thumbTint({ profile_color: '#3366ff', username: 'ana' }), '#3366ff', 'the streamer\'s own colour');
++assert.match(_thumbTint({ profile_color: 'red;background:url(x)', username: 'ana' }), /^hsl\(\d+ 45% 34%\)$/, 'anything but a plain colour is not used');
++assert.strictEqual(_thumbTint({ username: 'ana' }), _thumbTint({ username: 'ana' }), 'the same name, the same colour');
++assert.notStrictEqual(_thumbTint({ username: 'ana' }), _thumbTint({ username: 'kai' }), 'another name, another colour');
++assert.match(_thumbTint({}), /^hsl\(0 45% 34%\)$/);
++assert.match(src, /<div class="stream-card-thumb\$\{[^}]+\}" style="--thumb-tint:\$\{esc\(_thumbTint\(s\)\)\}">/, 'the card wears it');
++const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'style.css'), 'utf8');
++assert.match(css, /\.stream-card-thumb \{\s*aspect-ratio: 16\/9;[\s\S]{0,200}var\(--thumb-tint/, 'the box is drawn in it');
++console.log('thumb tint: all checks passed');