openvibers/openvibe.community Commits
Font Awesome out, the OpenVibe glyphs in (plan L54 K4) (#208)
Changes
@@@ -76,7 +76,7 @@var codeWrap = $('[data-code]');// A Markdown paste shows its document; its source (inside "Markdown source") already wraps.if (codeWrap && !codeWrap.closest('.paste-source')) {-- var wrapBtn = document.createElement('button'); wrapBtn.type = 'button'; wrapBtn.className = 'btn btn-sm'; wrapBtn.innerHTML = '<i class="fa-solid fa-text-width" aria-hidden="true"></i> Wrap';++ var wrapBtn = document.createElement('button'); wrapBtn.type = 'button'; wrapBtn.className = 'btn btn-sm'; wrapBtn.innerHTML = '<i class="fa-solid fa-align-left" aria-hidden="true"></i> Wrap';wrapBtn.addEventListener('click', function () { codeWrap.classList.toggle('wrap'); }, { signal });var actions = $('.actions'); if (actions) actions.appendChild(wrapBtn);}@@@ -96,9 +96,9 @@ async function createApp(opts = {}) {// Shared chrome (theme-loader, navbar, footer, history, ov-mark) comes from the Network.// Cloudflare Web Analytics: Cloudflare injects its beacon at the edge and the privacy text says it may// measure performance; script-src loads the beacon, connect-src is where it reports.-- scriptSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://cdnjs.cloudflare.com', 'https://static.cloudflareinsights.com'],-- styleSrc: ["'self'", "'unsafe-inline'", 'https://cdnjs.cloudflare.com', 'https://fonts.googleapis.com', 'https://openvibe.network'],-- fontSrc: ["'self'", 'https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com', 'data:'],++ scriptSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://static.cloudflareinsights.com'],++ styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com', 'https://openvibe.network'],++ fontSrc: ["'self'", 'https://fonts.gstatic.com', 'data:'],// Screenshots serve from openvibe.media (which may 302 to object storage); avatars from Live/Network.imgSrc: ["'self'", 'data:', 'blob:', 'https:'],connectSrc: ["'self'", 'https://openvibe.network', 'https://openvibe.live', 'https://openvibe.media', 'https://openvibe.events', 'https://inventory.openvibe.network', 'https://cloudflareinsights.com'], // inventory: what people wear (items.js)@@@ -80,7 +80,7 @@ function topicPages(base, pages) {function topicRow(t, space) {const base = `/s/${space.slug}/t/${t.slug}`;-- const icon = t.locked ? 'fa-lock' : t.pinned ? 'fa-thumbtack' : t.reply_count >= 15 ? 'fa-fire-flame-curved' : 'fa-comment';++ const icon = t.locked ? 'fa-lock' : t.pinned ? 'fa-thumbtack' : t.reply_count >= 15 ? 'fa-fire' : 'fa-comment';const label = t.locked ? 'Locked' : t.pinned ? 'Sticky' : t.reply_count >= 15 ? 'Hot topic' : 'Topic';const who = (a) => (a ? wearName(a, a.display_name || a.username || 'someone') : 'someone');return `<tr class="${t.pinned ? 'sticky' : ''}">@@@ -189,9 +189,9 @@ function pasteCards(p) {*/function crosspostBlock(c, base) {if (!c) return '';-- const from = c.from ? `<p class="crosspost-note"><i class="fa-solid fa-shuffle" aria-hidden="true"></i> Crossposted from <strong>${esc(c.from.space)}</strong>: <a href="${esc(c.from.url)}">${esc(c.from.title)}</a></p>` : '';++ const from = c.from ? `<p class="crosspost-note"><i class="fa-solid fa-right-left" aria-hidden="true"></i> Crossposted from <strong>${esc(c.from.space)}</strong>: <a href="${esc(c.from.url)}">${esc(c.from.title)}</a></p>` : '';const to = (c.to || []).length ? `<p class="crosspost-note small muted">Also in ${c.to.map((x) => `<a href="${esc(x.url)}">${esc(x.space)}</a>`).join(', ')}</p>` : '';-- const form = (c.targets || []).length ? `<details class="crosspost-form"><summary><i class="fa-solid fa-shuffle" aria-hidden="true"></i> Crosspost</summary>++ const form = (c.targets || []).length ? `<details class="crosspost-form"><summary><i class="fa-solid fa-right-left" aria-hidden="true"></i> Crosspost</summary><form class="inline-form" method="post" action="${esc(base)}/crosspost"><label class="sr-only" for="crosspost-to">Crosspost to</label><select id="crosspost-to" name="to">${c.targets.map((t) => `<option value="${esc(t.slug)}">${esc(t.name)}</option>`).join('')}</select><button class="btn btn-sm" type="submit">Crosspost</button></form></details>` : '';return from || to || form ? `<div class="crosspost">${from}${to}${form}</div>` : '';}@@@ -143,8 +143,9 @@ function renderPage(o) {...(o.styles || []).map((name) => `<link rel="stylesheet" href="${ovServe.url(name)}">`),...(o.inlineStyles || []).map((css) => `<style>${css}</style>`),`<link rel="stylesheet" href="${asset('css/community.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">',`<script src="${asset('js/community.js')}" defer></script>`,++ // 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>`,// openvibe-shared items.js + items.css: community.js loads them only on a page that shows someone.`<meta name="ov-items" content="${escapeHtml(ovServe.url('items.js'))} ${escapeHtml(ovServe.url('items.css'))}">`,// openvibe-shared kit-web.js (rails' arrows, card menus, copy buttons): community.js loads it only on a pagennew file mode 100644@@@ -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.Community'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.Community 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/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`);@@@ -15,8 +15,8 @@ const BUDGETS = {// questions with their FAQPage JSON-LD; was 26 at 22.0htmlBrotliKB: 10.3, // 10.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 8.6 with the Part of OpenVibe section (Shared 4.13, 2026-10-26); 7.3 on 10-22 (the same shelves); was 6.5 at 5.7jsFiles: 7, // 6: openvibe-shared/shell adds web-runtime.js (raised with the shell, 2026-10-04)-- jsRawKB: 340, // 339.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 338.3 with openvibe-shared 4.64.0: the look travels between sites, the Look panel in the menu (2026-10-31); 337.5 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 335.4 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 332.4 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 329.8 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); 322 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 314 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; 306.5 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 300.7 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 286.6 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 276.8 with shared 4.11.0's seasonal navbar; 267.5 with openvibe-shared 4.9.0 (status text twins in every theme, 2026-10-23); 251.3 (was 208.8 before the shell's web-runtime.js)-- jsBrotliKB: 84, // 83.1 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 82.3 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 81.7 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); 80.2 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 78.1 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; 74.4 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 71.7 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 69.1 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 65.5 with shared 4.11.0's seasonal navbar; 59.9 (was 48.9)++ jsRawKB: 408, // 408: /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); 339.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 338.3 with openvibe-shared 4.64.0: the look travels between sites, the Look panel in the menu (2026-10-31); 337.5 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 335.4 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 332.4 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 329.8 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); 322 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 314 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; 306.5 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 300.7 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 286.6 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 276.8 with shared 4.11.0's seasonal navbar; 267.5 with openvibe-shared 4.9.0 (status text twins in every theme, 2026-10-23); 251.3 (was 208.8 before the shell's web-runtime.js)++ jsBrotliKB: 100.5, // 100.5: /shared/ov-glyphs.js in place of Font Awesome (2026-11-01, plan L54 K4); 83.1 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 82.3 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 81.7 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); 80.2 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 78.1 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; 74.4 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 71.7 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 69.1 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 65.5 with shared 4.11.0's seasonal navbar; 59.9 (was 48.9)cssFiles: 5, // 5 the short home: + kit.css (numbers, lists, chips, the fold) and room.css (cards and rails), the shared kit's look; 3cssRawKB: 120, // 117.7 openvibe-shared 4.71.0 (2026-11-01: the navbar in OpenVibe glyphs, the icons setting); 114.3 with openvibe-shared 4.62.0: the shared sidebar in kit.css, the glyphs and their fonts (2026-10-31); 109.0 the phone home (2026-10-30: the chips and the fold rules); 104.8 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); 102.6 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 97.3 the short home (2026-10-29): kit.css 25.6 KB + room.css 15.7 KB, while community.css sheds the old shelves' rules and the page 10 of its 15 phone screens; 54.9 openvibe-shared 4.26.1 (hero visual); 47.2 with Shared 4.13.0 (2026-10-26); 40.6 on 10-22 (the home shelves' styles in community.css); was 40 at 36.5 with the showcase homecssBrotliKB: 25, // 23.8 with openvibe-shared 4.62.0: the shared sidebar in kit.css, the glyphs and their fonts (2026-10-31); 22.1 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); 21.7 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 20.4 the short home (kit.css + room.css, as above; was 11.5 once Kit 2 had its own files in openvibe-shared v4.37.0); 9.5 with Shared 4.13.0 (2026-10-26); 7.7 // raised 11 -> 12 for the openvibe-shared Kit 2 pin (kit.css grew)@@@ -93,7 +93,7 @@ const { boot, check, done } = require('./helpers/app');has(head, '<link rel="alternate" type="application/rss+xml" title="OpenVibe.Community — latest pastes" href="/feed.xml">');has(head, '/css/community.css?v=');has(head, '/js/community.js?v=');-- has(head, 'font-awesome');++ assert.ok(!head.includes('font-awesome'), 'the head carries no Font Awesome stylesheet');has(head, '<link rel="icon"');assert.match(head, /<meta name="ov-boost" content="community@[^"]+">/);for (const js of ['theme-loader.js', 'navbar.js', 'footer.js', 'boost.js']) has(head, `/shared/${js}?v=`);
@@ -76,7 +76,7 @@
@@ -76,7 +76,7 @@
var codeWrap = $('[data-code]');
var codeWrap = $('[data-code]');
// A Markdown paste shows its document; its source (inside "Markdown source") already wraps.
// A Markdown paste shows its document; its source (inside "Markdown source") already wraps.
if (codeWrap && !codeWrap.closest('.paste-source')) {
if (codeWrap && !codeWrap.closest('.paste-source')) {
- var wrapBtn = document.createElement('button'); wrapBtn.type = 'button'; wrapBtn.className = 'btn btn-sm'; wrapBtn.innerHTML = '<i class="fa-solid fa-text-width" aria-hidden="true"></i> Wrap';
+ var wrapBtn = document.createElement('button'); wrapBtn.type = 'button'; wrapBtn.className = 'btn btn-sm'; wrapBtn.innerHTML = '<i class="fa-solid fa-align-left" aria-hidden="true"></i> Wrap';
wrapBtn.addEventListener('click', function () { codeWrap.classList.toggle('wrap'); }, { signal });
wrapBtn.addEventListener('click', function () { codeWrap.classList.toggle('wrap'); }, { signal });
var actions = $('.actions'); if (actions) actions.appendChild(wrapBtn);
var actions = $('.actions'); if (actions) actions.appendChild(wrapBtn);
}
}
@@ -96,9 +96,9 @@ async function createApp(opts = {}) {
@@ -96,9 +96,9 @@ async function createApp(opts = {}) {
// Shared chrome (theme-loader, navbar, footer, history, ov-mark) comes from the Network.
// Shared chrome (theme-loader, navbar, footer, history, ov-mark) comes from the Network.
// Cloudflare Web Analytics: Cloudflare injects its beacon at the edge and the privacy text says it may
// Cloudflare Web Analytics: Cloudflare injects its beacon at the edge and the privacy text says it may
// measure performance; script-src loads the beacon, connect-src is where it reports.
// measure performance; script-src loads the beacon, connect-src is where it reports.
- scriptSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://cdnjs.cloudflare.com', 'https://static.cloudflareinsights.com'],
- styleSrc: ["'self'", "'unsafe-inline'", 'https://cdnjs.cloudflare.com', 'https://fonts.googleapis.com', 'https://openvibe.network'],
- fontSrc: ["'self'", 'https://cdnjs.cloudflare.com', 'https://fonts.gstatic.com', 'data:'],
+ scriptSrc: ["'self'", "'unsafe-inline'", 'https://openvibe.network', 'https://static.cloudflareinsights.com'],
+ styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com', 'https://openvibe.network'],
+ fontSrc: ["'self'", 'https://fonts.gstatic.com', 'data:'],
// Screenshots serve from openvibe.media (which may 302 to object storage); avatars from Live/Network.
// Screenshots serve from openvibe.media (which may 302 to object storage); avatars from Live/Network.
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
imgSrc: ["'self'", 'data:', 'blob:', 'https:'],
connectSrc: ["'self'", 'https://openvibe.network', 'https://openvibe.live', 'https://openvibe.media', 'https://openvibe.events', 'https://inventory.openvibe.network', 'https://cloudflareinsights.com'], // inventory: what people wear (items.js)
connectSrc: ["'self'", 'https://openvibe.network', 'https://openvibe.live', 'https://openvibe.media', 'https://openvibe.events', 'https://inventory.openvibe.network', 'https://cloudflareinsights.com'], // inventory: what people wear (items.js)
@@ -80,7 +80,7 @@ function topicPages(base, pages) {
@@ -80,7 +80,7 @@ function topicPages(base, pages) {
function topicRow(t, space) {
function topicRow(t, space) {
const base = `/s/${space.slug}/t/${t.slug}`;
const base = `/s/${space.slug}/t/${t.slug}`;
- const icon = t.locked ? 'fa-lock' : t.pinned ? 'fa-thumbtack' : t.reply_count >= 15 ? 'fa-fire-flame-curved' : 'fa-comment';
+ const icon = t.locked ? 'fa-lock' : t.pinned ? 'fa-thumbtack' : t.reply_count >= 15 ? 'fa-fire' : 'fa-comment';
const label = t.locked ? 'Locked' : t.pinned ? 'Sticky' : t.reply_count >= 15 ? 'Hot topic' : 'Topic';
const label = t.locked ? 'Locked' : t.pinned ? 'Sticky' : t.reply_count >= 15 ? 'Hot topic' : 'Topic';
const who = (a) => (a ? wearName(a, a.display_name || a.username || 'someone') : 'someone');
const who = (a) => (a ? wearName(a, a.display_name || a.username || 'someone') : 'someone');
return `<tr class="${t.pinned ? 'sticky' : ''}">
return `<tr class="${t.pinned ? 'sticky' : ''}">
@@ -189,9 +189,9 @@ function pasteCards(p) {
@@ -189,9 +189,9 @@ function pasteCards(p) {
*/
*/
function crosspostBlock(c, base) {
function crosspostBlock(c, base) {
if (!c) return '';
if (!c) return '';
- const from = c.from ? `<p class="crosspost-note"><i class="fa-solid fa-shuffle" aria-hidden="true"></i> Crossposted from <strong>${esc(c.from.space)}</strong>: <a href="${esc(c.from.url)}">${esc(c.from.title)}</a></p>` : '';
+ const from = c.from ? `<p class="crosspost-note"><i class="fa-solid fa-right-left" aria-hidden="true"></i> Crossposted from <strong>${esc(c.from.space)}</strong>: <a href="${esc(c.from.url)}">${esc(c.from.title)}</a></p>` : '';
const to = (c.to || []).length ? `<p class="crosspost-note small muted">Also in ${c.to.map((x) => `<a href="${esc(x.url)}">${esc(x.space)}</a>`).join(', ')}</p>` : '';
const to = (c.to || []).length ? `<p class="crosspost-note small muted">Also in ${c.to.map((x) => `<a href="${esc(x.url)}">${esc(x.space)}</a>`).join(', ')}</p>` : '';
- const form = (c.targets || []).length ? `<details class="crosspost-form"><summary><i class="fa-solid fa-shuffle" aria-hidden="true"></i> Crosspost</summary>
+ const form = (c.targets || []).length ? `<details class="crosspost-form"><summary><i class="fa-solid fa-right-left" aria-hidden="true"></i> Crosspost</summary>
<form class="inline-form" method="post" action="${esc(base)}/crosspost"><label class="sr-only" for="crosspost-to">Crosspost to</label><select id="crosspost-to" name="to">${c.targets.map((t) => `<option value="${esc(t.slug)}">${esc(t.name)}</option>`).join('')}</select><button class="btn btn-sm" type="submit">Crosspost</button></form></details>` : '';
<form class="inline-form" method="post" action="${esc(base)}/crosspost"><label class="sr-only" for="crosspost-to">Crosspost to</label><select id="crosspost-to" name="to">${c.targets.map((t) => `<option value="${esc(t.slug)}">${esc(t.name)}</option>`).join('')}</select><button class="btn btn-sm" type="submit">Crosspost</button></form></details>` : '';
return from || to || form ? `<div class="crosspost">${from}${to}${form}</div>` : '';
return from || to || form ? `<div class="crosspost">${from}${to}${form}</div>` : '';
}
}
@@ -143,8 +143,9 @@ function renderPage(o) {
@@ -143,8 +143,9 @@ function renderPage(o) {
...(o.styles || []).map((name) => `<link rel="stylesheet" href="${ovServe.url(name)}">`),
...(o.styles || []).map((name) => `<link rel="stylesheet" href="${ovServe.url(name)}">`),
...(o.inlineStyles || []).map((css) => `<style>${css}</style>`),
...(o.inlineStyles || []).map((css) => `<style>${css}</style>`),
`<link rel="stylesheet" href="${asset('css/community.css')}">`,
`<link rel="stylesheet" href="${asset('css/community.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">',
`<script src="${asset('js/community.js')}" defer></script>`,
`<script src="${asset('js/community.js')}" defer></script>`,
+ // 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>`,
// openvibe-shared items.js + items.css: community.js loads them only on a page that shows someone.
// openvibe-shared items.js + items.css: community.js loads them only on a page that shows someone.
`<meta name="ov-items" content="${escapeHtml(ovServe.url('items.js'))} ${escapeHtml(ovServe.url('items.css'))}">`,
`<meta name="ov-items" content="${escapeHtml(ovServe.url('items.js'))} ${escapeHtml(ovServe.url('items.css'))}">`,
// openvibe-shared kit-web.js (rails' arrows, card menus, copy buttons): community.js loads it only on a page
// openvibe-shared kit-web.js (rails' arrows, card menus, copy buttons): community.js loads it only on a page
new file mode 100644
new file mode 100644
@@ -0,0 +1,79 @@
@@ -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.Community'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.Community 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/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`);
@@ -15,8 +15,8 @@ const BUDGETS = {
@@ -15,8 +15,8 @@ const BUDGETS = {
// questions with their FAQPage JSON-LD; was 26 at 22.0
// questions with their FAQPage JSON-LD; was 26 at 22.0
htmlBrotliKB: 10.3, // 10.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 8.6 with the Part of OpenVibe section (Shared 4.13, 2026-10-26); 7.3 on 10-22 (the same shelves); was 6.5 at 5.7
htmlBrotliKB: 10.3, // 10.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 8.6 with the Part of OpenVibe section (Shared 4.13, 2026-10-26); 7.3 on 10-22 (the same shelves); was 6.5 at 5.7
jsFiles: 7, // 6: openvibe-shared/shell adds web-runtime.js (raised with the shell, 2026-10-04)
jsFiles: 7, // 6: openvibe-shared/shell adds web-runtime.js (raised with the shell, 2026-10-04)
- jsRawKB: 340, // 339.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 338.3 with openvibe-shared 4.64.0: the look travels between sites, the Look panel in the menu (2026-10-31); 337.5 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 335.4 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 332.4 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 329.8 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); 322 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 314 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; 306.5 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 300.7 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 286.6 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 276.8 with shared 4.11.0's seasonal navbar; 267.5 with openvibe-shared 4.9.0 (status text twins in every theme, 2026-10-23); 251.3 (was 208.8 before the shell's web-runtime.js)
- jsBrotliKB: 84, // 83.1 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 82.3 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 81.7 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); 80.2 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 78.1 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; 74.4 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 71.7 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 69.1 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 65.5 with shared 4.11.0's seasonal navbar; 59.9 (was 48.9)
+ jsRawKB: 408, // 408: /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); 339.1 with openvibe-shared 4.66.0: the mark with depth and motion, ov-fx loaded on request (2026-10-31); 338.3 with openvibe-shared 4.64.0: the look travels between sites, the Look panel in the menu (2026-10-31); 337.5 with openvibe-shared 4.59.0: the Pair mark beside the name, set in the brand face (2026-10-31); 335.4 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 332.4 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 329.8 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); 322 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 314 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; 306.5 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 300.7 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 286.6 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 276.8 with shared 4.11.0's seasonal navbar; 267.5 with openvibe-shared 4.9.0 (status text twins in every theme, 2026-10-23); 251.3 (was 208.8 before the shell's web-runtime.js)
+ jsBrotliKB: 100.5, // 100.5: /shared/ov-glyphs.js in place of Font Awesome (2026-11-01, plan L54 K4); 83.1 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 82.3 with openvibe-shared 4.52.0: the Dock's loader and its Chats tab (2026-10-30); 81.7 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); 80.2 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 78.1 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; 74.4 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 71.7 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 69.1 Shared 4.12.0: the light/dark switch, the seasonal mark and the theme pairs; 65.5 with shared 4.11.0's seasonal navbar; 59.9 (was 48.9)
cssFiles: 5, // 5 the short home: + kit.css (numbers, lists, chips, the fold) and room.css (cards and rails), the shared kit's look; 3
cssFiles: 5, // 5 the short home: + kit.css (numbers, lists, chips, the fold) and room.css (cards and rails), the shared kit's look; 3
cssRawKB: 120, // 117.7 openvibe-shared 4.71.0 (2026-11-01: the navbar in OpenVibe glyphs, the icons setting); 114.3 with openvibe-shared 4.62.0: the shared sidebar in kit.css, the glyphs and their fonts (2026-10-31); 109.0 the phone home (2026-10-30: the chips and the fold rules); 104.8 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); 102.6 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 97.3 the short home (2026-10-29): kit.css 25.6 KB + room.css 15.7 KB, while community.css sheds the old shelves' rules and the page 10 of its 15 phone screens; 54.9 openvibe-shared 4.26.1 (hero visual); 47.2 with Shared 4.13.0 (2026-10-26); 40.6 on 10-22 (the home shelves' styles in community.css); was 40 at 36.5 with the showcase home
cssRawKB: 120, // 117.7 openvibe-shared 4.71.0 (2026-11-01: the navbar in OpenVibe glyphs, the icons setting); 114.3 with openvibe-shared 4.62.0: the shared sidebar in kit.css, the glyphs and their fonts (2026-10-31); 109.0 the phone home (2026-10-30: the chips and the fold rules); 104.8 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); 102.6 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 97.3 the short home (2026-10-29): kit.css 25.6 KB + room.css 15.7 KB, while community.css sheds the old shelves' rules and the page 10 of its 15 phone screens; 54.9 openvibe-shared 4.26.1 (hero visual); 47.2 with Shared 4.13.0 (2026-10-26); 40.6 on 10-22 (the home shelves' styles in community.css); was 40 at 36.5 with the showcase home
cssBrotliKB: 25, // 23.8 with openvibe-shared 4.62.0: the shared sidebar in kit.css, the glyphs and their fonts (2026-10-31); 22.1 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); 21.7 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 20.4 the short home (kit.css + room.css, as above; was 11.5 once Kit 2 had its own files in openvibe-shared v4.37.0); 9.5 with Shared 4.13.0 (2026-10-26); 7.7 // raised 11 -> 12 for the openvibe-shared Kit 2 pin (kit.css grew)
cssBrotliKB: 25, // 23.8 with openvibe-shared 4.62.0: the shared sidebar in kit.css, the glyphs and their fonts (2026-10-31); 22.1 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); 21.7 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 20.4 the short home (kit.css + room.css, as above; was 11.5 once Kit 2 had its own files in openvibe-shared v4.37.0); 9.5 with Shared 4.13.0 (2026-10-26); 7.7 // raised 11 -> 12 for the openvibe-shared Kit 2 pin (kit.css grew)
@@ -93,7 +93,7 @@ const { boot, check, done } = require('./helpers/app');
@@ -93,7 +93,7 @@ const { boot, check, done } = require('./helpers/app');
has(head, '<link rel="alternate" type="application/rss+xml" title="OpenVibe.Community — latest pastes" href="/feed.xml">');
has(head, '<link rel="alternate" type="application/rss+xml" title="OpenVibe.Community — latest pastes" href="/feed.xml">');
has(head, '/css/community.css?v=');
has(head, '/css/community.css?v=');
has(head, '/js/community.js?v=');
has(head, '/js/community.js?v=');
- has(head, 'font-awesome');
+ assert.ok(!head.includes('font-awesome'), 'the head carries no Font Awesome stylesheet');
has(head, '<link rel="icon"');
has(head, '<link rel="icon"');
assert.match(head, /<meta name="ov-boost" content="community@[^"]+">/);
assert.match(head, /<meta name="ov-boost" content="community@[^"]+">/);
for (const js of ['theme-loader.js', 'navbar.js', 'footer.js', 'boost.js']) has(head, `/shared/${js}?v=`);
for (const js of ['theme-loader.js', 'navbar.js', 'footer.js', 'boost.js']) has(head, `/shared/${js}?v=`);