openvibers/openvibe.live Commits

Home: no tabs again; every part stacked on every screen (#873)

OP OpenVibers committed 8 h ago · ec209576f9 · 5 files, +58 −94

The owner, 2026-11-13, of the phone tabs added on 11-12 (#865): "get rid of these tabs ... I want ... how it was back
... so that the homepage has all the content and it's more SEO friendly". The same rule as 10-31 and 11-07: never
hide content behind tabs. The tab wrapper, its radios and bar, the ov-parts stylesheet link and the code that opened a
closed tab are gone; For you stays first. Critical selectors regenerated; budgets within limits.

Changes

ddiff --git a/public/css/critical-selectors.json b/public/css/critical-selectors.json
iindex 94038192..f5400d0c 100644
---- a/public/css/critical-selectors.json
++++ b/public/css/critical-selectors.json
@@@ -16,7 +16,6 @@
],
"selectors": [
"#app",
-- "#openvibe-bell-mount",
"#page-home",
"#page-home .container > .section-header:first-child",
"#page-home .hero",
@@@ -27,8 +26,6 @@
"*::before",
".badge",
".btn",
-- ".btn-danger",
-- ".btn-follow",
".btn-lg",
".btn-openvibe-bucks-mini",
".btn-openvibe-coins-mini",
@@@ -55,7 +52,6 @@
".ch-offline-screen",
".ch-panel-bar > .ch-vods-filter-bar",
".ch-panel-bar > .clips-taken-filters",
-- ".ch-panel-hint b",
".ch-panel-hint i",
".ch-tab",
".ch-tab i",
@@@ -65,7 +61,6 @@
".ch-tab.active",
".ch-tab.active::after",
".ch-tabs",
-- ".chat-ai-badge",
".chat-avatar",
".chat-avatar-letter",
".chat-avatar-wrap",
@@@ -78,24 +73,29 @@
".chat-crossfeed-badge",
".chat-crossfeed-global",
".chat-crossfeed-stream",
++ ".chat-edited",
".chat-emote",
++ ".chat-emote-animated",
".chat-header",
".chat-header-actions",
".chat-input-area",
".chat-input-row",
".chat-input-row .chat-send-btn",
++ ".chat-input-row .ovc-v2 textarea.chat-textarea",
++ ".chat-input-row > .ovc-v2",
".chat-link",
".chat-messages",
".chat-mode-select",
".chat-mode-select option:disabled",
".chat-msg",
".chat-msg .chat-user",
-- ".chat-msg-crossfeed",
".chat-msg-actions",
++ ".chat-msg-crossfeed",
++ ".chat-msg-tts-off",
++ ".chat-msg-tts-off .chat-user::after",
".chat-msg.system",
++ ".chat-msg:first-child > .chat-msg-actions",
".chat-nickels-float .rewards-coin-balance",
-- ".chat-relay-badge",
-- ".chat-relay-badge i",
".chat-send-btn",
".chat-settings-btn",
".chat-sidebar",
@@@ -117,6 +117,19 @@
".chat-tts-toggle",
".chat-tts-toggle.tts-active",
".chat-users-panel",
++ ".clip-chat-avatar",
++ ".clip-chat-card",
++ ".clip-chat-cta",
++ ".clip-chat-cta i",
++ ".clip-chat-dur",
++ ".clip-chat-info",
++ ".clip-chat-label",
++ ".clip-chat-meta",
++ ".clip-chat-meta > span",
++ ".clip-chat-play",
++ ".clip-chat-thumb",
++ ".clip-chat-thumb img",
++ ".clip-chat-title",
".container",
".content-container",
".cta-ambient",
@@@ -133,21 +146,13 @@
".cta-method-num",
".cta-section-label",
".cta-section-label i",
-- ".digest-chip",
-- ".digest-hot-line b",
-- ".digest-hot-line i",
-- ".digest-stat b",
-- ".digest-stat i",
-- ".digest-title i",
-- ".egg-key",
-- ".egg-key--letter",
".emote-picker",
".emote-picker-btn",
".emote-picker-btn.gif-btn",
".emote-picker-grid",
".empty-state",
-- ".empty-state i",
".fab-label",
++ ".fcw-input-area .chat-input-row .ovc-v2 textarea.chat-textarea",
".fcw-unread-badge",
".floating-chat-fab",
".floating-chat-widget",
@@@ -165,13 +170,13 @@
".global-chat-main .chat-input-area",
".global-chat-main .chat-input-row",
".global-chat-main .chat-input-row .chat-textarea",
++ ".global-chat-main .chat-input-row .ovc-v2 textarea.chat-textarea",
".global-chat-main .chat-input-row input",
".global-chat-messages",
-- ".goal-bar",
-- ".goal-fill",
".hero",
".hero .hero-tagline",
".hero h1",
++ ".hero h1 .ov-mark",
".hero p",
".hero-aurora",
".hero-bg",
@@@ -182,7 +187,6 @@
".hero-float",
".hero-float img",
".hero-float--clip .hero-float-badge",
-- ".hero-float--moment .hero-float-badge",
".hero-float--paste .hero-float-badge",
".hero-float--vod .hero-float-badge",
".hero-float-body",
@@@ -197,11 +201,11 @@
".hero-rotate-wrap",
".hero-rotate.visible",
".hero-scrim",
-- ".hero-slogan-timer b",
".hero-slogan-timer i",
".hero-stat",
".hero-stat > i",
".hero-stat > i::before",
++ ".hero-stat--live > i",
".hero-stat-chart-hint",
".hero-stat-delta b",
".hero-stat-label",
@@@ -275,21 +279,17 @@
".offline-global-chat .chat-input-area",
".offline-global-chat .chat-input-row",
".offline-global-chat .chat-input-row .chat-textarea",
++ ".offline-global-chat .chat-input-row .ovc-v2 textarea.chat-textarea",
".offline-global-chat .chat-input-row input",
".offline-global-chat .chat-messages",
".openvibe-navbar.navbar",
".openvibe-navbar.navbar .openvibe-navbar-links a.ovnav-link",
".openvibe-navbar.navbar .openvibe-navbar-links a.ovnav-link.active",
".openvibe-navbar.navbar.ovnav-top",
-- ".ov-comet",
-- ".ov-dot",
-- ".ov-glow",
-- ".ov-orbit",
-- ".ov-orbit--tail",
++ ".ov-mark--ph",
".ov-rail",
".ov-rail > .moments-row",
".ov-rail-bar",
-- ".ov-rail-bar > span",
".ov-rail-bar[hidden]",
".ov-rail-btn",
".ov-rail-btn:disabled",
@@@ -297,15 +297,9 @@
".ov-rail-prev",
".ov-rail-track",
".ov-rail-track > *",
-- ".ov-rail.is-scrollable > .ov-rail-track",
".ov-rail:not(.is-end) > .ov-rail-track",
".ov-rail:not(.is-scrollable) > .ov-rail-btn",
".ov-rail:not(.is-start) > .ov-rail-track",
-- ".ov-ring",
-- ".ov-spark",
-- ".ov-spark--2",
-- ".ov-spark--3",
-- ".ov-v",
".page",
".page.active",
".paste-modal",
@@@ -313,21 +307,14 @@
".paste-modal-overlay",
".paste-modal-upload",
".pastes-grid",
-- ".pastes-page-info",
".pastes-pagination",
".pc-tools-row > button",
".pc-tools-row > button.btn-openvibe-bucks-mini",
".pc-tools-row > button.btn-openvibe-coins-mini",
".pc-tools-row > button.chat-nickels-float",
".pc-tools-row > button.chat-tts-toggle.tts-active",
-- ".pulse-act i",
".pulse-grid",
-- ".pulse-kicker i",
-- ".pulse-rank b",
-- ".pulse-sub b",
".rewards-panel",
-- ".rs-lite .ov-orbit",
-- ".rs-lite .ov-spark",
".screenshot-details-grid",
".section-header",
".section-header .badge",
@@@ -343,12 +330,16 @@
".section-header h2 > i:first-child",
".section-header h2 > i:first-child::before",
".section-header::after",
-- ".sgg-title i",
".stream-card",
++ ".stream-card-audio",
++ ".stream-card-audio i",
".stream-card-avatar",
".stream-card-avatar img",
".stream-card-duration",
".stream-card-info",
++ ".stream-card-live",
++ ".stream-card-live::before",
++ ".stream-card-metaright i",
".stream-card-streamer",
".stream-card-thumb",
".stream-card-thumb i",
@@@ -357,12 +348,15 @@
".stream-card-viewers",
".stream-card-viewers b",
".stream-card-viewers i",
++ ".stream-card.is-live",
++ ".stream-card.is-live .stream-card-avatar",
++ ".stream-card.is-live .stream-card-info",
++ ".stream-card.is-live .stream-card-thumb img",
++ ".stream-card.is-live::after",
++ ".stream-card.is-live::before",
".stream-grid",
".stream-layout",
-- ".streamer-group-card .card-ai-overview",
".streamer-group-name",
-- ".streamer-group-stream-info",
-- ".streamer-group-stream-thumb",
".toast-container",
".tour",
".vc-channel-body",
@@@ -381,7 +375,6 @@
"a",
"body",
"body .floating-chat-fab",
-- "body .messenger-toggle",
"body .mobile-chat-toggle",
"body :is(.modal-overlay, .bc-ws-confirm-overlay, .bc-modal, .bc-perm-overlay, .bucks-log-overlay, .user-ai-modal-overlay, .paste-modal-overlay, .vc-modal-overlay, .chat-slur-nudge-modal, .cosmetics-modal-overlay, .link-trust-overlay, .link-preview-overlay, .cgw-pop-overlay, .image-censor-overlay)",
"body.ov-nav-overlay .navbar.ovnav-top",
@@@ -395,7 +388,6 @@
"input",
"select",
"select.form-input",
-- "textarea",
-- ".hero h1 .ov-mark"
++ "textarea"
]
}
ddiff --git a/public/features.json b/public/features.json
iindex 32027e21..3e286583 100644
---- a/public/features.json
++++ b/public/features.json
@@@ -35,7 +35,7 @@
"css": [
"/css/features/home.css",
"/css/home-fx.css",
-- "/css/home-tour.css", "/shared/ov-parts.css"
++ "/css/home-tour.css"
],
"js": [
"/shared/kit-web.js",
ddiff --git a/public/fragments/home.html b/public/fragments/home.html
iindex d64fd424..6a2687ee 100644
---- a/public/fragments/home.html
++++ b/public/fragments/home.html
@@@ -97,16 +97,10 @@
<div class="agent-cards" id="home-agents-grid"></div>
</div>
-- <!-- Below Live Now, a phone (600 px and under) shows one part at a time as tabs: For you, Catch up, OpenVibe (the
-- owner, 2026-11-11: "consolidate these into tabs, self-contained tabs, so the page isn't so freaking huge … at
-- least on mobile"). Every part stays in this HTML for crawlers and readers; a wider screen stacks them as before.
-- openvibe-shared ov-parts.css (showcase.parts()) draws it with radios and :has(), no script. -->
-- <div class="ov-parts ov-parts--front home-parts" data-parts="3">
-- <input type="radio" class="ov-parts-r" name="ovp-home" id="ovp-home-foryou" value="foryou" checked>
-- <input type="radio" class="ov-parts-r" name="ovp-home" id="ovp-home-catchup" value="catchup">
-- <input type="radio" class="ov-parts-r" name="ovp-home" id="ovp-home-openvibe" value="openvibe">
-- <div class="ov-parts-bar" role="group" aria-label="The rest of the home page"><label for="ovp-home-foryou">For you</label><label for="ovp-home-catchup">Catch up</label><label for="ovp-home-openvibe">OpenVibe</label></div>
-- <section class="ov-part" data-part="foryou" aria-label="For you">
++ <!-- Below Live Now, every part of the home is stacked on every screen: For you, Catch up (the videos, clips and
++ pastes), OpenVibe. No tabs (the owner, 2026-11-13: "get rid of these tabs … I want … how it was back"; the
++ same as 10-31 and 11-07: never hide content behind tabs). Everything is in the page for readers and crawlers. -->
++ <section class="home-part" data-part="foryou" aria-label="For you">
<!-- Around OpenVibe: the digest, the star spotlight and the pulse, every one shown on every screen (the owner,
2026-10-31: "don't reduce size by removing features"). -->
<div class="home-around" id="home-around">
@@@ -142,7 +136,7 @@
</div>
</div><!-- /home-around -->
</section>
-- <section class="ov-part" data-part="catchup" aria-label="Catch up">
++ <section class="home-part" data-part="catchup" aria-label="Catch up">
<!-- What people made, above the introduction to the site (roadmap 32.7: the home leads with live
content and creators): channels that were just on, their recordings, the clips people took
@@@ -195,7 +189,7 @@
</div>
</div><!-- /home-catchup -->
</section>
-- <section class="ov-part" data-part="openvibe" aria-label="OpenVibe">
++ <section class="home-part" data-part="openvibe" aria-label="OpenVibe">
<!-- New here?: the restream tour and the About banner, one after the other on every screen (2026-11-07: the
tab row that showed one at a time on a phone is gone; the owner's rule is never to hide content behind
@@@ -375,7 +369,6 @@
</footer>
<!--/ov:network--></div>
</section>
-- </div><!-- /ov-parts -->
</div>
ddiff --git a/public/js/app-home.js b/public/js/app-home.js
iindex 7a5c9ecd..a69842cb 100644
---- a/public/js/app-home.js
++++ b/public/js/app-home.js
@@@ -1213,27 +1213,11 @@ function demoteHomeIntroSections() {
if (!last) return;
block.dataset.ovDemoted = '1';
block.classList.add('home-intro-demoted');
-- // On a phone the intro is in the OpenVibe tab (home-parts): it moves to the end of its own part, never out of it.
-- const part = block.closest('.ov-part');
++ // The intro stays inside the OpenVibe part (it holds New here? and the network): to the end of that part.
++ const part = block.closest('.home-part');
try { (part || last).appendChild(block); } catch { /* */ }
}
--/* A link to something inside a phone tab that is not open (#home-network, #ovnet-g-streams…) opens that tab first, so
-- * the jump lands on what it names. Wide screens stack the parts and need nothing. */
--function openHomePartFor(hash) {
-- let el = null;
-- try { el = hash && hash.length > 1 ? document.getElementById(decodeURIComponent(hash.slice(1))) : null; } catch { el = null; }
-- const part = el && el.closest('.home-parts > .ov-part');
-- if (!part) return;
-- const radio = document.getElementById(`ovp-home-${part.dataset.part}`);
-- if (radio && !radio.checked) { radio.checked = true; requestAnimationFrame(() => { try { el.scrollIntoView({ block: 'start' }); } catch { /* */ } }); }
--}
--window.addEventListener('hashchange', () => openHomePartFor(location.hash));
--document.addEventListener('click', (e) => {
-- const a = e.target && e.target.closest && e.target.closest('a[href^="#"]');
-- if (a) openHomePartFor(a.getAttribute('href'));
--}, true);
--
/* ── The OpenVibe network (2026-11-07 redesign) ──────────────────────────────────────────────────────────────
* The section's tiles are in the page already (scripts/build-network-section.js, from OpenVibe.Network's frame), so
* crawlers and a reader without scripts get every site. Here it comes alive:
@@@ -1368,7 +1352,6 @@ function initHomeNetwork() {
async function loadHome() {
initHomeNetwork(); // the OpenVibe network: every site, alive (no tabs: everything shows)
demoteHomeIntroSections();
-- openHomePartFor(location.hash);
void loadHomeChangelog();
_initHomeAbout();
updateNavHeroTransparency(); // transparent nav over the hero at the top
ddiff --git a/test/home-tabs.test.js b/test/home-tabs.test.js
iindex 5da90474..8d4920cc 100644
---- a/test/home-tabs.test.js
++++ b/test/home-tabs.test.js
@@@ -1,5 +1,5 @@
/**
-- * Home tabs: there are none any more (2026-11-07, the owner's rule: never hide content behind tabs).
++ * Home tabs: there are none (2026-11-07 and again 2026-11-13, the owner's rule: never hide content behind tabs).
*
* The explainer at the foot (New here?: the restream tour and the About banner) shows both, one after the other, and
* the OpenVibe network is its own section with every site. The content blocks are never tabbed again (the owner, 2026-10-31: "now I can't see the videos and clips on the homepage…
@@@ -138,25 +138,21 @@ check('the OpenVibe network is its own section: every site a real link, by group
assert.ok(/data-ovnet-switcher><span class="ovnet-orb" aria-hidden="true"><svg class="ovmb"[^>]*><rect class="ovmb-bar"/.test(net) && net.includes('<b>Every site is in the menu</b>') && !/one tap away in the/.test(net), 'the line about the menu carries the menu button as the bar draws it now, and says where it is');
});
--check('on a phone the rest of the home is three parts as tabs (For you, Catch up, OpenVibe), every one in the HTML', () => {
-- // The owner, 2026-11-11: "consolidate these into tabs, self-contained tabs, so the page isn't so freaking huge … at
-- // least on mobile". openvibe-shared ov-parts.css draws it (radios and :has(), no script); wide screens stack them.
-- const start = home.indexOf('<div class="ov-parts ov-parts--front home-parts" data-parts="3">');
-- assert.ok(start > home.indexOf('id="stream-grid-live"'), 'below Live Now');
-- const parts = [...home.slice(start).matchAll(/<section class="ov-part" data-part="([a-z]+)"/g)].map((m) => m[1]);
++check('the rest of the home is three parts stacked on every screen (For you, Catch up, OpenVibe), with no tabs', () => {
++ // The owner, 2026-11-13, of the phone tabs added on 11-12: "get rid of these tabs … I want … how it was back … so that
++ // the homepage has all the content and it's more SEO friendly". The same rule as 10-31 and 11-07.
++ assert.ok(!home.includes('ov-parts') && !home.includes('ovp-home') && !/type="radio"[^>]*name="ovp-/.test(home), 'no tab wrapper, radios or bar');
++ const parts = [...home.matchAll(/<section class="home-part" data-part="([a-z]+)"/g)].map((m) => m[1]);
assert.deepStrictEqual(parts, ['foryou', 'catchup', 'openvibe']);
-- for (const [id, label] of [['foryou', 'For you'], ['catchup', 'Catch up'], ['openvibe', 'OpenVibe']]) {
-- assert.ok(home.includes(`<input type="radio" class="ov-parts-r" name="ovp-home" id="ovp-home-${id}" value="${id}"`), `${id}: its radio`);
-- assert.ok(home.includes(`<label for="ovp-home-${id}">${label}</label>`), `${id}: its tab`);
-- }
const at = (needle) => home.indexOf(needle);
const sec = (id) => at(`data-part="${id}"`);
++ assert.ok(at('id="stream-grid-live"') < sec('foryou'), 'below Live Now');
assert.ok(sec('foryou') < at('id="home-around"') && at('id="home-around"') < sec('catchup'), 'For you holds Around OpenVibe');
-- assert.ok(sec('catchup') < at('id="home-catchup"') && at('id="home-catchup"') < sec('openvibe'), 'Catch up holds the rails');
++ assert.ok(sec('catchup') < at('id="home-catchup"') && at('id="home-catchup"') < sec('openvibe'), 'Catch up holds the videos, clips and pastes');
assert.ok(sec('openvibe') < at('id="home-intro"') && at('id="home-intro"') < at('id="home-network"'), 'OpenVibe holds New here? and the network');
const features = JSON.parse(fs.readFileSync(path.join(__dirname, '../public/features.json'), 'utf8'));
-- assert.ok(JSON.stringify(features).includes('/shared/ov-parts.css'), 'the home links the tabs\' stylesheet (blocking, so nothing jumps)');
-- assert.ok(/function openHomePartFor\(hash\)/.test(js) && /closest\('\.ov-part'\)/.test(js), 'a link into a closed tab opens it; a returning reader\'s New here? stays inside its tab');
++ assert.ok(!JSON.stringify(features).includes('/shared/ov-parts.css'), 'the home links no tab stylesheet');
++ assert.ok(!/openHomePartFor/.test(js), 'no code that opens a closed tab, because there are none');
});
check('the network section is built by its script, between its markers, and nothing in it is a tab', () => {