openvibers/openvibe.live Commits

Star of OpenVibe: compact by default; demote the two explainer sections after 18 visits

OP OpenVibers committed 16 Sept 2026 · 624192fa5e · 3 files, +13 −6

The Star section opened as a full-width feature panel — large avatar, kicker, name, chips, the
reason it picked them, a bio in two languages, an AI overview, a last-live note, and when they
were on air an entire stream card. That is a lot of front page for one streamer. Collapsed it is
now who they are and the button to go watch them; everything else sits behind a disclosure and
the card animates between the two heights instead of jumping.

"One stream. Everywhere." and the About panel are the pitch: they exist to tell a first-time
visitor what this place is. After 18 home page visits they move to the bottom of the page instead
of sitting between a returning viewer and the streams. Same content, still reachable, out of the
way. The count is per-browser, and a cleared cache just means someone sees the pitch again.

Both disclosures use one shared height-animation helper now rather than each rolling its own.

Also: [hidden] is a browser rule, so any author `display` declaration silently beats it — which
meant the Star disclosure never actually hid anything. Same trap the hero strip fell into
earlier today; stated explicitly in both places now.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01CMxJm1dB8rdsaMSgNjVa82

Changes

ddiff --git a/public/css/i18n-star.css b/public/css/i18n-star.css
iindex 0a56a4ca..0b520dc6 100644
---- a/public/css/i18n-star.css
++++ b/public/css/i18n-star.css
@@@ -244,7 +244,10 @@
.star-card.is-compact .star-actions { margin-top: 0; }
.star-card.is-compact .star-btn-watch { font-size: 0.86rem; padding: 8px 15px; }
--/* The disclosure. Fades up as the card grows — see animateHeightChange(). */
++/* The disclosure. Fades up as the card grows — see animateHeightChange().
++ [hidden] is a UA rule, so any author `display` here silently beats it and the panel never
++ actually hides. Stated explicitly, the same trap the hero strip fell into. */
++.star-more[hidden] { display: none !important; }
.star-more { display: block; }
.star-more.is-entering { animation: starMoreIn 0.42s cubic-bezier(.2, 1, .3, 1); }
@keyframes starMoreIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
ddiff --git a/public/index.html b/public/index.html
iindex 1b2ed5b8..635a5d34 100644
---- a/public/index.html
++++ b/public/index.html
@@@ -91,7 +91,7 @@
<link rel="stylesheet" href="/css/style.css?v=194">
<link rel="stylesheet" href="/css/home-fx.css?v=13">
<link rel="stylesheet" href="/css/home-tour.css?v=7">
-- <link rel="stylesheet" href="/css/i18n-star.css?v=6">
++ <link rel="stylesheet" href="/css/i18n-star.css?v=7">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/chat-shared.css?v=1" media="print" onload="this.media='all';this.onload=null">
<link rel="stylesheet" href="/css/broadcast.css?v=11" media="print" onload="this.media='all';this.onload=null">
@@@ -5131,7 +5131,7 @@ curl -s https://openvibe.media/live/@Goosely/chat-insight.json | jq ".chat_insig
<script src="/live-notify.js?v=3" defer></script>
<script src="/js/ov-skeleton.js?v=1" defer></script>
<script src="/js/ov-odometer.js?v=3" defer></script>
--<script src="/js/app.js?v=165" defer></script>
++<script src="/js/app.js?v=166" defer></script>
<script src="/js/stream-player.js?v=22" defer></script>
<script src="/js/stream-pip.js?v=1" defer></script>
<script src="/js/chat-shared.js?v=3" defer></script>
ddiff --git a/public/js/app.js b/public/js/app.js
iindex 76e62760..c9434d28 100644
---- a/public/js/app.js
++++ b/public/js/app.js
@@@ -2254,15 +2254,19 @@ function demoteHomeIntroSections() {
const views = _homeIntroViews(true);
if (views < HOME_INTRO_VIEWS_BEFORE_DEMOTING) return;
const container = document.querySelector('#page-home .container');
-- const anchorEl = document.getElementById('home-changelog-wrapper');
-- if (!container || !anchorEl) return;
++ if (!container) return;
++ // Land them before the changelog if it is a sibling, otherwise at the end of the container.
++ // Reading the anchor's parent rather than assuming it means a future markup change cannot
++ // silently turn this into a no-op.
++ const changelog = document.getElementById('home-changelog-wrapper');
++ const anchorEl = changelog && changelog.parentElement === container ? changelog : null;
for (const id of ['home-tour-mount', 'home-cta-banner']) {
const el = document.getElementById(id);
// Already moved (a re-render, or a second visit in the same session)? Leave it be.
if (!el || el.dataset.ovDemoted) continue;
el.dataset.ovDemoted = '1';
el.classList.add('home-intro-demoted');
-- try { container.insertBefore(el, anchorEl); } catch { /* */ }
++ try { if (anchorEl) container.insertBefore(el, anchorEl); else container.appendChild(el); } catch { /* */ }
}
}