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

OP OpenVibers committed 21 Sept 2026 · bd55b19fc3 · 2 files, +55 −15

The inline stroke sprite (added for self-containment) rendered as thin,
jagged "line icons" at nav size. navIcon() now probes for Font Awesome
on the host page and uses the solid glyphs; the sprite stays as the
fallback for pages without FA and is swapped for the real glyphs once
fonts are ready. `icons: 'fa' | 'svg'` in init() forces either.

sites.js: taglines/descriptions for the 13 "soon" sites now describe
what the plan says they are (Codes = developer portal, Trade =
informational watchlists, News = sourced stories, ...).

Changes

ddiff --git a/packages/openvibe-shared/navbar.js b/packages/openvibe-shared/navbar.js
iindex 57cf71ea..793f3895 100644
---- a/packages/openvibe-shared/navbar.js
++++ b/packages/openvibe-shared/navbar.js
@@@ -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
ddiff --git a/server/chrome/sites.js b/server/chrome/sites.js
iindex fbe23596..d7bde9e4 100644
---- a/server/chrome/sites.js
++++ b/server/chrome/sites.js
@@@ -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]));