openvibers/openvibe.live Commits

Offline channel: the header and screen stay invisible (still laid out) until the channel's data, instead of outlines the social links then pushed down 135 px (CLS 0.12 → 0 on /@goosely, phone) (#225)

OP OpenVibers committed 27 days ago · aa876ccb85 · 3 files, +7 −8

Changes

ddiff --git a/public/css/features/channel.css b/public/css/features/channel.css
iindex 45ac464d..bbdd7238 100644
---- a/public/css/features/channel.css
++++ b/public/css/features/channel.css
@@@ -1092,10 +1092,9 @@
comes, and the page shows what the server sent, all of it. */
@media (scripting: enabled) {
#page-channel:not(.ch-ready) .channel-below-fold { display: none; }
-- #page-channel:not(.ch-data) :is(#ch-display-name-offline, #ch-username-offline, #ch-follower-count-offline, #ch-category-badge-offline) {
-- width: fit-content; color: transparent; background: color-mix(in srgb, var(--text-muted) 14%, transparent); border-color: transparent; border-radius: 6px;
-- }
-- #page-channel:not(.ch-data) #ch-avatar-offline { color: transparent; }
++ /* The offline header and screen stay invisible (still laid out) until the data is in: a channel's social links add
++ rows no outline can reserve (four pills are 135 px on a phone), and the header grew under the reader (CLS 0.12). */
++ #page-channel:not(.ch-data) :is(#ch-offline-header, #ch-offline-screen) { visibility: hidden; }
}
.ch-analytics-period-btns { display:flex; gap:4px; margin-left:auto; }
.analytics-stat-cards {
ddiff --git a/public/js/app-channel.js b/public/js/app-channel.js
iindex 1907053c..d1138b13 100644
---- a/public/js/app-channel.js
++++ b/public/js/app-channel.js
@@@ -618,8 +618,8 @@ function _watchTabsClip() {
upd();
}
--// A direct visit paints the channel's markup before its data. Until the data (ch-data) channel.css outlines the
--// header's placeholder words; until the first render, and offline until the "Most watched" block and the discover
++// A direct visit paints the channel's markup before its data. Until the data (ch-data) channel.css keeps the offline
++// header and screen invisible but laid out (a channel's social links add rows nothing can reserve); until the first render, and offline until the "Most watched" block and the discover
// board that land above the tabs (ch-ready), it keeps the tabs out of the layout and the page at full height, so the
// data arriving pushes nothing on screen down. Never longer than 2.5 s after the data.
let _channelReadyTimer = null;
ddiff --git a/test/first-paint-stability.test.js b/test/first-paint-stability.test.js
iindex a988ce05..7b616d7b 100644
---- a/test/first-paint-stability.test.js
++++ b/test/first-paint-stability.test.js
@@@ -40,7 +40,7 @@ assert.ok(chJs.includes('return _fillOfflineExplore(ch && ch.username);') && chJ
assert.ok(chCss.includes('#page-channel:not(.ch-ready) .channel-below-fold { display: none; }'), 'tabs out of the layout until ready');
assert.ok(chCss.includes('#page-channel.page:not(.ch-ready) { min-height: 100vh; }'), 'the page keeps a full viewport until ready (beats "#page-channel.page { min-height: 0 }")');
assert.ok(chCss.indexOf('#page-channel.page:not(.ch-ready)') > chCss.indexOf('#page-channel.page { min-height: 0; }'), '…after the rule it overrides');
--assert.ok(/#page-channel:not\(\.ch-data\) :is\(#ch-display-name-offline/.test(chCss), 'placeholder words are outlines until the data');
++assert.ok(chCss.includes('#page-channel:not(.ch-data) :is(#ch-offline-header, #ch-offline-screen) { visibility: hidden; }'), 'the offline header and screen are invisible (still laid out) until the data: social links add rows no outline can reserve');
ok('the channel holds its tabs and height until its data and offline blocks are in');
// ── VOD and clip pages ──
@@@ -80,7 +80,7 @@ const scriptingBlock = (css, rule) => {
const open = css.lastIndexOf('@media (scripting: enabled) {', at);
return at > 0 && open !== -1 && css.slice(open, at).split('}').length - 1 < css.slice(open, at).split('{').length - 1;
};
--for (const rule of ['#page-channel:not(.ch-ready) .channel-below-fold { display: none; }', '#page-channel:not(.ch-data) #ch-avatar-offline', '#page-channel.page:not(.ch-ready) { min-height: 100vh; }']) {
++for (const rule of ['#page-channel:not(.ch-ready) .channel-below-fold { display: none; }', '#page-channel:not(.ch-data) :is(#ch-offline-header, #ch-offline-screen)', '#page-channel.page:not(.ch-ready) { min-height: 100vh; }']) {
assert.ok(scriptingBlock(chCss, rule), `channel.css: "${rule}" only with scripting`);
}
for (const rule of ['#page-vod-player:not(.mp-ready)', '#page-clip-player:not(.mp-ready) #clp-comments', ':is(#page-vod-player, #page-clip-player):not(.mp-data) :is(#vp-avatar']) {