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)
Changes
@@@ -4,6 +4,16 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking 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@@@ -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 hostpage 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;@@@ -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"@@@ -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": {@@@ -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 ──────────────────────────{
@@ -4,6 +4,16 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -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.
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).
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
## 2.14.1 — 2026-10-08
- shipped.js names every site (Space, Services, Actor, Run, Watch, Download and the rest): the update log showed
- shipped.js names every site (Space, Services, Actor, Run, Watch, Download and the rest): the update log showed
@@ -27,6 +27,19 @@
@@ -27,6 +27,19 @@
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--text-primary, #e0e0e0);
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; }
.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
/* 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. */
page might set on bare "nav a" / "a" is reset here, so the brand looks the same everywhere. */
@@ -1489,9 +1502,31 @@
@@ -1489,9 +1502,31 @@
}
}
_navEl = nav;
_navEl = nav;
attachAvatarFallbacks(nav);
attachAvatarFallbacks(nav);
+ hookTop();
return nav;
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
// 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.
// active-link state, and a login from here returns here.
let _boostHooked = false;
let _boostHooked = false;
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "2.14.1",
+ "version": "2.15.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "2.14.1",
+ "version": "2.15.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"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",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -13,7 +13,7 @@ const { brotli } = require('../scripts/size-report');
@@ -13,7 +13,7 @@ const { brotli } = require('../scripts/size-report');
const ROOT = path.join(__dirname, '..');
const ROOT = path.join(__dirname, '..');
const NAVBAR_PATH = path.join(ROOT, 'navbar.js');
const NAVBAR_PATH = path.join(ROOT, 'navbar.js');
const ICONS = require('../nav-icons');
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 ──────────────────────────
// ── size budget and generated block ──────────────────────────
{
{