'use strict'; // ═══════════════════════════════════════════════════════════════ // openvibe-shared/app-icon — one app icon design for every OpenVibe site. // // The icon is the OpenVibe mark (openvibe-shared/mark: the Pair, a ring O and a round V) on the network's deep navy, // with a soft glow behind it. 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 heavy form on a rounded tile, tuned for 16–32 px. // manifest({ site, name, shortName, description, startUrl }) web app manifest with shared colours. // SITES accent (the glow and the share cards) and the light in the O per site: Live's is the // red record light, as in ov-mark.js's variants; white means none. // 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' }, space: { accent: ['#818cf8', '#4f46e5'], dot: '#ffffff' }, ai: { accent: ['#22d3ee', '#2563eb'], dot: '#ffffff' }, }; const of = (site) => SITES[site] || SITES.network; const MARK = require('./mark'); // The mark centred on 0,0 (openvibe-shared/mark draws it centred on 50,50 in a 100-unit box), scaled. function mark(site, scale, form = 'pair') { const c = of(site), g = MARK.geo(form), { body } = MARK.art({ form, id: 'ov', depth: true }); const light = c.dot && c.dot.toLowerCase() !== '#ffffff' ? `` : ''; return `${body}${light}`; } function defs(site, form = 'pair') { const [a] = of(site).accent; return `${MARK.art({ form, id: 'ov', depth: true }).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 } = {}) { // The mark's farthest point (the V's caps) is 50 units from its centre: × 4 is 200 of the 256 half-width, inside // the maskable safe zone (40 % of the width, 204.8). A rounded tile carries it at three quarters of its width. const scale = maskable ? 4 : 4.2; return `${defs(site)} ${mark(site, scale)}`; } /** Small sizes: the heavy form, as wide as the tile allows, and the tile rounded because nothing masks a favicon. */ function favicon({ site = 'network' } = {}) { return `${defs(site, 'heavy')}${mark(site, 0.58, 'heavy')}`; } 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' }], }; } // First paint must never depend on another server. These two inline tags give every page the network's // dark canvas and the visitor's cached theme immediately; theme-loader.js (deferred, from the Network) // reconciles with the account afterwards. If the Network is slow or restarting, the page is still // styled and readable instead of blank white. const CRITICAL = `` + ``; /** 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 [``, '', '', CRITICAL, iconBase ? `` : '', iconBase ? `` : ''].filter(Boolean).join('\n'); } module.exports = { svg, favicon, manifest, headTags, CRITICAL, SITES, BG };