openvibers/openvibe.community Commits
Shared app icon head tags; vendor sync
Changes
@@@ -103,7 +103,6 @@ function renderPage(o) {<meta name="description" content="${escapeHtml(description)}"><link rel="canonical" href="${escapeHtml(canonical)}"><meta name="robots" content="${escapeHtml(robots)}">--<meta name="theme-color" content="#0a0f1c"><meta property="og:site_name" content="${SITE_NAME}"><meta property="og:type" content="${escapeHtml(ogType)}"><meta property="og:title" content="${escapeHtml(o.title || SITE_NAME)}">@@@ -116,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)}">--<link rel="icon" href="/favicon.svg" type="image/svg+xml">++${require('openvibe-shared/app-icon').headTags({ site: 'community' })}<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')}">@@@ -378,7 +378,7 @@.openvibe-switcher-item.active { background: rgba(192,150,92,0.08); }.openvibe-switcher-item.active::after {content: '✓'; position: absolute; right: 12px; top: 50%; transform: translateY(-50%);-- color: var(--accent, #8b5cf6); font-weight: 700; font-size: 16px;++ color: var(--accent, #3b82f6); font-weight: 700; font-size: 16px;}.openvibe-switcher-item img { width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0; }.openvibe-switcher-item .info { flex: 1; line-height: 1.3; }@@@ -411,7 +411,7 @@transition: all .15s;}.openvibe-switcher-actions .btn-add {-- background: var(--accent, #8b5cf6); color: #fff;++ background: var(--accent, #3b82f6); color: #fff;}.openvibe-switcher-actions .btn-add:hover { background: var(--accent-dark, #a07840); }.openvibe-switcher-actions .btn-signout {nnew file mode 100644@@@ -0,0 +1,74 @@++'use strict';++// ═══════════════════════════════════════════════════════════════++// openvibe-shared/app-icon — one app icon design for every OpenVibe site.++//++// The icon is the OV mark as it appears in the navbar (ring, comet, white V, dot) on the network's++// deep navy, tinted per site. No text: a home-screen icon is a few millimetres wide.++// svg({ site, maskable }) full-bleed square SVG. maskable keeps the mark inside the 40 % radius++// safe zone, so round, squircle and teardrop masks never clip it.++// favicon({ site }) the same mark on a rounded tile, tuned for 16–32 px.++// manifest({ site, name, shortName, description, startUrl }) web app manifest with shared colours.++// SITES accent + dot colour per site (the dot is the one thing that differs,++// matching ov-mark.js variants: Live is "on air" red).++// PNG sets are rendered from these by scripts/build-app-icons.js.++// ═══════════════════════════════════════════════════════════════++const BG = '#0a0f1c', BG_EDGE = '#060912';++const SITES = {++ network: { accent: ['#60a5fa', '#2563eb'], dot: '#ffffff' },++ live: { accent: ['#60a5fa', '#2563eb'], dot: '#ef4444' },++ tools: { accent: ['#38bdf8', '#2563eb'], dot: '#ffffff' },++ community: { accent: ['#34d399', '#2563eb'], dot: '#ffffff' },++ games: { accent: ['#a78bfa', '#2563eb'], dot: '#facc15' },++ media: { accent: ['#fbbf24', '#2563eb'], dot: '#ffffff' },++};++const of = (site) => SITES[site] || SITES.network;++++function mark(site, scale) {++ // Drawn in a 48-unit box centred on 0,0 (same geometry as ov-mark.js), then scaled.++ const c = of(site);++ return `<g transform="scale(${scale})">++<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="${c.dot}"/></g>`;++}++++function defs(site) {++ const [a, b] = of(site).accent;++ return `<defs><linearGradient id="ring" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="${a}"/><stop offset="1" stop-color="${b}"/></linearGradient>++<radialGradient id="glow"><stop offset="0" stop-color="${a}" stop-opacity=".34"/><stop offset="1" stop-color="${a}" stop-opacity="0"/></radialGradient>++<radialGradient id="bg" cx=".5" cy=".38" r=".8"><stop offset="0" stop-color="#16223a"/><stop offset=".6" stop-color="${BG}"/><stop offset="1" stop-color="${BG_EDGE}"/></radialGradient></defs>`;++}++++/** Full-bleed square. `maskable`: mark radius ≤ 40 % of the half-width; otherwise it fills more of the tile. */++function svg({ site = 'network', maskable = true, size = 512 } = {}) {++ const scale = maskable ? 3.7 : 4.6; // 21u glow radius × 3.7 ≈ 78u of the 256u half-width… well inside 40 %++ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="-256 -256 512 512" width="${size}" height="${size}">${defs(site)}++<rect x="-256" y="-256" width="512" height="512" fill="url(#bg)"${maskable ? '' : ' rx="112"'}/>${mark(site, scale * (maskable ? 1.55 : 1.6))}</svg>`;++}++++/** Small sizes: thicker strokes read better, and the tile is rounded because nothing masks a favicon. */++function favicon({ site = 'network' } = {}) {++ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 -32 64 64">${defs(site)}<rect x="-32" y="-32" width="64" height="64" rx="15" fill="url(#bg)"/>${mark(site, 1.22)}</svg>`;++}++++function manifest({ site = 'network', name, shortName, description, startUrl = '/', iconBase = '/assets' }) {++ const sizes = [72, 192, 512];++ return {++ name, short_name: shortName || name, description, start_url: startUrl, scope: '/', display: 'standalone',++ background_color: BG, theme_color: '#3b82f6',++ icons: [...sizes.map(s => ({ src: `${iconBase}/logo-${s}.png`, sizes: `${s}x${s}`, type: 'image/png', purpose: 'any' })),++ ...[192, 512].map(s => ({ src: `${iconBase}/logo-maskable-${s}.png`, sizes: `${s}x${s}`, type: 'image/png', purpose: 'maskable' })),++ { src: `${iconBase}/logo.svg`, sizes: 'any', type: 'image/svg+xml', purpose: 'any' }],++ };++}++++/** <head> tags every site shares: SVG favicon (inline, no request), theme colour, and the PNG touch icon when the site ships one. */++function headTags({ site = 'network', iconBase = null } = {}) {++ const uri = 'data:image/svg+xml,' + encodeURIComponent(favicon({ site })).replace(/'/g, '%27');++ return [`<link rel="icon" type="image/svg+xml" href="${uri}">`, '<meta name="theme-color" content="#3b82f6">', `<meta name="color-scheme" content="dark light">`,++ iconBase ? `<link rel="apple-touch-icon" href="${iconBase}/logo-192.png">` : '', iconBase ? `<link rel="manifest" href="/manifest.webmanifest">` : ''].filter(Boolean).join('\n');++}++++module.exports = { svg, favicon, manifest, headTags, SITES, BG };@@@ -27,6 +27,7 @@})(typeof globalThis !== 'undefined' ? globalThis : this, function () {'use strict';++ const root = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : {});// ── Shared chrome data (https://openvibe.network/api/chrome) ─────────────────────────────// Sites ordered by real use, footer copy and per-site legal links. Cached per host for 30// minutes in localStorage and refreshed in the background, so pages paint from cache and the@@@ -281,7 +282,7 @@.ovf-col{display:flex;flex-direction:column;gap:8px;min-width:0}.ovf-col h3{margin:0 0 2px;font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--text-muted,#8b93ad)}.ovf-col a{color:var(--text-secondary,#b6bdd2);font-size:.86rem;text-decoration:none;line-height:1.4;width:fit-content;transition:color .15s,transform .15s}--.ovf-col a:hover{color:var(--accent,#a78bfa);transform:translateX(2px)}++.ovf-col a:hover{color:var(--accent,#60a5fa);transform:translateX(2px)}.ovf-more{font-weight:600;opacity:.85}.ovf-more i{font-size:.7rem;transition:transform .15s}.ovf-more:hover i{transform:translateX(3px)}.ovf-brand{display:inline-flex;align-items:center;gap:9px;color:var(--text-primary,#f1f4fb);font-size:1.05rem;font-weight:700;text-decoration:none}.ovf-brand--sm{font-size:.9rem}@@@ -289,23 +290,23 @@.ovf-social{display:flex;gap:8px;margin-top:4px}.ovf-social a{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;color:var(--text-secondary,#b6bdd2);background:var(--bg-tertiary,#1a1b28);border:1px solid var(--border,rgba(255,255,255,.08));font-size:.95rem;transition:color .15s,border-color .15s,transform .15s}--.ovf-social a:hover{color:var(--accent,#a78bfa);border-color:var(--accent,#a78bfa);transform:translateY(-2px)}++.ovf-social a:hover{color:var(--accent,#60a5fa);border-color:var(--accent,#60a5fa);transform:translateY(-2px)}.ovf-account{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 18px;margin-top:clamp(18px,2.5vw,26px);padding:10px 14px;border-radius:14px;-- background:color-mix(in srgb, var(--accent,#a78bfa) 8%, var(--bg-tertiary,#1a1b28));-- border:1px solid color-mix(in srgb, var(--accent,#a78bfa) 26%, var(--border,rgba(255,255,255,.08)));++ background:color-mix(in srgb, var(--accent,#60a5fa) 8%, var(--bg-tertiary,#1a1b28));++ border:1px solid color-mix(in srgb, var(--accent,#60a5fa) 26%, var(--border,rgba(255,255,255,.08)));animation:ovfIn .4s ease-out both}@keyframes ovfIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}.ovf-account-who{display:inline-flex;align-items:center;gap:8px;font-size:.84rem;color:var(--text-secondary,#b6bdd2)}.ovf-account-who strong{color:var(--text-primary,#f1f4fb)}.ovf-avatar{width:24px;height:24px;border-radius:50%;overflow:hidden;display:grid;place-items:center;-- background:var(--accent,#a78bfa);color:#fff;font-size:.7rem;font-weight:800;flex:none}++ background:var(--accent,#60a5fa);color:#fff;font-size:.7rem;font-weight:800;flex:none}.ovf-avatar img{width:100%;height:100%;object-fit:cover}.ovf-account-links{display:flex;flex-wrap:wrap;gap:6px}.ovf-account-links a{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:999px;font-size:.78rem;font-weight:600;color:var(--text-secondary,#b6bdd2);background:var(--bg-secondary,#12131c);border:1px solid var(--border,rgba(255,255,255,.08));text-decoration:none;transition:color .15s,border-color .15s,transform .15s}--.ovf-account-links a:hover{color:var(--accent,#a78bfa);border-color:var(--accent,#a78bfa);transform:translateY(-1px)}++.ovf-account-links a:hover{color:var(--accent,#60a5fa);border-color:var(--accent,#60a5fa);transform:translateY(-1px)}.ovf-account-links i{font-size:.72rem;opacity:.8}.ovf-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;margin-top:clamp(20px,3vw,30px);padding-top:16px;border-top:1px solid var(--border,rgba(255,255,255,.08));@@@ -313,7 +314,7 @@.ovf-copy{color:var(--text-muted,#8b93ad);font-size:.78rem}.ovf-legal-inline,.ovf-compact-links{display:flex;flex-wrap:wrap;gap:14px}.ovf-legal-inline a,.ovf-compact-links a{color:var(--text-muted,#8b93ad);font-size:.78rem;text-decoration:none;transition:color .15s}--.ovf-legal-inline a:hover,.ovf-compact-links a:hover{color:var(--accent,#a78bfa)}++.ovf-legal-inline a:hover,.ovf-compact-links a:hover{color:var(--accent,#60a5fa)}.ovf-compact-meta{color:var(--text-muted,#8b93ad);font-size:.74rem}@media (max-width:900px){.ovf-cols{grid-template-columns:repeat(2,minmax(0,1fr))}.ovf-col--brand{grid-column:1/-1}}@media (max-width:560px){@@@ -119,6 +119,7 @@ function page(kind, site) {catch { head = `<title>${esc(d.title)}</title><meta name="description" content="${esc(d.description)}"><link rel="canonical" href="${url}">`; }return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">${head}++${require('./app-icon').headTags({ site: site.id })}<script src="https://openvibe.network/shared/theme-loader.js"></script><style>${CSS}</style></head><body><noscript><p style="padding:12px 20px;margin:0"><a href="/">${esc(site.name)}</a></p></noscript><main>${article(kind, site)}</main><div id="ov-footer"></div>@@@ -102,19 +102,19 @@.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; } }-- .openvibe-navbar-brand .flame { font-size: 18px; text-decoration: none; color: var(--accent, #8b5cf6); display: inline-grid; place-items: center; width: 28px; height: 28px; flex: none; }++ .openvibe-navbar-brand .flame { font-size: 18px; text-decoration: none; color: var(--accent, #3b82f6); display: inline-grid; place-items: center; width: 28px; height: 28px; flex: none; }.openvibe-navbar-brand .name { display: inline-flex; align-items: baseline; font-size: 15px; font-weight: 700; letter-spacing: -.3px; white-space: nowrap; line-height: 1; }/* Segments: subdomain · OpenVibe · TLD. The part that names *this* site is the loud one. */.openvibe-navbar-brand .b-sub { color: var(--text-primary, #e0e0e0); }.openvibe-navbar-brand .b-core { color: var(--text-primary, #e0e0e0); }-- .openvibe-navbar-brand .b-tld { color: var(--accent-light, var(--accent, #a78bfa)); }++ .openvibe-navbar-brand .b-tld { color: var(--accent-light, var(--accent, #60a5fa)); }.openvibe-navbar-brand.has-sub .b-core { color: var(--text-secondary, #b0b0b8); font-weight: 600; }.openvibe-navbar-brand.has-sub .b-tld { color: var(--text-secondary, #b0b0b8); font-weight: 600; }-- .openvibe-navbar-brand .b-dot { color: var(--accent, #8b5cf6); opacity: .75; margin: 0 1px; font-weight: 800; }++ .openvibe-navbar-brand .b-dot { color: var(--accent, #3b82f6); opacity: .75; margin: 0 1px; font-weight: 800; }.openvibe-navbar-brand .b-sub, .openvibe-navbar-brand .b-core, .openvibe-navbar-brand .b-tld { transition: color .2s, opacity .2s; }-- .openvibe-navbar-brand:hover .b-tld, .openvibe-navbar-brand:hover .b-sub { color: var(--accent-light, #a78bfa); }++ .openvibe-navbar-brand:hover .b-tld, .openvibe-navbar-brand:hover .b-sub { color: var(--accent-light, #60a5fa); }.openvibe-navbar-brand:hover .b-dot { opacity: 1; }-- .openvibe-navbar-brand .b-tag { font-size: 9px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: var(--accent-light, #a78bfa); background: var(--accent-glow, rgba(139,92,246,.14)); border-radius: 4px; padding: 2px 5px; margin-left: 6px; align-self: center; }++ .openvibe-navbar-brand .b-tag { font-size: 9px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: var(--accent-light, #60a5fa); background: var(--accent-glow, rgba(59,130,246,.14)); border-radius: 4px; padding: 2px 5px; margin-left: 6px; align-self: center; }/* Compact: drop the trailing segments on narrow viewports, keep what identifies the page. */.openvibe-navbar[data-compact="always"] .openvibe-navbar-brand.has-sub .b-core,.openvibe-navbar[data-compact="always"] .openvibe-navbar-brand.has-sub .b-tld,@@@ -132,10 +132,10 @@.openvibe-navbar-links a .icon { margin-right: 5px; opacity: .8; }.openvibe-navbar-dropdown-recent { padding: 6px 8px 2px; border-bottom: 1px solid var(--border, #333340); }.openvibe-navbar-dropdown-recent .label { font-size: 10px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: var(--text-muted, #707080); padding: 2px 8px 4px; display: flex; justify-content: space-between; align-items: center; }-- .openvibe-navbar-dropdown-recent .label a { color: var(--accent-light, #a78bfa); text-decoration: none; font-weight: 600; letter-spacing: 0; text-transform: none; }++ .openvibe-navbar-dropdown-recent .label a { color: var(--accent-light, #60a5fa); text-decoration: none; font-weight: 600; letter-spacing: 0; text-transform: none; }.openvibe-navbar-dropdown-recent .item { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; font-size: 12px; color: var(--text-secondary, #b0b0b8); text-decoration: none; transition: background .12s; min-width: 0; }.openvibe-navbar-dropdown-recent .item:hover { background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); }-- .openvibe-navbar-dropdown-recent .item .icon { width: 18px; text-align: center; color: var(--accent-light, #a78bfa); flex: none; }++ .openvibe-navbar-dropdown-recent .item .icon { width: 18px; text-align: center; color: var(--accent-light, #60a5fa); flex: none; }.openvibe-navbar-dropdown-recent .item .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.openvibe-navbar-dropdown-recent .item .s { margin-left: auto; font-size: 10px; color: var(--text-muted, #707080); flex: none; }.openvibe-navbar-dropdown-menu .sep { height: 1px; background: var(--border, #333340); margin: 4px -8px; }@@@ -147,7 +147,7 @@transition: all .15s;}.openvibe-navbar-links a:hover { background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); }-- .openvibe-navbar-links a.active { background: var(--bg-tertiary, #2a2a38); color: var(--accent-light, #a78bfa); }++ .openvibe-navbar-links a.active { background: var(--bg-tertiary, #2a2a38); color: var(--accent-light, #60a5fa); }.openvibe-navbar-spacer { flex: 1; }@@@ -158,14 +158,14 @@border: 2px solid var(--border, #333340); transition: border-color .2s;object-fit: cover;}-- .openvibe-navbar-avatar:hover { border-color: var(--accent, #8b5cf6); }++ .openvibe-navbar-avatar:hover { border-color: var(--accent, #3b82f6); }.openvibe-navbar-login {padding: 6px 16px; border-radius: 6px; font-size: 13px; font-weight: 600;-- background: var(--accent, #8b5cf6); color: #fff; border: none; cursor: pointer;++ background: var(--accent, #3b82f6); color: #fff; border: none; cursor: pointer;transition: background .15s; text-decoration: none; display: inline-flex; align-items: center;}-- .openvibe-navbar-login:hover { background: var(--accent-dark, #6d28d9); }++ .openvibe-navbar-login:hover { background: var(--accent-dark, #2563eb); }.openvibe-navbar-dropdown {position: absolute; top: 48px; right: 8px;@@@ -186,7 +186,7 @@.openvibe-navbar-dropdown-header .info { line-height: 1.3; }.openvibe-navbar-dropdown-header .info .name { font-size: 14px; font-weight: 600; }.openvibe-navbar-dropdown-header .info .email { font-size: 11px; color: var(--text-muted, #707080); }-- .openvibe-navbar-dropdown-header .info .anon-tag { font-size: 10px; color: var(--accent-light, #a78bfa); }++ .openvibe-navbar-dropdown-header .info .anon-tag { font-size: 10px; color: var(--accent-light, #60a5fa); }.openvibe-navbar-dropdown-accounts {padding: 6px 8px; border-bottom: 1px solid var(--border, #333340);@@@ -199,7 +199,7 @@}.openvibe-navbar-dropdown-accounts .account-item:hover { background: var(--bg-hover, #2f2f3d); }.openvibe-navbar-dropdown-accounts .account-item img { width: 24px; height: 24px; border-radius: 50%; }-- .openvibe-navbar-dropdown-accounts .account-item.active { color: var(--accent-light, #a78bfa); font-weight: 600; }++ .openvibe-navbar-dropdown-accounts .account-item.active { color: var(--accent-light, #60a5fa); font-weight: 600; }.openvibe-navbar-dropdown-accounts .add-account {display: flex; align-items: center; gap: 8px; padding: 6px 8px;border-radius: 6px; cursor: pointer; font-size: 12px;@@@ -222,11 +222,11 @@.openvibe-navbar .openvibe-network-badge {font-size: 10px; padding: 2px 8px; border-radius: 4px;-- background: rgba(139,92,246,0.1); color: var(--accent-light, #a78bfa);++ background: rgba(59,130,246,0.1); color: var(--accent-light, #60a5fa);font-weight: 500; cursor: pointer; border: 1px solid transparent;transition: all .15s;}-- .openvibe-navbar .openvibe-network-badge:hover { border-color: var(--accent-dark, #6d28d9); }++ .openvibe-navbar .openvibe-network-badge:hover { border-color: var(--accent-dark, #2563eb); }@media (max-width: 600px) {.openvibe-navbar-links { display: none; }@@@ -665,6 +665,19 @@});}++ /** One anonymous page-view count per page load (host only), which is what orders the network's++ * navigation. Skipped when the browser asks not to be tracked. */++ let _counted = false;++ function countView() {++ if (_counted || _config.countViews === false) return; _counted = true;++ try {++ if (navigator.globalPrivacyControl || navigator.doNotTrack === '1') return;++ if (!/^https:$/.test(location.protocol)) return;++ const url = 'https://openvibe.network/api/chrome/hit';++ if (navigator.sendBeacon) navigator.sendBeacon(url);++ } catch { /* */ }++ }++/** The network's most used sites, after the page's own links (networkLinks: false turns it off). */function networkLinksHTML(pageLinks) {if (_config.networkLinks === false) return '';@@@ -1118,7 +1131,7 @@function makeAvatarPlaceholder(user, size = 64) {const initial = getAvatarInitial(user);-- const bg = user?.profile_color || '#8b5cf6';++ const bg = user?.profile_color || '#3b82f6';const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}"><rect width="100%" height="100%" rx="${Math.round(size / 2)}" fill="${bg}"/>@@@ -1200,13 +1213,13 @@</div><div class="openvibe-navbar-spacer"></div><div class="openvibe-navbar-right">-- <a class="openvibe-network-badge" href="https://openvibe.network" title="Connected to OpenVibe"><span class="ov-mark" data-size="16" data-static="1"></span> OpenVibe</a><div id="openvibe-bell-mount"></div>${u ? avatarImg(u, 64, 'openvibe-navbar-avatar', 'openvibe-avatar-btn') :`<a class="openvibe-navbar-login" id="openvibe-login-btn" href="${escapeAttr(loginHref)}">Sign In</a>`}</div>`;bindLauncher(nav);++ countView();// Pages that wire the bell themselves run right after init(); give them the first go.setTimeout(() => mountBell(nav, u), 0);@@@ -55,26 +55,26 @@.openvibe-toast-container { position: fixed; top: 82px; right: 14px; z-index: 100000; display: flex; flex-direction: column; gap: 6px; pointer-events: none; max-width: 340px; width: 100%; }.openvibe-toast { pointer-events: all; background: color-mix(in srgb, var(--bg-card, #22222c) 94%, transparent); border: 1px solid color-mix(in srgb, var(--border, #333340) 82%, transparent); border-radius: 10px; padding: 11px 12px; display: flex; gap: 10px; align-items: flex-start; box-shadow: 0 10px 24px rgba(0,0,0,0.28); animation: openvibe-toast-in .24s cubic-bezier(.34,1.56,.64,1); cursor: pointer; position: relative; overflow: hidden; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; color: var(--text-primary, #e0e0e0); transition: border-color .2s; }-- .openvibe-toast:hover { border-color: var(--accent, #8b5cf6); }++ .openvibe-toast:hover { border-color: var(--accent, #3b82f6); }.openvibe-toast.removing { animation: openvibe-toast-out .3s ease forwards; }.openvibe-toast-icon { font-size: 18px; flex-shrink: 0; line-height: 1; margin-top: 1px; }.openvibe-toast-icon img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; display: block; }.openvibe-toast-body { flex: 1; min-width: 0; }.openvibe-toast-title { font-weight: 600; font-size: 12px; margin-bottom: 2px; display: flex; align-items: center; gap: 6px; }-- .openvibe-toast-title .service-badge { font-size: 9px; padding: 1px 6px; border-radius: 3px; background: color-mix(in srgb, var(--accent, #8b5cf6) 18%, transparent); color: var(--accent-light, #a78bfa); font-weight: 500; }++ .openvibe-toast-title .service-badge { font-size: 9px; padding: 1px 6px; border-radius: 3px; background: color-mix(in srgb, var(--accent, #3b82f6) 18%, transparent); color: var(--accent-light, #60a5fa); font-weight: 500; }.openvibe-toast-msg { font-size: 11px; color: var(--text-secondary, #b0b0b8); line-height: 1.35; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }.openvibe-toast-close { position: absolute; top: 8px; right: 10px; background: none; border: none; color: var(--text-muted, #707080); cursor: pointer; font-size: 14px; padding: 2px; line-height: 1; }.openvibe-toast-close:hover { color: var(--text-primary, #e0e0e0); }-- .openvibe-toast-progress { position: absolute; bottom: 0; left: 0; height: 2px; background: var(--accent, #8b5cf6); }++ .openvibe-toast-progress { position: absolute; bottom: 0; left: 0; height: 2px; background: var(--accent, #3b82f6); }.openvibe-toast.priority-high { border-left: 3px solid var(--warning, #f39c12); }.openvibe-toast.priority-critical { border-left: 3px solid var(--live-red, #e74c3c); background: rgba(231,76,60,0.06); }.openvibe-toast-actions { display: flex; gap: 6px; margin-top: 8px; }.openvibe-toast-actions button { padding: 5px 12px; border-radius: 5px; border: 1px solid var(--border, #333340); background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); font-size: 11px; font-weight: 600; cursor: pointer; }-- .openvibe-toast-actions button.primary { background: var(--accent, #8b5cf6); color: #fff; border-color: var(--accent, #8b5cf6); }++ .openvibe-toast-actions button.primary { background: var(--accent, #3b82f6); color: #fff; border-color: var(--accent, #3b82f6); }.openvibe-bell { position: relative; cursor: pointer; padding: 6px; display: inline-flex; align-items: center; justify-content: center; }.openvibe-bell svg { width: 22px; height: 22px; fill: var(--text-secondary, #b0b0b8); transition: fill .2s; }-- .openvibe-bell:hover svg, .openvibe-bell.open svg { fill: var(--accent-light, #a78bfa); }++ .openvibe-bell:hover svg, .openvibe-bell.open svg { fill: var(--accent-light, #60a5fa); }.openvibe-bell .badge { position: absolute; top: 0; right: 0; min-width: 16px; height: 16px; padding: 0 4px; background: var(--live-red, #e74c3c); color: #fff; border-radius: 8px; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; line-height: 1; pointer-events: none; animation: openvibe-badge-pulse .4s ease; }.openvibe-bell .badge.hidden { display: none; }.openvibe-bell.ringing svg { animation: openvibe-bell-ring .6s ease; }@@@ -90,27 +90,27 @@.openvibe-nv-actions { display: flex; gap: 4px; align-items: center; }.openvibe-nv-actions button, .openvibe-nv-actions a { background: none; border: 1px solid transparent; color: var(--text-muted, #707080); cursor: pointer; font-size: 12px; padding: 5px 8px; border-radius: 6px; text-decoration: none; display: inline-flex; align-items: center; gap: 5px; line-height: 1; }.openvibe-nv-actions button:hover, .openvibe-nv-actions a:hover { background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); }-- .openvibe-nv-actions button.on { color: var(--accent-light, #a78bfa); border-color: color-mix(in srgb, var(--accent, #8b5cf6) 40%, transparent); }++ .openvibe-nv-actions button.on { color: var(--accent-light, #60a5fa); border-color: color-mix(in srgb, var(--accent, #3b82f6) 40%, transparent); }.openvibe-nv-actions button:disabled { opacity: .45; cursor: default; }.openvibe-nv-actions svg { width: 14px; height: 14px; fill: currentColor; }.openvibe-nv-tools { padding: 8px 12px; border-bottom: 1px solid var(--border, #333340); display: flex; flex-direction: column; gap: 8px; }.openvibe-nv-search { position: relative; }.openvibe-nv-search input { width: 100%; box-sizing: border-box; padding: 7px 28px 7px 30px; border-radius: 8px; border: 1px solid var(--border, #333340); background: var(--bg-input, var(--bg-tertiary, #1e1e28)); color: var(--text-primary, #e0e0e0); font-size: 12px; outline: none; }-- .openvibe-nv-search input:focus { border-color: var(--accent, #8b5cf6); }++ .openvibe-nv-search input:focus { border-color: var(--accent, #3b82f6); }.openvibe-nv-search svg { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; fill: var(--text-muted, #707080); pointer-events: none; }.openvibe-nv-search .clear { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--text-muted, #707080); cursor: pointer; font-size: 14px; display: none; }.openvibe-nv-search.has-q .clear { display: block; }.openvibe-nv-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }.openvibe-nv-chips button { border: 1px solid var(--border, #333340); background: transparent; color: var(--text-secondary, #b0b0b8); font-size: 11px; padding: 4px 10px; border-radius: 999px; cursor: pointer; line-height: 1.2; }-- .openvibe-nv-chips button:hover { border-color: var(--accent, #8b5cf6); color: var(--text-primary, #e0e0e0); }-- .openvibe-nv-chips button.active { background: color-mix(in srgb, var(--accent, #8b5cf6) 18%, transparent); border-color: var(--accent, #8b5cf6); color: var(--accent-light, #a78bfa); }++ .openvibe-nv-chips button:hover { border-color: var(--accent, #3b82f6); color: var(--text-primary, #e0e0e0); }++ .openvibe-nv-chips button.active { background: color-mix(in srgb, var(--accent, #3b82f6) 18%, transparent); border-color: var(--accent, #3b82f6); color: var(--accent-light, #60a5fa); }.openvibe-nv-chips select { margin-left: auto; border: 1px solid var(--border, #333340); background: var(--bg-input, var(--bg-tertiary, #1e1e28)); color: var(--text-secondary, #b0b0b8); font-size: 11px; padding: 4px 8px; border-radius: 8px; cursor: pointer; }.openvibe-nv-list { flex: 1; overflow-y: auto; padding: 4px 0; overscroll-behavior: contain; }.openvibe-notif-item { padding: 10px 12px 10px 14px; display: flex; gap: 10px; align-items: flex-start; cursor: pointer; transition: background .15s; border-left: 3px solid transparent; position: relative; }.openvibe-notif-item:hover { background: var(--bg-hover, #2f2f3d); }-- .openvibe-notif-item.unread { background: color-mix(in srgb, var(--accent, #8b5cf6) 7%, transparent); border-left-color: var(--accent, #8b5cf6); }++ .openvibe-notif-item.unread { background: color-mix(in srgb, var(--accent, #3b82f6) 7%, transparent); border-left-color: var(--accent, #3b82f6); }.openvibe-notif-item .icon { font-size: 20px; flex-shrink: 0; margin-top: 2px; width: 32px; text-align: center; }.openvibe-notif-item .icon img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; display: block; }.openvibe-notif-item .content { flex: 1; min-width: 0; }@@@ -126,9 +126,9 @@.openvibe-nv-empty .icon { font-size: 34px; margin-bottom: 8px; display: block; opacity: .5; }.openvibe-nv-more { display: flex; justify-content: center; padding: 10px; }.openvibe-nv-more button { border: 1px solid var(--border, #333340); background: transparent; color: var(--text-secondary, #b0b0b8); font-size: 12px; padding: 6px 14px; border-radius: 8px; cursor: pointer; }-- .openvibe-nv-more .spinner { width: 16px; height: 16px; border: 2px solid var(--border, #333340); border-top-color: var(--accent, #8b5cf6); border-radius: 50%; animation: openvibe-spin .8s linear infinite; }++ .openvibe-nv-more .spinner { width: 16px; height: 16px; border: 2px solid var(--border, #333340); border-top-color: var(--accent, #3b82f6); border-radius: 50%; animation: openvibe-spin .8s linear infinite; }.openvibe-nv-foot { padding: 8px 12px; border-top: 1px solid var(--border, #333340); font-size: 11px; color: var(--text-muted, #707080); display: flex; justify-content: space-between; align-items: center; gap: 8px; }-- .openvibe-nv-foot a { color: var(--accent-light, #a78bfa); text-decoration: none; }++ .openvibe-nv-foot a { color: var(--accent-light, #60a5fa); text-decoration: none; }.openvibe-nv-note { font-size: 11px; color: var(--text-muted, #707080); padding: 6px 12px; background: color-mix(in srgb, var(--warning, #f39c12) 10%, transparent); border-bottom: 1px solid var(--border, #333340); }@media (max-width: 460px) {@@@ -87,6 +87,8 @@.ov-icon .ovi-prog{fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;transform:rotate(-90deg);transform-origin:24px 24px;stroke-dasharray:125.7;stroke-dashoffset:125.7;transition:stroke-dashoffset .35s cubic-bezier(.2,.8,.2,1),opacity .2s;opacity:0}.ov-icon .ovi-glyph{fill:none;stroke:var(--ovi-glyph,var(--text-primary,#e6edf7));stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;transform-origin:24px 24px;transition:transform .25s cubic-bezier(.2,1.4,.3,1),stroke .2s}.ov-icon .ovi-glyph circle[r=".6"],.ov-icon .ovi-glyph circle[r=".7"]{fill:currentColor;stroke:none}++.ov-icon[style*="--ovi-size:2"] .ovi-glyph,.ov-icon[data-size^="2"] .ovi-glyph{stroke-width:2.3}++.ov-icon[style*="--ovi-size:2"] .ovi-ring,.ov-icon[data-size^="2"] .ovi-ring{stroke-width:3;opacity:.34}.ov-icon:hover .ovi-glyph,a:hover>.ov-icon .ovi-glyph,button:hover>.ov-icon .ovi-glyph{transform:scale(1.1)}.ov-icon:hover .ovi-bg,a:hover>.ov-icon .ovi-bg{opacity:.18}.ov-icon[data-fx=none] .ovi-comet{display:none}@@@ -14,7 +14,8 @@"./url-resolver": "./url-resolver.js","./seo": "./seo.js","./icons": "./ov-icons.js",-- "./legal": "./legal.js"++ "./legal": "./legal.js",++ "./app-icon": "./app-icon.js"},"license": "MIT","dependencies": {nnew file mode 100644@@@ -0,0 +1,18 @@++#!/usr/bin/env node++'use strict';++// Render a site's icon set from openvibe-shared/app-icon.++// node build-app-icons.js <site> <outDir> [--sharp <path-to-sharp-module>]++// Writes logo.svg (favicon), logo-72/192/512.png (rounded tile) and logo-maskable-192/512.png.++const fs = require('fs'); const path = require('path');++const icon = require('../app-icon');++const [site, outDir] = process.argv.slice(2);++if (!site || !outDir) { console.error('usage: build-app-icons.js <site> <outDir> [--sharp <module path>]'); process.exit(1); }++const i = process.argv.indexOf('--sharp');++const sharp = require(i > 0 ? path.resolve(process.argv[i + 1]) : 'sharp');++(async () => {++ fs.mkdirSync(outDir, { recursive: true });++ fs.writeFileSync(path.join(outDir, 'logo.svg'), icon.favicon({ site }));++ for (const s of [72, 192, 512]) await sharp(Buffer.from(icon.svg({ site, maskable: false })), { density: 300 }).resize(s, s).png({ compressionLevel: 9 }).toFile(path.join(outDir, `logo-${s}.png`));++ for (const s of [192, 512]) await sharp(Buffer.from(icon.svg({ site, maskable: true })), { density: 300 }).resize(s, s).png({ compressionLevel: 9 }).toFile(path.join(outDir, `logo-maskable-${s}.png`));++ console.log(`icons for ${site} → ${outDir}`);++})().catch(e => { console.error(e); process.exit(1); });@@@ -24,33 +24,33 @@const CSS = `.ovtip{position:fixed;z-index:30000;max-width:min(330px,calc(100vw - 24px));padding:12px 14px;border-radius:14px;background:linear-gradient(165deg,var(--bg-card,#171826) 0%,var(--bg-secondary,#12131c) 100%);-- border:1px solid color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 45%, var(--border,rgba(255,255,255,.1)));-- box-shadow:0 20px 50px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.04),0 0 30px color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 22%, transparent);++ border:1px solid color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 45%, var(--border,rgba(255,255,255,.1)));++ box-shadow:0 20px 50px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.04),0 0 30px color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 22%, transparent);color:var(--text-secondary,#b6bdd2);font-size:.83rem;line-height:1.55;pointer-events:none;opacity:0;transform:translateY(6px) scale(.97);transition:opacity .18s ease,transform .22s cubic-bezier(.2,1.3,.3,1);overflow:hidden}.ovtip.is-on{opacity:1;transform:none}.ovtip.is-interactive{pointer-events:auto}.ovtip::before{content:'';position:absolute;inset:0;pointer-events:none;-- background:radial-gradient(120% 90% at 0% 0%, color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 16%, transparent), transparent 60%)}++ background:radial-gradient(120% 90% at 0% 0%, color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 16%, transparent), transparent 60%)}/* A light sweep on open — draws the eye without moving anything around it. */.ovtip::after{content:'';position:absolute;top:0;bottom:0;left:-60%;width:45%;pointer-events:none;-- background:linear-gradient(100deg,transparent,color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 26%, transparent),transparent)}++ background:linear-gradient(100deg,transparent,color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 26%, transparent),transparent)}.ovtip.is-on::after{animation:ovtipSweep .9s cubic-bezier(.3,.7,.3,1) .1s 1}@keyframes ovtipSweep{to{transform:translateX(420%)}}.ovtip-head{display:flex;align-items:center;gap:8px;margin-bottom:6px;position:relative}.ovtip-ico{width:26px;height:26px;border-radius:8px;display:grid;place-items:center;flex:none;font-size:.78rem;color:#fff;-- background:linear-gradient(135deg,var(--ovtip-accent,var(--accent,#a78bfa)),color-mix(in srgb,var(--ovtip-accent,var(--accent,#a78bfa)) 55%,#000));-- box-shadow:0 4px 12px color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 45%, transparent)}++ background:linear-gradient(135deg,var(--ovtip-accent,var(--accent,#60a5fa)),color-mix(in srgb,var(--ovtip-accent,var(--accent,#60a5fa)) 55%,#000));++ box-shadow:0 4px 12px color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 45%, transparent)}.ovtip-title{font-size:.9rem;font-weight:800;color:var(--text-primary,#f1f4fb);letter-spacing:-.01em}.ovtip-body{position:relative}.ovtip-body b,.ovtip-body strong{color:var(--text-primary,#f1f4fb);font-weight:700}--.ovtip-body em{color:var(--ovtip-accent,var(--accent,#a78bfa));font-style:normal;font-weight:600}++.ovtip-body em{color:var(--ovtip-accent,var(--accent,#60a5fa));font-style:normal;font-weight:600}.ovtip-body code{background:var(--bg-tertiary,#1a1b28);padding:1px 6px;border-radius:5px;font-size:.78rem}.ovtip-foot{margin-top:8px;padding-top:8px;border-top:1px dashed var(--border,rgba(255,255,255,.1));font-size:.74rem;color:var(--text-muted,#8b93ad);display:flex;align-items:center;gap:6px;position:relative}.ovtip-arrow{position:fixed;z-index:30001;width:10px;height:10px;transform:rotate(45deg);opacity:0;transition:opacity .18s;-- background:var(--bg-card,#171826);border:1px solid color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 45%, var(--border,rgba(255,255,255,.1)))}++ background:var(--bg-card,#171826);border:1px solid color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 45%, var(--border,rgba(255,255,255,.1)))}.ovtip-arrow.is-on{opacity:1}.ovtip-arrow.from-top{border-top:0;border-left:0}.ovtip-arrow.from-bottom{border-bottom:0;border-right:0}@@@ -59,8 +59,8 @@padding:9px 12px;border-radius:10px;font:inherit;font-size:.8rem;font-weight:750;cursor:pointer;transition:transform .15s,filter .15s}.ovtip-go{color:#fff;border:0;-- background:linear-gradient(135deg,var(--ovtip-accent,var(--accent,#a78bfa)),color-mix(in srgb,var(--ovtip-accent,var(--accent,#a78bfa)) 60%,#000));-- box-shadow:0 6px 16px color-mix(in srgb,var(--ovtip-accent,var(--accent,#a78bfa)) 40%,transparent)}++ background:linear-gradient(135deg,var(--ovtip-accent,var(--accent,#60a5fa)),color-mix(in srgb,var(--ovtip-accent,var(--accent,#60a5fa)) 60%,#000));++ box-shadow:0 6px 16px color-mix(in srgb,var(--ovtip-accent,var(--accent,#60a5fa)) 40%,transparent)}.ovtip-dismiss{flex:0 0 auto;color:var(--text-muted,#8b93ad);background:none;border:1px solid var(--border,rgba(255,255,255,.12))}.ovtip-go:active,.ovtip-dismiss:active{transform:scale(.975)}.ovtip-go i{font-size:.72rem}@@@ -30,7 +30,7 @@animation: openvibe-card-in .2s ease; overflow: hidden;}.openvibe-usercard-banner {-- height: 60px; background: linear-gradient(135deg, var(--accent-dark, #a07840), var(--accent, #8b5cf6));++ height: 60px; background: linear-gradient(135deg, var(--accent-dark, #a07840), var(--accent, #3b82f6));position: relative;}.openvibe-usercard-avatar {
@@ -103,7 +103,6 @@ function renderPage(o) {
@@ -103,7 +103,6 @@ function renderPage(o) {
<meta name="description" content="${escapeHtml(description)}">
<meta name="description" content="${escapeHtml(description)}">
<link rel="canonical" href="${escapeHtml(canonical)}">
<link rel="canonical" href="${escapeHtml(canonical)}">
<meta name="robots" content="${escapeHtml(robots)}">
<meta name="robots" content="${escapeHtml(robots)}">
-<meta name="theme-color" content="#0a0f1c">
<meta property="og:site_name" content="${SITE_NAME}">
<meta property="og:site_name" content="${SITE_NAME}">
<meta property="og:type" content="${escapeHtml(ogType)}">
<meta property="og:type" content="${escapeHtml(ogType)}">
<meta property="og:title" content="${escapeHtml(o.title || SITE_NAME)}">
<meta property="og:title" content="${escapeHtml(o.title || SITE_NAME)}">
@@ -116,7 +115,7 @@ ${o.modified ? `<meta property="article:modified_time" content="${escapeHtml(o.m
@@ -116,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:title" content="${escapeHtml(o.title || SITE_NAME)}">
<meta name="twitter:description" content="${escapeHtml(description)}">
<meta name="twitter:description" content="${escapeHtml(description)}">
<meta name="twitter:image" content="${escapeHtml(ogImage)}">
<meta name="twitter:image" content="${escapeHtml(ogImage)}">
-<link rel="icon" href="/favicon.svg" type="image/svg+xml">
+${require('openvibe-shared/app-icon').headTags({ site: 'community' })}
<link rel="alternate" type="application/rss+xml" title="${SITE_NAME} — latest pastes" href="/feed.xml">
<link rel="alternate" type="application/rss+xml" title="${SITE_NAME} — latest pastes" href="/feed.xml">
<script src="${NETWORK_URL}/shared/theme-loader.js"></script>
<script src="${NETWORK_URL}/shared/theme-loader.js"></script>
<link rel="stylesheet" href="${asset('css/community.css')}">
<link rel="stylesheet" href="${asset('css/community.css')}">
@@ -378,7 +378,7 @@
@@ -378,7 +378,7 @@
.openvibe-switcher-item.active { background: rgba(192,150,92,0.08); }
.openvibe-switcher-item.active { background: rgba(192,150,92,0.08); }
.openvibe-switcher-item.active::after {
.openvibe-switcher-item.active::after {
content: '✓'; position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
content: '✓'; position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
- color: var(--accent, #8b5cf6); font-weight: 700; font-size: 16px;
+ color: var(--accent, #3b82f6); font-weight: 700; font-size: 16px;
}
}
.openvibe-switcher-item img { width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0; }
.openvibe-switcher-item img { width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0; }
.openvibe-switcher-item .info { flex: 1; line-height: 1.3; }
.openvibe-switcher-item .info { flex: 1; line-height: 1.3; }
@@ -411,7 +411,7 @@
@@ -411,7 +411,7 @@
transition: all .15s;
transition: all .15s;
}
}
.openvibe-switcher-actions .btn-add {
.openvibe-switcher-actions .btn-add {
- background: var(--accent, #8b5cf6); color: #fff;
+ background: var(--accent, #3b82f6); color: #fff;
}
}
.openvibe-switcher-actions .btn-add:hover { background: var(--accent-dark, #a07840); }
.openvibe-switcher-actions .btn-add:hover { background: var(--accent-dark, #a07840); }
.openvibe-switcher-actions .btn-signout {
.openvibe-switcher-actions .btn-signout {
new file mode 100644
new file mode 100644
@@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
+'use strict';
+// ═══════════════════════════════════════════════════════════════
+// openvibe-shared/app-icon — one app icon design for every OpenVibe site.
+//
+// The icon is the OV mark as it appears in the navbar (ring, comet, white V, dot) on the network's
+// deep navy, tinted per site. No text: a home-screen icon is a few millimetres wide.
+// svg({ site, maskable }) full-bleed square SVG. maskable keeps the mark inside the 40 % radius
+// safe zone, so round, squircle and teardrop masks never clip it.
+// favicon({ site }) the same mark on a rounded tile, tuned for 16–32 px.
+// manifest({ site, name, shortName, description, startUrl }) web app manifest with shared colours.
+// SITES accent + dot colour per site (the dot is the one thing that differs,
+// matching ov-mark.js variants: Live is "on air" red).
+// PNG sets are rendered from these by scripts/build-app-icons.js.
+// ═══════════════════════════════════════════════════════════════
+const BG = '#0a0f1c', BG_EDGE = '#060912';
+const SITES = {
+ network: { accent: ['#60a5fa', '#2563eb'], dot: '#ffffff' },
+ live: { accent: ['#60a5fa', '#2563eb'], dot: '#ef4444' },
+ tools: { accent: ['#38bdf8', '#2563eb'], dot: '#ffffff' },
+ community: { accent: ['#34d399', '#2563eb'], dot: '#ffffff' },
+ games: { accent: ['#a78bfa', '#2563eb'], dot: '#facc15' },
+ media: { accent: ['#fbbf24', '#2563eb'], dot: '#ffffff' },
+};
+const of = (site) => SITES[site] || SITES.network;
+
+function mark(site, scale) {
+ // Drawn in a 48-unit box centred on 0,0 (same geometry as ov-mark.js), then scaled.
+ const c = of(site);
+ return `<g transform="scale(${scale})">
+<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="${c.dot}"/></g>`;
+}
+
+function defs(site) {
+ const [a, b] = of(site).accent;
+ return `<defs><linearGradient id="ring" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="${a}"/><stop offset="1" stop-color="${b}"/></linearGradient>
+<radialGradient id="glow"><stop offset="0" stop-color="${a}" stop-opacity=".34"/><stop offset="1" stop-color="${a}" stop-opacity="0"/></radialGradient>
+<radialGradient id="bg" cx=".5" cy=".38" r=".8"><stop offset="0" stop-color="#16223a"/><stop offset=".6" stop-color="${BG}"/><stop offset="1" stop-color="${BG_EDGE}"/></radialGradient></defs>`;
+}
+
+/** Full-bleed square. `maskable`: mark radius ≤ 40 % of the half-width; otherwise it fills more of the tile. */
+function svg({ site = 'network', maskable = true, size = 512 } = {}) {
+ const scale = maskable ? 3.7 : 4.6; // 21u glow radius × 3.7 ≈ 78u of the 256u half-width… well inside 40 %
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="-256 -256 512 512" width="${size}" height="${size}">${defs(site)}
+<rect x="-256" y="-256" width="512" height="512" fill="url(#bg)"${maskable ? '' : ' rx="112"'}/>${mark(site, scale * (maskable ? 1.55 : 1.6))}</svg>`;
+}
+
+/** Small sizes: thicker strokes read better, and the tile is rounded because nothing masks a favicon. */
+function favicon({ site = 'network' } = {}) {
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="-32 -32 64 64">${defs(site)}<rect x="-32" y="-32" width="64" height="64" rx="15" fill="url(#bg)"/>${mark(site, 1.22)}</svg>`;
+}
+
+function manifest({ site = 'network', name, shortName, description, startUrl = '/', iconBase = '/assets' }) {
+ const sizes = [72, 192, 512];
+ return {
+ name, short_name: shortName || name, description, start_url: startUrl, scope: '/', display: 'standalone',
+ background_color: BG, theme_color: '#3b82f6',
+ icons: [...sizes.map(s => ({ src: `${iconBase}/logo-${s}.png`, sizes: `${s}x${s}`, type: 'image/png', purpose: 'any' })),
+ ...[192, 512].map(s => ({ src: `${iconBase}/logo-maskable-${s}.png`, sizes: `${s}x${s}`, type: 'image/png', purpose: 'maskable' })),
+ { src: `${iconBase}/logo.svg`, sizes: 'any', type: 'image/svg+xml', purpose: 'any' }],
+ };
+}
+
+/** <head> tags every site shares: SVG favicon (inline, no request), theme colour, and the PNG touch icon when the site ships one. */
+function headTags({ site = 'network', iconBase = null } = {}) {
+ const uri = 'data:image/svg+xml,' + encodeURIComponent(favicon({ site })).replace(/'/g, '%27');
+ return [`<link rel="icon" type="image/svg+xml" href="${uri}">`, '<meta name="theme-color" content="#3b82f6">', `<meta name="color-scheme" content="dark light">`,
+ iconBase ? `<link rel="apple-touch-icon" href="${iconBase}/logo-192.png">` : '', iconBase ? `<link rel="manifest" href="/manifest.webmanifest">` : ''].filter(Boolean).join('\n');
+}
+
+module.exports = { svg, favicon, manifest, headTags, SITES, BG };
@@ -27,6 +27,7 @@
@@ -27,6 +27,7 @@
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
'use strict';
'use strict';
+ const root = typeof globalThis !== 'undefined' ? globalThis : (typeof window !== 'undefined' ? window : {});
// ── Shared chrome data (https://openvibe.network/api/chrome) ─────────────────────────────
// ── Shared chrome data (https://openvibe.network/api/chrome) ─────────────────────────────
// Sites ordered by real use, footer copy and per-site legal links. Cached per host for 30
// Sites ordered by real use, footer copy and per-site legal links. Cached per host for 30
// minutes in localStorage and refreshed in the background, so pages paint from cache and the
// minutes in localStorage and refreshed in the background, so pages paint from cache and the
@@ -281,7 +282,7 @@
@@ -281,7 +282,7 @@
.ovf-col{display:flex;flex-direction:column;gap:8px;min-width:0}
.ovf-col{display:flex;flex-direction:column;gap:8px;min-width:0}
.ovf-col h3{margin:0 0 2px;font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--text-muted,#8b93ad)}
.ovf-col h3{margin:0 0 2px;font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase;color:var(--text-muted,#8b93ad)}
.ovf-col a{color:var(--text-secondary,#b6bdd2);font-size:.86rem;text-decoration:none;line-height:1.4;width:fit-content;transition:color .15s,transform .15s}
.ovf-col a{color:var(--text-secondary,#b6bdd2);font-size:.86rem;text-decoration:none;line-height:1.4;width:fit-content;transition:color .15s,transform .15s}
-.ovf-col a:hover{color:var(--accent,#a78bfa);transform:translateX(2px)}
+.ovf-col a:hover{color:var(--accent,#60a5fa);transform:translateX(2px)}
.ovf-more{font-weight:600;opacity:.85}.ovf-more i{font-size:.7rem;transition:transform .15s}.ovf-more:hover i{transform:translateX(3px)}
.ovf-more{font-weight:600;opacity:.85}.ovf-more i{font-size:.7rem;transition:transform .15s}.ovf-more:hover i{transform:translateX(3px)}
.ovf-brand{display:inline-flex;align-items:center;gap:9px;color:var(--text-primary,#f1f4fb);font-size:1.05rem;font-weight:700;text-decoration:none}
.ovf-brand{display:inline-flex;align-items:center;gap:9px;color:var(--text-primary,#f1f4fb);font-size:1.05rem;font-weight:700;text-decoration:none}
.ovf-brand--sm{font-size:.9rem}
.ovf-brand--sm{font-size:.9rem}
@@ -289,23 +290,23 @@
@@ -289,23 +290,23 @@
.ovf-social{display:flex;gap:8px;margin-top:4px}
.ovf-social{display:flex;gap:8px;margin-top:4px}
.ovf-social a{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;color:var(--text-secondary,#b6bdd2);
.ovf-social a{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;color:var(--text-secondary,#b6bdd2);
background:var(--bg-tertiary,#1a1b28);border:1px solid var(--border,rgba(255,255,255,.08));font-size:.95rem;transition:color .15s,border-color .15s,transform .15s}
background:var(--bg-tertiary,#1a1b28);border:1px solid var(--border,rgba(255,255,255,.08));font-size:.95rem;transition:color .15s,border-color .15s,transform .15s}
-.ovf-social a:hover{color:var(--accent,#a78bfa);border-color:var(--accent,#a78bfa);transform:translateY(-2px)}
+.ovf-social a:hover{color:var(--accent,#60a5fa);border-color:var(--accent,#60a5fa);transform:translateY(-2px)}
.ovf-account{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 18px;
.ovf-account{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px 18px;
margin-top:clamp(18px,2.5vw,26px);padding:10px 14px;border-radius:14px;
margin-top:clamp(18px,2.5vw,26px);padding:10px 14px;border-radius:14px;
- background:color-mix(in srgb, var(--accent,#a78bfa) 8%, var(--bg-tertiary,#1a1b28));
- border:1px solid color-mix(in srgb, var(--accent,#a78bfa) 26%, var(--border,rgba(255,255,255,.08)));
+ background:color-mix(in srgb, var(--accent,#60a5fa) 8%, var(--bg-tertiary,#1a1b28));
+ border:1px solid color-mix(in srgb, var(--accent,#60a5fa) 26%, var(--border,rgba(255,255,255,.08)));
animation:ovfIn .4s ease-out both}
animation:ovfIn .4s ease-out both}
@keyframes ovfIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@keyframes ovfIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.ovf-account-who{display:inline-flex;align-items:center;gap:8px;font-size:.84rem;color:var(--text-secondary,#b6bdd2)}
.ovf-account-who{display:inline-flex;align-items:center;gap:8px;font-size:.84rem;color:var(--text-secondary,#b6bdd2)}
.ovf-account-who strong{color:var(--text-primary,#f1f4fb)}
.ovf-account-who strong{color:var(--text-primary,#f1f4fb)}
.ovf-avatar{width:24px;height:24px;border-radius:50%;overflow:hidden;display:grid;place-items:center;
.ovf-avatar{width:24px;height:24px;border-radius:50%;overflow:hidden;display:grid;place-items:center;
- background:var(--accent,#a78bfa);color:#fff;font-size:.7rem;font-weight:800;flex:none}
+ background:var(--accent,#60a5fa);color:#fff;font-size:.7rem;font-weight:800;flex:none}
.ovf-avatar img{width:100%;height:100%;object-fit:cover}
.ovf-avatar img{width:100%;height:100%;object-fit:cover}
.ovf-account-links{display:flex;flex-wrap:wrap;gap:6px}
.ovf-account-links{display:flex;flex-wrap:wrap;gap:6px}
.ovf-account-links a{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:999px;font-size:.78rem;font-weight:600;
.ovf-account-links a{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;border-radius:999px;font-size:.78rem;font-weight:600;
color:var(--text-secondary,#b6bdd2);background:var(--bg-secondary,#12131c);border:1px solid var(--border,rgba(255,255,255,.08));
color:var(--text-secondary,#b6bdd2);background:var(--bg-secondary,#12131c);border:1px solid var(--border,rgba(255,255,255,.08));
text-decoration:none;transition:color .15s,border-color .15s,transform .15s}
text-decoration:none;transition:color .15s,border-color .15s,transform .15s}
-.ovf-account-links a:hover{color:var(--accent,#a78bfa);border-color:var(--accent,#a78bfa);transform:translateY(-1px)}
+.ovf-account-links a:hover{color:var(--accent,#60a5fa);border-color:var(--accent,#60a5fa);transform:translateY(-1px)}
.ovf-account-links i{font-size:.72rem;opacity:.8}
.ovf-account-links i{font-size:.72rem;opacity:.8}
.ovf-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;
.ovf-bar{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:10px;
margin-top:clamp(20px,3vw,30px);padding-top:16px;border-top:1px solid var(--border,rgba(255,255,255,.08));
margin-top:clamp(20px,3vw,30px);padding-top:16px;border-top:1px solid var(--border,rgba(255,255,255,.08));
@@ -313,7 +314,7 @@
@@ -313,7 +314,7 @@
.ovf-copy{color:var(--text-muted,#8b93ad);font-size:.78rem}
.ovf-copy{color:var(--text-muted,#8b93ad);font-size:.78rem}
.ovf-legal-inline,.ovf-compact-links{display:flex;flex-wrap:wrap;gap:14px}
.ovf-legal-inline,.ovf-compact-links{display:flex;flex-wrap:wrap;gap:14px}
.ovf-legal-inline a,.ovf-compact-links a{color:var(--text-muted,#8b93ad);font-size:.78rem;text-decoration:none;transition:color .15s}
.ovf-legal-inline a,.ovf-compact-links a{color:var(--text-muted,#8b93ad);font-size:.78rem;text-decoration:none;transition:color .15s}
-.ovf-legal-inline a:hover,.ovf-compact-links a:hover{color:var(--accent,#a78bfa)}
+.ovf-legal-inline a:hover,.ovf-compact-links a:hover{color:var(--accent,#60a5fa)}
.ovf-compact-meta{color:var(--text-muted,#8b93ad);font-size:.74rem}
.ovf-compact-meta{color:var(--text-muted,#8b93ad);font-size:.74rem}
@media (max-width:900px){.ovf-cols{grid-template-columns:repeat(2,minmax(0,1fr))}.ovf-col--brand{grid-column:1/-1}}
@media (max-width:900px){.ovf-cols{grid-template-columns:repeat(2,minmax(0,1fr))}.ovf-col--brand{grid-column:1/-1}}
@media (max-width:560px){
@media (max-width:560px){
@@ -119,6 +119,7 @@ function page(kind, site) {
@@ -119,6 +119,7 @@ function page(kind, site) {
catch { head = `<title>${esc(d.title)}</title><meta name="description" content="${esc(d.description)}"><link rel="canonical" href="${url}">`; }
catch { head = `<title>${esc(d.title)}</title><meta name="description" content="${esc(d.description)}"><link rel="canonical" href="${url}">`; }
return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
${head}
${head}
+${require('./app-icon').headTags({ site: site.id })}
<script src="https://openvibe.network/shared/theme-loader.js"></script><style>${CSS}</style></head><body>
<script src="https://openvibe.network/shared/theme-loader.js"></script><style>${CSS}</style></head><body>
<noscript><p style="padding:12px 20px;margin:0"><a href="/">${esc(site.name)}</a></p></noscript>
<noscript><p style="padding:12px 20px;margin:0"><a href="/">${esc(site.name)}</a></p></noscript>
<main>${article(kind, site)}</main><div id="ov-footer"></div>
<main>${article(kind, site)}</main><div id="ov-footer"></div>
@@ -102,19 +102,19 @@
@@ -102,19 +102,19 @@
.ovnav-launcher .ovl-empty { padding: 18px 8px; color: var(--text-secondary, #a8b3c4); font-size: 13px; }
.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 (max-width: 480px) { .ovnav-launcher { left: 8px; } .ovnav-launcher .ovl-fams { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) { .ovnav-launcher { transition: none; } }
@media (prefers-reduced-motion: reduce) { .ovnav-launcher { transition: none; } }
- .openvibe-navbar-brand .flame { font-size: 18px; text-decoration: none; color: var(--accent, #8b5cf6); display: inline-grid; place-items: center; width: 28px; height: 28px; flex: none; }
+ .openvibe-navbar-brand .flame { font-size: 18px; text-decoration: none; color: var(--accent, #3b82f6); display: inline-grid; place-items: center; width: 28px; height: 28px; flex: none; }
.openvibe-navbar-brand .name { display: inline-flex; align-items: baseline; font-size: 15px; font-weight: 700; letter-spacing: -.3px; white-space: nowrap; line-height: 1; }
.openvibe-navbar-brand .name { display: inline-flex; align-items: baseline; font-size: 15px; font-weight: 700; letter-spacing: -.3px; white-space: nowrap; line-height: 1; }
/* Segments: subdomain · OpenVibe · TLD. The part that names *this* site is the loud one. */
/* Segments: subdomain · OpenVibe · TLD. The part that names *this* site is the loud one. */
.openvibe-navbar-brand .b-sub { color: var(--text-primary, #e0e0e0); }
.openvibe-navbar-brand .b-sub { color: var(--text-primary, #e0e0e0); }
.openvibe-navbar-brand .b-core { color: var(--text-primary, #e0e0e0); }
.openvibe-navbar-brand .b-core { color: var(--text-primary, #e0e0e0); }
- .openvibe-navbar-brand .b-tld { color: var(--accent-light, var(--accent, #a78bfa)); }
+ .openvibe-navbar-brand .b-tld { color: var(--accent-light, var(--accent, #60a5fa)); }
.openvibe-navbar-brand.has-sub .b-core { color: var(--text-secondary, #b0b0b8); font-weight: 600; }
.openvibe-navbar-brand.has-sub .b-core { color: var(--text-secondary, #b0b0b8); font-weight: 600; }
.openvibe-navbar-brand.has-sub .b-tld { color: var(--text-secondary, #b0b0b8); font-weight: 600; }
.openvibe-navbar-brand.has-sub .b-tld { color: var(--text-secondary, #b0b0b8); font-weight: 600; }
- .openvibe-navbar-brand .b-dot { color: var(--accent, #8b5cf6); opacity: .75; margin: 0 1px; font-weight: 800; }
+ .openvibe-navbar-brand .b-dot { color: var(--accent, #3b82f6); opacity: .75; margin: 0 1px; font-weight: 800; }
.openvibe-navbar-brand .b-sub, .openvibe-navbar-brand .b-core, .openvibe-navbar-brand .b-tld { transition: color .2s, opacity .2s; }
.openvibe-navbar-brand .b-sub, .openvibe-navbar-brand .b-core, .openvibe-navbar-brand .b-tld { transition: color .2s, opacity .2s; }
- .openvibe-navbar-brand:hover .b-tld, .openvibe-navbar-brand:hover .b-sub { color: var(--accent-light, #a78bfa); }
+ .openvibe-navbar-brand:hover .b-tld, .openvibe-navbar-brand:hover .b-sub { color: var(--accent-light, #60a5fa); }
.openvibe-navbar-brand:hover .b-dot { opacity: 1; }
.openvibe-navbar-brand:hover .b-dot { opacity: 1; }
- .openvibe-navbar-brand .b-tag { font-size: 9px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: var(--accent-light, #a78bfa); background: var(--accent-glow, rgba(139,92,246,.14)); border-radius: 4px; padding: 2px 5px; margin-left: 6px; align-self: center; }
+ .openvibe-navbar-brand .b-tag { font-size: 9px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: var(--accent-light, #60a5fa); background: var(--accent-glow, rgba(59,130,246,.14)); border-radius: 4px; padding: 2px 5px; margin-left: 6px; align-self: center; }
/* Compact: drop the trailing segments on narrow viewports, keep what identifies the page. */
/* Compact: drop the trailing segments on narrow viewports, keep what identifies the page. */
.openvibe-navbar[data-compact="always"] .openvibe-navbar-brand.has-sub .b-core,
.openvibe-navbar[data-compact="always"] .openvibe-navbar-brand.has-sub .b-core,
.openvibe-navbar[data-compact="always"] .openvibe-navbar-brand.has-sub .b-tld,
.openvibe-navbar[data-compact="always"] .openvibe-navbar-brand.has-sub .b-tld,
@@ -132,10 +132,10 @@
@@ -132,10 +132,10 @@
.openvibe-navbar-links a .icon { margin-right: 5px; opacity: .8; }
.openvibe-navbar-links a .icon { margin-right: 5px; opacity: .8; }
.openvibe-navbar-dropdown-recent { padding: 6px 8px 2px; border-bottom: 1px solid var(--border, #333340); }
.openvibe-navbar-dropdown-recent { padding: 6px 8px 2px; border-bottom: 1px solid var(--border, #333340); }
.openvibe-navbar-dropdown-recent .label { font-size: 10px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: var(--text-muted, #707080); padding: 2px 8px 4px; display: flex; justify-content: space-between; align-items: center; }
.openvibe-navbar-dropdown-recent .label { font-size: 10px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase; color: var(--text-muted, #707080); padding: 2px 8px 4px; display: flex; justify-content: space-between; align-items: center; }
- .openvibe-navbar-dropdown-recent .label a { color: var(--accent-light, #a78bfa); text-decoration: none; font-weight: 600; letter-spacing: 0; text-transform: none; }
+ .openvibe-navbar-dropdown-recent .label a { color: var(--accent-light, #60a5fa); text-decoration: none; font-weight: 600; letter-spacing: 0; text-transform: none; }
.openvibe-navbar-dropdown-recent .item { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; font-size: 12px; color: var(--text-secondary, #b0b0b8); text-decoration: none; transition: background .12s; min-width: 0; }
.openvibe-navbar-dropdown-recent .item { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; font-size: 12px; color: var(--text-secondary, #b0b0b8); text-decoration: none; transition: background .12s; min-width: 0; }
.openvibe-navbar-dropdown-recent .item:hover { background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); }
.openvibe-navbar-dropdown-recent .item:hover { background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); }
- .openvibe-navbar-dropdown-recent .item .icon { width: 18px; text-align: center; color: var(--accent-light, #a78bfa); flex: none; }
+ .openvibe-navbar-dropdown-recent .item .icon { width: 18px; text-align: center; color: var(--accent-light, #60a5fa); flex: none; }
.openvibe-navbar-dropdown-recent .item .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.openvibe-navbar-dropdown-recent .item .t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.openvibe-navbar-dropdown-recent .item .s { margin-left: auto; font-size: 10px; color: var(--text-muted, #707080); flex: none; }
.openvibe-navbar-dropdown-recent .item .s { margin-left: auto; font-size: 10px; color: var(--text-muted, #707080); flex: none; }
.openvibe-navbar-dropdown-menu .sep { height: 1px; background: var(--border, #333340); margin: 4px -8px; }
.openvibe-navbar-dropdown-menu .sep { height: 1px; background: var(--border, #333340); margin: 4px -8px; }
@@ -147,7 +147,7 @@
@@ -147,7 +147,7 @@
transition: all .15s;
transition: all .15s;
}
}
.openvibe-navbar-links a:hover { background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); }
.openvibe-navbar-links a:hover { background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); }
- .openvibe-navbar-links a.active { background: var(--bg-tertiary, #2a2a38); color: var(--accent-light, #a78bfa); }
+ .openvibe-navbar-links a.active { background: var(--bg-tertiary, #2a2a38); color: var(--accent-light, #60a5fa); }
.openvibe-navbar-spacer { flex: 1; }
.openvibe-navbar-spacer { flex: 1; }
@@ -158,14 +158,14 @@
@@ -158,14 +158,14 @@
border: 2px solid var(--border, #333340); transition: border-color .2s;
border: 2px solid var(--border, #333340); transition: border-color .2s;
object-fit: cover;
object-fit: cover;
}
}
- .openvibe-navbar-avatar:hover { border-color: var(--accent, #8b5cf6); }
+ .openvibe-navbar-avatar:hover { border-color: var(--accent, #3b82f6); }
.openvibe-navbar-login {
.openvibe-navbar-login {
padding: 6px 16px; border-radius: 6px; font-size: 13px; font-weight: 600;
padding: 6px 16px; border-radius: 6px; font-size: 13px; font-weight: 600;
- background: var(--accent, #8b5cf6); color: #fff; border: none; cursor: pointer;
+ background: var(--accent, #3b82f6); color: #fff; border: none; cursor: pointer;
transition: background .15s; text-decoration: none; display: inline-flex; align-items: center;
transition: background .15s; text-decoration: none; display: inline-flex; align-items: center;
}
}
- .openvibe-navbar-login:hover { background: var(--accent-dark, #6d28d9); }
+ .openvibe-navbar-login:hover { background: var(--accent-dark, #2563eb); }
.openvibe-navbar-dropdown {
.openvibe-navbar-dropdown {
position: absolute; top: 48px; right: 8px;
position: absolute; top: 48px; right: 8px;
@@ -186,7 +186,7 @@
@@ -186,7 +186,7 @@
.openvibe-navbar-dropdown-header .info { line-height: 1.3; }
.openvibe-navbar-dropdown-header .info { line-height: 1.3; }
.openvibe-navbar-dropdown-header .info .name { font-size: 14px; font-weight: 600; }
.openvibe-navbar-dropdown-header .info .name { font-size: 14px; font-weight: 600; }
.openvibe-navbar-dropdown-header .info .email { font-size: 11px; color: var(--text-muted, #707080); }
.openvibe-navbar-dropdown-header .info .email { font-size: 11px; color: var(--text-muted, #707080); }
- .openvibe-navbar-dropdown-header .info .anon-tag { font-size: 10px; color: var(--accent-light, #a78bfa); }
+ .openvibe-navbar-dropdown-header .info .anon-tag { font-size: 10px; color: var(--accent-light, #60a5fa); }
.openvibe-navbar-dropdown-accounts {
.openvibe-navbar-dropdown-accounts {
padding: 6px 8px; border-bottom: 1px solid var(--border, #333340);
padding: 6px 8px; border-bottom: 1px solid var(--border, #333340);
@@ -199,7 +199,7 @@
@@ -199,7 +199,7 @@
}
}
.openvibe-navbar-dropdown-accounts .account-item:hover { background: var(--bg-hover, #2f2f3d); }
.openvibe-navbar-dropdown-accounts .account-item:hover { background: var(--bg-hover, #2f2f3d); }
.openvibe-navbar-dropdown-accounts .account-item img { width: 24px; height: 24px; border-radius: 50%; }
.openvibe-navbar-dropdown-accounts .account-item img { width: 24px; height: 24px; border-radius: 50%; }
- .openvibe-navbar-dropdown-accounts .account-item.active { color: var(--accent-light, #a78bfa); font-weight: 600; }
+ .openvibe-navbar-dropdown-accounts .account-item.active { color: var(--accent-light, #60a5fa); font-weight: 600; }
.openvibe-navbar-dropdown-accounts .add-account {
.openvibe-navbar-dropdown-accounts .add-account {
display: flex; align-items: center; gap: 8px; padding: 6px 8px;
display: flex; align-items: center; gap: 8px; padding: 6px 8px;
border-radius: 6px; cursor: pointer; font-size: 12px;
border-radius: 6px; cursor: pointer; font-size: 12px;
@@ -222,11 +222,11 @@
@@ -222,11 +222,11 @@
.openvibe-navbar .openvibe-network-badge {
.openvibe-navbar .openvibe-network-badge {
font-size: 10px; padding: 2px 8px; border-radius: 4px;
font-size: 10px; padding: 2px 8px; border-radius: 4px;
- background: rgba(139,92,246,0.1); color: var(--accent-light, #a78bfa);
+ background: rgba(59,130,246,0.1); color: var(--accent-light, #60a5fa);
font-weight: 500; cursor: pointer; border: 1px solid transparent;
font-weight: 500; cursor: pointer; border: 1px solid transparent;
transition: all .15s;
transition: all .15s;
}
}
- .openvibe-navbar .openvibe-network-badge:hover { border-color: var(--accent-dark, #6d28d9); }
+ .openvibe-navbar .openvibe-network-badge:hover { border-color: var(--accent-dark, #2563eb); }
@media (max-width: 600px) {
@media (max-width: 600px) {
.openvibe-navbar-links { display: none; }
.openvibe-navbar-links { display: none; }
@@ -665,6 +665,19 @@
@@ -665,6 +665,19 @@
});
});
}
}
+ /** One anonymous page-view count per page load (host only), which is what orders the network's
+ * navigation. Skipped when the browser asks not to be tracked. */
+ let _counted = false;
+ function countView() {
+ if (_counted || _config.countViews === false) return; _counted = true;
+ try {
+ if (navigator.globalPrivacyControl || navigator.doNotTrack === '1') return;
+ if (!/^https:$/.test(location.protocol)) return;
+ const url = 'https://openvibe.network/api/chrome/hit';
+ if (navigator.sendBeacon) navigator.sendBeacon(url);
+ } catch { /* */ }
+ }
+
/** The network's most used sites, after the page's own links (networkLinks: false turns it off). */
/** The network's most used sites, after the page's own links (networkLinks: false turns it off). */
function networkLinksHTML(pageLinks) {
function networkLinksHTML(pageLinks) {
if (_config.networkLinks === false) return '';
if (_config.networkLinks === false) return '';
@@ -1118,7 +1131,7 @@
@@ -1118,7 +1131,7 @@
function makeAvatarPlaceholder(user, size = 64) {
function makeAvatarPlaceholder(user, size = 64) {
const initial = getAvatarInitial(user);
const initial = getAvatarInitial(user);
- const bg = user?.profile_color || '#8b5cf6';
+ const bg = user?.profile_color || '#3b82f6';
const svg = `
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
<rect width="100%" height="100%" rx="${Math.round(size / 2)}" fill="${bg}"/>
<rect width="100%" height="100%" rx="${Math.round(size / 2)}" fill="${bg}"/>
@@ -1200,13 +1213,13 @@
@@ -1200,13 +1213,13 @@
</div>
</div>
<div class="openvibe-navbar-spacer"></div>
<div class="openvibe-navbar-spacer"></div>
<div class="openvibe-navbar-right">
<div class="openvibe-navbar-right">
- <a class="openvibe-network-badge" href="https://openvibe.network" title="Connected to OpenVibe"><span class="ov-mark" data-size="16" data-static="1"></span> OpenVibe</a>
<div id="openvibe-bell-mount"></div>
<div id="openvibe-bell-mount"></div>
${u ? avatarImg(u, 64, 'openvibe-navbar-avatar', 'openvibe-avatar-btn') :
${u ? avatarImg(u, 64, 'openvibe-navbar-avatar', 'openvibe-avatar-btn') :
`<a class="openvibe-navbar-login" id="openvibe-login-btn" href="${escapeAttr(loginHref)}">Sign In</a>`}
`<a class="openvibe-navbar-login" id="openvibe-login-btn" href="${escapeAttr(loginHref)}">Sign In</a>`}
</div>
</div>
`;
`;
bindLauncher(nav);
bindLauncher(nav);
+ countView();
// Pages that wire the bell themselves run right after init(); give them the first go.
// Pages that wire the bell themselves run right after init(); give them the first go.
setTimeout(() => mountBell(nav, u), 0);
setTimeout(() => mountBell(nav, u), 0);
@@ -55,26 +55,26 @@
@@ -55,26 +55,26 @@
.openvibe-toast-container { position: fixed; top: 82px; right: 14px; z-index: 100000; display: flex; flex-direction: column; gap: 6px; pointer-events: none; max-width: 340px; width: 100%; }
.openvibe-toast-container { position: fixed; top: 82px; right: 14px; z-index: 100000; display: flex; flex-direction: column; gap: 6px; pointer-events: none; max-width: 340px; width: 100%; }
.openvibe-toast { pointer-events: all; background: color-mix(in srgb, var(--bg-card, #22222c) 94%, transparent); border: 1px solid color-mix(in srgb, var(--border, #333340) 82%, transparent); border-radius: 10px; padding: 11px 12px; display: flex; gap: 10px; align-items: flex-start; box-shadow: 0 10px 24px rgba(0,0,0,0.28); animation: openvibe-toast-in .24s cubic-bezier(.34,1.56,.64,1); cursor: pointer; position: relative; overflow: hidden; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; color: var(--text-primary, #e0e0e0); transition: border-color .2s; }
.openvibe-toast { pointer-events: all; background: color-mix(in srgb, var(--bg-card, #22222c) 94%, transparent); border: 1px solid color-mix(in srgb, var(--border, #333340) 82%, transparent); border-radius: 10px; padding: 11px 12px; display: flex; gap: 10px; align-items: flex-start; box-shadow: 0 10px 24px rgba(0,0,0,0.28); animation: openvibe-toast-in .24s cubic-bezier(.34,1.56,.64,1); cursor: pointer; position: relative; overflow: hidden; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; color: var(--text-primary, #e0e0e0); transition: border-color .2s; }
- .openvibe-toast:hover { border-color: var(--accent, #8b5cf6); }
+ .openvibe-toast:hover { border-color: var(--accent, #3b82f6); }
.openvibe-toast.removing { animation: openvibe-toast-out .3s ease forwards; }
.openvibe-toast.removing { animation: openvibe-toast-out .3s ease forwards; }
.openvibe-toast-icon { font-size: 18px; flex-shrink: 0; line-height: 1; margin-top: 1px; }
.openvibe-toast-icon { font-size: 18px; flex-shrink: 0; line-height: 1; margin-top: 1px; }
.openvibe-toast-icon img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; display: block; }
.openvibe-toast-icon img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; display: block; }
.openvibe-toast-body { flex: 1; min-width: 0; }
.openvibe-toast-body { flex: 1; min-width: 0; }
.openvibe-toast-title { font-weight: 600; font-size: 12px; margin-bottom: 2px; display: flex; align-items: center; gap: 6px; }
.openvibe-toast-title { font-weight: 600; font-size: 12px; margin-bottom: 2px; display: flex; align-items: center; gap: 6px; }
- .openvibe-toast-title .service-badge { font-size: 9px; padding: 1px 6px; border-radius: 3px; background: color-mix(in srgb, var(--accent, #8b5cf6) 18%, transparent); color: var(--accent-light, #a78bfa); font-weight: 500; }
+ .openvibe-toast-title .service-badge { font-size: 9px; padding: 1px 6px; border-radius: 3px; background: color-mix(in srgb, var(--accent, #3b82f6) 18%, transparent); color: var(--accent-light, #60a5fa); font-weight: 500; }
.openvibe-toast-msg { font-size: 11px; color: var(--text-secondary, #b0b0b8); line-height: 1.35; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.openvibe-toast-msg { font-size: 11px; color: var(--text-secondary, #b0b0b8); line-height: 1.35; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.openvibe-toast-close { position: absolute; top: 8px; right: 10px; background: none; border: none; color: var(--text-muted, #707080); cursor: pointer; font-size: 14px; padding: 2px; line-height: 1; }
.openvibe-toast-close { position: absolute; top: 8px; right: 10px; background: none; border: none; color: var(--text-muted, #707080); cursor: pointer; font-size: 14px; padding: 2px; line-height: 1; }
.openvibe-toast-close:hover { color: var(--text-primary, #e0e0e0); }
.openvibe-toast-close:hover { color: var(--text-primary, #e0e0e0); }
- .openvibe-toast-progress { position: absolute; bottom: 0; left: 0; height: 2px; background: var(--accent, #8b5cf6); }
+ .openvibe-toast-progress { position: absolute; bottom: 0; left: 0; height: 2px; background: var(--accent, #3b82f6); }
.openvibe-toast.priority-high { border-left: 3px solid var(--warning, #f39c12); }
.openvibe-toast.priority-high { border-left: 3px solid var(--warning, #f39c12); }
.openvibe-toast.priority-critical { border-left: 3px solid var(--live-red, #e74c3c); background: rgba(231,76,60,0.06); }
.openvibe-toast.priority-critical { border-left: 3px solid var(--live-red, #e74c3c); background: rgba(231,76,60,0.06); }
.openvibe-toast-actions { display: flex; gap: 6px; margin-top: 8px; }
.openvibe-toast-actions { display: flex; gap: 6px; margin-top: 8px; }
.openvibe-toast-actions button { padding: 5px 12px; border-radius: 5px; border: 1px solid var(--border, #333340); background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); font-size: 11px; font-weight: 600; cursor: pointer; }
.openvibe-toast-actions button { padding: 5px 12px; border-radius: 5px; border: 1px solid var(--border, #333340); background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); font-size: 11px; font-weight: 600; cursor: pointer; }
- .openvibe-toast-actions button.primary { background: var(--accent, #8b5cf6); color: #fff; border-color: var(--accent, #8b5cf6); }
+ .openvibe-toast-actions button.primary { background: var(--accent, #3b82f6); color: #fff; border-color: var(--accent, #3b82f6); }
.openvibe-bell { position: relative; cursor: pointer; padding: 6px; display: inline-flex; align-items: center; justify-content: center; }
.openvibe-bell { position: relative; cursor: pointer; padding: 6px; display: inline-flex; align-items: center; justify-content: center; }
.openvibe-bell svg { width: 22px; height: 22px; fill: var(--text-secondary, #b0b0b8); transition: fill .2s; }
.openvibe-bell svg { width: 22px; height: 22px; fill: var(--text-secondary, #b0b0b8); transition: fill .2s; }
- .openvibe-bell:hover svg, .openvibe-bell.open svg { fill: var(--accent-light, #a78bfa); }
+ .openvibe-bell:hover svg, .openvibe-bell.open svg { fill: var(--accent-light, #60a5fa); }
.openvibe-bell .badge { position: absolute; top: 0; right: 0; min-width: 16px; height: 16px; padding: 0 4px; background: var(--live-red, #e74c3c); color: #fff; border-radius: 8px; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; line-height: 1; pointer-events: none; animation: openvibe-badge-pulse .4s ease; }
.openvibe-bell .badge { position: absolute; top: 0; right: 0; min-width: 16px; height: 16px; padding: 0 4px; background: var(--live-red, #e74c3c); color: #fff; border-radius: 8px; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; line-height: 1; pointer-events: none; animation: openvibe-badge-pulse .4s ease; }
.openvibe-bell .badge.hidden { display: none; }
.openvibe-bell .badge.hidden { display: none; }
.openvibe-bell.ringing svg { animation: openvibe-bell-ring .6s ease; }
.openvibe-bell.ringing svg { animation: openvibe-bell-ring .6s ease; }
@@ -90,27 +90,27 @@
@@ -90,27 +90,27 @@
.openvibe-nv-actions { display: flex; gap: 4px; align-items: center; }
.openvibe-nv-actions { display: flex; gap: 4px; align-items: center; }
.openvibe-nv-actions button, .openvibe-nv-actions a { background: none; border: 1px solid transparent; color: var(--text-muted, #707080); cursor: pointer; font-size: 12px; padding: 5px 8px; border-radius: 6px; text-decoration: none; display: inline-flex; align-items: center; gap: 5px; line-height: 1; }
.openvibe-nv-actions button, .openvibe-nv-actions a { background: none; border: 1px solid transparent; color: var(--text-muted, #707080); cursor: pointer; font-size: 12px; padding: 5px 8px; border-radius: 6px; text-decoration: none; display: inline-flex; align-items: center; gap: 5px; line-height: 1; }
.openvibe-nv-actions button:hover, .openvibe-nv-actions a:hover { background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); }
.openvibe-nv-actions button:hover, .openvibe-nv-actions a:hover { background: var(--bg-hover, #2f2f3d); color: var(--text-primary, #e0e0e0); }
- .openvibe-nv-actions button.on { color: var(--accent-light, #a78bfa); border-color: color-mix(in srgb, var(--accent, #8b5cf6) 40%, transparent); }
+ .openvibe-nv-actions button.on { color: var(--accent-light, #60a5fa); border-color: color-mix(in srgb, var(--accent, #3b82f6) 40%, transparent); }
.openvibe-nv-actions button:disabled { opacity: .45; cursor: default; }
.openvibe-nv-actions button:disabled { opacity: .45; cursor: default; }
.openvibe-nv-actions svg { width: 14px; height: 14px; fill: currentColor; }
.openvibe-nv-actions svg { width: 14px; height: 14px; fill: currentColor; }
.openvibe-nv-tools { padding: 8px 12px; border-bottom: 1px solid var(--border, #333340); display: flex; flex-direction: column; gap: 8px; }
.openvibe-nv-tools { padding: 8px 12px; border-bottom: 1px solid var(--border, #333340); display: flex; flex-direction: column; gap: 8px; }
.openvibe-nv-search { position: relative; }
.openvibe-nv-search { position: relative; }
.openvibe-nv-search input { width: 100%; box-sizing: border-box; padding: 7px 28px 7px 30px; border-radius: 8px; border: 1px solid var(--border, #333340); background: var(--bg-input, var(--bg-tertiary, #1e1e28)); color: var(--text-primary, #e0e0e0); font-size: 12px; outline: none; }
.openvibe-nv-search input { width: 100%; box-sizing: border-box; padding: 7px 28px 7px 30px; border-radius: 8px; border: 1px solid var(--border, #333340); background: var(--bg-input, var(--bg-tertiary, #1e1e28)); color: var(--text-primary, #e0e0e0); font-size: 12px; outline: none; }
- .openvibe-nv-search input:focus { border-color: var(--accent, #8b5cf6); }
+ .openvibe-nv-search input:focus { border-color: var(--accent, #3b82f6); }
.openvibe-nv-search svg { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; fill: var(--text-muted, #707080); pointer-events: none; }
.openvibe-nv-search svg { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; fill: var(--text-muted, #707080); pointer-events: none; }
.openvibe-nv-search .clear { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--text-muted, #707080); cursor: pointer; font-size: 14px; display: none; }
.openvibe-nv-search .clear { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--text-muted, #707080); cursor: pointer; font-size: 14px; display: none; }
.openvibe-nv-search.has-q .clear { display: block; }
.openvibe-nv-search.has-q .clear { display: block; }
.openvibe-nv-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.openvibe-nv-chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.openvibe-nv-chips button { border: 1px solid var(--border, #333340); background: transparent; color: var(--text-secondary, #b0b0b8); font-size: 11px; padding: 4px 10px; border-radius: 999px; cursor: pointer; line-height: 1.2; }
.openvibe-nv-chips button { border: 1px solid var(--border, #333340); background: transparent; color: var(--text-secondary, #b0b0b8); font-size: 11px; padding: 4px 10px; border-radius: 999px; cursor: pointer; line-height: 1.2; }
- .openvibe-nv-chips button:hover { border-color: var(--accent, #8b5cf6); color: var(--text-primary, #e0e0e0); }
- .openvibe-nv-chips button.active { background: color-mix(in srgb, var(--accent, #8b5cf6) 18%, transparent); border-color: var(--accent, #8b5cf6); color: var(--accent-light, #a78bfa); }
+ .openvibe-nv-chips button:hover { border-color: var(--accent, #3b82f6); color: var(--text-primary, #e0e0e0); }
+ .openvibe-nv-chips button.active { background: color-mix(in srgb, var(--accent, #3b82f6) 18%, transparent); border-color: var(--accent, #3b82f6); color: var(--accent-light, #60a5fa); }
.openvibe-nv-chips select { margin-left: auto; border: 1px solid var(--border, #333340); background: var(--bg-input, var(--bg-tertiary, #1e1e28)); color: var(--text-secondary, #b0b0b8); font-size: 11px; padding: 4px 8px; border-radius: 8px; cursor: pointer; }
.openvibe-nv-chips select { margin-left: auto; border: 1px solid var(--border, #333340); background: var(--bg-input, var(--bg-tertiary, #1e1e28)); color: var(--text-secondary, #b0b0b8); font-size: 11px; padding: 4px 8px; border-radius: 8px; cursor: pointer; }
.openvibe-nv-list { flex: 1; overflow-y: auto; padding: 4px 0; overscroll-behavior: contain; }
.openvibe-nv-list { flex: 1; overflow-y: auto; padding: 4px 0; overscroll-behavior: contain; }
.openvibe-notif-item { padding: 10px 12px 10px 14px; display: flex; gap: 10px; align-items: flex-start; cursor: pointer; transition: background .15s; border-left: 3px solid transparent; position: relative; }
.openvibe-notif-item { padding: 10px 12px 10px 14px; display: flex; gap: 10px; align-items: flex-start; cursor: pointer; transition: background .15s; border-left: 3px solid transparent; position: relative; }
.openvibe-notif-item:hover { background: var(--bg-hover, #2f2f3d); }
.openvibe-notif-item:hover { background: var(--bg-hover, #2f2f3d); }
- .openvibe-notif-item.unread { background: color-mix(in srgb, var(--accent, #8b5cf6) 7%, transparent); border-left-color: var(--accent, #8b5cf6); }
+ .openvibe-notif-item.unread { background: color-mix(in srgb, var(--accent, #3b82f6) 7%, transparent); border-left-color: var(--accent, #3b82f6); }
.openvibe-notif-item .icon { font-size: 20px; flex-shrink: 0; margin-top: 2px; width: 32px; text-align: center; }
.openvibe-notif-item .icon { font-size: 20px; flex-shrink: 0; margin-top: 2px; width: 32px; text-align: center; }
.openvibe-notif-item .icon img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; display: block; }
.openvibe-notif-item .icon img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; display: block; }
.openvibe-notif-item .content { flex: 1; min-width: 0; }
.openvibe-notif-item .content { flex: 1; min-width: 0; }
@@ -126,9 +126,9 @@
@@ -126,9 +126,9 @@
.openvibe-nv-empty .icon { font-size: 34px; margin-bottom: 8px; display: block; opacity: .5; }
.openvibe-nv-empty .icon { font-size: 34px; margin-bottom: 8px; display: block; opacity: .5; }
.openvibe-nv-more { display: flex; justify-content: center; padding: 10px; }
.openvibe-nv-more { display: flex; justify-content: center; padding: 10px; }
.openvibe-nv-more button { border: 1px solid var(--border, #333340); background: transparent; color: var(--text-secondary, #b0b0b8); font-size: 12px; padding: 6px 14px; border-radius: 8px; cursor: pointer; }
.openvibe-nv-more button { border: 1px solid var(--border, #333340); background: transparent; color: var(--text-secondary, #b0b0b8); font-size: 12px; padding: 6px 14px; border-radius: 8px; cursor: pointer; }
- .openvibe-nv-more .spinner { width: 16px; height: 16px; border: 2px solid var(--border, #333340); border-top-color: var(--accent, #8b5cf6); border-radius: 50%; animation: openvibe-spin .8s linear infinite; }
+ .openvibe-nv-more .spinner { width: 16px; height: 16px; border: 2px solid var(--border, #333340); border-top-color: var(--accent, #3b82f6); border-radius: 50%; animation: openvibe-spin .8s linear infinite; }
.openvibe-nv-foot { padding: 8px 12px; border-top: 1px solid var(--border, #333340); font-size: 11px; color: var(--text-muted, #707080); display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.openvibe-nv-foot { padding: 8px 12px; border-top: 1px solid var(--border, #333340); font-size: 11px; color: var(--text-muted, #707080); display: flex; justify-content: space-between; align-items: center; gap: 8px; }
- .openvibe-nv-foot a { color: var(--accent-light, #a78bfa); text-decoration: none; }
+ .openvibe-nv-foot a { color: var(--accent-light, #60a5fa); text-decoration: none; }
.openvibe-nv-note { font-size: 11px; color: var(--text-muted, #707080); padding: 6px 12px; background: color-mix(in srgb, var(--warning, #f39c12) 10%, transparent); border-bottom: 1px solid var(--border, #333340); }
.openvibe-nv-note { font-size: 11px; color: var(--text-muted, #707080); padding: 6px 12px; background: color-mix(in srgb, var(--warning, #f39c12) 10%, transparent); border-bottom: 1px solid var(--border, #333340); }
@media (max-width: 460px) {
@media (max-width: 460px) {
@@ -87,6 +87,8 @@
@@ -87,6 +87,8 @@
.ov-icon .ovi-prog{fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;transform:rotate(-90deg);transform-origin:24px 24px;stroke-dasharray:125.7;stroke-dashoffset:125.7;transition:stroke-dashoffset .35s cubic-bezier(.2,.8,.2,1),opacity .2s;opacity:0}
.ov-icon .ovi-prog{fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;transform:rotate(-90deg);transform-origin:24px 24px;stroke-dasharray:125.7;stroke-dashoffset:125.7;transition:stroke-dashoffset .35s cubic-bezier(.2,.8,.2,1),opacity .2s;opacity:0}
.ov-icon .ovi-glyph{fill:none;stroke:var(--ovi-glyph,var(--text-primary,#e6edf7));stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;transform-origin:24px 24px;transition:transform .25s cubic-bezier(.2,1.4,.3,1),stroke .2s}
.ov-icon .ovi-glyph{fill:none;stroke:var(--ovi-glyph,var(--text-primary,#e6edf7));stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;transform-origin:24px 24px;transition:transform .25s cubic-bezier(.2,1.4,.3,1),stroke .2s}
.ov-icon .ovi-glyph circle[r=".6"],.ov-icon .ovi-glyph circle[r=".7"]{fill:currentColor;stroke:none}
.ov-icon .ovi-glyph circle[r=".6"],.ov-icon .ovi-glyph circle[r=".7"]{fill:currentColor;stroke:none}
+.ov-icon[style*="--ovi-size:2"] .ovi-glyph,.ov-icon[data-size^="2"] .ovi-glyph{stroke-width:2.3}
+.ov-icon[style*="--ovi-size:2"] .ovi-ring,.ov-icon[data-size^="2"] .ovi-ring{stroke-width:3;opacity:.34}
.ov-icon:hover .ovi-glyph,a:hover>.ov-icon .ovi-glyph,button:hover>.ov-icon .ovi-glyph{transform:scale(1.1)}
.ov-icon:hover .ovi-glyph,a:hover>.ov-icon .ovi-glyph,button:hover>.ov-icon .ovi-glyph{transform:scale(1.1)}
.ov-icon:hover .ovi-bg,a:hover>.ov-icon .ovi-bg{opacity:.18}
.ov-icon:hover .ovi-bg,a:hover>.ov-icon .ovi-bg{opacity:.18}
.ov-icon[data-fx=none] .ovi-comet{display:none}
.ov-icon[data-fx=none] .ovi-comet{display:none}
@@ -14,7 +14,8 @@
@@ -14,7 +14,8 @@
"./url-resolver": "./url-resolver.js",
"./url-resolver": "./url-resolver.js",
"./seo": "./seo.js",
"./seo": "./seo.js",
"./icons": "./ov-icons.js",
"./icons": "./ov-icons.js",
- "./legal": "./legal.js"
+ "./legal": "./legal.js",
+ "./app-icon": "./app-icon.js"
},
},
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
new file mode 100644
new file mode 100644
@@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
+#!/usr/bin/env node
+'use strict';
+// Render a site's icon set from openvibe-shared/app-icon.
+// node build-app-icons.js <site> <outDir> [--sharp <path-to-sharp-module>]
+// Writes logo.svg (favicon), logo-72/192/512.png (rounded tile) and logo-maskable-192/512.png.
+const fs = require('fs'); const path = require('path');
+const icon = require('../app-icon');
+const [site, outDir] = process.argv.slice(2);
+if (!site || !outDir) { console.error('usage: build-app-icons.js <site> <outDir> [--sharp <module path>]'); process.exit(1); }
+const i = process.argv.indexOf('--sharp');
+const sharp = require(i > 0 ? path.resolve(process.argv[i + 1]) : 'sharp');
+(async () => {
+ fs.mkdirSync(outDir, { recursive: true });
+ fs.writeFileSync(path.join(outDir, 'logo.svg'), icon.favicon({ site }));
+ for (const s of [72, 192, 512]) await sharp(Buffer.from(icon.svg({ site, maskable: false })), { density: 300 }).resize(s, s).png({ compressionLevel: 9 }).toFile(path.join(outDir, `logo-${s}.png`));
+ for (const s of [192, 512]) await sharp(Buffer.from(icon.svg({ site, maskable: true })), { density: 300 }).resize(s, s).png({ compressionLevel: 9 }).toFile(path.join(outDir, `logo-maskable-${s}.png`));
+ console.log(`icons for ${site} → ${outDir}`);
+})().catch(e => { console.error(e); process.exit(1); });
@@ -24,33 +24,33 @@
@@ -24,33 +24,33 @@
const CSS = `
const CSS = `
.ovtip{position:fixed;z-index:30000;max-width:min(330px,calc(100vw - 24px));padding:12px 14px;border-radius:14px;
.ovtip{position:fixed;z-index:30000;max-width:min(330px,calc(100vw - 24px));padding:12px 14px;border-radius:14px;
background:linear-gradient(165deg,var(--bg-card,#171826) 0%,var(--bg-secondary,#12131c) 100%);
background:linear-gradient(165deg,var(--bg-card,#171826) 0%,var(--bg-secondary,#12131c) 100%);
- border:1px solid color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 45%, var(--border,rgba(255,255,255,.1)));
- box-shadow:0 20px 50px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.04),0 0 30px color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 22%, transparent);
+ border:1px solid color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 45%, var(--border,rgba(255,255,255,.1)));
+ box-shadow:0 20px 50px rgba(0,0,0,.55),0 0 0 1px rgba(255,255,255,.04),0 0 30px color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 22%, transparent);
color:var(--text-secondary,#b6bdd2);font-size:.83rem;line-height:1.55;pointer-events:none;opacity:0;
color:var(--text-secondary,#b6bdd2);font-size:.83rem;line-height:1.55;pointer-events:none;opacity:0;
transform:translateY(6px) scale(.97);transition:opacity .18s ease,transform .22s cubic-bezier(.2,1.3,.3,1);
transform:translateY(6px) scale(.97);transition:opacity .18s ease,transform .22s cubic-bezier(.2,1.3,.3,1);
overflow:hidden}
overflow:hidden}
.ovtip.is-on{opacity:1;transform:none}
.ovtip.is-on{opacity:1;transform:none}
.ovtip.is-interactive{pointer-events:auto}
.ovtip.is-interactive{pointer-events:auto}
.ovtip::before{content:'';position:absolute;inset:0;pointer-events:none;
.ovtip::before{content:'';position:absolute;inset:0;pointer-events:none;
- background:radial-gradient(120% 90% at 0% 0%, color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 16%, transparent), transparent 60%)}
+ background:radial-gradient(120% 90% at 0% 0%, color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 16%, transparent), transparent 60%)}
/* A light sweep on open — draws the eye without moving anything around it. */
/* A light sweep on open — draws the eye without moving anything around it. */
.ovtip::after{content:'';position:absolute;top:0;bottom:0;left:-60%;width:45%;pointer-events:none;
.ovtip::after{content:'';position:absolute;top:0;bottom:0;left:-60%;width:45%;pointer-events:none;
- background:linear-gradient(100deg,transparent,color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 26%, transparent),transparent)}
+ background:linear-gradient(100deg,transparent,color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 26%, transparent),transparent)}
.ovtip.is-on::after{animation:ovtipSweep .9s cubic-bezier(.3,.7,.3,1) .1s 1}
.ovtip.is-on::after{animation:ovtipSweep .9s cubic-bezier(.3,.7,.3,1) .1s 1}
@keyframes ovtipSweep{to{transform:translateX(420%)}}
@keyframes ovtipSweep{to{transform:translateX(420%)}}
.ovtip-head{display:flex;align-items:center;gap:8px;margin-bottom:6px;position:relative}
.ovtip-head{display:flex;align-items:center;gap:8px;margin-bottom:6px;position:relative}
.ovtip-ico{width:26px;height:26px;border-radius:8px;display:grid;place-items:center;flex:none;font-size:.78rem;color:#fff;
.ovtip-ico{width:26px;height:26px;border-radius:8px;display:grid;place-items:center;flex:none;font-size:.78rem;color:#fff;
- background:linear-gradient(135deg,var(--ovtip-accent,var(--accent,#a78bfa)),color-mix(in srgb,var(--ovtip-accent,var(--accent,#a78bfa)) 55%,#000));
- box-shadow:0 4px 12px color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 45%, transparent)}
+ background:linear-gradient(135deg,var(--ovtip-accent,var(--accent,#60a5fa)),color-mix(in srgb,var(--ovtip-accent,var(--accent,#60a5fa)) 55%,#000));
+ box-shadow:0 4px 12px color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 45%, transparent)}
.ovtip-title{font-size:.9rem;font-weight:800;color:var(--text-primary,#f1f4fb);letter-spacing:-.01em}
.ovtip-title{font-size:.9rem;font-weight:800;color:var(--text-primary,#f1f4fb);letter-spacing:-.01em}
.ovtip-body{position:relative}
.ovtip-body{position:relative}
.ovtip-body b,.ovtip-body strong{color:var(--text-primary,#f1f4fb);font-weight:700}
.ovtip-body b,.ovtip-body strong{color:var(--text-primary,#f1f4fb);font-weight:700}
-.ovtip-body em{color:var(--ovtip-accent,var(--accent,#a78bfa));font-style:normal;font-weight:600}
+.ovtip-body em{color:var(--ovtip-accent,var(--accent,#60a5fa));font-style:normal;font-weight:600}
.ovtip-body code{background:var(--bg-tertiary,#1a1b28);padding:1px 6px;border-radius:5px;font-size:.78rem}
.ovtip-body code{background:var(--bg-tertiary,#1a1b28);padding:1px 6px;border-radius:5px;font-size:.78rem}
.ovtip-foot{margin-top:8px;padding-top:8px;border-top:1px dashed var(--border,rgba(255,255,255,.1));
.ovtip-foot{margin-top:8px;padding-top:8px;border-top:1px dashed var(--border,rgba(255,255,255,.1));
font-size:.74rem;color:var(--text-muted,#8b93ad);display:flex;align-items:center;gap:6px;position:relative}
font-size:.74rem;color:var(--text-muted,#8b93ad);display:flex;align-items:center;gap:6px;position:relative}
.ovtip-arrow{position:fixed;z-index:30001;width:10px;height:10px;transform:rotate(45deg);opacity:0;transition:opacity .18s;
.ovtip-arrow{position:fixed;z-index:30001;width:10px;height:10px;transform:rotate(45deg);opacity:0;transition:opacity .18s;
- background:var(--bg-card,#171826);border:1px solid color-mix(in srgb, var(--ovtip-accent,var(--accent,#a78bfa)) 45%, var(--border,rgba(255,255,255,.1)))}
+ background:var(--bg-card,#171826);border:1px solid color-mix(in srgb, var(--ovtip-accent,var(--accent,#60a5fa)) 45%, var(--border,rgba(255,255,255,.1)))}
.ovtip-arrow.is-on{opacity:1}
.ovtip-arrow.is-on{opacity:1}
.ovtip-arrow.from-top{border-top:0;border-left:0}
.ovtip-arrow.from-top{border-top:0;border-left:0}
.ovtip-arrow.from-bottom{border-bottom:0;border-right:0}
.ovtip-arrow.from-bottom{border-bottom:0;border-right:0}
@@ -59,8 +59,8 @@
@@ -59,8 +59,8 @@
padding:9px 12px;border-radius:10px;font:inherit;font-size:.8rem;font-weight:750;cursor:pointer;
padding:9px 12px;border-radius:10px;font:inherit;font-size:.8rem;font-weight:750;cursor:pointer;
transition:transform .15s,filter .15s}
transition:transform .15s,filter .15s}
.ovtip-go{color:#fff;border:0;
.ovtip-go{color:#fff;border:0;
- background:linear-gradient(135deg,var(--ovtip-accent,var(--accent,#a78bfa)),color-mix(in srgb,var(--ovtip-accent,var(--accent,#a78bfa)) 60%,#000));
- box-shadow:0 6px 16px color-mix(in srgb,var(--ovtip-accent,var(--accent,#a78bfa)) 40%,transparent)}
+ background:linear-gradient(135deg,var(--ovtip-accent,var(--accent,#60a5fa)),color-mix(in srgb,var(--ovtip-accent,var(--accent,#60a5fa)) 60%,#000));
+ box-shadow:0 6px 16px color-mix(in srgb,var(--ovtip-accent,var(--accent,#60a5fa)) 40%,transparent)}
.ovtip-dismiss{flex:0 0 auto;color:var(--text-muted,#8b93ad);background:none;border:1px solid var(--border,rgba(255,255,255,.12))}
.ovtip-dismiss{flex:0 0 auto;color:var(--text-muted,#8b93ad);background:none;border:1px solid var(--border,rgba(255,255,255,.12))}
.ovtip-go:active,.ovtip-dismiss:active{transform:scale(.975)}
.ovtip-go:active,.ovtip-dismiss:active{transform:scale(.975)}
.ovtip-go i{font-size:.72rem}
.ovtip-go i{font-size:.72rem}
@@ -30,7 +30,7 @@
@@ -30,7 +30,7 @@
animation: openvibe-card-in .2s ease; overflow: hidden;
animation: openvibe-card-in .2s ease; overflow: hidden;
}
}
.openvibe-usercard-banner {
.openvibe-usercard-banner {
- height: 60px; background: linear-gradient(135deg, var(--accent-dark, #a07840), var(--accent, #8b5cf6));
+ height: 60px; background: linear-gradient(135deg, var(--accent-dark, #a07840), var(--accent, #3b82f6));
position: relative;
position: relative;
}
}
.openvibe-usercard-avatar {
.openvibe-usercard-avatar {