openvibers/openvibe.sites Commits
openvibe-shared v2.15.0 (was v2.13.2); notices rebuilt (#25)
Changes
@@@ -38,5 +38,5 @@ Report security issues through [SECURITY.md](SECURITY.md).<!-- versions:start -->- openvibe-contracts: v0.117.0--- openvibe-shared: v2.13.2++- openvibe-shared: v2.15.0<!-- versions:end -->@@@ -26,7 +26,7 @@"status.openvibe.network (status notice)"],"contracts": "openvibe-contracts v0.117.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",-- "shared": "openvibe-shared v2.13.2",++ "shared": "openvibe-shared v2.15.0","deployed": true,"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file","features": [@@@ -8,17 +8,7 @@onLogin: null, onLogout: null, loginUrl: null, sessionUrl: null, logoutUrl: null,brand: null, brandName: null, brandIcon: null, compact: 'auto',links: null, menu: null, recent: true,-- // history: { type: 'tool'|'stream'|'paste'|'page'|…, title, url, icon } — recorded for the-- // signed-in user once auth resolves (cross-site "Recently used" / History on the Network).-- history: null,-- // silentLogin: 'https://site/auth/login?silent=1&next={url}' — when nobody is signed in here-- // but this browser has signed in to the network before (ov_sso_hint), try one silent-- // prompt=none round trip per tab so a session on one site becomes a session on all.-- silentLogin: null,-- // fedcm: false to opt out; 'optional' (default) shows the browser's native chip the first-- // time and re-authenticates silently afterwards; 'silent' only re-authenticates.-- fedcm: 'optional',-- fedcmLogin: null, // POST target for the assertion (default: this site's /auth/fedcm)++ history: null, silentLogin: null, fedcm: 'optional', fedcmLogin: null, // README.md, "The navbar"};const _runtimeMenu = { before: [], after: [] };let _runtimeLinks = null;@@@ -37,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. */@@@ -592,6 +595,10 @@} catch { return null; }}++ // A render's document/window listeners and panels; the next render (each boost page move) aborts them, so the old bar can go.++ let _renderAbort = null;++ const onDocument = () => (_renderAbort ? { signal: _renderAbort.signal } : undefined);++function bindLauncher(nav) {const btn = nav.querySelector('#openvibe-launcher-btn'); if (!btn) return;let panel = null;@@@ -646,7 +653,7 @@ev.preventDefault(); const n = i + (ev.key === 'ArrowDown' ? 1 : -1); (links[n] || (n < 0 ? input : links[0])).focus();});panel.addEventListener('click', (ev) => ev.stopPropagation());-- document.addEventListener('click', close);++ document.addEventListener('click', close, onDocument());launcherCatalog().then((c) => { if (c) { cat = c; paint(cat, input.value.trim().toLowerCase()); } });}panel.classList.add('open'); btn.setAttribute('aria-expanded', 'true');@@@ -697,7 +704,7 @@}closeDrawer();});-- document.addEventListener('click', (e) => { if (!e.target.closest || !e.target.closest('.ovnav-dd')) nav.querySelectorAll('.ovnav-dd.open').forEach(x => x.classList.remove('open')); });++ document.addEventListener('click', (e) => { if (!e.target.closest || !e.target.closest('.ovnav-dd')) nav.querySelectorAll('.ovnav-dd.open').forEach(x => x.classList.remove('open')); }, onDocument());// Dropdowns hang below the links row, which scrolls sideways and would clip them: fixed, under the link (less a filtered bar's offset).const placeDd = (dd) => {const m = dd && dd.querySelector(':scope>.ovnav-dd-menu'); if (!m) return;@@@ -710,7 +717,7 @@nav.addEventListener('pointerover', place); nav.addEventListener('focusin', place);if (burger && drawer) {burger.addEventListener('click', (e) => { e.stopPropagation(); const open = drawer.classList.toggle('open'); burger.setAttribute('aria-expanded', String(open)); });-- document.addEventListener('click', (e) => { if (drawer.classList.contains('open') && !drawer.contains(e.target) && !burger.contains(e.target)) closeDrawer(); });++ document.addEventListener('click', (e) => { if (drawer.classList.contains('open') && !drawer.contains(e.target) && !burger.contains(e.target)) closeDrawer(); }, onDocument());regPanel(drawer, 'nav-drawer', closeDrawer);}}@@@ -720,7 +727,7 @@const _panelQueue = [];function regPanel(el, id, close) {if (!el) return;-- const opts = { el, openClass: 'open', id, close };++ const opts = Object.assign({ el, openClass: 'open', id, close }, onDocument());if (root.OpenVibePanels) return root.OpenVibePanels.register(opts);_panelQueue.push(opts);if (document.getElementById('ov-panels-loader')) return;@@@ -745,7 +752,7 @@const LABEL = { text: { 100: 'Default', 112: 'Large', 125: 'Largest' }, motion: { auto: 'On', reduced: 'Calm' } };const paint = () => { const d = L.display.get(); rows.forEach(r => { const k = r.getAttribute('data-ov-display'); r.querySelector('.ud-val').textContent = LABEL[k][d[k]] || ''; }); };rows.forEach(r => r.addEventListener('click', (e) => { e.stopPropagation(); const k = r.getAttribute('data-ov-display'), o = L.display.options[k], cur = L.display.get()[k]; L.display.set({ [k]: o[(o.indexOf(cur) + 1) % o.length] }); paint(); }));-- root.addEventListener('ov:display', paint); paint();++ root.addEventListener('ov:display', paint, onDocument()); paint();}/** OpenCoins balance in the menu header (one request when the menu first opens). */@@@ -784,7 +791,7 @@box.hidden = false;};box.addEventListener('click', (e) => { const b = e.target.closest('button[data-k]'); if (!b) return; L.display.set({ [b.dataset.k]: b.dataset.v }); paint(); });-- root.addEventListener('ov:display', paint);++ root.addEventListener('ov:display', paint, onDocument());paint();}@@@ -1319,6 +1326,8 @@// A site may put state classes on the bar (Live's transparent hero mode). A re-render must not lose them.let carried = []; try { carried = Array.from((_navEl && _navEl.classList) || []).filter(c => c !== 'openvibe-navbar'); } catch { carried = []; }if (_navEl) _navEl.remove();++ if (_renderAbort) { _renderAbort.abort(); _panelQueue.length = 0; }++ _renderAbort = typeof AbortController === 'function' ? new AbortController() : null;const nav = document.createElement('nav');nav.className = 'openvibe-navbar';@@@ -1335,8 +1344,7 @@const loginHref = resolveLoginHref(currentHost(), window.location.href);const addAccountHref = `${_config.apiBase}/login?add_account=1&return=${encodeURIComponent(window.location.href)}`;-- // The OV brand mark is a self-contained drop-in (mounts every .ov-mark it finds). A page may already carry its own-- // async <script src=".../ov-mark.js?v=…"> that has not run yet: never load a second copy.++ // The OV brand mark is a drop-in (mounts every .ov-mark); never a second copy of a page's own tag.if (!window.__ovMark && !document.getElementById('ov-mark-loader') && !(document.querySelector && document.querySelector('script[src*="/ov-mark.js"]'))) {try { const sc = document.createElement('script'); sc.id = 'ov-mark-loader'; sc.src = sibling('ov-mark.js'); sc.async = true; document.head.appendChild(sc); } catch { /* */ }}@@@ -1444,7 +1452,7 @@// Close on outside clickdocument.addEventListener('click', e => {if (!nav.contains(e.target)) dropdown.classList.remove('open');-- });++ }, onDocument());// Account switchingdropdown.querySelectorAll('[data-account-id]').forEach(el => {@@@ -1494,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;@@@ -8,18 +8,22 @@* • <body class="ov-panel-open"> while any panel is open (floating buttons step aside)* • a freshly opened panel starts scrolled to the top*-- * OpenVibePanels.register({ el, openClass: 'show', close: () => …, id?: 'user-menu' })-- * OpenVibePanels.closeAll(exceptEl?) · OpenVibePanels.anyOpen()++ * OpenVibePanels.register({ el, openClass: 'show', close: () => …, id?: 'user-menu', signal? }) → unregister()++ * OpenVibePanels.unregister(el) · OpenVibePanels.closeAll(exceptEl?) · OpenVibePanels.anyOpen()** It watches the panel's class instead of wrapping the site's click handlers, so it cannot fight them: a* panel is "open" exactly when its element has `openClass`, however that came about.++ *++ * A registration holds its element until it is unregistered: markup that is thrown away and rebuilt (the navbar++ * re-renders on every openvibe-shared/boost page move) passes an AbortSignal, or calls unregister, so the old++ * element can be collected.*/(function (root) {'use strict';if (root.OpenVibePanels) return;-- if (typeof document === 'undefined') { const stub = { register() {}, closeAll() {}, anyOpen: () => false }; if (typeof module !== 'undefined') module.exports = stub; return; }++ if (typeof document === 'undefined') { const stub = { register: () => () => {}, unregister() {}, closeAll() {}, anyOpen: () => false }; if (typeof module !== 'undefined') module.exports = stub; return; }-- const panels = []; // { el, openClass, close, id }++ const panels = []; // { el, openClass, close, id, unregister }const isOpen = (p) => p.el.isConnected && p.el.classList.contains(p.openClass);const anyOpen = () => panels.some(isOpen);const syncBody = () => document.body && document.body.classList.toggle('ov-panel-open', anyOpen());@@@ -32,22 +36,35 @@}function register(opts) {-- const el = opts && opts.el; if (!el || panels.some(p => p.el === el)) return;++ const el = opts && opts.el, signal = opts && opts.signal;++ if (!el || (signal && signal.aborted)) return () => {};++ const known = panels.find(p => p.el === el); if (known) return known.unregister;const p = { el, openClass: opts.openClass || 'open', id: opts.id || '', close: typeof opts.close === 'function' ? opts.close : () => el.classList.remove(opts.openClass || 'open') };-- panels.push(p);let was = isOpen(p);-- new MutationObserver(() => {++ const watch = new MutationObserver(() => {const now = isOpen(p); if (now === was) return; was = now;if (now) { closeAll(el); try { el.scrollTop = 0; } catch { /* */ } }syncBody();-- }).observe(el, { attributes: true, attributeFilter: ['class'] });++ });++ p.unregister = () => {++ const i = panels.indexOf(p); if (i < 0) return;++ panels.splice(i, 1);++ watch.disconnect();++ syncBody();++ };++ panels.push(p);++ watch.observe(el, { attributes: true, attributeFilter: ['class'] });++ if (signal && typeof signal.addEventListener === 'function') signal.addEventListener('abort', p.unregister, { once: true });syncBody();++ return p.unregister;}++ function unregister(el) { const p = panels.find(x => x.el === el); if (p) p.unregister(); }++document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && anyOpen()) closeAll(); });root.addEventListener('orientationchange', () => closeAll(), { passive: true });-- const api = { register, closeAll, anyOpen };++ const api = { register, unregister, closeAll, anyOpen };root.OpenVibePanels = api;if (typeof module !== 'undefined' && module.exports) module.exports = api;})(typeof window !== 'undefined' ? window : globalThis);@@@ -34,7 +34,18 @@}(typeof globalThis !== 'undefined' ? globalThis : this, function () {'use strict';const API = 'https://openvibe.network/api/v1/changelog';-- const SITE_NAMES = { live: 'Live', network: 'Network', tools: 'Tools', media: 'Media', community: 'Community', chat: 'Chat', games: 'Games', blog: 'Blog', wiki: 'Wiki', news: 'News', reviews: 'Reviews', deals: 'Deals', coupons: 'Coupons', trade: 'Trade', codes: 'Codes', host: 'Host', ai: 'AI', search: 'Search', sources: 'Sources', events: 'Events', billing: 'Billing', tips: 'Tips', vip: 'VIP', openre: 'OpenRe', bot: 'Bot', sites: 'Sites', realtime: 'Realtime' };++ // Every OpenVibe service's display name (openvibe-contracts site.name). A service missing here still reads as a name++ // (siteName capitalises its id), never as a lowercase id next to capitalised ones.++ const SITE_NAMES = {++ live: 'Live', network: 'Network', tools: 'Tools', media: 'Media', community: 'Community', chat: 'Chat', games: 'Games',++ blog: 'Blog', wiki: 'Wiki', news: 'News', reviews: 'Reviews', deals: 'Deals', coupons: 'Coupons', trade: 'Trade',++ codes: 'Codes', host: 'Host', ai: 'AI', search: 'Search', sources: 'Sources', events: 'Events', billing: 'Billing',++ tips: 'Tips', vip: 'VIP', openre: 'OpenRe', bot: 'Bot', sites: 'Sites', realtime: 'Realtime', space: 'Space',++ services: 'Services', actor: 'Actor', run: 'Run', watch: 'Watch', node: 'Node', zone: 'Zone', download: 'Download',++ video: 'Video', pics: 'Pics', rent: 'Rent', homes: 'Homes', work: 'Work', food: 'Food', help: 'Help', quest: 'Quest',++ website: 'Website', fund: 'Fund', 'media-hub': 'Media Hub', extensions: 'Extensions', contracts: 'Contracts', sdk: 'SDK',++ shared: 'Shared', examples: 'Examples', publishing: 'Publishing',++ };const STYLE_ID = 'ov-shipped-style';const TEXT = 'var(--text-primary,var(--text,inherit))';const MUTED = 'var(--text-secondary,var(--text-muted,#8b93ad))';@@@ -98,7 +109,7 @@function safeUrl(u) { return /^https:\/\//.test(String(u || '')) ? String(u) : null; }function localHref(u) { const s = String(u || ''); return /^\/(?!\/)/.test(s) || /^https:\/\//.test(s) ? s : null; }function clamp(n, lo, hi, d) { const v = parseInt(n, 10); return Number.isFinite(v) ? Math.min(Math.max(v, lo), hi) : d; }-- const siteName = (id) => SITE_NAMES[id] || id;++ const siteName = (id) => SITE_NAMES[id] || String(id || '').replace(/(^|-)([a-z])/g, (m, d, c) => `${d ? ' ' : ''}${c.toUpperCase()}`);function timeEl(iso) { const t = el('time', { datetime: iso, 'data-ov-ago': '' }, ago(iso)); t.title = new Date(iso).toLocaleString(); return t; }function rocket() {const ns = 'http://www.w3.org/2000/svg';@@@ -371,5 +382,5 @@return out;}-- return { latest, list, mount: list, log, scan, render, renderLatest, appendDays, serviceFor, ago, API, SITE_NAMES };++ return { latest, list, mount: list, log, scan, render, renderLatest, appendDays, serviceFor, ago, API, SITE_NAMES, siteName };}));@@@ -5,7 +5,7 @@"booted_at": null,"contracts_version": null,"packages": {-- "openvibe-shared": "2.13.2"++ "openvibe-shared": "2.15.0"},"min_client_release": null,"mixed_version_window_hours": 24,@@@ -5,7 +5,7 @@"booted_at": null,"contracts_version": null,"packages": {-- "openvibe-shared": "2.13.2"++ "openvibe-shared": "2.15.0"},"min_client_release": null,"mixed_version_window_hours": 24,@@@ -7,7 +7,7 @@"name": "openvibe-sites","dependencies": {"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.2"++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0"},"engines": {"node": ">=20"@@@ -197,9 +197,9 @@}},"node_modules/openvibe-shared": {-- "version": "2.13.2",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.2",-- "integrity": "sha512-rvLq6uKd0j84qxZXwGX+gE6+e8SJaP8bBilWu9Eosjgfc70Z4bPJOUJUiLNRte7fU5hW/ea97/JoCLdiwXmPQA==",++ "version": "2.15.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0",++ "integrity": "sha512-xApTyiUBVDQLTdnKEjjr/9uUU4+T5gofz9DqXv0MrLVINmW88uwbfoEht3kAlG9mp1792U6kIFEblJDHn2wyMQ==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0"@@@ -8,7 +8,7 @@},"dependencies": {"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.2"++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0"},"engines": {"node": ">=20"
@@ -38,5 +38,5 @@ Report security issues through [SECURITY.md](SECURITY.md).
@@ -38,5 +38,5 @@ Report security issues through [SECURITY.md](SECURITY.md).
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.117.0
- openvibe-contracts: v0.117.0
-- openvibe-shared: v2.13.2
+- openvibe-shared: v2.15.0
<!-- versions:end -->
<!-- versions:end -->
@@ -26,7 +26,7 @@
@@ -26,7 +26,7 @@
"status.openvibe.network (status notice)"
"status.openvibe.network (status notice)"
],
],
"contracts": "openvibe-contracts v0.117.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",
"contracts": "openvibe-contracts v0.117.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",
- "shared": "openvibe-shared v2.13.2",
+ "shared": "openvibe-shared v2.15.0",
"deployed": true,
"deployed": true,
"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file",
"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file",
"features": [
"features": [
@@ -8,17 +8,7 @@
@@ -8,17 +8,7 @@
onLogin: null, onLogout: null, loginUrl: null, sessionUrl: null, logoutUrl: null,
onLogin: null, onLogout: null, loginUrl: null, sessionUrl: null, logoutUrl: null,
brand: null, brandName: null, brandIcon: null, compact: 'auto',
brand: null, brandName: null, brandIcon: null, compact: 'auto',
links: null, menu: null, recent: true,
links: null, menu: null, recent: true,
- // history: { type: 'tool'|'stream'|'paste'|'page'|…, title, url, icon } — recorded for the
- // signed-in user once auth resolves (cross-site "Recently used" / History on the Network).
- history: null,
- // silentLogin: 'https://site/auth/login?silent=1&next={url}' — when nobody is signed in here
- // but this browser has signed in to the network before (ov_sso_hint), try one silent
- // prompt=none round trip per tab so a session on one site becomes a session on all.
- silentLogin: null,
- // fedcm: false to opt out; 'optional' (default) shows the browser's native chip the first
- // time and re-authenticates silently afterwards; 'silent' only re-authenticates.
- fedcm: 'optional',
- fedcmLogin: null, // POST target for the assertion (default: this site's /auth/fedcm)
+ history: null, silentLogin: null, fedcm: 'optional', fedcmLogin: null, // README.md, "The navbar"
};
};
const _runtimeMenu = { before: [], after: [] };
const _runtimeMenu = { before: [], after: [] };
let _runtimeLinks = null;
let _runtimeLinks = null;
@@ -37,6 +27,19 @@
@@ -37,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. */
@@ -592,6 +595,10 @@
@@ -592,6 +595,10 @@
} catch { return null; }
} catch { return null; }
}
}
+ // A render's document/window listeners and panels; the next render (each boost page move) aborts them, so the old bar can go.
+ let _renderAbort = null;
+ const onDocument = () => (_renderAbort ? { signal: _renderAbort.signal } : undefined);
+
function bindLauncher(nav) {
function bindLauncher(nav) {
const btn = nav.querySelector('#openvibe-launcher-btn'); if (!btn) return;
const btn = nav.querySelector('#openvibe-launcher-btn'); if (!btn) return;
let panel = null;
let panel = null;
@@ -646,7 +653,7 @@
@@ -646,7 +653,7 @@
ev.preventDefault(); const n = i + (ev.key === 'ArrowDown' ? 1 : -1); (links[n] || (n < 0 ? input : links[0])).focus();
ev.preventDefault(); const n = i + (ev.key === 'ArrowDown' ? 1 : -1); (links[n] || (n < 0 ? input : links[0])).focus();
});
});
panel.addEventListener('click', (ev) => ev.stopPropagation());
panel.addEventListener('click', (ev) => ev.stopPropagation());
- document.addEventListener('click', close);
+ document.addEventListener('click', close, onDocument());
launcherCatalog().then((c) => { if (c) { cat = c; paint(cat, input.value.trim().toLowerCase()); } });
launcherCatalog().then((c) => { if (c) { cat = c; paint(cat, input.value.trim().toLowerCase()); } });
}
}
panel.classList.add('open'); btn.setAttribute('aria-expanded', 'true');
panel.classList.add('open'); btn.setAttribute('aria-expanded', 'true');
@@ -697,7 +704,7 @@
@@ -697,7 +704,7 @@
}
}
closeDrawer();
closeDrawer();
});
});
- document.addEventListener('click', (e) => { if (!e.target.closest || !e.target.closest('.ovnav-dd')) nav.querySelectorAll('.ovnav-dd.open').forEach(x => x.classList.remove('open')); });
+ document.addEventListener('click', (e) => { if (!e.target.closest || !e.target.closest('.ovnav-dd')) nav.querySelectorAll('.ovnav-dd.open').forEach(x => x.classList.remove('open')); }, onDocument());
// Dropdowns hang below the links row, which scrolls sideways and would clip them: fixed, under the link (less a filtered bar's offset).
// Dropdowns hang below the links row, which scrolls sideways and would clip them: fixed, under the link (less a filtered bar's offset).
const placeDd = (dd) => {
const placeDd = (dd) => {
const m = dd && dd.querySelector(':scope>.ovnav-dd-menu'); if (!m) return;
const m = dd && dd.querySelector(':scope>.ovnav-dd-menu'); if (!m) return;
@@ -710,7 +717,7 @@
@@ -710,7 +717,7 @@
nav.addEventListener('pointerover', place); nav.addEventListener('focusin', place);
nav.addEventListener('pointerover', place); nav.addEventListener('focusin', place);
if (burger && drawer) {
if (burger && drawer) {
burger.addEventListener('click', (e) => { e.stopPropagation(); const open = drawer.classList.toggle('open'); burger.setAttribute('aria-expanded', String(open)); });
burger.addEventListener('click', (e) => { e.stopPropagation(); const open = drawer.classList.toggle('open'); burger.setAttribute('aria-expanded', String(open)); });
- document.addEventListener('click', (e) => { if (drawer.classList.contains('open') && !drawer.contains(e.target) && !burger.contains(e.target)) closeDrawer(); });
+ document.addEventListener('click', (e) => { if (drawer.classList.contains('open') && !drawer.contains(e.target) && !burger.contains(e.target)) closeDrawer(); }, onDocument());
regPanel(drawer, 'nav-drawer', closeDrawer);
regPanel(drawer, 'nav-drawer', closeDrawer);
}
}
}
}
@@ -720,7 +727,7 @@
@@ -720,7 +727,7 @@
const _panelQueue = [];
const _panelQueue = [];
function regPanel(el, id, close) {
function regPanel(el, id, close) {
if (!el) return;
if (!el) return;
- const opts = { el, openClass: 'open', id, close };
+ const opts = Object.assign({ el, openClass: 'open', id, close }, onDocument());
if (root.OpenVibePanels) return root.OpenVibePanels.register(opts);
if (root.OpenVibePanels) return root.OpenVibePanels.register(opts);
_panelQueue.push(opts);
_panelQueue.push(opts);
if (document.getElementById('ov-panels-loader')) return;
if (document.getElementById('ov-panels-loader')) return;
@@ -745,7 +752,7 @@
@@ -745,7 +752,7 @@
const LABEL = { text: { 100: 'Default', 112: 'Large', 125: 'Largest' }, motion: { auto: 'On', reduced: 'Calm' } };
const LABEL = { text: { 100: 'Default', 112: 'Large', 125: 'Largest' }, motion: { auto: 'On', reduced: 'Calm' } };
const paint = () => { const d = L.display.get(); rows.forEach(r => { const k = r.getAttribute('data-ov-display'); r.querySelector('.ud-val').textContent = LABEL[k][d[k]] || ''; }); };
const paint = () => { const d = L.display.get(); rows.forEach(r => { const k = r.getAttribute('data-ov-display'); r.querySelector('.ud-val').textContent = LABEL[k][d[k]] || ''; }); };
rows.forEach(r => r.addEventListener('click', (e) => { e.stopPropagation(); const k = r.getAttribute('data-ov-display'), o = L.display.options[k], cur = L.display.get()[k]; L.display.set({ [k]: o[(o.indexOf(cur) + 1) % o.length] }); paint(); }));
rows.forEach(r => r.addEventListener('click', (e) => { e.stopPropagation(); const k = r.getAttribute('data-ov-display'), o = L.display.options[k], cur = L.display.get()[k]; L.display.set({ [k]: o[(o.indexOf(cur) + 1) % o.length] }); paint(); }));
- root.addEventListener('ov:display', paint); paint();
+ root.addEventListener('ov:display', paint, onDocument()); paint();
}
}
/** OpenCoins balance in the menu header (one request when the menu first opens). */
/** OpenCoins balance in the menu header (one request when the menu first opens). */
@@ -784,7 +791,7 @@
@@ -784,7 +791,7 @@
box.hidden = false;
box.hidden = false;
};
};
box.addEventListener('click', (e) => { const b = e.target.closest('button[data-k]'); if (!b) return; L.display.set({ [b.dataset.k]: b.dataset.v }); paint(); });
box.addEventListener('click', (e) => { const b = e.target.closest('button[data-k]'); if (!b) return; L.display.set({ [b.dataset.k]: b.dataset.v }); paint(); });
- root.addEventListener('ov:display', paint);
+ root.addEventListener('ov:display', paint, onDocument());
paint();
paint();
}
}
@@ -1319,6 +1326,8 @@
@@ -1319,6 +1326,8 @@
// A site may put state classes on the bar (Live's transparent hero mode). A re-render must not lose them.
// A site may put state classes on the bar (Live's transparent hero mode). A re-render must not lose them.
let carried = []; try { carried = Array.from((_navEl && _navEl.classList) || []).filter(c => c !== 'openvibe-navbar'); } catch { carried = []; }
let carried = []; try { carried = Array.from((_navEl && _navEl.classList) || []).filter(c => c !== 'openvibe-navbar'); } catch { carried = []; }
if (_navEl) _navEl.remove();
if (_navEl) _navEl.remove();
+ if (_renderAbort) { _renderAbort.abort(); _panelQueue.length = 0; }
+ _renderAbort = typeof AbortController === 'function' ? new AbortController() : null;
const nav = document.createElement('nav');
const nav = document.createElement('nav');
nav.className = 'openvibe-navbar';
nav.className = 'openvibe-navbar';
@@ -1335,8 +1344,7 @@
@@ -1335,8 +1344,7 @@
const loginHref = resolveLoginHref(currentHost(), window.location.href);
const loginHref = resolveLoginHref(currentHost(), window.location.href);
const addAccountHref = `${_config.apiBase}/login?add_account=1&return=${encodeURIComponent(window.location.href)}`;
const addAccountHref = `${_config.apiBase}/login?add_account=1&return=${encodeURIComponent(window.location.href)}`;
- // The OV brand mark is a self-contained drop-in (mounts every .ov-mark it finds). A page may already carry its own
- // async <script src=".../ov-mark.js?v=…"> that has not run yet: never load a second copy.
+ // The OV brand mark is a drop-in (mounts every .ov-mark); never a second copy of a page's own tag.
if (!window.__ovMark && !document.getElementById('ov-mark-loader') && !(document.querySelector && document.querySelector('script[src*="/ov-mark.js"]'))) {
if (!window.__ovMark && !document.getElementById('ov-mark-loader') && !(document.querySelector && document.querySelector('script[src*="/ov-mark.js"]'))) {
try { const sc = document.createElement('script'); sc.id = 'ov-mark-loader'; sc.src = sibling('ov-mark.js'); sc.async = true; document.head.appendChild(sc); } catch { /* */ }
try { const sc = document.createElement('script'); sc.id = 'ov-mark-loader'; sc.src = sibling('ov-mark.js'); sc.async = true; document.head.appendChild(sc); } catch { /* */ }
}
}
@@ -1444,7 +1452,7 @@
@@ -1444,7 +1452,7 @@
// Close on outside click
// Close on outside click
document.addEventListener('click', e => {
document.addEventListener('click', e => {
if (!nav.contains(e.target)) dropdown.classList.remove('open');
if (!nav.contains(e.target)) dropdown.classList.remove('open');
- });
+ }, onDocument());
// Account switching
// Account switching
dropdown.querySelectorAll('[data-account-id]').forEach(el => {
dropdown.querySelectorAll('[data-account-id]').forEach(el => {
@@ -1494,9 +1502,31 @@
@@ -1494,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;
@@ -8,18 +8,22 @@
@@ -8,18 +8,22 @@
* • <body class="ov-panel-open"> while any panel is open (floating buttons step aside)
* • <body class="ov-panel-open"> while any panel is open (floating buttons step aside)
* • a freshly opened panel starts scrolled to the top
* • a freshly opened panel starts scrolled to the top
*
*
- * OpenVibePanels.register({ el, openClass: 'show', close: () => …, id?: 'user-menu' })
- * OpenVibePanels.closeAll(exceptEl?) · OpenVibePanels.anyOpen()
+ * OpenVibePanels.register({ el, openClass: 'show', close: () => …, id?: 'user-menu', signal? }) → unregister()
+ * OpenVibePanels.unregister(el) · OpenVibePanels.closeAll(exceptEl?) · OpenVibePanels.anyOpen()
*
*
* It watches the panel's class instead of wrapping the site's click handlers, so it cannot fight them: a
* It watches the panel's class instead of wrapping the site's click handlers, so it cannot fight them: a
* panel is "open" exactly when its element has `openClass`, however that came about.
* panel is "open" exactly when its element has `openClass`, however that came about.
+ *
+ * A registration holds its element until it is unregistered: markup that is thrown away and rebuilt (the navbar
+ * re-renders on every openvibe-shared/boost page move) passes an AbortSignal, or calls unregister, so the old
+ * element can be collected.
*/
*/
(function (root) {
(function (root) {
'use strict';
'use strict';
if (root.OpenVibePanels) return;
if (root.OpenVibePanels) return;
- if (typeof document === 'undefined') { const stub = { register() {}, closeAll() {}, anyOpen: () => false }; if (typeof module !== 'undefined') module.exports = stub; return; }
+ if (typeof document === 'undefined') { const stub = { register: () => () => {}, unregister() {}, closeAll() {}, anyOpen: () => false }; if (typeof module !== 'undefined') module.exports = stub; return; }
- const panels = []; // { el, openClass, close, id }
+ const panels = []; // { el, openClass, close, id, unregister }
const isOpen = (p) => p.el.isConnected && p.el.classList.contains(p.openClass);
const isOpen = (p) => p.el.isConnected && p.el.classList.contains(p.openClass);
const anyOpen = () => panels.some(isOpen);
const anyOpen = () => panels.some(isOpen);
const syncBody = () => document.body && document.body.classList.toggle('ov-panel-open', anyOpen());
const syncBody = () => document.body && document.body.classList.toggle('ov-panel-open', anyOpen());
@@ -32,22 +36,35 @@
@@ -32,22 +36,35 @@
}
}
function register(opts) {
function register(opts) {
- const el = opts && opts.el; if (!el || panels.some(p => p.el === el)) return;
+ const el = opts && opts.el, signal = opts && opts.signal;
+ if (!el || (signal && signal.aborted)) return () => {};
+ const known = panels.find(p => p.el === el); if (known) return known.unregister;
const p = { el, openClass: opts.openClass || 'open', id: opts.id || '', close: typeof opts.close === 'function' ? opts.close : () => el.classList.remove(opts.openClass || 'open') };
const p = { el, openClass: opts.openClass || 'open', id: opts.id || '', close: typeof opts.close === 'function' ? opts.close : () => el.classList.remove(opts.openClass || 'open') };
- panels.push(p);
let was = isOpen(p);
let was = isOpen(p);
- new MutationObserver(() => {
+ const watch = new MutationObserver(() => {
const now = isOpen(p); if (now === was) return; was = now;
const now = isOpen(p); if (now === was) return; was = now;
if (now) { closeAll(el); try { el.scrollTop = 0; } catch { /* */ } }
if (now) { closeAll(el); try { el.scrollTop = 0; } catch { /* */ } }
syncBody();
syncBody();
- }).observe(el, { attributes: true, attributeFilter: ['class'] });
+ });
+ p.unregister = () => {
+ const i = panels.indexOf(p); if (i < 0) return;
+ panels.splice(i, 1);
+ watch.disconnect();
+ syncBody();
+ };
+ panels.push(p);
+ watch.observe(el, { attributes: true, attributeFilter: ['class'] });
+ if (signal && typeof signal.addEventListener === 'function') signal.addEventListener('abort', p.unregister, { once: true });
syncBody();
syncBody();
+ return p.unregister;
}
}
+ function unregister(el) { const p = panels.find(x => x.el === el); if (p) p.unregister(); }
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && anyOpen()) closeAll(); });
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && anyOpen()) closeAll(); });
root.addEventListener('orientationchange', () => closeAll(), { passive: true });
root.addEventListener('orientationchange', () => closeAll(), { passive: true });
- const api = { register, closeAll, anyOpen };
+ const api = { register, unregister, closeAll, anyOpen };
root.OpenVibePanels = api;
root.OpenVibePanels = api;
if (typeof module !== 'undefined' && module.exports) module.exports = api;
if (typeof module !== 'undefined' && module.exports) module.exports = api;
})(typeof window !== 'undefined' ? window : globalThis);
})(typeof window !== 'undefined' ? window : globalThis);
@@ -34,7 +34,18 @@
@@ -34,7 +34,18 @@
}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
'use strict';
'use strict';
const API = 'https://openvibe.network/api/v1/changelog';
const API = 'https://openvibe.network/api/v1/changelog';
- const SITE_NAMES = { live: 'Live', network: 'Network', tools: 'Tools', media: 'Media', community: 'Community', chat: 'Chat', games: 'Games', blog: 'Blog', wiki: 'Wiki', news: 'News', reviews: 'Reviews', deals: 'Deals', coupons: 'Coupons', trade: 'Trade', codes: 'Codes', host: 'Host', ai: 'AI', search: 'Search', sources: 'Sources', events: 'Events', billing: 'Billing', tips: 'Tips', vip: 'VIP', openre: 'OpenRe', bot: 'Bot', sites: 'Sites', realtime: 'Realtime' };
+ // Every OpenVibe service's display name (openvibe-contracts site.name). A service missing here still reads as a name
+ // (siteName capitalises its id), never as a lowercase id next to capitalised ones.
+ const SITE_NAMES = {
+ live: 'Live', network: 'Network', tools: 'Tools', media: 'Media', community: 'Community', chat: 'Chat', games: 'Games',
+ blog: 'Blog', wiki: 'Wiki', news: 'News', reviews: 'Reviews', deals: 'Deals', coupons: 'Coupons', trade: 'Trade',
+ codes: 'Codes', host: 'Host', ai: 'AI', search: 'Search', sources: 'Sources', events: 'Events', billing: 'Billing',
+ tips: 'Tips', vip: 'VIP', openre: 'OpenRe', bot: 'Bot', sites: 'Sites', realtime: 'Realtime', space: 'Space',
+ services: 'Services', actor: 'Actor', run: 'Run', watch: 'Watch', node: 'Node', zone: 'Zone', download: 'Download',
+ video: 'Video', pics: 'Pics', rent: 'Rent', homes: 'Homes', work: 'Work', food: 'Food', help: 'Help', quest: 'Quest',
+ website: 'Website', fund: 'Fund', 'media-hub': 'Media Hub', extensions: 'Extensions', contracts: 'Contracts', sdk: 'SDK',
+ shared: 'Shared', examples: 'Examples', publishing: 'Publishing',
+ };
const STYLE_ID = 'ov-shipped-style';
const STYLE_ID = 'ov-shipped-style';
const TEXT = 'var(--text-primary,var(--text,inherit))';
const TEXT = 'var(--text-primary,var(--text,inherit))';
const MUTED = 'var(--text-secondary,var(--text-muted,#8b93ad))';
const MUTED = 'var(--text-secondary,var(--text-muted,#8b93ad))';
@@ -98,7 +109,7 @@
@@ -98,7 +109,7 @@
function safeUrl(u) { return /^https:\/\//.test(String(u || '')) ? String(u) : null; }
function safeUrl(u) { return /^https:\/\//.test(String(u || '')) ? String(u) : null; }
function localHref(u) { const s = String(u || ''); return /^\/(?!\/)/.test(s) || /^https:\/\//.test(s) ? s : null; }
function localHref(u) { const s = String(u || ''); return /^\/(?!\/)/.test(s) || /^https:\/\//.test(s) ? s : null; }
function clamp(n, lo, hi, d) { const v = parseInt(n, 10); return Number.isFinite(v) ? Math.min(Math.max(v, lo), hi) : d; }
function clamp(n, lo, hi, d) { const v = parseInt(n, 10); return Number.isFinite(v) ? Math.min(Math.max(v, lo), hi) : d; }
- const siteName = (id) => SITE_NAMES[id] || id;
+ const siteName = (id) => SITE_NAMES[id] || String(id || '').replace(/(^|-)([a-z])/g, (m, d, c) => `${d ? ' ' : ''}${c.toUpperCase()}`);
function timeEl(iso) { const t = el('time', { datetime: iso, 'data-ov-ago': '' }, ago(iso)); t.title = new Date(iso).toLocaleString(); return t; }
function timeEl(iso) { const t = el('time', { datetime: iso, 'data-ov-ago': '' }, ago(iso)); t.title = new Date(iso).toLocaleString(); return t; }
function rocket() {
function rocket() {
const ns = 'http://www.w3.org/2000/svg';
const ns = 'http://www.w3.org/2000/svg';
@@ -371,5 +382,5 @@
@@ -371,5 +382,5 @@
return out;
return out;
}
}
- return { latest, list, mount: list, log, scan, render, renderLatest, appendDays, serviceFor, ago, API, SITE_NAMES };
+ return { latest, list, mount: list, log, scan, render, renderLatest, appendDays, serviceFor, ago, API, SITE_NAMES, siteName };
}));
}));
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"booted_at": null,
"booted_at": null,
"contracts_version": null,
"contracts_version": null,
"packages": {
"packages": {
- "openvibe-shared": "2.13.2"
+ "openvibe-shared": "2.15.0"
},
},
"min_client_release": null,
"min_client_release": null,
"mixed_version_window_hours": 24,
"mixed_version_window_hours": 24,
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
"booted_at": null,
"booted_at": null,
"contracts_version": null,
"contracts_version": null,
"packages": {
"packages": {
- "openvibe-shared": "2.13.2"
+ "openvibe-shared": "2.15.0"
},
},
"min_client_release": null,
"min_client_release": null,
"mixed_version_window_hours": 24,
"mixed_version_window_hours": 24,
@@ -7,7 +7,7 @@
@@ -7,7 +7,7 @@
"name": "openvibe-sites",
"name": "openvibe-sites",
"dependencies": {
"dependencies": {
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.2"
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0"
},
},
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"
@@ -197,9 +197,9 @@
@@ -197,9 +197,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "2.13.2",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.2",
- "integrity": "sha512-rvLq6uKd0j84qxZXwGX+gE6+e8SJaP8bBilWu9Eosjgfc70Z4bPJOUJUiLNRte7fU5hW/ea97/JoCLdiwXmPQA==",
+ "version": "2.15.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0",
+ "integrity": "sha512-xApTyiUBVDQLTdnKEjjr/9uUU4+T5gofz9DqXv0MrLVINmW88uwbfoEht3kAlG9mp1792U6kIFEblJDHn2wyMQ==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0"
"jsonwebtoken": "^9.0.0"
@@ -8,7 +8,7 @@
@@ -8,7 +8,7 @@
},
},
"dependencies": {
"dependencies": {
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.117.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.13.2"
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.15.0"
},
},
"engines": {
"engines": {
"node": ">=20"
"node": ">=20"