openvibers/openvibe.live Commits

The hero frame's veil follows the theme (#535)

OP OpenVibers committed 12 days ago · be3eff0935 · 5 files, +15 −7

* The hero frame's veil follows the theme: white on light themes instead of a grey wash

* Merge branch 'main' into design/hero-veil-light

Changes

ddiff --git a/public/css/style.css b/public/css/style.css
iindex 52f3fe47..43dde265 100644
---- a/public/css/style.css
++++ b/public/css/style.css
@@@ -1106,7 +1106,10 @@ html[data-theme-mode="light"] .hero-scrim {
linear-gradient(180deg, color-mix(in srgb, var(--bg-primary) 55%, transparent) 0%, transparent 22%, transparent 78%, var(--bg-primary) 100%),
linear-gradient(color-mix(in srgb, var(--bg-primary) 64%, transparent), color-mix(in srgb, var(--bg-primary) 64%, transparent));
}
--html[data-theme-mode="light"] .hero-bg-layer { filter: brightness(1.25) saturate(.8) contrast(.85); }
++/* The frame's veil, baked into its background (home-fx.css keeps filters off this animating layer, so the filter that
++ was here never applied): black on dark themes, white on light ones, where the black veil turned the hero grey. */
++.hero-bg { --hero-veil: rgba(0, 0, 0, 0.5); }
++html[data-theme-mode="light"] .hero-bg { --hero-veil: rgba(255, 255, 255, 0.62); }
/* ── Hero background: one AI moment frame at a time, Ken-Burns crossfade ─────────── */
.hero-bg { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
ddiff --git a/public/js/app-home.js b/public/js/app-home.js
iindex 9550e78b..a06c2382 100644
---- a/public/js/app-home.js
++++ b/public/js/app-home.js
@@@ -723,7 +723,7 @@ function renderHeroBackground(media, moments) {
_heroBgActive = 0;
// Unless the server painted this frame (seo/pages.js): set it and restart the Ken Burns drift.
if (!(layers[0].style.backgroundImage || '').includes(frames[0])) {
-- layers[0].style.backgroundImage = `linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url("${frames[0]}")`;
++ layers[0].style.backgroundImage = `linear-gradient(var(--hero-veil, rgba(0,0,0,0.5)), var(--hero-veil, rgba(0,0,0,0.5))), url("${frames[0]}")`;
layers[0].classList.remove('active'); void layers[0].offsetWidth; layers[0].classList.add('active');
} else layers[0].classList.add('active');
layers[1].classList.remove('active');
@@@ -736,9 +736,9 @@ function renderHeroBackground(media, moments) {
const el = layers[next];
const pre = new Image();
pre.onload = () => {
-- // The darkening is part of the frame's own background rather than a filter or an overlay element:
-- // either of those becomes a second full-hero layer on top of an animating one.
-- el.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url("${frames[idx]}")`;
++ // The veil is part of the frame's own background rather than a filter or an overlay element: either of those
++ // becomes a second full-hero layer on top of an animating one. Its colour is the theme's (--hero-veil, style.css).
++ el.style.backgroundImage = `linear-gradient(var(--hero-veil, rgba(0,0,0,0.5)), var(--hero-veil, rgba(0,0,0,0.5))), url("${frames[idx]}")`;
el.classList.remove('active'); void el.offsetWidth; el.classList.add('active');
layers[_heroBgActive].classList.remove('active');
_heroBgActive = next;
ddiff --git a/server/seo/pages.js b/server/seo/pages.js
iindex 8e332a65..7c9f3282 100644
---- a/server/seo/pages.js
++++ b/server/seo/pages.js
@@@ -865,7 +865,7 @@ function render(meta, urlPath) {
// it summarises the same content the SPA renders.
if (meta.snapshot) html = _prerender(html, meta.snapshot + _summary(meta));
// The first hero layer shows that frame already (the same background app-home.js gives it).
-- if (meta.heroImage) html = html.replace('<div class="hero-bg-layer"></div>', `<div class="hero-bg-layer active" style="background-image:linear-gradient(rgba(0,0,0,0.5),rgba(0,0,0,0.5)),url(&quot;${esc(meta.heroImage)}&quot;)"></div>`);
++ if (meta.heroImage) html = html.replace('<div class="hero-bg-layer"></div>', `<div class="hero-bg-layer active" style="background-image:linear-gradient(var(--hero-veil,rgba(0,0,0,0.5)),var(--hero-veil,rgba(0,0,0,0.5))),url(&quot;${esc(meta.heroImage)}&quot;)"></div>`);
return html;
}
ddiff --git a/test/seo-ssr.test.js b/test/seo-ssr.test.js
iindex d8e17d3b..b1a3d72d 100644
---- a/test/seo-ssr.test.js
++++ b/test/seo-ssr.test.js
@@@ -297,7 +297,7 @@ const server = http.createServer(app).listen(0, '127.0.0.1', async () => {
assert.strictEqual(meta.heroImage, frame);
const html = pages.render(meta, '/');
assert.ok(head(html).includes(`<link rel="preload" as="image" href="${frame}" fetchpriority="high">`), 'preloaded in the head');
-- assert.ok(html.includes(`<div class="hero-bg-layer active" style="background-image:linear-gradient(rgba(0,0,0,0.5),rgba(0,0,0,0.5)),url(&quot;${frame}&quot;)"></div><div class="hero-bg-layer"></div>`), 'the first layer shows it');
++ assert.ok(html.includes(`<div class="hero-bg-layer active" style="background-image:linear-gradient(var(--hero-veil,rgba(0,0,0,0.5)),var(--hero-veil,rgba(0,0,0,0.5))),url(&quot;${frame}&quot;)"></div><div class="hero-bg-layer"></div>`), 'the first layer shows it');
// A thumbnail that is not a plain https URL is never written into the page.
await db.setState('home_hero_moments', JSON.stringify({ moments: [{ vodId: 100, thumbnail: 'https://x.test/a")%3Bbackground:url("//evil' }] }));
assert.strictEqual((await pages._pageMeta('/')).heroImage, null);
ddiff --git a/test/theme-tokens-defined.test.js b/test/theme-tokens-defined.test.js
iindex 600b17b3..12d37f41 100644
---- a/test/theme-tokens-defined.test.js
++++ b/test/theme-tokens-defined.test.js
@@@ -50,4 +50,9 @@ assert.match(channel, /\.ch-lang-chip \{[^}]*color: color-mix\(in srgb, #f59e0b
const allCss = files.filter((f) => f.endsWith('.css')).map((f) => fs.readFileSync(f, 'utf8')).join('\n');
assert.ok(!/(^|[^-\w])color:\s*(#b388ff|var\(--reward-color, #b388ff\))/m.test(allCss), 'no fixed pale violet text');
assert.match(style, /\.chat-relay-badge \{ color: color-mix\(in srgb, var\(--relay-color, #888\) 55%, var\(--text-primary\)\)/);
++// The hero frame's veil is the theme's: white on light themes (a black veil greyed the whole hero on Daylight).
++assert.match(style, /\.hero-bg \{ --hero-veil: rgba\(0, 0, 0, 0\.5\); \}/);
++assert.match(style, /html\[data-theme-mode="light"\] \.hero-bg \{ --hero-veil: rgba\(255, 255, 255, 0\.62\); \}/);
++const home = fs.readFileSync(path.join(root, 'js', 'app-home.js'), 'utf8');
++assert.ok(!/linear-gradient\(rgba\(0,0,0,0\.5\)/.test(home), 'no fixed black veil on the hero frames');
console.log('theme tokens defined: all checks passed');