openvibers/openvibe.tools Commits
Remove Google Fonts and use the OpenVibe house font stacks (#325)
Changes
@@@ -24,8 +24,8 @@--radius-sm: 6px;--radius-lg: 16px;--shadow: 0 4px 24px rgba(0,0,0,.35);-- --font: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;-- --font-mono: 'JetBrains Mono', 'Fira Code', monospace;++ --font: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;++ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--transition: .2s ease;--max-width: 860px;}@@@ -10,8 +10,6 @@<meta name="description" content="Convert, trim, merge, pitch-shift, speed-change, and process audio files online. Supports MP3, WAV, FLAC, OGG, M4A, OPUS, AAC, WMA, AIFF, and more."><meta property="og:title" content="Audio.OpenVibe — Online Audio Converter & Tools"><meta property="og:type" content="website">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -19,7 +17,6 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/style.css"></head><body>@@@ -95,9 +95,9 @@ app.use(helmet({contentSecurityPolicy: {directives: {defaultSrc: ["'self'"],-- scriptSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],-- styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"],-- fontSrc: ["'self'", "https://fonts.gstatic.com"],++ scriptSrc: ["'self'", "'unsafe-inline'", "https://openvibe.network", "https://static.cloudflareinsights.com"],++ styleSrc: ["'self'", "'unsafe-inline'"],++ fontSrc: ["'self'"],imgSrc: ["'self'", "data:", "blob:", jobsRuntime.mediaOrigin()], // job result previews are 302s to MediamediaSrc: ["'self'", "blob:", jobsRuntime.mediaOrigin()],// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).@@@ -30,7 +30,7 @@* { margin: 0; padding: 0; box-sizing: border-box; }body {-- font-family: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;++ font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;background: var(--bg-primary);color: var(--text-primary);min-height: 100vh;@@@ -10,8 +10,6 @@<meta name="description" content="Merge, split, compress, convert, rotate, reorder, watermark, and sign PDFs online. No sign-up required."><meta property="og:title" content="Docs.OpenVibe — Online PDF & Document Tools"><meta property="og:type" content="website">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -19,7 +17,6 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/style.css"></head><body>@@@ -112,9 +112,9 @@ app.use(helmet({contentSecurityPolicy: {directives: {defaultSrc: ["'self'"],-- scriptSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],-- styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"],-- fontSrc: ["'self'", "https://fonts.gstatic.com"],++ scriptSrc: ["'self'", "'unsafe-inline'", "https://openvibe.network", "https://static.cloudflareinsights.com"],++ styleSrc: ["'self'", "'unsafe-inline'"],++ fontSrc: ["'self'"],imgSrc: ["'self'", "data:", "blob:"],// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).@@@ -20,8 +20,8 @@--purple: #9b59b6;--orange: #e67e22;--cyan: #06b6d4;-- --font: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;-- --font-mono: 'JetBrains Mono', monospace;++ --font: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;++ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--radius: 10px;--shadow: 0 4px 24px rgba(0,0,0,0.4);}@@@ -22,8 +22,6 @@<meta name="twitter:title" content="Food.OpenVibe — Budget Grocery & Meal Planning"><meta name="twitter:description" content="Budget grocery finder, meal planning optimizer, food bank locator, and nutrition database for survival on a budget."><meta name="twitter:image" content="https://food.openvibe.tools/og.png">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -31,7 +29,6 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"><script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.css">@@@ -26,7 +26,7 @@function toast(msg, type = 'info') {const bg = { info: '#22c55e', error: '#ef4444', warn: '#f59e0b' };Toastify({ text: msg, duration: 3000, gravity: 'bottom', position: 'right',-- style: { background: bg[type] || bg.info, borderRadius: '10px', fontFamily: "'Outfit',sans-serif", fontSize: '12px', padding: '8px 16px' }++ style: { background: bg[type] || bg.info, borderRadius: '10px', fontFamily: "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif", fontSize: '12px', padding: '8px 16px' }}).showToast();}@@@ -85,9 +85,9 @@ app.use(helmet({contentSecurityPolicy: {directives: {defaultSrc: ["'self'"],-- scriptSrc: ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net', 'unpkg.com', 'fonts.googleapis.com', 'https://openvibe.network', "https://static.cloudflareinsights.com"],-- styleSrc: ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net', 'unpkg.com', 'fonts.googleapis.com', 'fonts.gstatic.com'],-- fontSrc: ["'self'", 'fonts.gstatic.com'],++ scriptSrc: ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net', 'unpkg.com', 'https://openvibe.network', "https://static.cloudflareinsights.com"],++ styleSrc: ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net', 'unpkg.com'],++ fontSrc: ["'self'"],imgSrc: ["'self'", 'data:', 'image.tmdb.org', '*.tile.openstreetmap.org', '*.basemaps.cartocdn.com', 'server.arcgisonline.com'],// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).@@@ -15,8 +15,6 @@<meta property="og:type" content="website"><meta property="og:url" content="https://audio.openvibe.tools"><link rel="canonical" href="https://audio.openvibe.tools">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -24,7 +22,6 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><script src="/shared/theme-loader.js" defer></script><style>:root {@@@ -48,7 +45,7 @@*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }html { background: var(--bg-primary); scroll-behavior: smooth; }-- body { min-height: 100vh; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; }++ body { min-height: 100vh; font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; }.container { max-width: 1200px; margin: 0 auto; padding: 20px; }@@@ -33,7 +33,7 @@--c-brd:var(--border,#1f2d47);--c-brd2:var(--border-light,#2c3d5c);--c-ok:var(--success-text,#22c55e);--c-warn:var(--warning-text,#f59e0b);--c-bad:var(--danger-text,#f26262);--c-shadow:var(--shadow,0 2px 12px rgba(0,0,0,.45));--c-shadow-lg:var(--shadow-lg,0 8px 32px rgba(0,0,0,.6));-- --r:14px;--r-s:10px;--mono:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;++ --r:14px;--r-s:10px;--mono:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;/* the SEO block under the tool (server/seo/render.js) */--tx:var(--c-tx);--tx2:var(--c-tx2);--tx3:var(--c-tx3);--bg2:var(--c-panel);--brd:var(--c-brd);--ac:var(--c-ac);}@@@ -220,9 +220,6 @@ table.ais td.v{font-family:var(--mono);word-break:break-all}#print-sheet .lab span{font:7pt/1.1 ui-monospace,Menlo,monospace;color:#000;margin-top:1mm;text-align:center;word-break:break-all}}</style>--<link rel="preconnect" href="https://fonts.googleapis.com">--<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>--<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"></head><body><div id="navbar-mount"></div>@@@ -15,8 +15,6 @@<meta property="og:type" content="website"><meta property="og:url" content="https://dev.openvibe.tools"><link rel="canonical" href="https://dev.openvibe.tools">--<link rel="preconnect" href="https://fonts.googleapis.com">--<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -24,12 +22,11 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>--<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><style>:root{--bg:var(--bg-primary,#0b0c12);--bg2:var(--bg-secondary,#12131c);--bg3:var(--bg-tertiary,#1a1b28);--card:var(--bg-card,rgba(22,24,36,.85));--tx:var(--text-primary,#f1f4fb);--tx2:var(--text-secondary,#a0a8c4);--tx3:var(--text-muted,#5e6580);--ac:var(--accent,#a78bfa);--acl:var(--accent-light,#c4b5fd);--brd:var(--border,rgba(255,255,255,.07));--r:16px}*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}html{background:var(--bg)}--body{min-height:100vh;font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--tx);-webkit-font-smoothing:antialiased}++body{min-height:100vh;font-family:Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;background:var(--bg);color:var(--tx);-webkit-font-smoothing:antialiased}a{color:inherit;text-decoration:none}.bg-glow{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}.bg-glow::before,.bg-glow::after{content:'';position:absolute;border-radius:50%;filter:blur(120px);opacity:.35;animation:gd 20s var(--ov-ease-in-out, ease-in-out) infinite alternate}@@@ -45,13 +42,13 @@ a{color:inherit;text-decoration:none}.mb{padding:7px 15px;border-radius:8px;background:var(--bg3);color:var(--tx2);font-size:.82rem;font-weight:600;cursor:pointer;border:1px solid var(--brd);transition:var(--ov-dur-2, .15s)}.mb:hover{background:#252738;color:var(--tx)}.mb.on{background:var(--ac);color:#fff;border-color:var(--ac)}.io{display:flex;gap:12px;margin-bottom:12px}--.io textarea{flex:1;min-height:200px;padding:14px;background:var(--bg2);color:var(--tx);border:1px solid var(--brd);border-radius:var(--r);font-family:'JetBrains Mono',monospace;font-size:.85rem;resize:vertical;outline:none;transition:border var(--ov-dur-2, .2s)}++.io textarea{flex:1;min-height:200px;padding:14px;background:var(--bg2);color:var(--tx);border:1px solid var(--brd);border-radius:var(--r);font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size:.85rem;resize:vertical;outline:none;transition:border var(--ov-dur-2, .2s)}.io textarea:focus{border-color:var(--ac)}.io textarea::placeholder{color:var(--tx3)}.acts{display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap}.ab{padding:8px 18px;border-radius:10px;font-size:.82rem;font-weight:600;cursor:pointer;transition:var(--ov-dur-2, .15s);border:none}.ab-go{background:var(--ac);color:#fff}.ab-go:hover{filter:brightness(1.15)}.ab-s{background:var(--bg3);color:var(--tx2);border:1px solid var(--brd)}.ab-s:hover{background:#252738;color:var(--tx)}--#out{min-height:100px;padding:16px;background:var(--bg2);border:1px solid var(--brd);border-radius:var(--r);font-family:'JetBrains Mono',monospace;font-size:.85rem;white-space:pre-wrap;word-break:break-all;overflow-x:auto;line-height:1.6}++#out{min-height:100px;padding:16px;background:var(--bg2);border:1px solid var(--brd);border-radius:var(--r);font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size:.85rem;white-space:pre-wrap;word-break:break-all;overflow-x:auto;line-height:1.6}#out:empty::before{content:'Output will appear here...';color:var(--tx3)}#out .err{color:var(--danger-text, #f87171)}#out .ok{color:var(--success-text, #34d399)}.dl{display:flex;font-size:.82rem;line-height:1.7}.dl .ln{width:34px;text-align:right;padding:0 6px;color:var(--tx3);user-select:none;flex-shrink:0}@@@ -393,7 +390,7 @@ P.csv=(s,m)=>{return{html:'<div style="overflow-x:auto"><table style="border-collapse:collapse;width:100%;font-size:.82rem">'+rows.map((r,ri)=>'<tr>'+r.map(c=>'<'+(ri?'td':'th')+' style="border:1px solid var(--brd);padding:6px 10px;text-align:left;'+(ri?'':'background:var(--bg3);font-weight:700')+'">'+H(c)+'</'+(ri?'td':'th')+'>').join('')+'</tr>').join('')+'</table></div>'};};P.sql=(s,m)=>DevEngine.sql(s,m);--P.md=(s,m)=>{const h=DevEngine.md(s);if(m==='To HTML')return h;return{html:'<div style="line-height:1.8;font-family:Inter,sans-serif;font-size:.92rem">'+h+'</div>'}};++P.md=(s,m)=>{const h=DevEngine.md(s);if(m==='To HTML')return h;return{html:'<div style="line-height:1.8;font-family:Inter,system-ui,-apple-system,Segoe UI,sans-serif;font-size:.92rem">'+h+'</div>'}};P.html=(s,m)=>{if(m==='Unescape'){const d=document.createElement('div');d.innerHTML=s;return d.textContent}return DevEngine.html(s,m);@@@ -15,8 +15,6 @@<meta property="og:type" content="website"><meta property="og:url" content="https://img.openvibe.tools"><link rel="canonical" href="https://img.openvibe.tools">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -24,7 +22,6 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><script src="/shared/theme-loader.js" defer></script><style>:root {@@@ -48,7 +45,7 @@*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }html { background: var(--bg-primary); scroll-behavior: smooth; }-- body { min-height: 100vh; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; }++ body { min-height: 100vh; font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; }.container { max-width: 1200px; margin: 0 auto; padding: 20px; }@@@ -15,8 +15,6 @@<meta property="og:type" content="website"><meta property="og:url" content="https://net.openvibe.tools"><link rel="canonical" href="https://net.openvibe.tools">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -24,7 +22,6 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><style>:root {--bg-primary: #0b0c12;@@@ -49,14 +46,14 @@--shadow: 0 24px 64px rgba(0,0,0,0.4);--radius: 16px;--radius-sm: 10px;-- --mono: 'JetBrains Mono', 'Fira Code', monospace;++ --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;}*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }html { background: var(--bg-primary); scroll-behavior: smooth; }body {min-height: 100vh;-- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;++ font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;background: var(--bg-primary);color: var(--text-primary);overflow-x: hidden;@@@ -103,7 +100,7 @@font-size: 15px; color: var(--text-primary); padding: 10px 0;font-family: var(--mono);}-- .search-bar input::placeholder { color: var(--text-muted); font-family: 'Inter', sans-serif; }++ .search-bar input::placeholder { color: var(--text-muted); font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif; }.search-bar .btn-go {padding: 10px 24px; border-radius: 10px; font-weight: 600; font-size: 14px;background: var(--cyan); color: #fff; white-space: nowrap;@@@ -151,9 +151,9 @@ app.use(helmet({directives: {defaultSrc: ["'self'"],// 'wasm-unsafe-eval': the barcode scanner's ZXing reader is WebAssembly (codes.html), served from /vendor here.-- scriptSrc: ["'self'", "'unsafe-inline'", "'wasm-unsafe-eval'", "https://openvibe.network", "https://openvibe.live", "cdn.jsdelivr.net", "fonts.googleapis.com", "https://static.cloudflareinsights.com"],-- styleSrc: ["'self'", "'unsafe-inline'", "fonts.googleapis.com", "fonts.gstatic.com"],-- fontSrc: ["'self'", "fonts.gstatic.com"],++ scriptSrc: ["'self'", "'unsafe-inline'", "'wasm-unsafe-eval'", "https://openvibe.network", "https://openvibe.live", "cdn.jsdelivr.net", "https://static.cloudflareinsights.com"],++ styleSrc: ["'self'", "'unsafe-inline'"],++ fontSrc: ["'self'"],imgSrc: ["'self'", "data:", "blob:", "https://openvibe.network", "https://openvibe.media"],// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).@@@ -30,7 +30,7 @@* { margin: 0; padding: 0; box-sizing: border-box; }body {-- font-family: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;++ font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;background: var(--bg-primary);color: var(--text-primary);min-height: 100vh;@@@ -208,7 +208,7 @@ body {border-radius: var(--radius-sm);background: var(--bg-tertiary);color: var(--text-secondary);-- font-family: 'JetBrains Mono', monospace;++ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size: 13px;font-weight: 500;cursor: pointer;@@@ -259,7 +259,7 @@ body {border-radius: var(--radius-sm);background: var(--bg-tertiary);color: var(--text-primary);-- font-family: 'JetBrains Mono', monospace;++ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size: 14px;width: 120px;outline: none;@@@ -10,8 +10,6 @@<meta name="description" content="Convert, compress, resize, and crop images online. Supports PNG, JPG, WebP, AVIF, HEIC, SVG, GIF, ICO, TIFF, BMP and more."><meta property="og:title" content="Img.OpenVibe — Online Image Converter & Tools"><meta property="og:type" content="website">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -19,7 +17,6 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/style.css"></head><body>@@@ -101,9 +101,9 @@ app.use(helmet({contentSecurityPolicy: {directives: {defaultSrc: ["'self'"],-- scriptSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],-- styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"],-- fontSrc: ["'self'", "https://fonts.gstatic.com"],++ scriptSrc: ["'self'", "'unsafe-inline'", "https://openvibe.network", "https://static.cloudflareinsights.com"],++ styleSrc: ["'self'", "'unsafe-inline'"],++ fontSrc: ["'self'"],imgSrc: ["'self'", "data:", "blob:", jobsRuntime.mediaOrigin()], // job result previews are 302s to Media// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).@@@ -21,8 +21,8 @@--cyan: #06b6d4;--orange: #fb923c;--pink: #ec4899;-- --font: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;-- --font-mono: 'JetBrains Mono', monospace;++ --font: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;++ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--radius: 10px;--shadow: 0 4px 24px rgba(0,0,0,0.4);}@@@ -24,8 +24,6 @@<meta name="twitter:image" content="https://maps.openvibe.tools/og.png"><!-- Fonts -->-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -33,7 +31,6 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"><!-- Font Awesome 6 -->@@@ -41,7 +41,7 @@function toast(msg, type = 'info') {const bg = { info: '#22c55e', success: '#059669', error: '#ef4444', warn: '#f59e0b', warning: '#f59e0b' };Toastify({ text: msg, duration: 3500, gravity: 'bottom', position: 'right',-- style: { background: bg[type] || bg.info, borderRadius: '10px', fontFamily: "'Outfit',sans-serif", fontSize: '12px', padding: '8px 16px' }++ style: { background: bg[type] || bg.info, borderRadius: '10px', fontFamily: "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif", fontSize: '12px', padding: '8px 16px' }}).showToast();}@@@ -816,7 +816,7 @@try {state.charts.types = new Chart($('#stats-chart'), {type:'doughnut', data:{ labels:labels.map(l=>l.charAt(0).toUpperCase()+l.slice(1)), datasets:[{data, backgroundColor:colors, borderWidth:0}] },-- options:{responsive:true, plugins:{legend:{position:'bottom',labels:{color:'#94a3b8',font:{family:'Outfit',size:11}}}}}++ options:{responsive:true, plugins:{legend:{position:'bottom',labels:{color:'#94a3b8',font:{family:'Inter, system-ui, -apple-system, Segoe UI, sans-serif',size:11}}}}}});} catch(e){}@@@ -827,7 +827,7 @@try {state.charts.sources = new Chart($('#sources-chart'), {type:'bar', data:{ labels:sl, datasets:[{label:'Locations',data:sd,backgroundColor:['#22c55e','#3b82f6','#f59e0b','#a855f7','#06b6d4','#ef4444','#ec4899','#64748b'],borderWidth:0,borderRadius:6}] },-- options:{responsive:true, scales:{x:{ticks:{color:'#94a3b8',font:{family:'Outfit',size:9}},grid:{display:false}},y:{ticks:{color:'#94a3b8'},grid:{color:'rgba(34,197,94,0.06)'}}}, plugins:{legend:{display:false}}}++ options:{responsive:true, scales:{x:{ticks:{color:'#94a3b8',font:{family:'Inter, system-ui, -apple-system, Segoe UI, sans-serif',size:9}},grid:{display:false}},y:{ticks:{color:'#94a3b8'},grid:{color:'rgba(34,197,94,0.06)'}}}, plugins:{legend:{display:false}}}});} catch(e){}@@@ -79,12 +79,12 @@ app.use(helmet({directives: {defaultSrc: ["'self'"],scriptSrc: ["'self'", "'unsafe-inline'", "unpkg.com", "cdn.jsdelivr.net", "static.cloudflareinsights.com", "https://openvibe.network"],-- styleSrc: ["'self'", "'unsafe-inline'", "unpkg.com", "cdn.jsdelivr.net", "fonts.googleapis.com"],++ styleSrc: ["'self'", "'unsafe-inline'", "unpkg.com", "cdn.jsdelivr.net"],imgSrc: ["'self'", "data:", "blob:", "*.tile.openstreetmap.org", "*.basemaps.cartocdn.com", "server.arcgisonline.com", "image.tmdb.org", "*.wp.com", "https://openvibe.media"],// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).connectSrc: ["'self'", "https://openvibe.chat", "nominatim.openstreetmap.org", "api.weather.gov", "api.open-meteo.com", "*.tile.openstreetmap.org", "*.basemaps.cartocdn.com", "server.arcgisonline.com", "https://openvibe.network", "https://openvibe.events", "https://cloudflareinsights.com"],-- fontSrc: ["'self'", "fonts.gstatic.com", "cdn.jsdelivr.net"],++ fontSrc: ["'self'", "cdn.jsdelivr.net"],// The navbar checks Network's session in a hidden /sso/check frame on typed arrivals.frameSrc: ["https://openvibe.network"],scriptSrcAttr: ["'unsafe-inline'"],@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Convert text into large ASCII art banners using block characters. Perfect for code comments, READMEs, and chat.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Create badges, stickers, and labels with custom text, backgrounds, shapes, and colors. Download as PNG.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Convert text to binary, hexadecimal, decimal, octal and back. See the raw bytes of your text.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Generate creative social media bios with fancy borders, symbols, and emoji. Perfect for Twitch, YouTube, Discord, Twitter.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Convert text to Braille Unicode characters and back. Supports A–Z, 0–9, and common punctuation.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Convert text into circled, negative circled, squared, negative squared, and regional indicator Unicode letters.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Convert text between UPPERCASE, lowercase, Title Case, camelCase, snake_case, kebab-case, PascalCase, sPoNgEbOb mocking case, and more.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Clean up messy text: remove invisible characters, fix smart quotes, strip Zalgo diacritics, normalize spacing, remove HTML tags.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Compare two texts side by side and see the differences highlighted. Paste your original and modified text to find what changed.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Count characters, words, sentences, paragraphs, and bytes. Estimate reading time and speaking time.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="HTML escape/unescape, URL encode/decode, JSON stringify/parse, Base64, and more.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Transform text into 30+ Unicode font styles: bold, italic, script, fraktur, double-struck, circled, squared, small caps, and more. Copy and paste anywhere.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="ICE raid alerts, know your rights resources, detention facility finder, emergency contacts, and preparedness tools for immigrant communities.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Text tools: fancy Unicode fonts, Zalgo glitch text, ASCII art, symbols, kaomoji, case converters, bio generators, and more. Part of the OpenVibe Network.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -186,7 +184,7 @@<a href="https://wordmark.openvibe.tools" class="tool-card"><div class="card-icon"><i class="fas fa-pen-nib"></i></div><h3>Wordmark Generator</h3>-- <p>Design text-only logos with Google Fonts selections, colors, effects, and transparent backgrounds.</p>++ <p>Design text-only logos with open font selections, colors, effects, and transparent backgrounds.</p><span class="card-domain">wordmark.openvibe.tools</span></a><a href="https://badge.openvibe.tools" class="tool-card">@@@ -1,7 +1,7 @@// ═══════════════════════════════════════════════════════════════// Logo.OpenVibe — Canvas-based Logo / Title / Badge Generation// Text rendering with effects, transparent PNG/WebP export,--// Google Fonts loading, preset templates. 100% client-side.++// openvibe.design font loading, preset templates. 100% client-side.// ═══════════════════════════════════════════════════════════════(function (root) {@@@ -152,7 +152,7 @@ const PRESETS = {},};--// ── Google Fonts loader ──────────────────────────────────────++// ── openvibe.design font loader ──────────────────────────────const loadedFonts = new Set(['Arial', 'Arial Black', 'Courier New', 'Georgia', 'Impact', 'Times New Roman', 'Verdana']);async function loadFont(fontName) {@@@ -160,7 +160,7 @@ async function loadFont(fontName) {try {const link = document.createElement('link');link.rel = 'stylesheet';-- link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontName)}:wght@400;700;900&display=swap`;++ link.href = `https://openvibe.design/fonts/css2?family=${encodeURIComponent(fontName)}:wght@400;700;900&display=swap`;document.head.appendChild(link);await document.fonts.load(`48px "${fontName}"`);loadedFonts.add(fontName);@@@ -382,7 +382,7 @@ function download(canvas, filename = 'openvibe-logo.png', format = 'png') {link.click();}--// ── Popular Google Fonts for logos ────────────────────────────++// ── Popular open fonts for logos ─────────────────────────────const POPULAR_FONTS = ['Inter', 'Montserrat', 'Playfair Display', 'Oswald', 'Roboto', 'Lato','Raleway', 'Poppins', 'Merriweather', 'Source Sans 3', 'Nunito',@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Paste JSON to beautify, minify, and validate it instantly. Syntax highlighting, error detection, and tree view.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Browse and copy Japanese text emoticons (kaomoji) sorted by mood: happy, sad, angry, love, lenny, table flip, and more.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -7,9 +7,7 @@<link rel="icon" type="image/svg+xml" data-ov-icon="tools" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-32%20-32%2064%2064%22%3E%3Cdefs%3E%3Cmask%20id%3D%22ovh%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%3E%3Crect%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%2224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fmask%3E%3ClinearGradient%20id%3D%22ovgo%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%223.09%22%20y1%3D%2223%22%20x2%3D%2257.09%22%20y2%3D%2277%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2387a6ff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%234268d6%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22ovgv%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%22%20y1%3D%2231.5%22%20x2%3D%220%22%20y2%3D%2268.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ae8df9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23784fd4%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22glow%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%22.3%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%0A%3CradialGradient%20id%3D%22bg%22%20cx%3D%22.5%22%20cy%3D%22.38%22%20r%3D%22.8%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2316223a%22%2F%3E%3Cstop%20offset%3D%22.6%22%20stop-color%3D%22%230a0f1c%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23060912%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20x%3D%22-32%22%20y%3D%22-32%22%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23bg)%22%2F%3E%3Cg%20transform%3D%22scale(0.58)%20translate(-50%20-50)%22%3E%3Cellipse%20cx%3D%2250%22%20cy%3D%2250%22%20rx%3D%2252%22%20ry%3D%2240%22%20fill%3D%22url(%23glow)%22%2F%3E%3Ccircle%20cx%3D%2230.09%22%20cy%3D%2250%22%20r%3D%2218%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgo)%22%20stroke-width%3D%2218%22%20mask%3D%22url(%23ovh)%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgv)%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"><meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light">-- <meta name="description" content="Create logos, title cards, wordmarks, badges, stickers, watermarks, and thumbnails. Transparent PNG export, Google Fonts, presets. Browser-based.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>++ <meta name="description" content="Create logos, title cards, wordmarks, badges, stickers, watermarks, and thumbnails. Transparent PNG export, open fonts, presets. Browser-based."><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -27,7 +25,7 @@<p class="hero-sub">Logo and design tools for streamers and creators. Title cards, wordmarks, badges, watermarks, thumbnails — all in your browser with transparent PNG export.</p><div class="hero-tags"><span class="hero-tag">Transparent PNG</span>-- <span class="hero-tag">Google Fonts</span>++ <span class="hero-tag">Open Fonts</span><span class="hero-tag">No Watermarks</span><span class="hero-tag">Open source</span></div>@@@ -65,7 +63,7 @@<a href="https://wordmark.openvibe.tools" class="tool-card"><div class="card-icon"><i class="fas fa-pen-nib"></i></div><h3>Wordmark Generator</h3>-- <p>Design clean text-only logos. Pick from 40+ Google Fonts, adjust weight, spacing, colors. Perfect for branding.</p>++ <p>Design clean text-only logos. Pick from 40+ open fonts, adjust weight, spacing, colors. Perfect for branding.</p><span class="card-domain">wordmark.openvibe.tools</span></a><a href="https://badge.openvibe.tools" class="tool-card">@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Write Markdown on the left, see rendered HTML on the right. Export as HTML.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Translate text to Morse code and back. Dots and dashes for your inner telegraph operator.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Generate unique gaming tags, usernames, and screen names in multiple styles.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Convert between Mexican Pesos (MXN) and US Dollars (USD) with live exchange rates. Fast, no signup.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Reverse text, flip it upside down, or mirror it. Full Unicode support with combining character awareness.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="The ultimate Mexican Spanish slang dictionary. Learn the real street Spanish — cabrón, chido, neta, güey and 100+ more with meanings, examples, and usage tips.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Convert titles into URL-friendly slugs. Strip diacritics, normalize whitespace, clean special characters.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Sort lines alphabetically, numerically, or randomly. Remove duplicates instantly.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Essential Spanish-English phrasebook with survival phrases, daily conversation, emergency expressions, and pronunciation tips.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Browse and click-to-copy arrows, stars, hearts, math symbols, currency, bullets, boxes, lines, and more special characters.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script></head>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Create stream and video thumbnails with bold text overlays. Upload a background, add text, download.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><link rel="stylesheet" href="/shared/ov-upload.css"><script src="/shared/theme-loader.js" defer></script>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Generate stylish title cards with preset effects, custom fonts, colors, and transparent PNG export.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Generate transparent text PNG images. Pick font, size, color — download with no background.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Inspect text character by character: see Unicode code points, character names, byte counts. Detect invisible characters.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Add text watermarks to images. Position, tile, adjust opacity and font. Download watermarked images.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><link rel="stylesheet" href="/shared/ov-upload.css"><script src="/shared/theme-loader.js" defer></script>@@@ -7,9 +7,7 @@<link rel="icon" type="image/svg+xml" data-ov-icon="tools" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-32%20-32%2064%2064%22%3E%3Cdefs%3E%3Cmask%20id%3D%22ovh%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%3E%3Crect%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%2224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fmask%3E%3ClinearGradient%20id%3D%22ovgo%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%223.09%22%20y1%3D%2223%22%20x2%3D%2257.09%22%20y2%3D%2277%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2387a6ff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%234268d6%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22ovgv%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%22%20y1%3D%2231.5%22%20x2%3D%220%22%20y2%3D%2268.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ae8df9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23784fd4%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22glow%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%22.3%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%0A%3CradialGradient%20id%3D%22bg%22%20cx%3D%22.5%22%20cy%3D%22.38%22%20r%3D%22.8%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2316223a%22%2F%3E%3Cstop%20offset%3D%22.6%22%20stop-color%3D%22%230a0f1c%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23060912%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20x%3D%22-32%22%20y%3D%22-32%22%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23bg)%22%2F%3E%3Cg%20transform%3D%22scale(0.58)%20translate(-50%20-50)%22%3E%3Cellipse%20cx%3D%2250%22%20cy%3D%2250%22%20rx%3D%2252%22%20ry%3D%2240%22%20fill%3D%22url(%23glow)%22%2F%3E%3Ccircle%20cx%3D%2230.09%22%20cy%3D%2250%22%20r%3D%2218%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgo)%22%20stroke-width%3D%2218%22%20mask%3D%22url(%23ovh)%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgv)%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"><meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light">-- <meta name="description" content="Create clean text-only wordmark logos with 46 Google Fonts, custom weight, spacing, and color.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>++ <meta name="description" content="Create clean text-only wordmark logos with 46 open fonts, custom weight, spacing, and color."><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><script src="/shared/theme-loader.js" defer></script><style>@@@ -8,8 +8,6 @@<meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light"><meta name="description" content="Generate creepy Zalgo glitch text with adjustable intensity. Control above, middle, and below combining characters.">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -17,7 +15,7 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">++ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/shared.css"><link rel="stylesheet" href="/shared/kit.css"><script src="/shared/theme-loader.js" defer></script>@@@ -64,9 +64,9 @@ app.use(helmet({contentSecurityPolicy: {directives: {defaultSrc: ["'self'"],-- scriptSrc: ["'self'", "'unsafe-inline'", "cdn.jsdelivr.net", "fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],-- styleSrc: ["'self'", "'unsafe-inline'", "fonts.googleapis.com", "fonts.gstatic.com"],-- fontSrc: ["'self'", "fonts.gstatic.com"],++ scriptSrc: ["'self'", "'unsafe-inline'", "cdn.jsdelivr.net", "https://openvibe.network", "https://static.cloudflareinsights.com"],++ styleSrc: ["'self'", "'unsafe-inline'", "https://openvibe.design"],++ fontSrc: ["'self'", "https://openvibe.design"],imgSrc: ["'self'", "data:", "blob:"],// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).@@@ -16,8 +16,8 @@--radius-sm: 8px;--radius-lg: 16px;--shadow: 0 4px 24px rgba(0, 0, 0, 0.35);-- --font: 'Outfit', sans-serif;-- --font-mono: 'JetBrains Mono', monospace;++ --font: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;++ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--max-w: 720px;}@@@ -7,8 +7,6 @@<link rel="icon" type="image/svg+xml" data-ov-icon="tools" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-32%20-32%2064%2064%22%3E%3Cdefs%3E%3Cmask%20id%3D%22ovh%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%3E%3Crect%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%2224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fmask%3E%3ClinearGradient%20id%3D%22ovgo%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%223.09%22%20y1%3D%2223%22%20x2%3D%2257.09%22%20y2%3D%2277%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2387a6ff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%234268d6%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22ovgv%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%22%20y1%3D%2231.5%22%20x2%3D%220%22%20y2%3D%2268.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ae8df9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23784fd4%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22glow%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%22.3%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%0A%3CradialGradient%20id%3D%22bg%22%20cx%3D%22.5%22%20cy%3D%22.38%22%20r%3D%22.8%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2316223a%22%2F%3E%3Cstop%20offset%3D%22.6%22%20stop-color%3D%22%230a0f1c%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23060912%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20x%3D%22-32%22%20y%3D%22-32%22%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23bg)%22%2F%3E%3Cg%20transform%3D%22scale(0.58)%20translate(-50%20-50)%22%3E%3Cellipse%20cx%3D%2250%22%20cy%3D%2250%22%20rx%3D%2252%22%20ry%3D%2240%22%20fill%3D%22url(%23glow)%22%2F%3E%3Ccircle%20cx%3D%2230.09%22%20cy%3D%2250%22%20r%3D%2218%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgo)%22%20stroke-width%3D%2218%22%20mask%3D%22url(%23ovh)%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgv)%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"><meta name="theme-color" content="#0d131d"><meta name="color-scheme" content="dark light">-- <link rel="preconnect" href="https://fonts.googleapis.com">-- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.Paints the cached theme synchronously so the default palette never flashes. ReadslocalStorage only: no network, nothing to block on. theme-loader.js still runs after@@@ -16,7 +14,6 @@<script>(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();</script>-- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><link rel="stylesheet" href="/css/style.css"><script src="/shared/ov-mark.js" defer></script></head>@@@ -79,9 +79,9 @@ app.use(helmet({contentSecurityPolicy: {directives: {defaultSrc: ["'self'"],-- scriptSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],-- styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"],-- fontSrc: ["'self'", "https://fonts.gstatic.com"],++ scriptSrc: ["'self'", "'unsafe-inline'", "https://openvibe.network", "https://static.cloudflareinsights.com"],++ styleSrc: ["'self'", "'unsafe-inline'"],++ fontSrc: ["'self'"],imgSrc: ["'self'", "data:", "https://i.ytimg.com", "https://img.youtube.com", "https://*.ggpht.com", "https://openvibe.network", "https://openvibe.media"],// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).connectSrc: ["'self'", "https://openvibe.network", "https://openvibe.chat", "https://openvibe.tools", "https://*.openvibe.tools", "https://cloudflareinsights.com", "https://openvibe.events"],nnew file mode 100644@@@ -0,0 +1,52 @@++'use strict';++// No Google Fonts (the owner, 2026-11-12: "make sure no remnants of font awesome or other libraries are still being++// used rather than our openvibe.design systems"). Every app draws with the house stack — Inter/system-ui for text and++// ui-monospace for code — and no page, stylesheet or script asks fonts.googleapis.com or fonts.gstatic.com for a face.++// node test/no-google-fonts.test.js++const assert = require('assert');++const fs = require('fs');++const path = require('path');++const ROOT = path.join(__dirname, '..');++++const walk = (dir, ok, out = []) => {++ if (!fs.existsSync(dir)) return out;++ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {++ if (e.name === 'node_modules' || e.name === 'vendor' || e.name === 'data' || e.name.startsWith('.')) continue;++ const p = path.join(dir, e.name);++ if (e.isDirectory()) walk(p, ok, out); else if (ok.test(e.name)) out.push(p);++ }++ return out;++};++const rel = (p) => path.relative(ROOT, p);++const GOOGLE_FONTS = /fonts\.(?:googleapis|gstatic)\.com/;++++const apps = path.join(ROOT, 'apps');++const pages = walk(apps, /\.html$/);++const styles = walk(apps, /\.css$/);++const scripts = walk(apps, /\.js$/);++++for (const p of pages) {++ assert.ok(!GOOGLE_FONTS.test(fs.readFileSync(p, 'utf8')), `${rel(p)} links Google Fonts`);++}++for (const p of styles) {++ assert.ok(!GOOGLE_FONTS.test(fs.readFileSync(p, 'utf8')), `${rel(p)} imports Google Fonts`);++}++for (const p of scripts) {++ assert.ok(!GOOGLE_FONTS.test(fs.readFileSync(p, 'utf8')), `${rel(p)} requests Google Fonts`);++}++++// The logo maker keeps the 45 families it offered on main, now through openvibe.design's self-hosted CSS.++const logo = fs.readFileSync(path.join(apps, 'text', 'public', 'js', 'logo-engine.js'), 'utf8');++assert.match(logo, /https:\/\/openvibe\.design\/fonts\/css2\?family=/, 'the logo maker loads openvibe.design/fonts/css2');++const list = logo.match(/const POPULAR_FONTS = \[([\s\S]*?)\];/);++assert.ok(list, 'POPULAR_FONTS is a list');++const families = [...list[1].matchAll(/'([^']+)'/g)].map((m) => m[1]);++assert.strictEqual(families.length, 45, `the logo maker lists 45 families, not ${families.length}`);++assert.match(logo, /font: 'Orbitron'/, 'a preset keeps its display face');++++// The text app's CSP lets the service in, and nothing of Google.++const csp = fs.readFileSync(path.join(apps, 'text', 'server', 'index.js'), 'utf8');++assert.match(csp, /styleSrc: \[[^\]]*"https:\/\/openvibe\.design"/, 'style-src allows openvibe.design');++assert.match(csp, /fontSrc: \[[^\]]*"https:\/\/openvibe\.design"/, 'font-src allows openvibe.design');++++console.log(`no google fonts: ${pages.length} pages, ${styles.length} stylesheets, ${scripts.length} scripts clean; logo maker 45 families from openvibe.design`);
@@ -24,8 +24,8 @@
@@ -24,8 +24,8 @@
--radius-sm: 6px;
--radius-sm: 6px;
--radius-lg: 16px;
--radius-lg: 16px;
--shadow: 0 4px 24px rgba(0,0,0,.35);
--shadow: 0 4px 24px rgba(0,0,0,.35);
- --font: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
- --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
+ --font: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--transition: .2s ease;
--transition: .2s ease;
--max-width: 860px;
--max-width: 860px;
}
}
@@ -10,8 +10,6 @@
@@ -10,8 +10,6 @@
<meta name="description" content="Convert, trim, merge, pitch-shift, speed-change, and process audio files online. Supports MP3, WAV, FLAC, OGG, M4A, OPUS, AAC, WMA, AIFF, and more.">
<meta name="description" content="Convert, trim, merge, pitch-shift, speed-change, and process audio files online. Supports MP3, WAV, FLAC, OGG, M4A, OPUS, AAC, WMA, AIFF, and more.">
<meta property="og:title" content="Audio.OpenVibe — Online Audio Converter & Tools">
<meta property="og:title" content="Audio.OpenVibe — Online Audio Converter & Tools">
<meta property="og:type" content="website">
<meta property="og:type" content="website">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -19,7 +17,6 @@
@@ -19,7 +17,6 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/style.css">
</head>
</head>
<body>
<body>
@@ -95,9 +95,9 @@ app.use(helmet({
@@ -95,9 +95,9 @@ app.use(helmet({
contentSecurityPolicy: {
contentSecurityPolicy: {
directives: {
directives: {
defaultSrc: ["'self'"],
defaultSrc: ["'self'"],
- scriptSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],
- styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"],
- fontSrc: ["'self'", "https://fonts.gstatic.com"],
+ scriptSrc: ["'self'", "'unsafe-inline'", "https://openvibe.network", "https://static.cloudflareinsights.com"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ fontSrc: ["'self'"],
imgSrc: ["'self'", "data:", "blob:", jobsRuntime.mediaOrigin()], // job result previews are 302s to Media
imgSrc: ["'self'", "data:", "blob:", jobsRuntime.mediaOrigin()], // job result previews are 302s to Media
mediaSrc: ["'self'", "blob:", jobsRuntime.mediaOrigin()],
mediaSrc: ["'self'", "blob:", jobsRuntime.mediaOrigin()],
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
@@ -30,7 +30,7 @@
@@ -30,7 +30,7 @@
* { margin: 0; padding: 0; box-sizing: border-box; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
body {
- font-family: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;
+ font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
background: var(--bg-primary);
background: var(--bg-primary);
color: var(--text-primary);
color: var(--text-primary);
min-height: 100vh;
min-height: 100vh;
@@ -10,8 +10,6 @@
@@ -10,8 +10,6 @@
<meta name="description" content="Merge, split, compress, convert, rotate, reorder, watermark, and sign PDFs online. No sign-up required.">
<meta name="description" content="Merge, split, compress, convert, rotate, reorder, watermark, and sign PDFs online. No sign-up required.">
<meta property="og:title" content="Docs.OpenVibe — Online PDF & Document Tools">
<meta property="og:title" content="Docs.OpenVibe — Online PDF & Document Tools">
<meta property="og:type" content="website">
<meta property="og:type" content="website">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -19,7 +17,6 @@
@@ -19,7 +17,6 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/style.css">
</head>
</head>
<body>
<body>
@@ -112,9 +112,9 @@ app.use(helmet({
@@ -112,9 +112,9 @@ app.use(helmet({
contentSecurityPolicy: {
contentSecurityPolicy: {
directives: {
directives: {
defaultSrc: ["'self'"],
defaultSrc: ["'self'"],
- scriptSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],
- styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"],
- fontSrc: ["'self'", "https://fonts.gstatic.com"],
+ scriptSrc: ["'self'", "'unsafe-inline'", "https://openvibe.network", "https://static.cloudflareinsights.com"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ fontSrc: ["'self'"],
imgSrc: ["'self'", "data:", "blob:"],
imgSrc: ["'self'", "data:", "blob:"],
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
@@ -20,8 +20,8 @@
@@ -20,8 +20,8 @@
--purple: #9b59b6;
--purple: #9b59b6;
--orange: #e67e22;
--orange: #e67e22;
--cyan: #06b6d4;
--cyan: #06b6d4;
- --font: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;
- --font-mono: 'JetBrains Mono', monospace;
+ --font: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--radius: 10px;
--radius: 10px;
--shadow: 0 4px 24px rgba(0,0,0,0.4);
--shadow: 0 4px 24px rgba(0,0,0,0.4);
}
}
@@ -22,8 +22,6 @@
@@ -22,8 +22,6 @@
<meta name="twitter:title" content="Food.OpenVibe — Budget Grocery & Meal Planning">
<meta name="twitter:title" content="Food.OpenVibe — Budget Grocery & Meal Planning">
<meta name="twitter:description" content="Budget grocery finder, meal planning optimizer, food bank locator, and nutrition database for survival on a budget.">
<meta name="twitter:description" content="Budget grocery finder, meal planning optimizer, food bank locator, and nutrition database for survival on a budget.">
<meta name="twitter:image" content="https://food.openvibe.tools/og.png">
<meta name="twitter:image" content="https://food.openvibe.tools/og.png">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -31,7 +29,6 @@
@@ -31,7 +29,6 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/src/toastify.min.css">
@@ -26,7 +26,7 @@
@@ -26,7 +26,7 @@
function toast(msg, type = 'info') {
function toast(msg, type = 'info') {
const bg = { info: '#22c55e', error: '#ef4444', warn: '#f59e0b' };
const bg = { info: '#22c55e', error: '#ef4444', warn: '#f59e0b' };
Toastify({ text: msg, duration: 3000, gravity: 'bottom', position: 'right',
Toastify({ text: msg, duration: 3000, gravity: 'bottom', position: 'right',
- style: { background: bg[type] || bg.info, borderRadius: '10px', fontFamily: "'Outfit',sans-serif", fontSize: '12px', padding: '8px 16px' }
+ style: { background: bg[type] || bg.info, borderRadius: '10px', fontFamily: "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif", fontSize: '12px', padding: '8px 16px' }
}).showToast();
}).showToast();
}
}
@@ -85,9 +85,9 @@ app.use(helmet({
@@ -85,9 +85,9 @@ app.use(helmet({
contentSecurityPolicy: {
contentSecurityPolicy: {
directives: {
directives: {
defaultSrc: ["'self'"],
defaultSrc: ["'self'"],
- scriptSrc: ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net', 'unpkg.com', 'fonts.googleapis.com', 'https://openvibe.network', "https://static.cloudflareinsights.com"],
- styleSrc: ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net', 'unpkg.com', 'fonts.googleapis.com', 'fonts.gstatic.com'],
- fontSrc: ["'self'", 'fonts.gstatic.com'],
+ scriptSrc: ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net', 'unpkg.com', 'https://openvibe.network', "https://static.cloudflareinsights.com"],
+ styleSrc: ["'self'", "'unsafe-inline'", 'cdn.jsdelivr.net', 'unpkg.com'],
+ fontSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'image.tmdb.org', '*.tile.openstreetmap.org', '*.basemaps.cartocdn.com', 'server.arcgisonline.com'],
imgSrc: ["'self'", 'data:', 'image.tmdb.org', '*.tile.openstreetmap.org', '*.basemaps.cartocdn.com', 'server.arcgisonline.com'],
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
@@ -15,8 +15,6 @@
@@ -15,8 +15,6 @@
<meta property="og:type" content="website">
<meta property="og:type" content="website">
<meta property="og:url" content="https://audio.openvibe.tools">
<meta property="og:url" content="https://audio.openvibe.tools">
<link rel="canonical" href="https://audio.openvibe.tools">
<link rel="canonical" href="https://audio.openvibe.tools">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -24,7 +22,6 @@
@@ -24,7 +22,6 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
:root {
:root {
@@ -48,7 +45,7 @@
@@ -48,7 +45,7 @@
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--bg-primary); scroll-behavior: smooth; }
html { background: var(--bg-primary); scroll-behavior: smooth; }
- body { min-height: 100vh; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; }
+ body { min-height: 100vh; font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
@@ -33,7 +33,7 @@
@@ -33,7 +33,7 @@
--c-brd:var(--border,#1f2d47);--c-brd2:var(--border-light,#2c3d5c);
--c-brd:var(--border,#1f2d47);--c-brd2:var(--border-light,#2c3d5c);
--c-ok:var(--success-text,#22c55e);--c-warn:var(--warning-text,#f59e0b);--c-bad:var(--danger-text,#f26262);
--c-ok:var(--success-text,#22c55e);--c-warn:var(--warning-text,#f59e0b);--c-bad:var(--danger-text,#f26262);
--c-shadow:var(--shadow,0 2px 12px rgba(0,0,0,.45));--c-shadow-lg:var(--shadow-lg,0 8px 32px rgba(0,0,0,.6));
--c-shadow:var(--shadow,0 2px 12px rgba(0,0,0,.45));--c-shadow-lg:var(--shadow-lg,0 8px 32px rgba(0,0,0,.6));
- --r:14px;--r-s:10px;--mono:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;
+ --r:14px;--r-s:10px;--mono:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
/* the SEO block under the tool (server/seo/render.js) */
/* the SEO block under the tool (server/seo/render.js) */
--tx:var(--c-tx);--tx2:var(--c-tx2);--tx3:var(--c-tx3);--bg2:var(--c-panel);--brd:var(--c-brd);--ac:var(--c-ac);
--tx:var(--c-tx);--tx2:var(--c-tx2);--tx3:var(--c-tx3);--bg2:var(--c-panel);--brd:var(--c-brd);--ac:var(--c-ac);
}
}
@@ -220,9 +220,6 @@ table.ais td.v{font-family:var(--mono);word-break:break-all}
@@ -220,9 +220,6 @@ table.ais td.v{font-family:var(--mono);word-break:break-all}
#print-sheet .lab span{font:7pt/1.1 ui-monospace,Menlo,monospace;color:#000;margin-top:1mm;text-align:center;word-break:break-all}
#print-sheet .lab span{font:7pt/1.1 ui-monospace,Menlo,monospace;color:#000;margin-top:1mm;text-align:center;word-break:break-all}
}
}
</style>
</style>
-<link rel="preconnect" href="https://fonts.googleapis.com">
-<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
-<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
</head>
</head>
<body>
<body>
<div id="navbar-mount"></div>
<div id="navbar-mount"></div>
@@ -15,8 +15,6 @@
@@ -15,8 +15,6 @@
<meta property="og:type" content="website">
<meta property="og:type" content="website">
<meta property="og:url" content="https://dev.openvibe.tools">
<meta property="og:url" content="https://dev.openvibe.tools">
<link rel="canonical" href="https://dev.openvibe.tools">
<link rel="canonical" href="https://dev.openvibe.tools">
-<link rel="preconnect" href="https://fonts.googleapis.com">
-<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -24,12 +22,11 @@
@@ -24,12 +22,11 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
-<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
<style>
:root{--bg:var(--bg-primary,#0b0c12);--bg2:var(--bg-secondary,#12131c);--bg3:var(--bg-tertiary,#1a1b28);--card:var(--bg-card,rgba(22,24,36,.85));--tx:var(--text-primary,#f1f4fb);--tx2:var(--text-secondary,#a0a8c4);--tx3:var(--text-muted,#5e6580);--ac:var(--accent,#a78bfa);--acl:var(--accent-light,#c4b5fd);--brd:var(--border,rgba(255,255,255,.07));--r:16px}
:root{--bg:var(--bg-primary,#0b0c12);--bg2:var(--bg-secondary,#12131c);--bg3:var(--bg-tertiary,#1a1b28);--card:var(--bg-card,rgba(22,24,36,.85));--tx:var(--text-primary,#f1f4fb);--tx2:var(--text-secondary,#a0a8c4);--tx3:var(--text-muted,#5e6580);--ac:var(--accent,#a78bfa);--acl:var(--accent-light,#c4b5fd);--brd:var(--border,rgba(255,255,255,.07));--r:16px}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{background:var(--bg)}
html{background:var(--bg)}
-body{min-height:100vh;font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--tx);-webkit-font-smoothing:antialiased}
+body{min-height:100vh;font-family:Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;background:var(--bg);color:var(--tx);-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}
a{color:inherit;text-decoration:none}
.bg-glow{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.bg-glow{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.bg-glow::before,.bg-glow::after{content:'';position:absolute;border-radius:50%;filter:blur(120px);opacity:.35;animation:gd 20s var(--ov-ease-in-out, ease-in-out) infinite alternate}
.bg-glow::before,.bg-glow::after{content:'';position:absolute;border-radius:50%;filter:blur(120px);opacity:.35;animation:gd 20s var(--ov-ease-in-out, ease-in-out) infinite alternate}
@@ -45,13 +42,13 @@ a{color:inherit;text-decoration:none}
@@ -45,13 +42,13 @@ a{color:inherit;text-decoration:none}
.mb{padding:7px 15px;border-radius:8px;background:var(--bg3);color:var(--tx2);font-size:.82rem;font-weight:600;cursor:pointer;border:1px solid var(--brd);transition:var(--ov-dur-2, .15s)}
.mb{padding:7px 15px;border-radius:8px;background:var(--bg3);color:var(--tx2);font-size:.82rem;font-weight:600;cursor:pointer;border:1px solid var(--brd);transition:var(--ov-dur-2, .15s)}
.mb:hover{background:#252738;color:var(--tx)}.mb.on{background:var(--ac);color:#fff;border-color:var(--ac)}
.mb:hover{background:#252738;color:var(--tx)}.mb.on{background:var(--ac);color:#fff;border-color:var(--ac)}
.io{display:flex;gap:12px;margin-bottom:12px}
.io{display:flex;gap:12px;margin-bottom:12px}
-.io textarea{flex:1;min-height:200px;padding:14px;background:var(--bg2);color:var(--tx);border:1px solid var(--brd);border-radius:var(--r);font-family:'JetBrains Mono',monospace;font-size:.85rem;resize:vertical;outline:none;transition:border var(--ov-dur-2, .2s)}
+.io textarea{flex:1;min-height:200px;padding:14px;background:var(--bg2);color:var(--tx);border:1px solid var(--brd);border-radius:var(--r);font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size:.85rem;resize:vertical;outline:none;transition:border var(--ov-dur-2, .2s)}
.io textarea:focus{border-color:var(--ac)}.io textarea::placeholder{color:var(--tx3)}
.io textarea:focus{border-color:var(--ac)}.io textarea::placeholder{color:var(--tx3)}
.acts{display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap}
.acts{display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap}
.ab{padding:8px 18px;border-radius:10px;font-size:.82rem;font-weight:600;cursor:pointer;transition:var(--ov-dur-2, .15s);border:none}
.ab{padding:8px 18px;border-radius:10px;font-size:.82rem;font-weight:600;cursor:pointer;transition:var(--ov-dur-2, .15s);border:none}
.ab-go{background:var(--ac);color:#fff}.ab-go:hover{filter:brightness(1.15)}
.ab-go{background:var(--ac);color:#fff}.ab-go:hover{filter:brightness(1.15)}
.ab-s{background:var(--bg3);color:var(--tx2);border:1px solid var(--brd)}.ab-s:hover{background:#252738;color:var(--tx)}
.ab-s{background:var(--bg3);color:var(--tx2);border:1px solid var(--brd)}.ab-s:hover{background:#252738;color:var(--tx)}
-#out{min-height:100px;padding:16px;background:var(--bg2);border:1px solid var(--brd);border-radius:var(--r);font-family:'JetBrains Mono',monospace;font-size:.85rem;white-space:pre-wrap;word-break:break-all;overflow-x:auto;line-height:1.6}
+#out{min-height:100px;padding:16px;background:var(--bg2);border:1px solid var(--brd);border-radius:var(--r);font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;font-size:.85rem;white-space:pre-wrap;word-break:break-all;overflow-x:auto;line-height:1.6}
#out:empty::before{content:'Output will appear here...';color:var(--tx3)}
#out:empty::before{content:'Output will appear here...';color:var(--tx3)}
#out .err{color:var(--danger-text, #f87171)}#out .ok{color:var(--success-text, #34d399)}
#out .err{color:var(--danger-text, #f87171)}#out .ok{color:var(--success-text, #34d399)}
.dl{display:flex;font-size:.82rem;line-height:1.7}.dl .ln{width:34px;text-align:right;padding:0 6px;color:var(--tx3);user-select:none;flex-shrink:0}
.dl{display:flex;font-size:.82rem;line-height:1.7}.dl .ln{width:34px;text-align:right;padding:0 6px;color:var(--tx3);user-select:none;flex-shrink:0}
@@ -393,7 +390,7 @@ P.csv=(s,m)=>{
@@ -393,7 +390,7 @@ P.csv=(s,m)=>{
return{html:'<div style="overflow-x:auto"><table style="border-collapse:collapse;width:100%;font-size:.82rem">'+rows.map((r,ri)=>'<tr>'+r.map(c=>'<'+(ri?'td':'th')+' style="border:1px solid var(--brd);padding:6px 10px;text-align:left;'+(ri?'':'background:var(--bg3);font-weight:700')+'">'+H(c)+'</'+(ri?'td':'th')+'>').join('')+'</tr>').join('')+'</table></div>'};
return{html:'<div style="overflow-x:auto"><table style="border-collapse:collapse;width:100%;font-size:.82rem">'+rows.map((r,ri)=>'<tr>'+r.map(c=>'<'+(ri?'td':'th')+' style="border:1px solid var(--brd);padding:6px 10px;text-align:left;'+(ri?'':'background:var(--bg3);font-weight:700')+'">'+H(c)+'</'+(ri?'td':'th')+'>').join('')+'</tr>').join('')+'</table></div>'};
};
};
P.sql=(s,m)=>DevEngine.sql(s,m);
P.sql=(s,m)=>DevEngine.sql(s,m);
-P.md=(s,m)=>{const h=DevEngine.md(s);if(m==='To HTML')return h;return{html:'<div style="line-height:1.8;font-family:Inter,sans-serif;font-size:.92rem">'+h+'</div>'}};
+P.md=(s,m)=>{const h=DevEngine.md(s);if(m==='To HTML')return h;return{html:'<div style="line-height:1.8;font-family:Inter,system-ui,-apple-system,Segoe UI,sans-serif;font-size:.92rem">'+h+'</div>'}};
P.html=(s,m)=>{
P.html=(s,m)=>{
if(m==='Unescape'){const d=document.createElement('div');d.innerHTML=s;return d.textContent}
if(m==='Unescape'){const d=document.createElement('div');d.innerHTML=s;return d.textContent}
return DevEngine.html(s,m);
return DevEngine.html(s,m);
@@ -15,8 +15,6 @@
@@ -15,8 +15,6 @@
<meta property="og:type" content="website">
<meta property="og:type" content="website">
<meta property="og:url" content="https://img.openvibe.tools">
<meta property="og:url" content="https://img.openvibe.tools">
<link rel="canonical" href="https://img.openvibe.tools">
<link rel="canonical" href="https://img.openvibe.tools">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -24,7 +22,6 @@
@@ -24,7 +22,6 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
:root {
:root {
@@ -48,7 +45,7 @@
@@ -48,7 +45,7 @@
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--bg-primary); scroll-behavior: smooth; }
html { background: var(--bg-primary); scroll-behavior: smooth; }
- body { min-height: 100vh; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; }
+ body { min-height: 100vh; font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif; background: var(--bg-primary); color: var(--text-primary); line-height: 1.6; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
@@ -15,8 +15,6 @@
@@ -15,8 +15,6 @@
<meta property="og:type" content="website">
<meta property="og:type" content="website">
<meta property="og:url" content="https://net.openvibe.tools">
<meta property="og:url" content="https://net.openvibe.tools">
<link rel="canonical" href="https://net.openvibe.tools">
<link rel="canonical" href="https://net.openvibe.tools">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -24,7 +22,6 @@
@@ -24,7 +22,6 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
<style>
:root {
:root {
--bg-primary: #0b0c12;
--bg-primary: #0b0c12;
@@ -49,14 +46,14 @@
@@ -49,14 +46,14 @@
--shadow: 0 24px 64px rgba(0,0,0,0.4);
--shadow: 0 24px 64px rgba(0,0,0,0.4);
--radius: 16px;
--radius: 16px;
--radius-sm: 10px;
--radius-sm: 10px;
- --mono: 'JetBrains Mono', 'Fira Code', monospace;
+ --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--bg-primary); scroll-behavior: smooth; }
html { background: var(--bg-primary); scroll-behavior: smooth; }
body {
body {
min-height: 100vh;
min-height: 100vh;
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
+ font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
background: var(--bg-primary);
background: var(--bg-primary);
color: var(--text-primary);
color: var(--text-primary);
overflow-x: hidden;
overflow-x: hidden;
@@ -103,7 +100,7 @@
@@ -103,7 +100,7 @@
font-size: 15px; color: var(--text-primary); padding: 10px 0;
font-size: 15px; color: var(--text-primary); padding: 10px 0;
font-family: var(--mono);
font-family: var(--mono);
}
}
- .search-bar input::placeholder { color: var(--text-muted); font-family: 'Inter', sans-serif; }
+ .search-bar input::placeholder { color: var(--text-muted); font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif; }
.search-bar .btn-go {
.search-bar .btn-go {
padding: 10px 24px; border-radius: 10px; font-weight: 600; font-size: 14px;
padding: 10px 24px; border-radius: 10px; font-weight: 600; font-size: 14px;
background: var(--cyan); color: #fff; white-space: nowrap;
background: var(--cyan); color: #fff; white-space: nowrap;
@@ -151,9 +151,9 @@ app.use(helmet({
@@ -151,9 +151,9 @@ app.use(helmet({
directives: {
directives: {
defaultSrc: ["'self'"],
defaultSrc: ["'self'"],
// 'wasm-unsafe-eval': the barcode scanner's ZXing reader is WebAssembly (codes.html), served from /vendor here.
// 'wasm-unsafe-eval': the barcode scanner's ZXing reader is WebAssembly (codes.html), served from /vendor here.
- scriptSrc: ["'self'", "'unsafe-inline'", "'wasm-unsafe-eval'", "https://openvibe.network", "https://openvibe.live", "cdn.jsdelivr.net", "fonts.googleapis.com", "https://static.cloudflareinsights.com"],
- styleSrc: ["'self'", "'unsafe-inline'", "fonts.googleapis.com", "fonts.gstatic.com"],
- fontSrc: ["'self'", "fonts.gstatic.com"],
+ scriptSrc: ["'self'", "'unsafe-inline'", "'wasm-unsafe-eval'", "https://openvibe.network", "https://openvibe.live", "cdn.jsdelivr.net", "https://static.cloudflareinsights.com"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ fontSrc: ["'self'"],
imgSrc: ["'self'", "data:", "blob:", "https://openvibe.network", "https://openvibe.media"],
imgSrc: ["'self'", "data:", "blob:", "https://openvibe.network", "https://openvibe.media"],
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
@@ -30,7 +30,7 @@
@@ -30,7 +30,7 @@
* { margin: 0; padding: 0; box-sizing: border-box; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
body {
- font-family: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;
+ font-family: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
background: var(--bg-primary);
background: var(--bg-primary);
color: var(--text-primary);
color: var(--text-primary);
min-height: 100vh;
min-height: 100vh;
@@ -208,7 +208,7 @@ body {
@@ -208,7 +208,7 @@ body {
border-radius: var(--radius-sm);
border-radius: var(--radius-sm);
background: var(--bg-tertiary);
background: var(--bg-tertiary);
color: var(--text-secondary);
color: var(--text-secondary);
- font-family: 'JetBrains Mono', monospace;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 13px;
font-size: 13px;
font-weight: 500;
font-weight: 500;
cursor: pointer;
cursor: pointer;
@@ -259,7 +259,7 @@ body {
@@ -259,7 +259,7 @@ body {
border-radius: var(--radius-sm);
border-radius: var(--radius-sm);
background: var(--bg-tertiary);
background: var(--bg-tertiary);
color: var(--text-primary);
color: var(--text-primary);
- font-family: 'JetBrains Mono', monospace;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 14px;
font-size: 14px;
width: 120px;
width: 120px;
outline: none;
outline: none;
@@ -10,8 +10,6 @@
@@ -10,8 +10,6 @@
<meta name="description" content="Convert, compress, resize, and crop images online. Supports PNG, JPG, WebP, AVIF, HEIC, SVG, GIF, ICO, TIFF, BMP and more.">
<meta name="description" content="Convert, compress, resize, and crop images online. Supports PNG, JPG, WebP, AVIF, HEIC, SVG, GIF, ICO, TIFF, BMP and more.">
<meta property="og:title" content="Img.OpenVibe — Online Image Converter & Tools">
<meta property="og:title" content="Img.OpenVibe — Online Image Converter & Tools">
<meta property="og:type" content="website">
<meta property="og:type" content="website">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -19,7 +17,6 @@
@@ -19,7 +17,6 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/style.css">
</head>
</head>
<body>
<body>
@@ -101,9 +101,9 @@ app.use(helmet({
@@ -101,9 +101,9 @@ app.use(helmet({
contentSecurityPolicy: {
contentSecurityPolicy: {
directives: {
directives: {
defaultSrc: ["'self'"],
defaultSrc: ["'self'"],
- scriptSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],
- styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"],
- fontSrc: ["'self'", "https://fonts.gstatic.com"],
+ scriptSrc: ["'self'", "'unsafe-inline'", "https://openvibe.network", "https://static.cloudflareinsights.com"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ fontSrc: ["'self'"],
imgSrc: ["'self'", "data:", "blob:", jobsRuntime.mediaOrigin()], // job result previews are 302s to Media
imgSrc: ["'self'", "data:", "blob:", jobsRuntime.mediaOrigin()], // job result previews are 302s to Media
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
@@ -21,8 +21,8 @@
@@ -21,8 +21,8 @@
--cyan: #06b6d4;
--cyan: #06b6d4;
--orange: #fb923c;
--orange: #fb923c;
--pink: #ec4899;
--pink: #ec4899;
- --font: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;
- --font-mono: 'JetBrains Mono', monospace;
+ --font: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--radius: 10px;
--radius: 10px;
--shadow: 0 4px 24px rgba(0,0,0,0.4);
--shadow: 0 4px 24px rgba(0,0,0,0.4);
}
}
@@ -24,8 +24,6 @@
@@ -24,8 +24,6 @@
<meta name="twitter:image" content="https://maps.openvibe.tools/og.png">
<meta name="twitter:image" content="https://maps.openvibe.tools/og.png">
<!-- Fonts -->
<!-- Fonts -->
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -33,7 +31,6 @@
@@ -33,7 +31,6 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;600&family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<!-- Font Awesome 6 -->
<!-- Font Awesome 6 -->
@@ -41,7 +41,7 @@
@@ -41,7 +41,7 @@
function toast(msg, type = 'info') {
function toast(msg, type = 'info') {
const bg = { info: '#22c55e', success: '#059669', error: '#ef4444', warn: '#f59e0b', warning: '#f59e0b' };
const bg = { info: '#22c55e', success: '#059669', error: '#ef4444', warn: '#f59e0b', warning: '#f59e0b' };
Toastify({ text: msg, duration: 3500, gravity: 'bottom', position: 'right',
Toastify({ text: msg, duration: 3500, gravity: 'bottom', position: 'right',
- style: { background: bg[type] || bg.info, borderRadius: '10px', fontFamily: "'Outfit',sans-serif", fontSize: '12px', padding: '8px 16px' }
+ style: { background: bg[type] || bg.info, borderRadius: '10px', fontFamily: "Inter, system-ui, -apple-system, 'Segoe UI', sans-serif", fontSize: '12px', padding: '8px 16px' }
}).showToast();
}).showToast();
}
}
@@ -816,7 +816,7 @@
@@ -816,7 +816,7 @@
try {
try {
state.charts.types = new Chart($('#stats-chart'), {
state.charts.types = new Chart($('#stats-chart'), {
type:'doughnut', data:{ labels:labels.map(l=>l.charAt(0).toUpperCase()+l.slice(1)), datasets:[{data, backgroundColor:colors, borderWidth:0}] },
type:'doughnut', data:{ labels:labels.map(l=>l.charAt(0).toUpperCase()+l.slice(1)), datasets:[{data, backgroundColor:colors, borderWidth:0}] },
- options:{responsive:true, plugins:{legend:{position:'bottom',labels:{color:'#94a3b8',font:{family:'Outfit',size:11}}}}}
+ options:{responsive:true, plugins:{legend:{position:'bottom',labels:{color:'#94a3b8',font:{family:'Inter, system-ui, -apple-system, Segoe UI, sans-serif',size:11}}}}}
});
});
} catch(e){}
} catch(e){}
@@ -827,7 +827,7 @@
@@ -827,7 +827,7 @@
try {
try {
state.charts.sources = new Chart($('#sources-chart'), {
state.charts.sources = new Chart($('#sources-chart'), {
type:'bar', data:{ labels:sl, datasets:[{label:'Locations',data:sd,backgroundColor:['#22c55e','#3b82f6','#f59e0b','#a855f7','#06b6d4','#ef4444','#ec4899','#64748b'],borderWidth:0,borderRadius:6}] },
type:'bar', data:{ labels:sl, datasets:[{label:'Locations',data:sd,backgroundColor:['#22c55e','#3b82f6','#f59e0b','#a855f7','#06b6d4','#ef4444','#ec4899','#64748b'],borderWidth:0,borderRadius:6}] },
- options:{responsive:true, scales:{x:{ticks:{color:'#94a3b8',font:{family:'Outfit',size:9}},grid:{display:false}},y:{ticks:{color:'#94a3b8'},grid:{color:'rgba(34,197,94,0.06)'}}}, plugins:{legend:{display:false}}}
+ options:{responsive:true, scales:{x:{ticks:{color:'#94a3b8',font:{family:'Inter, system-ui, -apple-system, Segoe UI, sans-serif',size:9}},grid:{display:false}},y:{ticks:{color:'#94a3b8'},grid:{color:'rgba(34,197,94,0.06)'}}}, plugins:{legend:{display:false}}}
});
});
} catch(e){}
} catch(e){}
@@ -79,12 +79,12 @@ app.use(helmet({
@@ -79,12 +79,12 @@ app.use(helmet({
directives: {
directives: {
defaultSrc: ["'self'"],
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", "unpkg.com", "cdn.jsdelivr.net", "static.cloudflareinsights.com", "https://openvibe.network"],
scriptSrc: ["'self'", "'unsafe-inline'", "unpkg.com", "cdn.jsdelivr.net", "static.cloudflareinsights.com", "https://openvibe.network"],
- styleSrc: ["'self'", "'unsafe-inline'", "unpkg.com", "cdn.jsdelivr.net", "fonts.googleapis.com"],
+ styleSrc: ["'self'", "'unsafe-inline'", "unpkg.com", "cdn.jsdelivr.net"],
imgSrc: ["'self'", "data:", "blob:", "*.tile.openstreetmap.org", "*.basemaps.cartocdn.com", "server.arcgisonline.com", "image.tmdb.org", "*.wp.com", "https://openvibe.media"],
imgSrc: ["'self'", "data:", "blob:", "*.tile.openstreetmap.org", "*.basemaps.cartocdn.com", "server.arcgisonline.com", "image.tmdb.org", "*.wp.com", "https://openvibe.media"],
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
connectSrc: ["'self'", "https://openvibe.chat", "nominatim.openstreetmap.org", "api.weather.gov", "api.open-meteo.com", "*.tile.openstreetmap.org", "*.basemaps.cartocdn.com", "server.arcgisonline.com", "https://openvibe.network", "https://openvibe.events", "https://cloudflareinsights.com"],
connectSrc: ["'self'", "https://openvibe.chat", "nominatim.openstreetmap.org", "api.weather.gov", "api.open-meteo.com", "*.tile.openstreetmap.org", "*.basemaps.cartocdn.com", "server.arcgisonline.com", "https://openvibe.network", "https://openvibe.events", "https://cloudflareinsights.com"],
- fontSrc: ["'self'", "fonts.gstatic.com", "cdn.jsdelivr.net"],
+ fontSrc: ["'self'", "cdn.jsdelivr.net"],
// The navbar checks Network's session in a hidden /sso/check frame on typed arrivals.
// The navbar checks Network's session in a hidden /sso/check frame on typed arrivals.
frameSrc: ["https://openvibe.network"],
frameSrc: ["https://openvibe.network"],
scriptSrcAttr: ["'unsafe-inline'"],
scriptSrcAttr: ["'unsafe-inline'"],
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Convert text into large ASCII art banners using block characters. Perfect for code comments, READMEs, and chat.">
<meta name="description" content="Convert text into large ASCII art banners using block characters. Perfect for code comments, READMEs, and chat.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Create badges, stickers, and labels with custom text, backgrounds, shapes, and colors. Download as PNG.">
<meta name="description" content="Create badges, stickers, and labels with custom text, backgrounds, shapes, and colors. Download as PNG.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Convert text to binary, hexadecimal, decimal, octal and back. See the raw bytes of your text.">
<meta name="description" content="Convert text to binary, hexadecimal, decimal, octal and back. See the raw bytes of your text.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Generate creative social media bios with fancy borders, symbols, and emoji. Perfect for Twitch, YouTube, Discord, Twitter.">
<meta name="description" content="Generate creative social media bios with fancy borders, symbols, and emoji. Perfect for Twitch, YouTube, Discord, Twitter.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Convert text to Braille Unicode characters and back. Supports A–Z, 0–9, and common punctuation.">
<meta name="description" content="Convert text to Braille Unicode characters and back. Supports A–Z, 0–9, and common punctuation.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Convert text into circled, negative circled, squared, negative squared, and regional indicator Unicode letters.">
<meta name="description" content="Convert text into circled, negative circled, squared, negative squared, and regional indicator Unicode letters.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Convert text between UPPERCASE, lowercase, Title Case, camelCase, snake_case, kebab-case, PascalCase, sPoNgEbOb mocking case, and more.">
<meta name="description" content="Convert text between UPPERCASE, lowercase, Title Case, camelCase, snake_case, kebab-case, PascalCase, sPoNgEbOb mocking case, and more.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Clean up messy text: remove invisible characters, fix smart quotes, strip Zalgo diacritics, normalize spacing, remove HTML tags.">
<meta name="description" content="Clean up messy text: remove invisible characters, fix smart quotes, strip Zalgo diacritics, normalize spacing, remove HTML tags.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Compare two texts side by side and see the differences highlighted. Paste your original and modified text to find what changed.">
<meta name="description" content="Compare two texts side by side and see the differences highlighted. Paste your original and modified text to find what changed.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Count characters, words, sentences, paragraphs, and bytes. Estimate reading time and speaking time.">
<meta name="description" content="Count characters, words, sentences, paragraphs, and bytes. Estimate reading time and speaking time.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="HTML escape/unescape, URL encode/decode, JSON stringify/parse, Base64, and more.">
<meta name="description" content="HTML escape/unescape, URL encode/decode, JSON stringify/parse, Base64, and more.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Transform text into 30+ Unicode font styles: bold, italic, script, fraktur, double-struck, circled, squared, small caps, and more. Copy and paste anywhere.">
<meta name="description" content="Transform text into 30+ Unicode font styles: bold, italic, script, fraktur, double-struck, circled, squared, small caps, and more. Copy and paste anywhere.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="ICE raid alerts, know your rights resources, detention facility finder, emergency contacts, and preparedness tools for immigrant communities.">
<meta name="description" content="ICE raid alerts, know your rights resources, detention facility finder, emergency contacts, and preparedness tools for immigrant communities.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Text tools: fancy Unicode fonts, Zalgo glitch text, ASCII art, symbols, kaomoji, case converters, bio generators, and more. Part of the OpenVibe Network.">
<meta name="description" content="Text tools: fancy Unicode fonts, Zalgo glitch text, ASCII art, symbols, kaomoji, case converters, bio generators, and more. Part of the OpenVibe Network.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -186,7 +184,7 @@
@@ -186,7 +184,7 @@
<a href="https://wordmark.openvibe.tools" class="tool-card">
<a href="https://wordmark.openvibe.tools" class="tool-card">
<div class="card-icon"><i class="fas fa-pen-nib"></i></div>
<div class="card-icon"><i class="fas fa-pen-nib"></i></div>
<h3>Wordmark Generator</h3>
<h3>Wordmark Generator</h3>
- <p>Design text-only logos with Google Fonts selections, colors, effects, and transparent backgrounds.</p>
+ <p>Design text-only logos with open font selections, colors, effects, and transparent backgrounds.</p>
<span class="card-domain">wordmark.openvibe.tools</span>
<span class="card-domain">wordmark.openvibe.tools</span>
</a>
</a>
<a href="https://badge.openvibe.tools" class="tool-card">
<a href="https://badge.openvibe.tools" class="tool-card">
@@ -1,7 +1,7 @@
@@ -1,7 +1,7 @@
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
// Logo.OpenVibe — Canvas-based Logo / Title / Badge Generation
// Logo.OpenVibe — Canvas-based Logo / Title / Badge Generation
// Text rendering with effects, transparent PNG/WebP export,
// Text rendering with effects, transparent PNG/WebP export,
-// Google Fonts loading, preset templates. 100% client-side.
+// openvibe.design font loading, preset templates. 100% client-side.
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
(function (root) {
(function (root) {
@@ -152,7 +152,7 @@ const PRESETS = {
@@ -152,7 +152,7 @@ const PRESETS = {
},
},
};
};
-// ── Google Fonts loader ──────────────────────────────────────
+// ── openvibe.design font loader ──────────────────────────────
const loadedFonts = new Set(['Arial', 'Arial Black', 'Courier New', 'Georgia', 'Impact', 'Times New Roman', 'Verdana']);
const loadedFonts = new Set(['Arial', 'Arial Black', 'Courier New', 'Georgia', 'Impact', 'Times New Roman', 'Verdana']);
async function loadFont(fontName) {
async function loadFont(fontName) {
@@ -160,7 +160,7 @@ async function loadFont(fontName) {
@@ -160,7 +160,7 @@ async function loadFont(fontName) {
try {
try {
const link = document.createElement('link');
const link = document.createElement('link');
link.rel = 'stylesheet';
link.rel = 'stylesheet';
- link.href = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fontName)}:wght@400;700;900&display=swap`;
+ link.href = `https://openvibe.design/fonts/css2?family=${encodeURIComponent(fontName)}:wght@400;700;900&display=swap`;
document.head.appendChild(link);
document.head.appendChild(link);
await document.fonts.load(`48px "${fontName}"`);
await document.fonts.load(`48px "${fontName}"`);
loadedFonts.add(fontName);
loadedFonts.add(fontName);
@@ -382,7 +382,7 @@ function download(canvas, filename = 'openvibe-logo.png', format = 'png') {
@@ -382,7 +382,7 @@ function download(canvas, filename = 'openvibe-logo.png', format = 'png') {
link.click();
link.click();
}
}
-// ── Popular Google Fonts for logos ────────────────────────────
+// ── Popular open fonts for logos ─────────────────────────────
const POPULAR_FONTS = [
const POPULAR_FONTS = [
'Inter', 'Montserrat', 'Playfair Display', 'Oswald', 'Roboto', 'Lato',
'Inter', 'Montserrat', 'Playfair Display', 'Oswald', 'Roboto', 'Lato',
'Raleway', 'Poppins', 'Merriweather', 'Source Sans 3', 'Nunito',
'Raleway', 'Poppins', 'Merriweather', 'Source Sans 3', 'Nunito',
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Paste JSON to beautify, minify, and validate it instantly. Syntax highlighting, error detection, and tree view.">
<meta name="description" content="Paste JSON to beautify, minify, and validate it instantly. Syntax highlighting, error detection, and tree view.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Browse and copy Japanese text emoticons (kaomoji) sorted by mood: happy, sad, angry, love, lenny, table flip, and more.">
<meta name="description" content="Browse and copy Japanese text emoticons (kaomoji) sorted by mood: happy, sad, angry, love, lenny, table flip, and more.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -7,9 +7,7 @@
@@ -7,9 +7,7 @@
<link rel="icon" type="image/svg+xml" data-ov-icon="tools" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-32%20-32%2064%2064%22%3E%3Cdefs%3E%3Cmask%20id%3D%22ovh%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%3E%3Crect%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%2224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fmask%3E%3ClinearGradient%20id%3D%22ovgo%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%223.09%22%20y1%3D%2223%22%20x2%3D%2257.09%22%20y2%3D%2277%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2387a6ff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%234268d6%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22ovgv%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%22%20y1%3D%2231.5%22%20x2%3D%220%22%20y2%3D%2268.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ae8df9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23784fd4%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22glow%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%22.3%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%0A%3CradialGradient%20id%3D%22bg%22%20cx%3D%22.5%22%20cy%3D%22.38%22%20r%3D%22.8%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2316223a%22%2F%3E%3Cstop%20offset%3D%22.6%22%20stop-color%3D%22%230a0f1c%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23060912%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20x%3D%22-32%22%20y%3D%22-32%22%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23bg)%22%2F%3E%3Cg%20transform%3D%22scale(0.58)%20translate(-50%20-50)%22%3E%3Cellipse%20cx%3D%2250%22%20cy%3D%2250%22%20rx%3D%2252%22%20ry%3D%2240%22%20fill%3D%22url(%23glow)%22%2F%3E%3Ccircle%20cx%3D%2230.09%22%20cy%3D%2250%22%20r%3D%2218%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgo)%22%20stroke-width%3D%2218%22%20mask%3D%22url(%23ovh)%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgv)%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
<link rel="icon" type="image/svg+xml" data-ov-icon="tools" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-32%20-32%2064%2064%22%3E%3Cdefs%3E%3Cmask%20id%3D%22ovh%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%3E%3Crect%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%2224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fmask%3E%3ClinearGradient%20id%3D%22ovgo%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%223.09%22%20y1%3D%2223%22%20x2%3D%2257.09%22%20y2%3D%2277%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2387a6ff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%234268d6%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22ovgv%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%22%20y1%3D%2231.5%22%20x2%3D%220%22%20y2%3D%2268.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ae8df9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23784fd4%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22glow%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%22.3%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%0A%3CradialGradient%20id%3D%22bg%22%20cx%3D%22.5%22%20cy%3D%22.38%22%20r%3D%22.8%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2316223a%22%2F%3E%3Cstop%20offset%3D%22.6%22%20stop-color%3D%22%230a0f1c%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23060912%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20x%3D%22-32%22%20y%3D%22-32%22%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23bg)%22%2F%3E%3Cg%20transform%3D%22scale(0.58)%20translate(-50%20-50)%22%3E%3Cellipse%20cx%3D%2250%22%20cy%3D%2250%22%20rx%3D%2252%22%20ry%3D%2240%22%20fill%3D%22url(%23glow)%22%2F%3E%3Ccircle%20cx%3D%2230.09%22%20cy%3D%2250%22%20r%3D%2218%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgo)%22%20stroke-width%3D%2218%22%20mask%3D%22url(%23ovh)%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgv)%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
- <meta name="description" content="Create logos, title cards, wordmarks, badges, stickers, watermarks, and thumbnails. Transparent PNG export, Google Fonts, presets. Browser-based.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+ <meta name="description" content="Create logos, title cards, wordmarks, badges, stickers, watermarks, and thumbnails. Transparent PNG export, open fonts, presets. Browser-based.">
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -27,7 +25,7 @@
@@ -27,7 +25,7 @@
<p class="hero-sub">Logo and design tools for streamers and creators. Title cards, wordmarks, badges, watermarks, thumbnails — all in your browser with transparent PNG export.</p>
<p class="hero-sub">Logo and design tools for streamers and creators. Title cards, wordmarks, badges, watermarks, thumbnails — all in your browser with transparent PNG export.</p>
<div class="hero-tags">
<div class="hero-tags">
<span class="hero-tag">Transparent PNG</span>
<span class="hero-tag">Transparent PNG</span>
- <span class="hero-tag">Google Fonts</span>
+ <span class="hero-tag">Open Fonts</span>
<span class="hero-tag">No Watermarks</span>
<span class="hero-tag">No Watermarks</span>
<span class="hero-tag">Open source</span>
<span class="hero-tag">Open source</span>
</div>
</div>
@@ -65,7 +63,7 @@
@@ -65,7 +63,7 @@
<a href="https://wordmark.openvibe.tools" class="tool-card">
<a href="https://wordmark.openvibe.tools" class="tool-card">
<div class="card-icon"><i class="fas fa-pen-nib"></i></div>
<div class="card-icon"><i class="fas fa-pen-nib"></i></div>
<h3>Wordmark Generator</h3>
<h3>Wordmark Generator</h3>
- <p>Design clean text-only logos. Pick from 40+ Google Fonts, adjust weight, spacing, colors. Perfect for branding.</p>
+ <p>Design clean text-only logos. Pick from 40+ open fonts, adjust weight, spacing, colors. Perfect for branding.</p>
<span class="card-domain">wordmark.openvibe.tools</span>
<span class="card-domain">wordmark.openvibe.tools</span>
</a>
</a>
<a href="https://badge.openvibe.tools" class="tool-card">
<a href="https://badge.openvibe.tools" class="tool-card">
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Write Markdown on the left, see rendered HTML on the right. Export as HTML.">
<meta name="description" content="Write Markdown on the left, see rendered HTML on the right. Export as HTML.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Translate text to Morse code and back. Dots and dashes for your inner telegraph operator.">
<meta name="description" content="Translate text to Morse code and back. Dots and dashes for your inner telegraph operator.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Generate unique gaming tags, usernames, and screen names in multiple styles.">
<meta name="description" content="Generate unique gaming tags, usernames, and screen names in multiple styles.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Convert between Mexican Pesos (MXN) and US Dollars (USD) with live exchange rates. Fast, no signup.">
<meta name="description" content="Convert between Mexican Pesos (MXN) and US Dollars (USD) with live exchange rates. Fast, no signup.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Reverse text, flip it upside down, or mirror it. Full Unicode support with combining character awareness.">
<meta name="description" content="Reverse text, flip it upside down, or mirror it. Full Unicode support with combining character awareness.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="The ultimate Mexican Spanish slang dictionary. Learn the real street Spanish — cabrón, chido, neta, güey and 100+ more with meanings, examples, and usage tips.">
<meta name="description" content="The ultimate Mexican Spanish slang dictionary. Learn the real street Spanish — cabrón, chido, neta, güey and 100+ more with meanings, examples, and usage tips.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Convert titles into URL-friendly slugs. Strip diacritics, normalize whitespace, clean special characters.">
<meta name="description" content="Convert titles into URL-friendly slugs. Strip diacritics, normalize whitespace, clean special characters.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Sort lines alphabetically, numerically, or randomly. Remove duplicates instantly.">
<meta name="description" content="Sort lines alphabetically, numerically, or randomly. Remove duplicates instantly.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Essential Spanish-English phrasebook with survival phrases, daily conversation, emergency expressions, and pronunciation tips.">
<meta name="description" content="Essential Spanish-English phrasebook with survival phrases, daily conversation, emergency expressions, and pronunciation tips.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Browse and click-to-copy arrows, stars, hearts, math symbols, currency, bullets, boxes, lines, and more special characters.">
<meta name="description" content="Browse and click-to-copy arrows, stars, hearts, math symbols, currency, bullets, boxes, lines, and more special characters.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
</head>
</head>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Create stream and video thumbnails with bold text overlays. Upload a background, add text, download.">
<meta name="description" content="Create stream and video thumbnails with bold text overlays. Upload a background, add text, download.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/shared/ov-upload.css">
<link rel="stylesheet" href="/shared/ov-upload.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Generate stylish title cards with preset effects, custom fonts, colors, and transparent PNG export.">
<meta name="description" content="Generate stylish title cards with preset effects, custom fonts, colors, and transparent PNG export.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Generate transparent text PNG images. Pick font, size, color — download with no background.">
<meta name="description" content="Generate transparent text PNG images. Pick font, size, color — download with no background.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Inspect text character by character: see Unicode code points, character names, byte counts. Detect invisible characters.">
<meta name="description" content="Inspect text character by character: see Unicode code points, character names, byte counts. Detect invisible characters.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Add text watermarks to images. Position, tile, adjust opacity and font. Download watermarked images.">
<meta name="description" content="Add text watermarks to images. Position, tile, adjust opacity and font. Download watermarked images.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/shared/ov-upload.css">
<link rel="stylesheet" href="/shared/ov-upload.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
@@ -7,9 +7,7 @@
@@ -7,9 +7,7 @@
<link rel="icon" type="image/svg+xml" data-ov-icon="tools" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-32%20-32%2064%2064%22%3E%3Cdefs%3E%3Cmask%20id%3D%22ovh%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%3E%3Crect%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%2224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fmask%3E%3ClinearGradient%20id%3D%22ovgo%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%223.09%22%20y1%3D%2223%22%20x2%3D%2257.09%22%20y2%3D%2277%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2387a6ff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%234268d6%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22ovgv%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%22%20y1%3D%2231.5%22%20x2%3D%220%22%20y2%3D%2268.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ae8df9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23784fd4%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22glow%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%22.3%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%0A%3CradialGradient%20id%3D%22bg%22%20cx%3D%22.5%22%20cy%3D%22.38%22%20r%3D%22.8%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2316223a%22%2F%3E%3Cstop%20offset%3D%22.6%22%20stop-color%3D%22%230a0f1c%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23060912%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20x%3D%22-32%22%20y%3D%22-32%22%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23bg)%22%2F%3E%3Cg%20transform%3D%22scale(0.58)%20translate(-50%20-50)%22%3E%3Cellipse%20cx%3D%2250%22%20cy%3D%2250%22%20rx%3D%2252%22%20ry%3D%2240%22%20fill%3D%22url(%23glow)%22%2F%3E%3Ccircle%20cx%3D%2230.09%22%20cy%3D%2250%22%20r%3D%2218%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgo)%22%20stroke-width%3D%2218%22%20mask%3D%22url(%23ovh)%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgv)%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
<link rel="icon" type="image/svg+xml" data-ov-icon="tools" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-32%20-32%2064%2064%22%3E%3Cdefs%3E%3Cmask%20id%3D%22ovh%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%3E%3Crect%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%2224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fmask%3E%3ClinearGradient%20id%3D%22ovgo%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%223.09%22%20y1%3D%2223%22%20x2%3D%2257.09%22%20y2%3D%2277%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2387a6ff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%234268d6%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22ovgv%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%22%20y1%3D%2231.5%22%20x2%3D%220%22%20y2%3D%2268.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ae8df9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23784fd4%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22glow%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%22.3%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%0A%3CradialGradient%20id%3D%22bg%22%20cx%3D%22.5%22%20cy%3D%22.38%22%20r%3D%22.8%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2316223a%22%2F%3E%3Cstop%20offset%3D%22.6%22%20stop-color%3D%22%230a0f1c%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23060912%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20x%3D%22-32%22%20y%3D%22-32%22%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23bg)%22%2F%3E%3Cg%20transform%3D%22scale(0.58)%20translate(-50%20-50)%22%3E%3Cellipse%20cx%3D%2250%22%20cy%3D%2250%22%20rx%3D%2252%22%20ry%3D%2240%22%20fill%3D%22url(%23glow)%22%2F%3E%3Ccircle%20cx%3D%2230.09%22%20cy%3D%2250%22%20r%3D%2218%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgo)%22%20stroke-width%3D%2218%22%20mask%3D%22url(%23ovh)%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgv)%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
- <meta name="description" content="Create clean text-only wordmark logos with 46 Google Fonts, custom weight, spacing, and color.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+ <meta name="description" content="Create clean text-only wordmark logos with 46 open fonts, custom weight, spacing, and color.">
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
<style>
<style>
@@ -8,8 +8,6 @@
@@ -8,8 +8,6 @@
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
<meta name="description" content="Generate creepy Zalgo glitch text with adjustable intensity. Control above, middle, and below combining characters.">
<meta name="description" content="Generate creepy Zalgo glitch text with adjustable intensity. Control above, middle, and below combining characters.">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -17,7 +15,7 @@
@@ -17,7 +15,7 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
+ <link href="https://openvibe.design/fonts/css2?family=Inter:wght@400;700;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/shared/kit.css">
<link rel="stylesheet" href="/shared/kit.css">
<script src="/shared/theme-loader.js" defer></script>
<script src="/shared/theme-loader.js" defer></script>
@@ -64,9 +64,9 @@ app.use(helmet({
@@ -64,9 +64,9 @@ app.use(helmet({
contentSecurityPolicy: {
contentSecurityPolicy: {
directives: {
directives: {
defaultSrc: ["'self'"],
defaultSrc: ["'self'"],
- scriptSrc: ["'self'", "'unsafe-inline'", "cdn.jsdelivr.net", "fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],
- styleSrc: ["'self'", "'unsafe-inline'", "fonts.googleapis.com", "fonts.gstatic.com"],
- fontSrc: ["'self'", "fonts.gstatic.com"],
+ scriptSrc: ["'self'", "'unsafe-inline'", "cdn.jsdelivr.net", "https://openvibe.network", "https://static.cloudflareinsights.com"],
+ styleSrc: ["'self'", "'unsafe-inline'", "https://openvibe.design"],
+ fontSrc: ["'self'", "https://openvibe.design"],
imgSrc: ["'self'", "data:", "blob:"],
imgSrc: ["'self'", "data:", "blob:"],
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
// The navbar's Messages count asks Chat (openvibe-shared 4.130.0).
@@ -16,8 +16,8 @@
@@ -16,8 +16,8 @@
--radius-sm: 8px;
--radius-sm: 8px;
--radius-lg: 16px;
--radius-lg: 16px;
--shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
--shadow: 0 4px 24px rgba(0, 0, 0, 0.35);
- --font: 'Outfit', sans-serif;
- --font-mono: 'JetBrains Mono', monospace;
+ --font: Inter, system-ui, -apple-system, 'Segoe UI', sans-serif;
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
--max-w: 720px;
--max-w: 720px;
}
}
@@ -7,8 +7,6 @@
@@ -7,8 +7,6 @@
<link rel="icon" type="image/svg+xml" data-ov-icon="tools" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-32%20-32%2064%2064%22%3E%3Cdefs%3E%3Cmask%20id%3D%22ovh%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%3E%3Crect%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%2224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fmask%3E%3ClinearGradient%20id%3D%22ovgo%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%223.09%22%20y1%3D%2223%22%20x2%3D%2257.09%22%20y2%3D%2277%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2387a6ff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%234268d6%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22ovgv%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%22%20y1%3D%2231.5%22%20x2%3D%220%22%20y2%3D%2268.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ae8df9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23784fd4%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22glow%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%22.3%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%0A%3CradialGradient%20id%3D%22bg%22%20cx%3D%22.5%22%20cy%3D%22.38%22%20r%3D%22.8%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2316223a%22%2F%3E%3Cstop%20offset%3D%22.6%22%20stop-color%3D%22%230a0f1c%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23060912%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20x%3D%22-32%22%20y%3D%22-32%22%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23bg)%22%2F%3E%3Cg%20transform%3D%22scale(0.58)%20translate(-50%20-50)%22%3E%3Cellipse%20cx%3D%2250%22%20cy%3D%2250%22%20rx%3D%2252%22%20ry%3D%2240%22%20fill%3D%22url(%23glow)%22%2F%3E%3Ccircle%20cx%3D%2230.09%22%20cy%3D%2250%22%20r%3D%2218%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgo)%22%20stroke-width%3D%2218%22%20mask%3D%22url(%23ovh)%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgv)%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
<link rel="icon" type="image/svg+xml" data-ov-icon="tools" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22-32%20-32%2064%2064%22%3E%3Cdefs%3E%3Cmask%20id%3D%22ovh%22%20maskUnits%3D%22userSpaceOnUse%22%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%3E%3Crect%20x%3D%22-20%22%20y%3D%22-20%22%20width%3D%22140%22%20height%3D%22140%22%20fill%3D%22%23fff%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%2224%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fmask%3E%3ClinearGradient%20id%3D%22ovgo%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%223.09%22%20y1%3D%2223%22%20x2%3D%2257.09%22%20y2%3D%2277%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2387a6ff%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%234268d6%22%2F%3E%3C%2FlinearGradient%3E%3ClinearGradient%20id%3D%22ovgv%22%20gradientUnits%3D%22userSpaceOnUse%22%20x1%3D%220%22%20y1%3D%2231.5%22%20x2%3D%220%22%20y2%3D%2268.5%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23ae8df9%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23784fd4%22%2F%3E%3C%2FlinearGradient%3E%0A%3CradialGradient%20id%3D%22glow%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%22.3%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2338bdf8%22%20stop-opacity%3D%220%22%2F%3E%3C%2FradialGradient%3E%0A%3CradialGradient%20id%3D%22bg%22%20cx%3D%22.5%22%20cy%3D%22.38%22%20r%3D%22.8%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%2316223a%22%2F%3E%3Cstop%20offset%3D%22.6%22%20stop-color%3D%22%230a0f1c%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%23060912%22%2F%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20x%3D%22-32%22%20y%3D%22-32%22%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2215%22%20fill%3D%22url(%23bg)%22%2F%3E%3Cg%20transform%3D%22scale(0.58)%20translate(-50%20-50)%22%3E%3Cellipse%20cx%3D%2250%22%20cy%3D%2250%22%20rx%3D%2252%22%20ry%3D%2240%22%20fill%3D%22url(%23glow)%22%2F%3E%3Ccircle%20cx%3D%2230.09%22%20cy%3D%2250%22%20r%3D%2218%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgo)%22%20stroke-width%3D%2218%22%20mask%3D%22url(%23ovh)%22%2F%3E%3Cpath%20d%3D%22M70.59%2068.5L53.31%2031.5M70.59%2068.5L88.41%2031.5%22%20fill%3D%22none%22%20stroke%3D%22url(%23ovgv)%22%20stroke-width%3D%2217%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
<meta name="theme-color" content="#0d131d">
<meta name="theme-color" content="#0d131d">
<meta name="color-scheme" content="dark light">
<meta name="color-scheme" content="dark light">
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
<!-- Critical theme bootstrap (ov-theme-boot) — MUST stay inline and above the stylesheets.
Paints the cached theme synchronously so the default palette never flashes. Reads
Paints the cached theme synchronously so the default palette never flashes. Reads
localStorage only: no network, nothing to block on. theme-loader.js still runs after
localStorage only: no network, nothing to block on. theme-loader.js still runs after
@@ -16,7 +14,6 @@
@@ -16,7 +14,6 @@
<script>
<script>
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
(function(){try{var raw=localStorage.getItem('ov_theme');if(!raw)return;var t=JSON.parse(raw),v=t&&t.variables;if(!v)return;var el=document.documentElement;for(var k in v)if(k.charAt(0)==='-')el.style.setProperty(k,v[k]);if(t.id)el.setAttribute('data-theme',t.id);}catch(_){}})();
</script>
</script>
- <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/style.css">
<script src="/shared/ov-mark.js" defer></script>
<script src="/shared/ov-mark.js" defer></script>
</head>
</head>
@@ -79,9 +79,9 @@ app.use(helmet({
@@ -79,9 +79,9 @@ app.use(helmet({
contentSecurityPolicy: {
contentSecurityPolicy: {
directives: {
directives: {
defaultSrc: ["'self'"],
defaultSrc: ["'self'"],
- scriptSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://openvibe.network", "https://static.cloudflareinsights.com"],
- styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "https://fonts.gstatic.com"],
- fontSrc: ["'self'", "https://fonts.gstatic.com"],
+ scriptSrc: ["'self'", "'unsafe-inline'", "https://openvibe.network", "https://static.cloudflareinsights.com"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ fontSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https://i.ytimg.com", "https://img.youtube.com", "https://*.ggpht.com", "https://openvibe.network", "https://openvibe.media"],
imgSrc: ["'self'", "data:", "https://i.ytimg.com", "https://img.youtube.com", "https://*.ggpht.com", "https://openvibe.network", "https://openvibe.media"],
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
// openvibe.events: release notifications (release-watch's EventSource, openvibe-shared 2.13.0).
connectSrc: ["'self'", "https://openvibe.network", "https://openvibe.chat", "https://openvibe.tools", "https://*.openvibe.tools", "https://cloudflareinsights.com", "https://openvibe.events"],
connectSrc: ["'self'", "https://openvibe.network", "https://openvibe.chat", "https://openvibe.tools", "https://*.openvibe.tools", "https://cloudflareinsights.com", "https://openvibe.events"],
new file mode 100644
new file mode 100644
@@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
+'use strict';
+// No Google Fonts (the owner, 2026-11-12: "make sure no remnants of font awesome or other libraries are still being
+// used rather than our openvibe.design systems"). Every app draws with the house stack — Inter/system-ui for text and
+// ui-monospace for code — and no page, stylesheet or script asks fonts.googleapis.com or fonts.gstatic.com for a face.
+// node test/no-google-fonts.test.js
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+const ROOT = path.join(__dirname, '..');
+
+const walk = (dir, ok, out = []) => {
+ if (!fs.existsSync(dir)) return out;
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
+ if (e.name === 'node_modules' || e.name === 'vendor' || e.name === 'data' || e.name.startsWith('.')) continue;
+ const p = path.join(dir, e.name);
+ if (e.isDirectory()) walk(p, ok, out); else if (ok.test(e.name)) out.push(p);
+ }
+ return out;
+};
+const rel = (p) => path.relative(ROOT, p);
+const GOOGLE_FONTS = /fonts\.(?:googleapis|gstatic)\.com/;
+
+const apps = path.join(ROOT, 'apps');
+const pages = walk(apps, /\.html$/);
+const styles = walk(apps, /\.css$/);
+const scripts = walk(apps, /\.js$/);
+
+for (const p of pages) {
+ assert.ok(!GOOGLE_FONTS.test(fs.readFileSync(p, 'utf8')), `${rel(p)} links Google Fonts`);
+}
+for (const p of styles) {
+ assert.ok(!GOOGLE_FONTS.test(fs.readFileSync(p, 'utf8')), `${rel(p)} imports Google Fonts`);
+}
+for (const p of scripts) {
+ assert.ok(!GOOGLE_FONTS.test(fs.readFileSync(p, 'utf8')), `${rel(p)} requests Google Fonts`);
+}
+
+// The logo maker keeps the 45 families it offered on main, now through openvibe.design's self-hosted CSS.
+const logo = fs.readFileSync(path.join(apps, 'text', 'public', 'js', 'logo-engine.js'), 'utf8');
+assert.match(logo, /https:\/\/openvibe\.design\/fonts\/css2\?family=/, 'the logo maker loads openvibe.design/fonts/css2');
+const list = logo.match(/const POPULAR_FONTS = \[([\s\S]*?)\];/);
+assert.ok(list, 'POPULAR_FONTS is a list');
+const families = [...list[1].matchAll(/'([^']+)'/g)].map((m) => m[1]);
+assert.strictEqual(families.length, 45, `the logo maker lists 45 families, not ${families.length}`);
+assert.match(logo, /font: 'Orbitron'/, 'a preset keeps its display face');
+
+// The text app's CSP lets the service in, and nothing of Google.
+const csp = fs.readFileSync(path.join(apps, 'text', 'server', 'index.js'), 'utf8');
+assert.match(csp, /styleSrc: \[[^\]]*"https:\/\/openvibe\.design"/, 'style-src allows openvibe.design');
+assert.match(csp, /fontSrc: \[[^\]]*"https:\/\/openvibe\.design"/, 'font-src allows openvibe.design');
+
+console.log(`no google fonts: ${pages.length} pages, ${styles.length} stylesheets, ${scripts.length} scripts clean; logo maker 45 families from openvibe.design`);