openvibers/openvibe.live Commits

Page moves never flash unstyled: openvibe-shared v1.30.1 (from v1.29.2) inserts a route's markup once its stylesheets are in, and showPage hands the page to ov.enter(), which keeps it hidden (in its space) until the route's styles and code are ready, runs a real progress bar from the click, shows 'Loading Content…' with the phase after 150 ms, then fades the page in (opacity only). Live's own timer-based route bar and page rise (ovRouteProgress, ovAnimatePageEnter, #ov-routebar, .page.is-entering) are gone. Measured locally at 400 ms latency. npm test 151/152, 1 skipped.

OP OpenVibers committed 29 Sept 2026 · c201d8c172 · 4 files, +11 −97


Changes

ddiff --git a/package-lock.json b/package-lock.json
iindex 2638502d..b154f2c1 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -27,7 +27,7 @@
"onnxruntime-node": "^1.20.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.16.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.30.1",
"sanitize-html": "^2.17.7",
"sdp-transform": "^3.0.0",
"sharp": "^0.33.0",
@@@ -2859,9 +2859,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "1.29.2",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",
-- "integrity": "sha512-w5daRSzujokYT1/Q3fu0KdcSZUBEv1pm66H8vIAI2SY0xtOaLqgfkXJpfIz1NgvJuyK8dhXBbLjM8Ow9RZ5x+A==",
++ "version": "1.30.1",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.30.1",
++ "integrity": "sha512-0mnEKv9FyiWtOiRAMtUTpGZasmJgRyVk1Eo97cYgxLfZNPMawYsYJxbA/N+WQhABmz2nYh2yl95xIu8fV5fF0g==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex 5c23a882..5ad2591c 100644
---- a/package.json
++++ b/package.json
@@@ -46,7 +46,7 @@
"onnxruntime-node": "^1.20.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.16.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.29.2",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v1.30.1",
"sanitize-html": "^2.17.7",
"sdp-transform": "^3.0.0",
"sharp": "^0.33.0",
ddiff --git a/public/css/style.css b/public/css/style.css
iindex 124b1916..dcbe2ede 100644
---- a/public/css/style.css
++++ b/public/css/style.css
@@@ -11311,50 +11311,10 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
#page-home.page { padding-bottom: 10px; }
--/* ═══════════════════════════════════════════════════════════════
-- Route-change motion (app.js: ovRouteProgress / ovAnimatePageEnter)
--
-- A route swap here is instant in the DOM and slow to fill from the network, so without a signal
-- the page reads as stuck for that gap. The bar is one element with a transform-only sweep; the
-- page rises the last few pixels into place. Neither can shift layout, and both strip themselves
-- on a timer so a swap interrupted mid-flight never leaves content invisible.
-- ═══════════════════════════════════════════════════════════════ */
--#ov-routebar {
-- position: fixed; top: 0; left: 0; right: 0; height: 2px;
-- z-index: 20000; pointer-events: none; opacity: 0;
-- background: linear-gradient(90deg,
-- transparent,
-- color-mix(in srgb, var(--accent) 85%, transparent) 35%,
-- var(--accent-light, var(--accent)) 55%,
-- color-mix(in srgb, var(--accent) 85%, transparent) 75%,
-- transparent);
-- transform: translateX(-100%);
--}
--#ov-routebar.is-run { opacity: 1; animation: ovRouteBar 0.62s cubic-bezier(.3, .8, .2, 1) both; }
--#ov-routebar.is-done { opacity: 0; transform: translateX(0); transition: opacity 0.3s ease; }
--@keyframes ovRouteBar {
-- from { transform: translateX(-100%); }
-- to { transform: translateX(100%); }
--}
--
--/* Opacity only. A running transform animation makes the element a containing block for its
-- position:fixed descendants, so while a page rose into place its fixed children — the mobile chat
-- sheet on a channel, the broadcast desk's chat button — were positioned against the page box
-- instead of the viewport and visibly jumped (measured: a bottom:0 probe sat 10px below the
-- viewport mid-entry). Opacity does not create a containing block. */
--.page.is-entering { animation: ovPageIn 0.36s cubic-bezier(.22, 1, .36, 1) both; }
--@keyframes ovPageIn {
-- from { opacity: 0; }
-- to { opacity: 1; }
--}
--
++/* Route changes (the bar, the loading line, the fade-in) are openvibe-shared web-runtime enter() + route-transition.js. */
@media (prefers-reduced-motion: reduce) {
-- #ov-routebar { display: none; }
-- .page.is-entering { animation: none !important; }
.brand-mark.is-spin svg, .brand-mark.is-settling svg { animation: none !important; }
}
--/* Phones do the page rise but skip the bar — it sits under the browser's own chrome anyway. */
--.rs-lite .page.is-entering { animation-duration: 0.32s; }
/* ═══════════════════════════════════════════════════════════════
ddiff --git a/public/js/app.js b/public/js/app.js
iindex c6a838e0..89d350ce 100644
---- a/public/js/app.js
++++ b/public/js/app.js
@@@ -1273,56 +1273,8 @@ function ovWakeBrandMark() {
}, BRAND_AWAKE_MS);
}
--/**
-- * A thin bar that runs across the top on a route change.
-- *
-- * Routes here are instant to swap and slow to fill — the shell is already up, the content arrives
-- * over the network. Without a signal the page looks broken for that gap. The bar is one element,
-- * transform-only, and removes itself.
-- */
--let _routeBarTimer = 0;
--function ovRouteProgress() {
-- let reduce = false;
-- try { reduce = matchMedia('(prefers-reduced-motion: reduce)').matches; } catch { /* */ }
-- if (reduce) return;
-- let bar = document.getElementById('ov-routebar');
-- if (!bar) {
-- bar = document.createElement('div');
-- bar.id = 'ov-routebar';
-- bar.setAttribute('aria-hidden', 'true');
-- document.body.appendChild(bar);
-- }
-- clearTimeout(_routeBarTimer);
-- bar.classList.remove('is-run', 'is-done');
-- void bar.offsetWidth;
-- bar.classList.add('is-run');
-- _routeBarTimer = setTimeout(() => {
-- bar.classList.remove('is-run');
-- bar.classList.add('is-done');
-- _routeBarTimer = setTimeout(() => bar.classList.remove('is-done'), 320);
-- }, 620);
--}
--
--/**
-- * The incoming page rises into place instead of appearing.
-- *
-- * Transform and opacity only, so it cannot shift layout, and the class is stripped on a timer —
-- * an element whose animation never ran (a route swapped again mid-flight, a hidden ancestor) must
-- * not be left holding opacity 0.
-- */
--let _pageEnterTimer = 0;
--function ovAnimatePageEnter(el) {
-- if (!el) return;
-- let reduce = false;
-- try { reduce = matchMedia('(prefers-reduced-motion: reduce)').matches; } catch { /* */ }
-- if (reduce) return;
-- clearTimeout(_pageEnterTimer);
-- document.querySelectorAll('.page.is-entering').forEach(p => p.classList.remove('is-entering'));
-- el.classList.remove('is-entering');
-- void el.offsetWidth;
-- el.classList.add('is-entering');
-- _pageEnterTimer = setTimeout(() => el.classList.remove('is-entering'), 700);
--}
++const _PAGE_LABELS = { 'vod-player': 'video', 'clip-player': 'clip', broadcast: 'Go Live', recap: 'recap' };
++function _pageLabel(page) { return _PAGE_LABELS[page] || String(page).replace(/-/g, ' ').replace(/^./, (c) => c.toUpperCase()); }
function showPage(page) {
const changed = currentPage !== page;
@@@ -1331,7 +1283,9 @@ function showPage(page) {
if (el) el.classList.add('active');
// The mark and the page transition are both "you moved" signals, so they fire on a real
// change only — re-rendering the page you are already on should not restart either.
-- if (changed) { ovWakeBrandMark(); ovRouteProgress(); ovAnimatePageEnter(el); }
++ // A page move: the page stays hidden until its route's styles and code are in (never unstyled), with a progress
++ // bar, a "Loading …" line if it takes a moment, and a fade-in (openvibe-shared web-runtime enter()).
++ if (changed) { ovWakeBrandMark(); if (window.ov && ov.enter && el) ov.enter(el, ov.route(location.pathname), { label: el.dataset.title || _pageLabel(page) }); }
// A fresh page load is not a "change" — currentPage is already the page being shown — so the
// mark would never take its first sleep and the SMIL orbits would loop for the whole session.
// Wake it once on arrival; the same timer puts it to sleep five seconds later.