openvibers/openvibe.mediahub Commits

Font Awesome out, the OpenVibe glyphs in (plan L54 K4) (#226)

OP OpenVibers committed 12 days ago · 47de6e8f35 · 6 files, +89 −7

* Font Awesome out, the OpenVibe glyphs in (plan L54 K4)

The owner, 2026-11-01: "get rid of Font Awesome completely and just use our own
openvibe.design system". The video layout's link to cdnjs' all.min.css is gone; the
icons come from the OpenVibe glyphs (openvibe-shared/glyphs) and the brand logos
(openvibe-shared/brands), which the layouts load for every server-rendered page. cdnjs
leaves the CSP: nothing else on the site used it.

Every fa- name MediaHub named that no glyph draws is now one that is drawn:
file-zipper -> box-archive.

test/no-font-awesome.test.js holds it there: no page links Font Awesome, nothing
draws with its font, and every fa- name is drawn.

* Size budget: the OpenVibe glyphs in place of Font Awesome (plan L54 K4)

The layout loads /shared/ov-glyphs.js so icons added later by the page's own script are drawn.
Font Awesome's stylesheet and font were external (uncounted here) and heavier.

Changes

ddiff --git a/server/app.js b/server/app.js
iindex 29a267e..64398b8 100644
---- a/server/app.js
++++ b/server/app.js
@@@ -120,8 +120,8 @@ async function createApp(opts = {}) {
defaultSrc: ["'self'"],
// The OpenVibe Frame (theme-loader, navbar, footer) comes from the Network; the inline init is ours.
scriptSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://static.cloudflareinsights.com'],
-- styleSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://fonts.googleapis.com', 'https://cdnjs.cloudflare.com'],
-- fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com', 'https://cdnjs.cloudflare.com'],
++ styleSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://fonts.googleapis.com'],
++ fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'],
// The Video home's live-now cards show OpenVibe.Live's own stream thumbnails (its /api/thumbnails).
imgSrc: ["'self'", 'data:', 'https://openvibe.network', 'https://openvibe.media', liveOrigin],
// The video player loads its media file from OpenVibe.Media (https://openvibe.media/v|/c), which may
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 51014dd..05e1b4e 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -97,6 +97,8 @@ ${appIcon.headTags({ site: 'media-hub' })}
<link rel="stylesheet" href="${asset('css/app.css')}">
${(o.styles || []).map((name) => `<link rel="stylesheet" href="${esc(ovServe.url(name))}">`).join('\n')}
${render(scripts)}
++<!-- The OpenVibe glyphs draw every fa- icon here (no Font Awesome since 2026-11-01). -->
++<script id="ov-glyphs-loader" src="${ovServe.url('ov-glyphs.js')}" defer></script>
<meta name="ov-boost" content="media-hub@${esc(RELEASE)}">
<script src="${ovServe.url('boost.js')}" data-main="#main" defer></script>`,
body: `<a class="skip" href="#main">Skip to content</a>
ddiff --git a/server/video/downloads.js b/server/video/downloads.js
iindex 703c5d0..7c6cc93 100644
---- a/server/video/downloads.js
++++ b/server/video/downloads.js
@@@ -255,7 +255,7 @@ function downloadMenu({ kind, id, item, has }) {
{ href: `${at}/chapters.json`, ext: 'json', purpose: 'for programs' }] }));
if (item.thumbnail_url) rows.push(row({ icon: 'fa-regular fa-image', label: 'Cover picture', about: null, links: [{ href: `${at}/thumbnail`, ext: 'jpg', text: 'Image' }] }));
rows.push(row({ icon: 'fa-solid fa-circle-info', label: 'Details', about: 'Title, channel, dates, summary', links: [{ href: `${at}/info.json`, ext: 'json' }] }));
-- const everything = html`<li class="ovv-dl-row ovv-dl-row--all"><i class="fa-solid fa-file-zipper" aria-hidden="true"></i><span class="ovv-dl-what"><span class="ovv-dl-label">Everything</span><span class="ovv-dl-about">All of the above but the video, in one .zip</span></span><span class="ovv-dl-formats"><a class="ovv-dl-format ovv-dl-format--main" href="${at}/all.zip" download rel="nofollow" title="${FORMAT_HELP.zip}" aria-label="Everything: ZIP (${FORMAT_HELP.zip})">ZIP</a></span></li>`;
++ const everything = html`<li class="ovv-dl-row ovv-dl-row--all"><i class="fa-solid fa-box-archive" aria-hidden="true"></i><span class="ovv-dl-what"><span class="ovv-dl-label">Everything</span><span class="ovv-dl-about">All of the above but the video, in one .zip</span></span><span class="ovv-dl-formats"><a class="ovv-dl-format ovv-dl-format--main" href="${at}/all.zip" download rel="nofollow" title="${FORMAT_HELP.zip}" aria-label="Everything: ZIP (${FORMAT_HELP.zip})">ZIP</a></span></li>`;
return html`<details class="ovv-dl" data-ovv-dl><summary class="ovv-btn"><i class="fa-solid fa-download" aria-hidden="true"></i> Download</summary>
<div class="ovv-dl-menu" role="group" aria-label="Download this ${kind === 'clip' ? 'clip' : 'recording'}">
<div class="ovv-dl-head"><span>Download this ${kind === 'clip' ? 'clip' : 'recording'}</span><button type="button" class="ovv-dl-close" data-ovv-dl-close aria-label="Close"><i class="fa-solid fa-xmark" aria-hidden="true"></i></button></div>
ddiff --git a/server/video/layout.js b/server/video/layout.js
iindex 2559a53..dba7540 100644
---- a/server/video/layout.js
++++ b/server/video/layout.js
@@@ -64,7 +64,8 @@ function renderPage(o) {
${appIcon.headTags({ site: 'media-hub' })}
<link rel="stylesheet" href="${asset('css/app.css')}">
<link rel="stylesheet" href="${asset('css/video.css')}">
--<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous" referrerpolicy="no-referrer">
++<!-- The OpenVibe glyphs draw every fa- icon here (no Font Awesome since 2026-11-01). -->
++<script id="ov-glyphs-loader" src="${ovServe.url('ov-glyphs.js')}" defer></script>
${o.player ? `<link rel="stylesheet" href="${ovServe.url('player.css')}">\n<script src="${ovServe.url('player.js')}" defer></script>\n` : ''}${(o.scripts || []).map((rel) => `<script src="${asset(rel)}" defer></script>`).join('\n')}`;
const account = signedIn
? html`Signed in as <strong>${viewer.displayName || viewer.username || 'you'}</strong> · <a href="/auth/logout?next=${encodeURIComponent(pathNow)}">Sign out</a>`
ddiff --git a/test/no-font-awesome.test.js b/test/no-font-awesome.test.js
nnew file mode 100644
iindex 0000000..5d18022
---- /dev/null
++++ b/test/no-font-awesome.test.js
@@@ -0,0 +1,79 @@
++'use strict';
++// No Font Awesome (plan L54 K4; the owner, 2026-11-01: "get rid of Font Awesome completely and just use our own
++// openvibe.design system"). OpenVibe.MediaHub's icons are the OpenVibe glyphs (openvibe-shared/glyphs, drawn in the browser by
++// /shared/ov-glyphs.js, which the Shared navbar loads on a page that names an icon) and the brand logos from
++// openvibe-shared/brands. This holds it there:
++// - no page or stylesheet links Font Awesome, and nothing draws with Font Awesome's font;
++// - every page that names an icon loads the navbar or the glyph drop-in;
++// - every fa- name OpenVibe.MediaHub uses is one the glyphs or the brands draw (a name nobody draws would show nothing).
++// node test/no-font-awesome.test.js
++const assert = require('assert');
++const fs = require('fs');
++const path = require('path');
++const glyphs = require('openvibe-shared/glyphs');
++const brands = require('openvibe-shared/brands');
++
++const ROOT = path.join(__dirname, '..');
++const walk = (dir, ok, out = []) => {
++ if (!fs.existsSync(dir)) return out;
++ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
++ if (e.name === 'node_modules' || e.name === 'vendor' || e.name.startsWith('.')) continue;
++ const p = path.join(dir, e.name);
++ if (e.isDirectory()) walk(p, ok, out); else if (ok.test(e.name)) out.push(p);
++ }
++ return out;
++};
++const rel = (p) => path.relative(ROOT, p);
++
++// The pages (whole HTML files) and the code that draws them.
++const PAGE_ROOTS = [];
++const CODE_ROOTS = ["public","server"];
++const LAYOUTS = ["server/video/layout.js","server/render/layout.js"]; // the server-rendered pages: their layout brings the navbar or the glyphs
++
++// A Font Awesome stylesheet, wherever it is written.
++const FA_CSS = /fontawesome[^"'\s]*\.css|font-awesome[^"'\s]*\.css|cdnjs\.cloudflare\.com\/ajax\/libs\/font-awesome/i;
++
++const pages = [];
++for (const r of PAGE_ROOTS) pages.push(...walk(path.join(ROOT, r), /\.html$/));
++for (const p of pages) {
++ const html = fs.readFileSync(p, 'utf8');
++ assert.ok(!/fontawesome|font-awesome/i.test(html), `${rel(p)} links Font Awesome`);
++ // A whole page (with a <head>; fragments are parts of a page) that names an icon loads what draws it.
++ if (/<head[\s>]/i.test(html) && /\bfa-[a-z]/.test(html) && !/\/shared\/navbar|ov-glyphs\.js/.test(html)) {
++ assert.fail(`${rel(p)} has fa- icons but loads neither the navbar nor /shared/ov-glyphs.js`);
++ }
++}
++
++// Nothing draws with Font Awesome's font.
++for (const r of CODE_ROOTS) {
++ for (const p of walk(path.join(ROOT, r), /\.css$/)) {
++ assert.ok(!/font-family:\s*["']?Font ?Awesome/i.test(fs.readFileSync(p, 'utf8')), `${rel(p)} draws with Font Awesome's font`);
++ }
++ for (const p of walk(path.join(ROOT, r), /\.(html|js|mjs|cjs)$/)) {
++ const t = fs.readFileSync(p, 'utf8');
++ assert.ok(!/['"]\d+px FontAwesome|Font Awesome 6 Free/.test(t), `${rel(p)} draws with Font Awesome's font`);
++ assert.ok(!FA_CSS.test(t), `${rel(p)} links Font Awesome`);
++ }
++}
++
++// The server-rendered pages get the glyphs from the Shared navbar (shell.page) or from the drop-in.
++for (const f of LAYOUTS) {
++ const t = fs.readFileSync(path.join(ROOT, f), 'utf8');
++ assert.ok(/shell\.page|ov-glyphs\.js|\/shared\/navbar/.test(t), `${f} renders pages without the navbar or /shared/ov-glyphs.js`);
++}
++
++// Every fa- name in the pages, the scripts and the server views is drawn by a glyph or a brand.
++const MOD = /^(solid|regular|light|thin|duotone|brands|sharp|classic|fw|lg|xl|xs|sm|2xs|2xl|\d+x|li|ul|border|inverse|pull-left|pull-right|rotate-\d+|rotate-by|flip-horizontal|flip-vertical|flip-both|width-auto|spin|spin-pulse|spin-reverse|pulse|beat|beat-fade|bounce|fade|flip|shake|stack|stack-1x|stack-2x|layers|layers-text|layers-counter|swap-opacity|stack-text)$/;
++const files = [];
++for (const r of CODE_ROOTS) files.push(...walk(path.join(ROOT, r), /\.(html|js)$/));
++const missing = new Map();
++for (const f of files) {
++ for (const m of fs.readFileSync(f, 'utf8').matchAll(/\bfa-([a-z0-9]+(?:-[a-z0-9]+)*)\b/g)) {
++ const n = m[1];
++ if (MOD.test(n) || glyphs.fromFa(n) || brands.fromFa(n)) continue;
++ if (!missing.has(n)) missing.set(n, rel(f));
++ }
++}
++assert.deepStrictEqual([...missing].map(([n, f]) => `fa-${n} (${f})`), [], 'every icon this site names is drawn by the OpenVibe glyphs or brands');
++
++console.log(`no font awesome: ${pages.length} pages, ${files.length} files, every fa- name drawn`);
ddiff --git a/test/perf-budget.test.js b/test/perf-budget.test.js
iindex 58edcfd..c2ea3cd 100644
---- a/test/perf-budget.test.js
++++ b/test/perf-budget.test.js
@@@ -17,9 +17,9 @@ const BUDGETS = {
htmlRawKB: 51, // 50.3 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 49.4 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 44.2 with the Part of OpenVibe section (Shared 4.13, 2026-10-26); measured 39.4 on 10-22: the home gained the backup command, the limits table, six questions (and their
// FAQPage JSON-LD) and next steps on the network; was 29.7 at 27.5
htmlBrotliKB: 12, // 10.6 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 9.4 on 10-22 (the same sections); was 7.7 at 7.1
-- jsFiles: 5, // 5 (theme-loader, web-runtime, navbar, footer, boost: openvibe-shared/shell)
-- jsRawKB: 331, // 328.2 openvibe-shared 4.70.0 (2026-11-01: the app bar, busy(), skeletons, view transitions); 324.8 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 323.2 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 321.1 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 318.1 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 315.6 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 307.8 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 300.5 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 293 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 287.2 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 273.2 with Shared 4.15.0 (2026-10-27: the compact footer opens into the whole one, the pulse line); 264.3 with Shared 4.13.0; 239.8
-- jsBrotliKB: 80.8, // 80.2 openvibe-shared 4.70.0 (2026-11-01: the app bar, busy(), skeletons, view transitions); 79.5 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 78.2 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 77.6 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 76.1 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 74.2 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 70.5 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 67.9 Shared 4.15.0; 65.3 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 56.5
++ jsFiles: 6, // 6: /shared/ov-glyphs.js in place of Font Awesome's stylesheet (2026-11-01, plan L54 K4); was: 5 (theme-loader, web-runtime, navbar, footer, boost: openvibe-shared/shell)
++ jsRawKB: 393, // 393: /shared/ov-glyphs.js in place of Font Awesome (2026-11-01, plan L54 K4; Font Awesome's CSS and font were external, uncounted, and heavier); 328.2 openvibe-shared 4.70.0 (2026-11-01: the app bar, busy(), skeletons, view transitions); 324.8 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 323.2 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 321.1 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 318.1 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 315.6 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 307.8 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 300.5 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 293 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 287.2 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 273.2 with Shared 4.15.0 (2026-10-27: the compact footer opens into the whole one, the pulse line); 264.3 with Shared 4.13.0; 239.8
++ jsBrotliKB: 96, // 96: /shared/ov-glyphs.js in place of Font Awesome (2026-11-01, plan L54 K4); 80.2 openvibe-shared 4.70.0 (2026-11-01: the app bar, busy(), skeletons, view transitions); 79.5 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 78.2 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 77.6 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 76.1 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 74.2 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 70.5 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 67.9 Shared 4.15.0; 65.3 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 56.5
cssFiles: 2, // 2 (app.css + the cached /shared/showcase.css)
cssRawKB: 34, // 33.5 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 29 openvibe-shared 4.26.1 (hero visual); 23.6 with Shared 4.13.0 (2026-10-26: account links); measured 16.6 (shared 4.1.8: four steps two by two); was 16.5 at 15.0
cssBrotliKB: 7.4, // 7.2 with openvibe-shared 4.48.0 (the site switcher on the mark, the menu's Start tab and quick tools; Kit's touch hit areas and the phone navbar fix, 4.44-4.47.3); 5.4 with Shared 4.13.0 (2026-10-26: account links); 3.7; 4.2 with the drive previews (10-15)