openvibers/openvibe.shared Commits

Shared 2.15.0: the navbar fades into the page at the top and sticks again; hero pages can run under it. (#36)

OP OpenVibers committed 8 Oct 2026 · fe83f2d1be · 5 files, +49 −4

Claude-Session: https://claude.ai/code/session_01K8AYn3FF3HE7CPfM262qdh

Co-authored-by: Claude Opus 5.5 <[email protected]>

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 457279d..c1599b4 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,16 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
++## 2.15.0 — 2026-10-08
++
++- The navbar fades into the page at the very top (no background or border) and brings its frosted bar back with a
++ smooth transition as you scroll; it keeps that state itself (`.ovnav-top`) on every render and scroll, so a page no
++ longer has to. A page whose hero runs under the bar sets `body.ov-nav-overlay`: the bar then sits over the hero with a
++ soft scrim for its text (a fixed bar, like Live's, needs no pull-up; `--ovnav-real-h` is 0 for it).
++- The navbar sticks again on sites that mount it in `#navbar-mount`: the wrapper was exactly the bar's height, so the bar
++ had no room to stick and scrolled away. The wrapper is sticky now.
++- navbar.js brotli budget 29 → 30 KB (29.4 KB with the fade).
++
## 2.14.1 — 2026-10-08
- shipped.js names every site (Space, Services, Actor, Run, Watch, Download and the rest): the update log showed
ddiff --git a/navbar.js b/navbar.js
iindex e88b821..7624f44 100644
---- a/navbar.js
++++ b/navbar.js
@@@ -27,6 +27,19 @@
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--text-primary, #e0e0e0);
}
++ /* At the very top the bar fades into the page (no background, no border); a scroll brings the frosted bar back.
++ A page whose hero runs under the bar opts in with body.ov-nav-overlay: the bar then sits over the hero, with a
++ soft scrim for the text. ovnav-top is kept by the navbar itself on every render and scroll. */
++ .openvibe-navbar { transition: background-color .45s cubic-bezier(.2,.7,.2,1), border-color .45s cubic-bezier(.2,.7,.2,1), box-shadow .45s cubic-bezier(.2,.7,.2,1), -webkit-backdrop-filter .45s, backdrop-filter .45s; }
++ .openvibe-navbar::before { content: ""; position: absolute; inset: 0 0 -28px; pointer-events: none; z-index: -1; opacity: 0; transition: opacity .45s cubic-bezier(.2,.7,.2,1); background: linear-gradient(180deg, rgba(4,7,14,.62), rgba(4,7,14,.28) 55%, rgba(4,7,14,0)); }
++ .openvibe-navbar:not(.ovnav-top) { background-color: color-mix(in srgb, var(--bg-secondary, #252530) 84%, transparent); -webkit-backdrop-filter: saturate(1.5) blur(16px); backdrop-filter: saturate(1.5) blur(16px); box-shadow: 0 12px 32px -14px rgba(0,0,0,.6); }
++ .openvibe-navbar.ovnav-top { background-color: transparent; border-bottom-color: transparent; box-shadow: none; -webkit-backdrop-filter: none; backdrop-filter: none; }
++ /* The bar is sticky, but a #navbar-mount wrapper exactly its height gave it no room to stick in: the wrapper sticks. */
++ #navbar-mount { position: sticky; top: 0; z-index: 10000; }
++ body.ov-nav-overlay .openvibe-navbar.ovnav-top::before { opacity: 1; }
++ body.ov-nav-overlay > #navbar-mount, body.ov-nav-overlay > .openvibe-navbar { margin-bottom: calc(-1 * var(--ovnav-real-h, 52px)); }
++ body.ov-panel-open .openvibe-navbar.ovnav-top { background-color: var(--bg-secondary, #252530); border-bottom-color: var(--border, #333340); }
++ @media (prefers-reduced-motion: reduce) { .openvibe-navbar, .openvibe-navbar::before { transition: none; } }
.openvibe-navbar-brand { display: flex; align-items: center; gap: 9px; text-decoration: none; color: inherit; margin-right: 8px; min-width: 0; }
/* Brand group: mark + linked wordmark + launcher read as one control. Every property a host
page might set on bare "nav a" / "a" is reset here, so the brand looks the same everywhere. */
@@@ -1489,9 +1502,31 @@
}
_navEl = nav;
attachAvatarFallbacks(nav);
++ hookTop();
return nav;
}
++ // The bar's at-the-top state (ovnav-top, see injectStyles), on every render and scroll. One window listener pair for
++ // the page's life, a frame at most per update; --ovnav-real-h is the bar's height for overlay pages.
++ let _topHooked = false;
++ function syncTop() {
++ if (!_navEl) return;
++ _navEl.classList.toggle('ovnav-top', (root.scrollY || (document.documentElement && document.documentElement.scrollTop) || 0) <= 4);
++ // A fixed bar (a site that positions it itself, like Live) takes no room, so an overlay page needs no pull-up.
++ const fixed = root.getComputedStyle && root.getComputedStyle(_navEl).position === 'fixed';
++ const de = document.documentElement;
++ if (de && de.style) de.style.setProperty('--ovnav-real-h', `${fixed ? 0 : (_navEl.offsetHeight || 52)}px`);
++ }
++ function hookTop() {
++ syncTop();
++ if (_topHooked || typeof root.addEventListener !== 'function') return;
++ _topHooked = true;
++ let raf = 0;
++ const later = () => { if (!raf) raf = (root.requestAnimationFrame || setTimeout)(() => { raf = 0; syncTop(); }); };
++ root.addEventListener('scroll', later, { passive: true });
++ root.addEventListener('resize', later, { passive: true });
++ }
++
// openvibe-shared/boost swapped the page in place: this is a new page for the history and the
// active-link state, and a login from here returns here.
let _boostHooked = false;
ddiff --git a/package-lock.json b/package-lock.json
iindex e0f7272..2fe2107 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -1,12 +1,12 @@
{
"name": "openvibe-shared",
-- "version": "2.14.1",
++ "version": "2.15.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvibe-shared",
-- "version": "2.14.1",
++ "version": "2.15.0",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex 21905ba..405f955 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "2.14.1",
++ "version": "2.15.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/test/nav-icons.test.js b/test/nav-icons.test.js
iindex 84fca94..3a4d3f5 100644
---- a/test/nav-icons.test.js
++++ b/test/nav-icons.test.js
@@@ -13,7 +13,7 @@ const { brotli } = require('../scripts/size-report');
const ROOT = path.join(__dirname, '..');
const NAVBAR_PATH = path.join(ROOT, 'navbar.js');
const ICONS = require('../nav-icons');
--const NAVBAR_BROTLI_BUDGET = 29 * 1024; // measured 27.6 KB on 2026-09-22 (39.0 KB with every glyph inline)
++const NAVBAR_BROTLI_BUDGET = 30 * 1024; // measured 27.6 KB on 2026-09-22 (39.0 KB with every glyph inline); 29.4 KB with the top-of-page fade (2.15.0)
// ── size budget and generated block ──────────────────────────
{