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.
Changes
@@@ -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"@@@ -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",@@@ -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; }/* ═══════════════════════════════════════════════════════════════@@@ -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.
@@ -27,7 +27,7 @@
@@ -27,7 +27,7 @@
"onnxruntime-node": "^1.20.0",
"onnxruntime-node": "^1.20.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.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-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",
"sanitize-html": "^2.17.7",
"sdp-transform": "^3.0.0",
"sdp-transform": "^3.0.0",
"sharp": "^0.33.0",
"sharp": "^0.33.0",
@@ -2859,9 +2859,9 @@
@@ -2859,9 +2859,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"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",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -46,7 +46,7 @@
@@ -46,7 +46,7 @@
"onnxruntime-node": "^1.20.0",
"onnxruntime-node": "^1.20.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.71.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-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",
"sanitize-html": "^2.17.7",
"sdp-transform": "^3.0.0",
"sdp-transform": "^3.0.0",
"sharp": "^0.33.0",
"sharp": "^0.33.0",
@@ -11311,50 +11311,10 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
@@ -11311,50 +11311,10 @@ a .arena-thread:hover { color: var(--text-primary); background: color-mix(in srg
#page-home.page { padding-bottom: 10px; }
#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) {
@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; }
.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; }
/* ═══════════════════════════════════════════════════════════════
/* ═══════════════════════════════════════════════════════════════
@@ -1273,56 +1273,8 @@ function ovWakeBrandMark() {
@@ -1273,56 +1273,8 @@ function ovWakeBrandMark() {
}, BRAND_AWAKE_MS);
}, 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) {
function showPage(page) {
const changed = currentPage !== page;
const changed = currentPage !== page;
@@ -1331,7 +1283,9 @@ function showPage(page) {
@@ -1331,7 +1283,9 @@ function showPage(page) {
if (el) el.classList.add('active');
if (el) el.classList.add('active');
// The mark and the page transition are both "you moved" signals, so they fire on a real
// 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.
// 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
// 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.
// 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.
// Wake it once on arrival; the same timer puts it to sleep five seconds later.