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)
Changes
@@@ -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 {@@@ -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;@@@ -40,7 +40,7 @@ assert.ok(chJs.includes('return _fillOfflineExplore(ch && ch.username);') && chJassert.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']) {
@@ -1092,10 +1092,9 @@
@@ -1092,10 +1092,9 @@
comes, and the page shows what the server sent, all of it. */
comes, and the page shows what the server sent, all of it. */
@media (scripting: enabled) {
@media (scripting: enabled) {
#page-channel:not(.ch-ready) .channel-below-fold { display: none; }
#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; }
.ch-analytics-period-btns { display:flex; gap:4px; margin-left:auto; }
.analytics-stat-cards {
.analytics-stat-cards {
@@ -618,8 +618,8 @@ function _watchTabsClip() {
@@ -618,8 +618,8 @@ function _watchTabsClip() {
upd();
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
// 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.
// data arriving pushes nothing on screen down. Never longer than 2.5 s after the data.
let _channelReadyTimer = null;
let _channelReadyTimer = null;
@@ -40,7 +40,7 @@ assert.ok(chJs.includes('return _fillOfflineExplore(ch && ch.username);') && chJ
@@ -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: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.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(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');
ok('the channel holds its tabs and height until its data and offline blocks are in');
// ── VOD and clip pages ──
// ── VOD and clip pages ──
@@ -80,7 +80,7 @@ const scriptingBlock = (css, rule) => {
@@ -80,7 +80,7 @@ const scriptingBlock = (css, rule) => {
const open = css.lastIndexOf('@media (scripting: enabled) {', at);
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;
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`);
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']) {
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']) {