openvibers/openvibe.media Commits

Brand assets and manifest; vendor sync

OP OpenVibers committed 18 Sept 2026 · 9d43fe709d · 11 files, +124 −3


Changes

ddiff --git a/server/brand/logo-192.png b/server/brand/logo-192.png
nnew file mode 100644
iindex 0000000..42bf776
BBinary files /dev/null and b/server/brand/logo-192.png differ
ddiff --git a/server/brand/logo-512.png b/server/brand/logo-512.png
nnew file mode 100644
iindex 0000000..0be7943
BBinary files /dev/null and b/server/brand/logo-512.png differ
ddiff --git a/server/brand/logo-72.png b/server/brand/logo-72.png
nnew file mode 100644
iindex 0000000..85d06ac
BBinary files /dev/null and b/server/brand/logo-72.png differ
ddiff --git a/server/brand/logo-maskable-192.png b/server/brand/logo-maskable-192.png
nnew file mode 100644
iindex 0000000..58affad
BBinary files /dev/null and b/server/brand/logo-maskable-192.png differ
ddiff --git a/server/brand/logo-maskable-512.png b/server/brand/logo-maskable-512.png
nnew file mode 100644
iindex 0000000..80eff50
BBinary files /dev/null and b/server/brand/logo-maskable-512.png differ
ddiff --git a/server/brand/logo.svg b/server/brand/logo.svg
nnew file mode 100644
iindex 0000000..19e22a3
---- /dev/null
++++ b/server/brand/logo.svg
@@@ -0,0 +1,8 @@
++<svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 -32 64 64"><defs><linearGradient id="ring" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fbbf24"/><stop offset="1" stop-color="#2563eb"/></linearGradient>
++<radialGradient id="glow"><stop offset="0" stop-color="#fbbf24" stop-opacity=".34"/><stop offset="1" stop-color="#fbbf24" stop-opacity="0"/></radialGradient>
++<radialGradient id="bg" cx=".5" cy=".38" r=".8"><stop offset="0" stop-color="#16223a"/><stop offset=".6" stop-color="#0a0f1c"/><stop offset="1" stop-color="#060912"/></radialGradient></defs><rect x="-32" y="-32" width="64" height="64" rx="15" fill="url(#bg)"/><g transform="scale(1.22)">
++<circle r="21" fill="url(#glow)"/>
++<circle r="18" fill="none" stroke="url(#ring)" stroke-width="4" opacity=".38"/>
++<path d="M0,-18 A18,18 0 0,1 17.4,4.6" fill="none" stroke="url(#ring)" stroke-width="4.4" stroke-linecap="round"/>
++<path d="M-9.5,-7 L0,10 L9.5,-7" fill="none" stroke="#fff" stroke-width="4.8" stroke-linecap="round" stroke-linejoin="round"/>
++<circle cx="17.4" cy="4.6" r="3.2" fill="#ffffff"/></g></svg>
\\ No newline at end of file
ddiff --git a/server/brand/manifest.webmanifest b/server/brand/manifest.webmanifest
nnew file mode 100644
iindex 0000000..d3f22bd
---- /dev/null
++++ b/server/brand/manifest.webmanifest
@@@ -0,0 +1,48 @@
++{
++ "name": "OpenVibe.Media",
++ "short_name": "Media",
++ "description": "Every public video, clip and file on OpenVibe",
++ "start_url": "/",
++ "scope": "/",
++ "display": "standalone",
++ "background_color": "#0a0f1c",
++ "theme_color": "#3b82f6",
++ "icons": [
++ {
++ "src": "/assets/logo-72.png",
++ "sizes": "72x72",
++ "type": "image/png",
++ "purpose": "any"
++ },
++ {
++ "src": "/assets/logo-192.png",
++ "sizes": "192x192",
++ "type": "image/png",
++ "purpose": "any"
++ },
++ {
++ "src": "/assets/logo-512.png",
++ "sizes": "512x512",
++ "type": "image/png",
++ "purpose": "any"
++ },
++ {
++ "src": "/assets/logo-maskable-192.png",
++ "sizes": "192x192",
++ "type": "image/png",
++ "purpose": "maskable"
++ },
++ {
++ "src": "/assets/logo-maskable-512.png",
++ "sizes": "512x512",
++ "type": "image/png",
++ "purpose": "maskable"
++ },
++ {
++ "src": "/assets/logo.svg",
++ "sizes": "any",
++ "type": "image/svg+xml",
++ "purpose": "any"
++ }
++ ]
++}
ddiff --git a/server/index.js b/server/index.js
iindex 5417b5e..9a51a44 100644
---- a/server/index.js
++++ b/server/index.js
@@@ -39,6 +39,9 @@ app.use((req, _res, next) => {
next();
});
{ const legal = require('openvibe-shared/legal'); app.get(legal.PATHS, legal.handler({ id: 'media', service: 'media', host: 'openvibe.media', name: 'OpenVibe.Media', profile: 'hosting' })); app.get('/tos', (_req, res) => res.redirect(301, '/terms')); }
++// Brand assets: icon set + web app manifest generated by openvibe-shared/scripts/build-app-icons.js
++app.use('/assets', express.static(require('path').join(__dirname, 'brand'), { maxAge: '7d' }));
++app.get('/manifest.webmanifest', (_req, res) => res.type('application/manifest+json').sendFile(require('path').join(__dirname, 'brand', 'manifest.webmanifest')));
const userAuth = require('./user-auth');
const userAuthConfig = {
baseUrl: config.publicUrl, networkUrl: config.network.url, networkInternalUrl: config.network.internalUrl,
ddiff --git a/server/public/browse.js b/server/public/browse.js
iindex 3936a93..6331cb5 100644
---- a/server/public/browse.js
++++ b/server/public/browse.js
@@@ -266,7 +266,7 @@ nav.tabs a .n{opacity:.75;font-size:.78rem;margin-left:.3rem}
.empty{padding:3rem;text-align:center;color:var(--muted)}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
--${require('openvibe-shared/app-icon').headTags({ site: 'media' })}
++${require('openvibe-shared/app-icon').headTags({ site: 'media', iconBase: '/assets' })}
</head><body>
<div id="navbar-mount"></div>
<header class="intro"><h1>Every public file on the network</h1><span class="sub">videos, clips, images, pastes &amp; thumbnails from all OpenVibe sites</span></header>
ddiff --git a/vendor/openvibe-shared/navbar.js b/vendor/openvibe-shared/navbar.js
iindex 0285bf3..8c1823f 100644
---- a/vendor/openvibe-shared/navbar.js
++++ b/vendor/openvibe-shared/navbar.js
@@@ -99,6 +99,14 @@
.ovnav-launcher .ovl-soon { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 4px 4px; }
.ovnav-launcher .ovl-soon a { font-size: 12px; font-weight: 600; padding: 4px 9px; border-radius: 999px; border: 1px solid var(--border, rgba(255,255,255,.1)); color: var(--text-secondary, #a8b3c4); }
.ovnav-launcher .ovl-soon a:hover { border-color: var(--accent, #3b82f6); color: var(--text-primary, #e6edf7); background: none; }
++ .ovnav-launcher .ovl-display { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; padding: 10px 4px 2px; border-top: 1px solid var(--border, rgba(255,255,255,.08)); font-size: 12px; color: var(--text-muted, #7d8aa0); }
++ .ovnav-launcher .ovl-display[hidden] { display: none; }
++ .ovnav-launcher .ovl-dl { font-weight: 700; letter-spacing: .6px; text-transform: uppercase; font-size: 11px; margin-right: 2px; }
++ .ovnav-launcher .ovl-seg { display: inline-flex; border: 1px solid var(--border, rgba(255,255,255,.12)); border-radius: 9px; overflow: hidden; }
++ .ovnav-launcher .ovl-seg button { all: unset; cursor: pointer; padding: 5px 9px; font-size: 12px; font-weight: 650; color: var(--text-secondary, #a8b3c4); }
++ .ovnav-launcher .ovl-seg button[aria-pressed="true"] { background: var(--accent, #3b82f6); color: var(--on-accent, #fff); }
++ .ovnav-launcher .ovl-seg button:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: -2px; }
++ .ovnav-launcher .ovl-display a { margin-left: auto; color: var(--accent-light, var(--accent, #60a5fa)); font-weight: 600; }
.ovnav-launcher .ovl-empty { padding: 18px 8px; color: var(--text-secondary, #a8b3c4); font-size: 13px; }
@media (max-width: 480px) { .ovnav-launcher { left: 8px; } .ovnav-launcher .ovl-fams { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) { .ovnav-launcher { transition: none; } }
@@@ -643,7 +651,8 @@
if (panel && panel.classList.contains('open')) return close();
if (!panel) {
panel = document.createElement('div'); panel.className = 'ovnav-launcher'; panel.setAttribute('role', 'dialog'); panel.setAttribute('aria-label', 'OpenVibe sites and tools');
-- panel.innerHTML = '<input type="search" class="ovl-q" placeholder="Find a tool or site" aria-label="Find a tool or site"><div class="ovl-body"></div>';
++ panel.innerHTML = '<input type="search" class="ovl-q" placeholder="Find a tool or site" aria-label="Find a tool or site"><div class="ovl-body"></div><div class="ovl-display" hidden></div>';
++ bindDisplayControls(panel.querySelector('.ovl-display'));
nav.appendChild(panel);
if (!root.OpenVibeIcons && !document.getElementById('ov-icons-loader')) { const sc = document.createElement('script'); sc.id = 'ov-icons-loader'; sc.src = 'https://openvibe.network/shared/ov-icons.js'; sc.async = true; document.head.appendChild(sc); }
let cat = null; paint(null, '');
@@@ -691,6 +700,21 @@
return `<span class="ovnav-sep" aria-hidden="true"></span>` + pick.map(n => `<a class="ovnav-net" href="${escapeAttr(n.url)}" title="${escapeAttr(n.tagline)}">${escapeAttr(n.name)}</a>`).join('');
}
++ /** Display settings in the launcher, for guests and members alike (theme-loader.js owns the state). */
++ function bindDisplayControls(box) {
++ const L = root.OpenVibeThemeLoader; if (!box || !L || !L.display) return;
++ const paint = () => {
++ const d = L.display.get();
++ const seg = (key, val, label, title) => `<button type="button" data-k="${key}" data-v="${val}" aria-pressed="${String(d[key]) === val}" title="${title}">${label}</button>`;
++ box.innerHTML = `<span class="ovl-dl">Display</span><span class="ovl-seg" role="group" aria-label="Text size">${seg('text', '100', 'A', 'Default text size')}${seg('text', '112', 'A+', 'Larger text')}${seg('text', '125', 'A++', 'Largest text')}</span>
++ <span class="ovl-seg" role="group" aria-label="Motion">${seg('motion', 'auto', 'Motion', 'Animations on')}${seg('motion', 'reduced', 'Calm', 'Reduce animations')}</span><a href="https://openvibe.network/themes">Themes</a>`;
++ 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);
++ paint();
++ }
++
// ── Notification bell (mounted by the navbar itself) ─────
function mountBell(nav, u) {
if (_config.notifications === false || !u || u.is_anon || !_config.token) return;
ddiff --git a/vendor/openvibe-shared/theme-loader.js b/vendor/openvibe-shared/theme-loader.js
iindex 94fb252..6a7a0eb 100644
---- a/vendor/openvibe-shared/theme-loader.js
++++ b/vendor/openvibe-shared/theme-loader.js
@@@ -241,11 +241,47 @@
}
// ── Auto-apply on load (synchronous, prevents FOUC) ─────
++ // ── Display preferences: motion + text size, one setting for every OpenVibe site ─────────────
++ // OpenVibeThemeLoader.display.get() → { motion: 'auto'|'reduced', text: '100'|'112'|'125' }
++ // OpenVibeThemeLoader.display.set({ motion, text }) applies here, in other tabs, and saves to the account
++ // Applied as <html data-ov-motion> and --ov-text-scale before first paint; the shared stylesheet
++ // below makes every shared component honour them, and sites can key their own CSS off the same hooks.
++ const DISPLAY_KEY = 'ov_display', DISPLAY_OPTS = { motion: ['auto', 'reduced'], text: ['100', '112', '125'] };
++ function readDisplay() {
++ const out = { motion: 'auto', text: '100' };
++ try { const d = JSON.parse(localStorage.getItem(DISPLAY_KEY) || 'null') || {}; for (const k in DISPLAY_OPTS) if (DISPLAY_OPTS[k].indexOf(String(d[k])) >= 0) out[k] = String(d[k]); } catch (e) { /* */ }
++ return out;
++ }
++ function applyDisplay(d) {
++ if (typeof document === 'undefined') return;
++ const el = document.documentElement;
++ if (d.motion === 'reduced') el.setAttribute('data-ov-motion', 'reduced'); else el.removeAttribute('data-ov-motion');
++ if (d.text === '100') { el.style.removeProperty('--ov-text-scale'); el.removeAttribute('data-ov-text'); } else { el.style.setProperty('--ov-text-scale', String(parseInt(d.text, 10) / 100)); el.setAttribute('data-ov-text', d.text); }
++ if (!document.getElementById('ov-display-css') && document.head) {
++ const st = document.createElement('style'); st.id = 'ov-display-css';
++ st.textContent = 'html[data-ov-text]{font-size:calc(100% * var(--ov-text-scale,1))}html[data-ov-motion=reduced] *,html[data-ov-motion=reduced] *::before,html[data-ov-motion=reduced] *::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}html[data-ov-motion=reduced] .ov-mark .o,html[data-ov-motion=reduced] .ov-icon .ovi-comet{display:none}';
++ document.head.appendChild(st);
++ }
++ try { window.dispatchEvent(new CustomEvent('ov:display', { detail: d })); } catch (e) { /* */ }
++ }
++ function setDisplay(patch, opts) {
++ const d = readDisplay();
++ for (const k in DISPLAY_OPTS) if (patch && DISPLAY_OPTS[k].indexOf(String(patch[k])) >= 0) d[k] = String(patch[k]);
++ try { localStorage.setItem(DISPLAY_KEY, JSON.stringify(d)); } catch (e) { /* */ }
++ applyDisplay(d);
++ if (!(opts && opts.local) && typeof fetch !== 'undefined') {
++ const token = authToken(); const headers = { 'Content-Type': 'application/json' }; if (token) headers.Authorization = 'Bearer ' + token;
++ if (token || /(?:^|; )ov_sso_hint=account/.test(document.cookie)) fetch(_config.apiBase + '/api/themes/me/display', { method: 'PUT', headers: headers, credentials: 'include', body: JSON.stringify(d) }).catch(function () {});
++ }
++ return d;
++ }
++ applyDisplay(readDisplay());
++
const activeId = resolveAndApply();
// The icon link may be parsed after this script: repaint once the head is complete.
if (typeof document !== 'undefined' && document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function () { _chromeKey = ''; syncChrome(); });
// A theme picked in another tab (or on another OpenVibe page of this site) applies here at once.
-- if (typeof window !== 'undefined') window.addEventListener('storage', function (e) { if (e.key === 'ov_theme') { try { resolveAndApply(); } catch (err) { /* */ } } });
++ if (typeof window !== 'undefined') window.addEventListener('storage', function (e) { if (e.key === 'ov_theme') { try { resolveAndApply(); } catch (err) { /* */ } } if (e.key === DISPLAY_KEY) applyDisplay(readDisplay()); });
// Look up a theme we don't ship in the built-in map (community/custom themes) from the
// central catalog. Without this, any non-built-in id fell through to the default 'vibe'
@@@ -271,6 +307,7 @@
fetch(_config.apiBase + '/api/themes/me/active', { headers: headers, credentials: 'include' })
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (data) {
++ if (data && data.display) { const cur = readDisplay(); if (JSON.stringify({ motion: data.display.motion || cur.motion, text: data.display.text || cur.text }) !== JSON.stringify(cur)) setDisplay(data.display, { local: true }); }
if (!data || !data.theme_id) return;
const serverId = data.theme_id;
const custom = (data.custom_variables && typeof data.custom_variables === 'object'
@@@ -292,6 +329,7 @@
// ── Public API ───────────────────────────────────────────
const OpenVibeThemeLoader = {
THEMES: THEMES,
++ display: { get: readDisplay, set: setDisplay, options: DISPLAY_OPTS },
apply: applyById,
applyVars: applyVars,
save: save,