openvibers/openvibe.network Commits
Shared navbar: Font Awesome solid icons when the page has FA; hub copy for the planned sites matches the realignment plan
Changes
@@@ -335,10 +335,49 @@'fa-house': '<path d="M4.5 11.5 12 5l7.5 6.500M6.5 10v8.500h11V10"/>','fa-gauge-high': '<path d="M4.5 17a8 8 0 1 1 15 0M12 13l3.5-4"/><circle cx="12" cy="13.5" r="1.3"/>',};++ // Icons: Font Awesome solid when the host page has Font Awesome (crisp, matches the rest of++ // the page); the inline stroke sprite only as a fallback for pages without it. The sprite++ // was drawn for self-containment, not looks — at 14px the thin strokes read as jagged++ // "line icons" next to everything else being solid. `icons: 'fa' | 'svg'` in init() forces one.++ let _faOk = null;++ function faAvailable() {++ if (_config.icons === 'fa') return true;++ if (_config.icons === 'svg') return false;++ if (_faOk !== null) return _faOk;++ if (!document.body) return false; // too early to probe; don't cache++ try {++ const probe = document.createElement('i');++ probe.className = 'fa-solid fa-house';++ probe.style.cssText = 'position:absolute;left:-9999px;top:-9999px;visibility:hidden';++ document.body.appendChild(probe);++ const ff = getComputedStyle(probe).fontFamily || '';++ probe.remove();++ _faOk = /font awesome/i.test(ff);++ } catch { _faOk = false; }++ return _faOk;++ }function navIcon(cls) {++ if (!cls) return '';++ if (faAvailable()) return `<i class="fa-solid ${escapeAttr(cls)}"></i>`;const key = String(cls || '').split(/\s+/).find(c => NAV_ICONS[c]);-- if (!key) return cls ? `<i class="fa-solid ${escapeAttr(cls)}"></i>` : '';-- return `<svg class="ovnav-ic" viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${NAV_ICONS[key]}</svg>`;++ if (!key) return `<i class="fa-solid ${escapeAttr(cls)}"></i>`;++ return `<svg class="ovnav-ic" data-fa="${escapeAttr(cls)}" viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision" aria-hidden="true" focusable="false">${NAV_ICONS[key]}</svg>`;++ }++ // If Font Awesome finishes loading after the navbar rendered (deferred stylesheet), swap the++ // sprite icons for the real glyphs in place.++ function upgradeIconsWhenFontsReady() {++ try {++ if (_faOk === true || _config.icons) return;++ const run = () => {++ _faOk = null;++ if (!faAvailable()) return;++ document.querySelectorAll('.openvibe-navbar svg.ovnav-ic[data-fa], .openvibe-navbar-dropdown svg.ovnav-ic[data-fa]').forEach(svg => {++ const i = document.createElement('i'); i.className = 'fa-solid ' + svg.getAttribute('data-fa'); svg.replaceWith(i);++ });++ };++ if (document.fonts && document.fonts.ready) document.fonts.ready.then(() => setTimeout(run, 0));++ window.addEventListener('load', () => setTimeout(run, 50), { once: true });++ } catch { /* */ }}// ─── Brand from hostname ───────────────────────────────────@@@ -1390,6 +1429,7 @@Object.assign(_config, opts);injectStyles();const el = render();++ upgradeIconsWhenFontsReady();// Pages that hand us a resolved user (openvibe.network, the tools// gateway hub) keep full control. Everyone else — pages that pass// only a token, or nothing at all — gets the user resolved from@@@ -12,19 +12,19 @@ const SITES = [{ id: 'games', name: 'Games', host: 'openvibe.games', icon: 'games', status: 'open', service: 'games', profile: 'games', tagline: 'Browser games', what: 'Scraplandia, a shared pixel canvas and other games that run in a tab and know your OpenVibe account.' },{ id: 'media', name: 'Media', host: 'openvibe.media', icon: 'media', status: 'open', service: 'media', profile: 'hosting', tagline: 'VODs, clips, files', what: 'The public media library behind every site: recorded streams, clips, images and uploads.' },{ id: 'network', name: 'Network', host: 'openvibe.network', icon: 'network', status: 'open', service: 'network', profile: 'account', tagline: 'Account and themes', what: 'The account behind every site: one sign-in, themes, notifications, history and linked services.' },-- { id: 'chat', name: 'Chat', host: 'openvibe.chat', icon: 'chat', status: 'soon', profile: 'ugc', tagline: 'Rooms and direct messages', what: 'Community chat rooms and direct messages across the network.' },-- { id: 'codes', name: 'Codes', host: 'openvibe.codes', icon: 'codes', status: 'soon', profile: 'ugc', tagline: 'Snippets and open source', what: 'Code snippets, gists and the source of OpenVibe itself.' },-- { id: 'blog', name: 'Blog', host: 'openvibe.blog', icon: 'blog', status: 'soon', profile: 'info', tagline: 'Updates from the builders', what: 'Release notes and stories from the people building OpenVibe.' },-- { id: 'wiki', name: 'Wiki', host: 'openvibe.wiki', icon: 'wiki', status: 'soon', profile: 'ugc', tagline: 'Guides and how-tos', what: 'Guides for streaming, tools and everything else on the network.' },-- { id: 'news', name: 'News', host: 'openvibe.news', icon: 'news', status: 'soon', profile: 'info', tagline: 'What is happening', what: 'News from the network and the communities on it.' },-- { id: 'reviews', name: 'Reviews', host: 'openvibe.reviews', icon: 'reviews', status: 'soon', profile: 'ugc', tagline: 'Honest reviews', what: 'Community reviews of gear, software and services.' },-- { id: 'tips', name: 'Tips', host: 'openvibe.tips', icon: 'tips', status: 'soon', profile: 'streaming', tagline: 'Support creators', what: 'Send support to the streamers and makers you like.' },-- { id: 'vip', name: 'VIP', host: 'openvibe.vip', icon: 'vip', status: 'soon', profile: 'account', tagline: 'Supporter perks', what: 'Perks for people who support the network.' },-- { id: 'trade', name: 'Trade', host: 'openvibe.trade', icon: 'trade', status: 'soon', profile: 'ugc', tagline: 'Swap and sell', what: 'A community marketplace for trades and sales.' },-- { id: 'host', name: 'Host', host: 'openvibe.host', icon: 'host', status: 'soon', profile: 'hosting', tagline: 'Hosting for projects', what: 'Simple hosting for community projects.' },-- { id: 'deals', name: 'Deals', host: 'openvibe.deals', icon: 'deals', status: 'soon', profile: 'info', tagline: 'Deals worth sharing', what: 'Deals found and vetted by the community.' },-- { id: 'coupons', name: 'Coupons', host: 'openvibe.coupons', icon: 'coupons', status: 'soon', profile: 'info', tagline: 'Codes that work', what: 'Coupon codes checked by real people.' },-- { id: 'stream', name: 'OpenRe.Stream', host: 'openre.stream', icon: 'stream', status: 'soon', profile: 'streaming', tagline: 'Restream anywhere', what: 'Send one stream to every platform at once.' },++ { id: 'chat', name: 'Chat', host: 'openvibe.chat', icon: 'chat', status: 'soon', profile: 'ugc', tagline: 'One chat for the network', what: 'Rooms, stream chat, direct messages, calls and text-to-speech under one identity.' },++ { id: 'codes', name: 'Codes', host: 'openvibe.codes', icon: 'codes', status: 'soon', profile: 'ugc', tagline: 'Build on OpenVibe', what: 'The developer portal: API and SDK docs, the service and capability registry, event schemas, credentials, playgrounds and mod publishing.' },++ { id: 'blog', name: 'Blog', host: 'openvibe.blog', icon: 'blog', status: 'soon', profile: 'info', tagline: 'Long-form, yours to keep', what: 'The official OpenVibe blog and a blog for every member: drafts, scheduling, media, comments and feeds.' },++ { id: 'wiki', name: 'Wiki', host: 'openvibe.wiki', icon: 'wiki', status: 'soon', profile: 'ugc', tagline: 'Knowledge, with sources', what: 'Wiki spaces with page trees, revision history, citations and discussion, editable together.' },++ { id: 'news', name: 'News', host: 'openvibe.news', icon: 'news', status: 'soon', profile: 'info', tagline: 'Sourced, not spun', what: 'Source-backed stories: clustered coverage, cited summaries, multiple perspectives and timelines.' },++ { id: 'reviews', name: 'Reviews', host: 'openvibe.reviews', icon: 'reviews', status: 'soon', profile: 'ugc', tagline: 'Reviews with receipts', what: 'Review signals gathered across sources with provenance, pros and cons, and community discussion. No invented ratings.' },++ { id: 'tips', name: 'Tips', host: 'openvibe.tips', icon: 'tips', status: 'soon', profile: 'streaming', tagline: 'Support creators', what: 'Tips, goals, paid messages and alerts for creators, settled by the network\'s billing ledger.' },++ { id: 'vip', name: 'VIP', host: 'openvibe.vip', icon: 'vip', status: 'soon', profile: 'account', tagline: 'Memberships and perks', what: 'Creator and network memberships: plans, perks and benefits recognised on every OpenVibe site.' },++ { id: 'trade', name: 'Trade', host: 'openvibe.trade', icon: 'trade', status: 'soon', profile: 'ugc', tagline: 'Watchlists and alerts', what: 'Informational watchlists, sourced market context and alerts. No custody, no order execution.' },++ { id: 'host', name: 'Host', host: 'openvibe.host', icon: 'host', status: 'soon', profile: 'hosting', tagline: 'Deploy and host', what: 'The network\'s deployment plane first, then simple hosting for community sites, bots and mods.' },++ { id: 'deals', name: 'Deals', host: 'openvibe.deals', icon: 'deals', status: 'soon', profile: 'info', tagline: 'Deals worth sharing', what: 'Deals submitted and voted on by the community, with source, price and freshness always shown.' },++ { id: 'coupons', name: 'Coupons', host: 'openvibe.coupons', icon: 'coupons', status: 'soon', profile: 'info', tagline: 'Codes that work', what: 'Coupon codes with merchant matching, restrictions, expiry and real-people validity reports.' },++ { id: 'stream', name: 'OpenRe.Stream', host: 'openre.stream', icon: 'stream', status: 'soon', profile: 'streaming', tagline: 'Restream anywhere', what: 'Ingest once and send the stream to every platform at once; keys, destinations and output health in one place.' },];const byHost = new Map(SITES.map(s => [s.host, s]));
@@ -335,10 +335,49 @@
@@ -335,10 +335,49 @@
'fa-house': '<path d="M4.5 11.5 12 5l7.5 6.500M6.5 10v8.500h11V10"/>',
'fa-house': '<path d="M4.5 11.5 12 5l7.5 6.500M6.5 10v8.500h11V10"/>',
'fa-gauge-high': '<path d="M4.5 17a8 8 0 1 1 15 0M12 13l3.5-4"/><circle cx="12" cy="13.5" r="1.3"/>',
'fa-gauge-high': '<path d="M4.5 17a8 8 0 1 1 15 0M12 13l3.5-4"/><circle cx="12" cy="13.5" r="1.3"/>',
};
};
+ // Icons: Font Awesome solid when the host page has Font Awesome (crisp, matches the rest of
+ // the page); the inline stroke sprite only as a fallback for pages without it. The sprite
+ // was drawn for self-containment, not looks — at 14px the thin strokes read as jagged
+ // "line icons" next to everything else being solid. `icons: 'fa' | 'svg'` in init() forces one.
+ let _faOk = null;
+ function faAvailable() {
+ if (_config.icons === 'fa') return true;
+ if (_config.icons === 'svg') return false;
+ if (_faOk !== null) return _faOk;
+ if (!document.body) return false; // too early to probe; don't cache
+ try {
+ const probe = document.createElement('i');
+ probe.className = 'fa-solid fa-house';
+ probe.style.cssText = 'position:absolute;left:-9999px;top:-9999px;visibility:hidden';
+ document.body.appendChild(probe);
+ const ff = getComputedStyle(probe).fontFamily || '';
+ probe.remove();
+ _faOk = /font awesome/i.test(ff);
+ } catch { _faOk = false; }
+ return _faOk;
+ }
function navIcon(cls) {
function navIcon(cls) {
+ if (!cls) return '';
+ if (faAvailable()) return `<i class="fa-solid ${escapeAttr(cls)}"></i>`;
const key = String(cls || '').split(/\s+/).find(c => NAV_ICONS[c]);
const key = String(cls || '').split(/\s+/).find(c => NAV_ICONS[c]);
- if (!key) return cls ? `<i class="fa-solid ${escapeAttr(cls)}"></i>` : '';
- return `<svg class="ovnav-ic" viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${NAV_ICONS[key]}</svg>`;
+ if (!key) return `<i class="fa-solid ${escapeAttr(cls)}"></i>`;
+ return `<svg class="ovnav-ic" data-fa="${escapeAttr(cls)}" viewBox="0 0 24 24" width="1em" height="1em" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision" aria-hidden="true" focusable="false">${NAV_ICONS[key]}</svg>`;
+ }
+ // If Font Awesome finishes loading after the navbar rendered (deferred stylesheet), swap the
+ // sprite icons for the real glyphs in place.
+ function upgradeIconsWhenFontsReady() {
+ try {
+ if (_faOk === true || _config.icons) return;
+ const run = () => {
+ _faOk = null;
+ if (!faAvailable()) return;
+ document.querySelectorAll('.openvibe-navbar svg.ovnav-ic[data-fa], .openvibe-navbar-dropdown svg.ovnav-ic[data-fa]').forEach(svg => {
+ const i = document.createElement('i'); i.className = 'fa-solid ' + svg.getAttribute('data-fa'); svg.replaceWith(i);
+ });
+ };
+ if (document.fonts && document.fonts.ready) document.fonts.ready.then(() => setTimeout(run, 0));
+ window.addEventListener('load', () => setTimeout(run, 50), { once: true });
+ } catch { /* */ }
}
}
// ─── Brand from hostname ───────────────────────────────────
// ─── Brand from hostname ───────────────────────────────────
@@ -1390,6 +1429,7 @@
@@ -1390,6 +1429,7 @@
Object.assign(_config, opts);
Object.assign(_config, opts);
injectStyles();
injectStyles();
const el = render();
const el = render();
+ upgradeIconsWhenFontsReady();
// Pages that hand us a resolved user (openvibe.network, the tools
// Pages that hand us a resolved user (openvibe.network, the tools
// gateway hub) keep full control. Everyone else — pages that pass
// gateway hub) keep full control. Everyone else — pages that pass
// only a token, or nothing at all — gets the user resolved from
// only a token, or nothing at all — gets the user resolved from
@@ -12,19 +12,19 @@ const SITES = [
@@ -12,19 +12,19 @@ const SITES = [
{ id: 'games', name: 'Games', host: 'openvibe.games', icon: 'games', status: 'open', service: 'games', profile: 'games', tagline: 'Browser games', what: 'Scraplandia, a shared pixel canvas and other games that run in a tab and know your OpenVibe account.' },
{ id: 'games', name: 'Games', host: 'openvibe.games', icon: 'games', status: 'open', service: 'games', profile: 'games', tagline: 'Browser games', what: 'Scraplandia, a shared pixel canvas and other games that run in a tab and know your OpenVibe account.' },
{ id: 'media', name: 'Media', host: 'openvibe.media', icon: 'media', status: 'open', service: 'media', profile: 'hosting', tagline: 'VODs, clips, files', what: 'The public media library behind every site: recorded streams, clips, images and uploads.' },
{ id: 'media', name: 'Media', host: 'openvibe.media', icon: 'media', status: 'open', service: 'media', profile: 'hosting', tagline: 'VODs, clips, files', what: 'The public media library behind every site: recorded streams, clips, images and uploads.' },
{ id: 'network', name: 'Network', host: 'openvibe.network', icon: 'network', status: 'open', service: 'network', profile: 'account', tagline: 'Account and themes', what: 'The account behind every site: one sign-in, themes, notifications, history and linked services.' },
{ id: 'network', name: 'Network', host: 'openvibe.network', icon: 'network', status: 'open', service: 'network', profile: 'account', tagline: 'Account and themes', what: 'The account behind every site: one sign-in, themes, notifications, history and linked services.' },
- { id: 'chat', name: 'Chat', host: 'openvibe.chat', icon: 'chat', status: 'soon', profile: 'ugc', tagline: 'Rooms and direct messages', what: 'Community chat rooms and direct messages across the network.' },
- { id: 'codes', name: 'Codes', host: 'openvibe.codes', icon: 'codes', status: 'soon', profile: 'ugc', tagline: 'Snippets and open source', what: 'Code snippets, gists and the source of OpenVibe itself.' },
- { id: 'blog', name: 'Blog', host: 'openvibe.blog', icon: 'blog', status: 'soon', profile: 'info', tagline: 'Updates from the builders', what: 'Release notes and stories from the people building OpenVibe.' },
- { id: 'wiki', name: 'Wiki', host: 'openvibe.wiki', icon: 'wiki', status: 'soon', profile: 'ugc', tagline: 'Guides and how-tos', what: 'Guides for streaming, tools and everything else on the network.' },
- { id: 'news', name: 'News', host: 'openvibe.news', icon: 'news', status: 'soon', profile: 'info', tagline: 'What is happening', what: 'News from the network and the communities on it.' },
- { id: 'reviews', name: 'Reviews', host: 'openvibe.reviews', icon: 'reviews', status: 'soon', profile: 'ugc', tagline: 'Honest reviews', what: 'Community reviews of gear, software and services.' },
- { id: 'tips', name: 'Tips', host: 'openvibe.tips', icon: 'tips', status: 'soon', profile: 'streaming', tagline: 'Support creators', what: 'Send support to the streamers and makers you like.' },
- { id: 'vip', name: 'VIP', host: 'openvibe.vip', icon: 'vip', status: 'soon', profile: 'account', tagline: 'Supporter perks', what: 'Perks for people who support the network.' },
- { id: 'trade', name: 'Trade', host: 'openvibe.trade', icon: 'trade', status: 'soon', profile: 'ugc', tagline: 'Swap and sell', what: 'A community marketplace for trades and sales.' },
- { id: 'host', name: 'Host', host: 'openvibe.host', icon: 'host', status: 'soon', profile: 'hosting', tagline: 'Hosting for projects', what: 'Simple hosting for community projects.' },
- { id: 'deals', name: 'Deals', host: 'openvibe.deals', icon: 'deals', status: 'soon', profile: 'info', tagline: 'Deals worth sharing', what: 'Deals found and vetted by the community.' },
- { id: 'coupons', name: 'Coupons', host: 'openvibe.coupons', icon: 'coupons', status: 'soon', profile: 'info', tagline: 'Codes that work', what: 'Coupon codes checked by real people.' },
- { id: 'stream', name: 'OpenRe.Stream', host: 'openre.stream', icon: 'stream', status: 'soon', profile: 'streaming', tagline: 'Restream anywhere', what: 'Send one stream to every platform at once.' },
+ { id: 'chat', name: 'Chat', host: 'openvibe.chat', icon: 'chat', status: 'soon', profile: 'ugc', tagline: 'One chat for the network', what: 'Rooms, stream chat, direct messages, calls and text-to-speech under one identity.' },
+ { id: 'codes', name: 'Codes', host: 'openvibe.codes', icon: 'codes', status: 'soon', profile: 'ugc', tagline: 'Build on OpenVibe', what: 'The developer portal: API and SDK docs, the service and capability registry, event schemas, credentials, playgrounds and mod publishing.' },
+ { id: 'blog', name: 'Blog', host: 'openvibe.blog', icon: 'blog', status: 'soon', profile: 'info', tagline: 'Long-form, yours to keep', what: 'The official OpenVibe blog and a blog for every member: drafts, scheduling, media, comments and feeds.' },
+ { id: 'wiki', name: 'Wiki', host: 'openvibe.wiki', icon: 'wiki', status: 'soon', profile: 'ugc', tagline: 'Knowledge, with sources', what: 'Wiki spaces with page trees, revision history, citations and discussion, editable together.' },
+ { id: 'news', name: 'News', host: 'openvibe.news', icon: 'news', status: 'soon', profile: 'info', tagline: 'Sourced, not spun', what: 'Source-backed stories: clustered coverage, cited summaries, multiple perspectives and timelines.' },
+ { id: 'reviews', name: 'Reviews', host: 'openvibe.reviews', icon: 'reviews', status: 'soon', profile: 'ugc', tagline: 'Reviews with receipts', what: 'Review signals gathered across sources with provenance, pros and cons, and community discussion. No invented ratings.' },
+ { id: 'tips', name: 'Tips', host: 'openvibe.tips', icon: 'tips', status: 'soon', profile: 'streaming', tagline: 'Support creators', what: 'Tips, goals, paid messages and alerts for creators, settled by the network\'s billing ledger.' },
+ { id: 'vip', name: 'VIP', host: 'openvibe.vip', icon: 'vip', status: 'soon', profile: 'account', tagline: 'Memberships and perks', what: 'Creator and network memberships: plans, perks and benefits recognised on every OpenVibe site.' },
+ { id: 'trade', name: 'Trade', host: 'openvibe.trade', icon: 'trade', status: 'soon', profile: 'ugc', tagline: 'Watchlists and alerts', what: 'Informational watchlists, sourced market context and alerts. No custody, no order execution.' },
+ { id: 'host', name: 'Host', host: 'openvibe.host', icon: 'host', status: 'soon', profile: 'hosting', tagline: 'Deploy and host', what: 'The network\'s deployment plane first, then simple hosting for community sites, bots and mods.' },
+ { id: 'deals', name: 'Deals', host: 'openvibe.deals', icon: 'deals', status: 'soon', profile: 'info', tagline: 'Deals worth sharing', what: 'Deals submitted and voted on by the community, with source, price and freshness always shown.' },
+ { id: 'coupons', name: 'Coupons', host: 'openvibe.coupons', icon: 'coupons', status: 'soon', profile: 'info', tagline: 'Codes that work', what: 'Coupon codes with merchant matching, restrictions, expiry and real-people validity reports.' },
+ { id: 'stream', name: 'OpenRe.Stream', host: 'openre.stream', icon: 'stream', status: 'soon', profile: 'streaming', tagline: 'Restream anywhere', what: 'Ingest once and send the stream to every platform at once; keys, destinations and output health in one place.' },
];
];
const byHost = new Map(SITES.map(s => [s.host, s]));
const byHost = new Map(SITES.map(s => [s.host, s]));