openvibers/openvibe.tools Commits
First paint never waits on the Network: inline dark canvas + cached-theme boot in the shared head tags, theme script deferred (43 tool pages); legacy paste host hands over to Community with the slug
Changes
@@@ -333,7 +333,7 @@<!-- Footer --><div id="ov-footer"></div>-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><script src="https://openvibe.network/shared/navbar.js"></script><script src="https://openvibe.network/shared/footer.js"></script><script src="https://openvibe.network/shared/notification-ui.js"></script>@@@ -218,7 +218,7 @@<!-- Footer --><div id="ov-footer"></div>-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><script src="https://openvibe.network/shared/navbar.js"></script><script src="https://openvibe.network/shared/footer.js"></script><script src="https://openvibe.network/shared/notification-ui.js"></script>@@@ -26,7 +26,7 @@</script><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>:root {--bg-primary: #0b0c12;@@@ -146,7 +146,7 @@ a{color:inherit;text-decoration:none}<div class="toast" id="toast"></div><script src="https://openvibe.network/shared/navbar.js"></script><script src="https://openvibe.network/shared/footer.js"></script>--<script src="https://openvibe.network/shared/theme-loader.js"></script>++<script src="https://openvibe.network/shared/theme-loader.js" defer></script><script>'use strict';const API='https://openvibe.tools/api/dev',$=s=>document.getElementById(s),@@@ -26,7 +26,7 @@</script><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>:root {--bg-primary: #0b0c12;@@@ -594,7 +594,7 @@<!-- Shared OpenVibe Navbar --><script src="https://openvibe.network/shared/navbar.js"></script><script src="https://openvibe.network/shared/footer.js"></script>-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><script>'use strict';@@@ -309,7 +309,7 @@<script src="https://openvibe.network/shared/navbar.js"></script><script src="https://openvibe.network/shared/footer.js"></script>-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><script>'use strict';@@@ -261,7 +261,16 @@ app.use((req, res, next) => {if (getRequestHost(req) !== 'pastes.openvibe.tools') return next();if (req.path.startsWith('/api/') || req.path.startsWith('/auth/')) return next();if (isStaticPath(req.path)) return next();-- return res.sendFile(path.join(__dirname, '..', 'public', 'paste.html'));++ // Pastes live on openvibe.community. Old links keep their paste: /<slug> and /p/<slug> → /p/<slug> there++ // (the page used to drop the slug and show the index). Signed-in visitors go through its silent sign-in.++ const COMMUNITY = (process.env.OV_COMMUNITY_URL || 'https://openvibe.community').replace(/\/$/, '');++ const m = /^\/(?:p\/)?([A-Za-z0-9][A-Za-z0-9_-]{1,80})\/?$/.exec(req.path);++ const target = m && !['new', 'my', 'pastes'].includes(m[1]) ? `/p/${m[1]}` : (req.path === '/new' ? '/new' : req.path === '/my' ? '/my' : '/pastes');++ if (/(?:^|;\s*)ov_sso_hint=account(?:;|$)/.test(String(req.headers.cookie || ''))) {++ res.set({ 'Cache-Control': 'private, no-store', Vary: 'Cookie' });++ return res.redirect(302, `${COMMUNITY}/auth/login?silent=1&next=${encodeURIComponent(target)}`);++ }++ return res.redirect(301, COMMUNITY + target);});// Dev tool subdomains → dev.html SPA@@@ -107,7 +107,7 @@ ${head}${appIcon.headTags({ site: 'tools', iconBase: '/assets' })}<link rel="alternate" type="application/json" href="${SITE}/api/catalog.json" title="Tool catalog"><link rel="preconnect" href="https://openvibe.network">--<script src="https://openvibe.network/shared/theme-loader.js"></script>++<script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>${CSS}</style></head><body><noscript><header class="topbar"><a href="${SITE}/">${NAME}</a><nav>${families.filter(f => f.path).map(f => `<a href="${esc(f.path)}">${esc(f.name)}</a>`).join('')}<a href="/all-tools">All tools</a></nav></header></noscript><main class="wrap">${body}@@@ -167,7 +167,7 @@<!-- Footer --><div id="ov-footer"></div>-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><script src="https://openvibe.network/shared/navbar.js"></script><script src="https://openvibe.network/shared/footer.js"></script><script src="https://openvibe.network/shared/notification-ui.js"></script>@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.badge-layout { display: grid; grid-template-columns: 340px 1fr; gap: 2rem; align-items: start; }.badge-controls { display: flex; flex-direction: column; gap: 1rem; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.compare-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }.compare-pane label { display: block; font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.escape-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }.escape-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.alert-banner {background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="hub-hero">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.json-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; min-height: 400px; }.json-pane { display: flex; flex-direction: column; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap" style="max-width: 1000px;">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="hub-hero">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.md-editor { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; min-height: 500px; }.md-pane { display: flex; flex-direction: column; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.converter-wrap {display: flex;@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.search-wrap {position: relative; margin-bottom: 20px;@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.phrase-search {width: 100%;@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap" style="max-width: 1000px;">@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.thumb-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }.thumb-preview { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.editor-layout { display: grid; grid-template-columns: 1fr 360px; gap: 2rem; align-items: start; }.preview-col { position: sticky; top: 100px; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.tp-layout { max-width: 700px; margin: 0 auto; }.canvas-wrap { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 2rem; text-align: center; margin: 2rem 0; background-image: linear-gradient(45deg, #141414 25%, transparent 25%), linear-gradient(-45deg, #141414 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #141414 75%), linear-gradient(-45deg, transparent 75%, #141414 75%); background-size: 16px 16px; background-position: 0 0, 0 8px, 8px -8px, -8px 0; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.char-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }.char-table th { text-align: left; padding: 8px 12px; color: var(--text-muted); font-weight: 600; border-bottom: 1px solid var(--border); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.wm-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }.wm-preview { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; text-align: center; min-height: 300px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>.wordmark-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1.5rem; margin-top: 2rem; }.wm-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; cursor: pointer; transition: all .2s; }@@@ -20,7 +20,7 @@<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"><link rel="stylesheet" href="/css/shared.css">-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script></head><body><div class="tool-wrap">@@@ -107,7 +107,7 @@<!--OV:CONTENT--><div id="ov-footer"></div>-- <script src="https://openvibe.network/shared/theme-loader.js"></script>++ <script src="https://openvibe.network/shared/theme-loader.js" defer></script><script src="https://openvibe.network/shared/navbar.js"></script><script src="https://openvibe.network/shared/footer.js"></script><script src="https://openvibe.network/shared/notification-ui.js"></script>@@@ -64,11 +64,18 @@ function manifest({ site = 'network', name, shortName, description, startUrl = '};}++// First paint must never depend on another server. These two inline tags give every page the network's++// dark canvas and the visitor's cached theme immediately; theme-loader.js (deferred, from the Network)++// reconciles with the account afterwards. If the Network is slow or restarting, the page is still++// styled and readable instead of blank white.++const CRITICAL = `<style id="ov-critical">html{background:${BG};color:#e6edf7;color-scheme:dark}</style>`++ + `<script>(function(){try{var r=localStorage.getItem('ov_theme');if(!r)return;var t=JSON.parse(r),v=t&&t.variables;if(!v)return;var e=document.documentElement;for(var k in v)if(k.charAt(0)==='-')e.style.setProperty(k,v[k]);if(v['--bg-primary'])e.style.background=v['--bg-primary'];if(v['--text-primary'])e.style.color=v['--text-primary'];if(v['--color-scheme']==='light'||v['--color-scheme']==='dark')e.style.colorScheme=v['--color-scheme'];if(t.id)e.setAttribute('data-theme',t.id)}catch(_){}})();</script>`;++/** <head> tags every site shares: SVG favicon (inline, no request), theme colour, and the PNG touch icon when the site ships one. */function headTags({ site = 'network', iconBase = null } = {}) {const uri = 'data:image/svg+xml,' + encodeURIComponent(favicon({ site })).replace(/'/g, '%27');-- return [`<link rel="icon" type="image/svg+xml" data-ov-icon="${String(site).replace(/[^a-z]/g, '')}" href="${uri}">`, '<meta name="theme-color" content="#0d131d">', `<meta name="color-scheme" content="dark light">`,++ return [`<link rel="icon" type="image/svg+xml" data-ov-icon="${String(site).replace(/[^a-z]/g, '')}" href="${uri}">`, '<meta name="theme-color" content="#0d131d">', '<meta name="color-scheme" content="dark">', CRITICAL,iconBase ? `<link rel="apple-touch-icon" href="${iconBase}/logo-192.png">` : '', iconBase ? `<link rel="manifest" href="/manifest.webmanifest">` : ''].filter(Boolean).join('\n');}--module.exports = { svg, favicon, manifest, headTags, SITES, BG };++module.exports = { svg, favicon, manifest, headTags, CRITICAL, SITES, BG };@@@ -179,7 +179,7 @@ function page(kind, site) {return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">${head}${require('./app-icon').headTags({ site: site.id })}--<script src="https://openvibe.network/shared/theme-loader.js"></script><style>${CSS}</style></head><body>++<script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>${CSS}</style></head><body><noscript><p style="padding:12px 20px;margin:0"><a href="/">${esc(site.name)}</a></p></noscript><main>${article(kind, site)}</main><div id="ov-footer"></div><script src="https://openvibe.network/shared/navbar.js" defer></script><script src="https://openvibe.network/shared/footer.js" defer></script>@@@ -108,6 +108,9 @@// Native controls, scrollbars and form fields follow the theme's light/dark mode.const scheme = vars['--color-scheme'];if (scheme === 'light' || scheme === 'dark') { el.style.colorScheme = scheme; el.setAttribute('data-theme-mode', scheme); }++ // The inline critical style paints a fixed dark canvas before any stylesheet; keep it in step with the theme.++ if (vars['--bg-primary']) el.style.background = vars['--bg-primary'];++ if (vars['--text-primary']) el.style.color = vars['--text-primary'];syncChrome();}
@@ -333,7 +333,7 @@
@@ -333,7 +333,7 @@
<!-- Footer -->
<!-- Footer -->
<div id="ov-footer"></div>
<div id="ov-footer"></div>
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/notification-ui.js"></script>
<script src="https://openvibe.network/shared/notification-ui.js"></script>
@@ -218,7 +218,7 @@
@@ -218,7 +218,7 @@
<!-- Footer -->
<!-- Footer -->
<div id="ov-footer"></div>
<div id="ov-footer"></div>
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/notification-ui.js"></script>
<script src="https://openvibe.network/shared/notification-ui.js"></script>
@@ -26,7 +26,7 @@
@@ -26,7 +26,7 @@
</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://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
:root {
:root {
--bg-primary: #0b0c12;
--bg-primary: #0b0c12;
@@ -146,7 +146,7 @@ a{color:inherit;text-decoration:none}
@@ -146,7 +146,7 @@ a{color:inherit;text-decoration:none}
<div class="toast" id="toast"></div>
<div class="toast" id="toast"></div>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
-<script src="https://openvibe.network/shared/theme-loader.js"></script>
+<script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<script>
<script>
'use strict';
'use strict';
const API='https://openvibe.tools/api/dev',$=s=>document.getElementById(s),
const API='https://openvibe.tools/api/dev',$=s=>document.getElementById(s),
@@ -26,7 +26,7 @@
@@ -26,7 +26,7 @@
</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://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
:root {
:root {
--bg-primary: #0b0c12;
--bg-primary: #0b0c12;
@@ -594,7 +594,7 @@
@@ -594,7 +594,7 @@
<!-- Shared OpenVibe Navbar -->
<!-- Shared OpenVibe Navbar -->
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<script>
<script>
'use strict';
'use strict';
@@ -309,7 +309,7 @@
@@ -309,7 +309,7 @@
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<script>
<script>
'use strict';
'use strict';
@@ -261,7 +261,16 @@ app.use((req, res, next) => {
@@ -261,7 +261,16 @@ app.use((req, res, next) => {
if (getRequestHost(req) !== 'pastes.openvibe.tools') return next();
if (getRequestHost(req) !== 'pastes.openvibe.tools') return next();
if (req.path.startsWith('/api/') || req.path.startsWith('/auth/')) return next();
if (req.path.startsWith('/api/') || req.path.startsWith('/auth/')) return next();
if (isStaticPath(req.path)) return next();
if (isStaticPath(req.path)) return next();
- return res.sendFile(path.join(__dirname, '..', 'public', 'paste.html'));
+ // Pastes live on openvibe.community. Old links keep their paste: /<slug> and /p/<slug> → /p/<slug> there
+ // (the page used to drop the slug and show the index). Signed-in visitors go through its silent sign-in.
+ const COMMUNITY = (process.env.OV_COMMUNITY_URL || 'https://openvibe.community').replace(/\/$/, '');
+ const m = /^\/(?:p\/)?([A-Za-z0-9][A-Za-z0-9_-]{1,80})\/?$/.exec(req.path);
+ const target = m && !['new', 'my', 'pastes'].includes(m[1]) ? `/p/${m[1]}` : (req.path === '/new' ? '/new' : req.path === '/my' ? '/my' : '/pastes');
+ if (/(?:^|;\s*)ov_sso_hint=account(?:;|$)/.test(String(req.headers.cookie || ''))) {
+ res.set({ 'Cache-Control': 'private, no-store', Vary: 'Cookie' });
+ return res.redirect(302, `${COMMUNITY}/auth/login?silent=1&next=${encodeURIComponent(target)}`);
+ }
+ return res.redirect(301, COMMUNITY + target);
});
});
// Dev tool subdomains → dev.html SPA
// Dev tool subdomains → dev.html SPA
@@ -107,7 +107,7 @@ ${head}
@@ -107,7 +107,7 @@ ${head}
${appIcon.headTags({ site: 'tools', iconBase: '/assets' })}
${appIcon.headTags({ site: 'tools', iconBase: '/assets' })}
<link rel="alternate" type="application/json" href="${SITE}/api/catalog.json" title="Tool catalog">
<link rel="alternate" type="application/json" href="${SITE}/api/catalog.json" title="Tool catalog">
<link rel="preconnect" href="https://openvibe.network">
<link rel="preconnect" href="https://openvibe.network">
-<script src="https://openvibe.network/shared/theme-loader.js"></script>
+<script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>${CSS}</style></head><body>
<style>${CSS}</style></head><body>
<noscript><header class="topbar"><a href="${SITE}/">${NAME}</a><nav>${families.filter(f => f.path).map(f => `<a href="${esc(f.path)}">${esc(f.name)}</a>`).join('')}<a href="/all-tools">All tools</a></nav></header></noscript>
<noscript><header class="topbar"><a href="${SITE}/">${NAME}</a><nav>${families.filter(f => f.path).map(f => `<a href="${esc(f.path)}">${esc(f.name)}</a>`).join('')}<a href="/all-tools">All tools</a></nav></header></noscript>
<main class="wrap">${body}
<main class="wrap">${body}
@@ -167,7 +167,7 @@
@@ -167,7 +167,7 @@
<!-- Footer -->
<!-- Footer -->
<div id="ov-footer"></div>
<div id="ov-footer"></div>
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/notification-ui.js"></script>
<script src="https://openvibe.network/shared/notification-ui.js"></script>
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.badge-layout { display: grid; grid-template-columns: 340px 1fr; gap: 2rem; align-items: start; }
.badge-layout { display: grid; grid-template-columns: 340px 1fr; gap: 2rem; align-items: start; }
.badge-controls { display: flex; flex-direction: column; gap: 1rem; }
.badge-controls { display: flex; flex-direction: column; gap: 1rem; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.compare-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.compare-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.compare-pane label { display: block; font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
.compare-pane label { display: block; font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.escape-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
.escape-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
.escape-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; }
.escape-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.alert-banner {
.alert-banner {
background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
background: linear-gradient(135deg, #dc2626 0%, #991b1b 100%);
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="hub-hero">
<div class="hub-hero">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.json-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; min-height: 400px; }
.json-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; min-height: 400px; }
.json-pane { display: flex; flex-direction: column; }
.json-pane { display: flex; flex-direction: column; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap" style="max-width: 1000px;">
<div class="tool-wrap" style="max-width: 1000px;">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="hub-hero">
<div class="hub-hero">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.md-editor { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; min-height: 500px; }
.md-editor { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; min-height: 500px; }
.md-pane { display: flex; flex-direction: column; }
.md-pane { display: flex; flex-direction: column; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.converter-wrap {
.converter-wrap {
display: flex;
display: flex;
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.search-wrap {
.search-wrap {
position: relative; margin-bottom: 20px;
position: relative; margin-bottom: 20px;
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.phrase-search {
.phrase-search {
width: 100%;
width: 100%;
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap" style="max-width: 1000px;">
<div class="tool-wrap" style="max-width: 1000px;">
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.thumb-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
.thumb-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
.thumb-preview { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.thumb-preview { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.editor-layout { display: grid; grid-template-columns: 1fr 360px; gap: 2rem; align-items: start; }
.editor-layout { display: grid; grid-template-columns: 1fr 360px; gap: 2rem; align-items: start; }
.preview-col { position: sticky; top: 100px; }
.preview-col { position: sticky; top: 100px; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.tp-layout { max-width: 700px; margin: 0 auto; }
.tp-layout { max-width: 700px; margin: 0 auto; }
.canvas-wrap { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 2rem; text-align: center; margin: 2rem 0; background-image: linear-gradient(45deg, #141414 25%, transparent 25%), linear-gradient(-45deg, #141414 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #141414 75%), linear-gradient(-45deg, transparent 75%, #141414 75%); background-size: 16px 16px; background-position: 0 0, 0 8px, 8px -8px, -8px 0; }
.canvas-wrap { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 2rem; text-align: center; margin: 2rem 0; background-image: linear-gradient(45deg, #141414 25%, transparent 25%), linear-gradient(-45deg, #141414 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #141414 75%), linear-gradient(-45deg, transparent 75%, #141414 75%); background-size: 16px 16px; background-position: 0 0, 0 8px, 8px -8px, -8px 0; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.char-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.char-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.char-table th { text-align: left; padding: 8px 12px; color: var(--text-muted); font-weight: 600; border-bottom: 1px solid var(--border); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
.char-table th { text-align: left; padding: 8px 12px; color: var(--text-muted); font-weight: 600; border-bottom: 1px solid var(--border); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.wm-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
.wm-layout { display: grid; grid-template-columns: 1fr 320px; gap: 2rem; align-items: start; }
.wm-preview { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; text-align: center; min-height: 300px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem; }
.wm-preview { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 1.5rem; text-align: center; min-height: 300px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<style>
<style>
.wordmark-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1.5rem; margin-top: 2rem; }
.wordmark-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1.5rem; margin-top: 2rem; }
.wm-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; cursor: pointer; transition: all .2s; }
.wm-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; cursor: pointer; transition: all .2s; }
@@ -20,7 +20,7 @@
@@ -20,7 +20,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="/css/shared.css">
<link rel="stylesheet" href="/css/shared.css">
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
</head>
</head>
<body>
<body>
<div class="tool-wrap">
<div class="tool-wrap">
@@ -107,7 +107,7 @@
@@ -107,7 +107,7 @@
<!--OV:CONTENT-->
<!--OV:CONTENT-->
<div id="ov-footer"></div>
<div id="ov-footer"></div>
- <script src="https://openvibe.network/shared/theme-loader.js"></script>
+ <script src="https://openvibe.network/shared/theme-loader.js" defer></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/navbar.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/footer.js"></script>
<script src="https://openvibe.network/shared/notification-ui.js"></script>
<script src="https://openvibe.network/shared/notification-ui.js"></script>
@@ -64,11 +64,18 @@ function manifest({ site = 'network', name, shortName, description, startUrl = '
@@ -64,11 +64,18 @@ function manifest({ site = 'network', name, shortName, description, startUrl = '
};
};
}
}
+// First paint must never depend on another server. These two inline tags give every page the network's
+// dark canvas and the visitor's cached theme immediately; theme-loader.js (deferred, from the Network)
+// reconciles with the account afterwards. If the Network is slow or restarting, the page is still
+// styled and readable instead of blank white.
+const CRITICAL = `<style id="ov-critical">html{background:${BG};color:#e6edf7;color-scheme:dark}</style>`
+ + `<script>(function(){try{var r=localStorage.getItem('ov_theme');if(!r)return;var t=JSON.parse(r),v=t&&t.variables;if(!v)return;var e=document.documentElement;for(var k in v)if(k.charAt(0)==='-')e.style.setProperty(k,v[k]);if(v['--bg-primary'])e.style.background=v['--bg-primary'];if(v['--text-primary'])e.style.color=v['--text-primary'];if(v['--color-scheme']==='light'||v['--color-scheme']==='dark')e.style.colorScheme=v['--color-scheme'];if(t.id)e.setAttribute('data-theme',t.id)}catch(_){}})();</script>`;
+
/** <head> tags every site shares: SVG favicon (inline, no request), theme colour, and the PNG touch icon when the site ships one. */
/** <head> tags every site shares: SVG favicon (inline, no request), theme colour, and the PNG touch icon when the site ships one. */
function headTags({ site = 'network', iconBase = null } = {}) {
function headTags({ site = 'network', iconBase = null } = {}) {
const uri = 'data:image/svg+xml,' + encodeURIComponent(favicon({ site })).replace(/'/g, '%27');
const uri = 'data:image/svg+xml,' + encodeURIComponent(favicon({ site })).replace(/'/g, '%27');
- return [`<link rel="icon" type="image/svg+xml" data-ov-icon="${String(site).replace(/[^a-z]/g, '')}" href="${uri}">`, '<meta name="theme-color" content="#0d131d">', `<meta name="color-scheme" content="dark light">`,
+ return [`<link rel="icon" type="image/svg+xml" data-ov-icon="${String(site).replace(/[^a-z]/g, '')}" href="${uri}">`, '<meta name="theme-color" content="#0d131d">', '<meta name="color-scheme" content="dark">', CRITICAL,
iconBase ? `<link rel="apple-touch-icon" href="${iconBase}/logo-192.png">` : '', iconBase ? `<link rel="manifest" href="/manifest.webmanifest">` : ''].filter(Boolean).join('\n');
iconBase ? `<link rel="apple-touch-icon" href="${iconBase}/logo-192.png">` : '', iconBase ? `<link rel="manifest" href="/manifest.webmanifest">` : ''].filter(Boolean).join('\n');
}
}
-module.exports = { svg, favicon, manifest, headTags, SITES, BG };
+module.exports = { svg, favicon, manifest, headTags, CRITICAL, SITES, BG };
@@ -179,7 +179,7 @@ function page(kind, site) {
@@ -179,7 +179,7 @@ function page(kind, site) {
return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
return `<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
${head}
${head}
${require('./app-icon').headTags({ site: site.id })}
${require('./app-icon').headTags({ site: site.id })}
-<script src="https://openvibe.network/shared/theme-loader.js"></script><style>${CSS}</style></head><body>
+<script src="https://openvibe.network/shared/theme-loader.js" defer></script><style>${CSS}</style></head><body>
<noscript><p style="padding:12px 20px;margin:0"><a href="/">${esc(site.name)}</a></p></noscript>
<noscript><p style="padding:12px 20px;margin:0"><a href="/">${esc(site.name)}</a></p></noscript>
<main>${article(kind, site)}</main><div id="ov-footer"></div>
<main>${article(kind, site)}</main><div id="ov-footer"></div>
<script src="https://openvibe.network/shared/navbar.js" defer></script><script src="https://openvibe.network/shared/footer.js" defer></script>
<script src="https://openvibe.network/shared/navbar.js" defer></script><script src="https://openvibe.network/shared/footer.js" defer></script>
@@ -108,6 +108,9 @@
@@ -108,6 +108,9 @@
// Native controls, scrollbars and form fields follow the theme's light/dark mode.
// Native controls, scrollbars and form fields follow the theme's light/dark mode.
const scheme = vars['--color-scheme'];
const scheme = vars['--color-scheme'];
if (scheme === 'light' || scheme === 'dark') { el.style.colorScheme = scheme; el.setAttribute('data-theme-mode', scheme); }
if (scheme === 'light' || scheme === 'dark') { el.style.colorScheme = scheme; el.setAttribute('data-theme-mode', scheme); }
+ // The inline critical style paints a fixed dark canvas before any stylesheet; keep it in step with the theme.
+ if (vars['--bg-primary']) el.style.background = vars['--bg-primary'];
+ if (vars['--text-primary']) el.style.color = vars['--text-primary'];
syncChrome();
syncChrome();
}
}