openvibers/openvibe.community Commits

Icon set and manifest; clearer home copy; vendor sync

OP OpenVibers committed 18 Sept 2026 · 19cc8dd34e · 11 files, +125 −7


Changes

ddiff --git a/public/assets/logo-192.png b/public/assets/logo-192.png
nnew file mode 100644
iindex 0000000..d5d4281
BBinary files /dev/null and b/public/assets/logo-192.png differ
ddiff --git a/public/assets/logo-512.png b/public/assets/logo-512.png
nnew file mode 100644
iindex 0000000..17b1c31
BBinary files /dev/null and b/public/assets/logo-512.png differ
ddiff --git a/public/assets/logo-72.png b/public/assets/logo-72.png
nnew file mode 100644
iindex 0000000..51ad5c3
BBinary files /dev/null and b/public/assets/logo-72.png differ
ddiff --git a/public/assets/logo-maskable-192.png b/public/assets/logo-maskable-192.png
nnew file mode 100644
iindex 0000000..c07b4a9
BBinary files /dev/null and b/public/assets/logo-maskable-192.png differ
ddiff --git a/public/assets/logo-maskable-512.png b/public/assets/logo-maskable-512.png
nnew file mode 100644
iindex 0000000..8d3dbb1
BBinary files /dev/null and b/public/assets/logo-maskable-512.png differ
ddiff --git a/public/assets/logo.svg b/public/assets/logo.svg
nnew file mode 100644
iindex 0000000..8554ef9
---- /dev/null
++++ b/public/assets/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="#34d399"/><stop offset="1" stop-color="#2563eb"/></linearGradient>
++<radialGradient id="glow"><stop offset="0" stop-color="#34d399" stop-opacity=".34"/><stop offset="1" stop-color="#34d399" 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/public/manifest.webmanifest b/public/manifest.webmanifest
nnew file mode 100644
iindex 0000000..765418d
---- /dev/null
++++ b/public/manifest.webmanifest
@@@ -0,0 +1,48 @@
++{
++ "name": "OpenVibe.Community",
++ "short_name": "Community",
++ "description": "Pastes, posts and people on the open network",
++ "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/render/layout.js b/server/render/layout.js
iindex a99dd50..e4afdd6 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -115,7 +115,7 @@ ${o.modified ? `<meta property="article:modified_time" content="${escapeHtml(o.m
<meta name="twitter:title" content="${escapeHtml(o.title || SITE_NAME)}">
<meta name="twitter:description" content="${escapeHtml(description)}">
<meta name="twitter:image" content="${escapeHtml(ogImage)}">
--${require('openvibe-shared/app-icon').headTags({ site: 'community' })}
++${require('openvibe-shared/app-icon').headTags({ site: 'community', iconBase: '/assets' })}
<link rel="alternate" type="application/rss+xml" title="${SITE_NAME} — latest pastes" href="/feed.xml">
<script src="${NETWORK_URL}/shared/theme-loader.js"></script>
<link rel="stylesheet" href="${asset('css/community.css')}">
ddiff --git a/server/render/pages.js b/server/render/pages.js
iindex fa1e570..4668627 100644
---- a/server/render/pages.js
++++ b/server/render/pages.js
@@@ -90,16 +90,16 @@ function homePage({ latest, trending, languages, user }) {
const body = `
<section class="hero">
<p class="eyebrow">OpenVibe.Community</p>
-- <h1>The people of OpenVibe</h1>
-- <p class="lede">A community-run home for the things the network makes together — starting with pastes. Open source, built in the open, and free speech within the rules.</p>
++ <h1>Share it with a link. Talk about it here.</h1>
++ <p class="lede">Paste code, logs, configs or a screenshot and get a short link that works anywhere. Reading and sharing need no account. Sign in with your OpenVibe account to edit, comment and keep a list of your own. Spaces and threads are next.</p>
<div class="hero-actions">
<a class="btn btn-primary" href="/new"><i class="fa-solid fa-plus" aria-hidden="true"></i> Start a paste</a>
<a class="btn" href="/pastes"><i class="fa-solid fa-paste" aria-hidden="true"></i> Browse pastes</a>
${user ? `<a class="btn btn-ghost" href="/my"><i class="fa-solid fa-user" aria-hidden="true"></i> My pastes</a>` : `<a class="btn btn-ghost" href="/auth/login?next=%2F"><i class="fa-solid fa-right-to-bracket" aria-hidden="true"></i> Sign in with your OpenVibe account</a>`}
</div>
<ul class="hero-points">
-- <li><i class="fa-solid fa-people-group" aria-hidden="true"></i> Community-run — moderated by the people using it</li>
-- <li><i class="fa-solid fa-comment" aria-hidden="true"></i> Free speech within the rules — say it, own it</li>
++ <li><i class="fa-solid fa-people-group" aria-hidden="true"></i> Run by the people using it, moderated in the open</li>
++ <li><i class="fa-solid fa-comment" aria-hidden="true"></i> Open expression within the rules: say it, own it</li>
<li><i class="fa-brands fa-github" aria-hidden="true"></i> Open source — <a href="https://github.com/OpenVibers/OpenVibe.Community" rel="noopener">read the code</a></li>
</ul>
</section>
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,