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

OP OpenVibers committed 18 Sept 2026 · cf941d8863 · 48 files, +67 −48


Changes

ddiff --git a/apps/audio/public/index.html b/apps/audio/public/index.html
iindex 518c1ca..43fda96 100644
---- a/apps/audio/public/index.html
++++ b/apps/audio/public/index.html
@@@ -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>
ddiff --git a/apps/docs/public/index.html b/apps/docs/public/index.html
iindex 7db9bf3..e93316c 100644
---- a/apps/docs/public/index.html
++++ b/apps/docs/public/index.html
@@@ -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>
ddiff --git a/apps/gateway/public/audio.html b/apps/gateway/public/audio.html
iindex 945716b..392c30d 100644
---- a/apps/gateway/public/audio.html
++++ b/apps/gateway/public/audio.html
@@@ -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;
ddiff --git a/apps/gateway/public/dev.html b/apps/gateway/public/dev.html
iindex 4864fda..05b8700 100644
---- a/apps/gateway/public/dev.html
++++ b/apps/gateway/public/dev.html
@@@ -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),
ddiff --git a/apps/gateway/public/img.html b/apps/gateway/public/img.html
iindex 5bfd5c6..e016ccf 100644
---- a/apps/gateway/public/img.html
++++ b/apps/gateway/public/img.html
@@@ -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;
ddiff --git a/apps/gateway/public/net.html b/apps/gateway/public/net.html
iindex f9d285b..95ae104 100644
---- a/apps/gateway/public/net.html
++++ b/apps/gateway/public/net.html
@@@ -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';
ddiff --git a/apps/gateway/public/paste.html b/apps/gateway/public/paste.html
iindex 296cc39..a985e48 100644
---- a/apps/gateway/public/paste.html
++++ b/apps/gateway/public/paste.html
@@@ -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';
ddiff --git a/apps/gateway/server/index.js b/apps/gateway/server/index.js
iindex 1ce3404..e87dd82 100644
---- a/apps/gateway/server/index.js
++++ b/apps/gateway/server/index.js
@@@ -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
ddiff --git a/apps/gateway/server/pages/site.js b/apps/gateway/server/pages/site.js
iindex ab56239..d34c0c8 100644
---- a/apps/gateway/server/pages/site.js
++++ b/apps/gateway/server/pages/site.js
@@@ -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}
ddiff --git a/apps/img/public/index.html b/apps/img/public/index.html
iindex 4c8910d..c89a4f9 100644
---- a/apps/img/public/index.html
++++ b/apps/img/public/index.html
@@@ -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>
ddiff --git a/apps/text/public/ascii.html b/apps/text/public/ascii.html
iindex ca1c3dc..116703e 100644
---- a/apps/text/public/ascii.html
++++ b/apps/text/public/ascii.html
@@@ -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">
ddiff --git a/apps/text/public/badge.html b/apps/text/public/badge.html
iindex be02965..93b4cc6 100644
---- a/apps/text/public/badge.html
++++ b/apps/text/public/badge.html
@@@ -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; }
ddiff --git a/apps/text/public/binary.html b/apps/text/public/binary.html
iindex ab263f7..2fde7be 100644
---- a/apps/text/public/binary.html
++++ b/apps/text/public/binary.html
@@@ -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">
ddiff --git a/apps/text/public/bio.html b/apps/text/public/bio.html
iindex faa4a09..084dd27 100644
---- a/apps/text/public/bio.html
++++ b/apps/text/public/bio.html
@@@ -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">
ddiff --git a/apps/text/public/braille.html b/apps/text/public/braille.html
iindex 04cff87..3975d47 100644
---- a/apps/text/public/braille.html
++++ b/apps/text/public/braille.html
@@@ -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">
ddiff --git a/apps/text/public/bubble.html b/apps/text/public/bubble.html
iindex 3ae4414..1faff72 100644
---- a/apps/text/public/bubble.html
++++ b/apps/text/public/bubble.html
@@@ -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">
ddiff --git a/apps/text/public/case.html b/apps/text/public/case.html
iindex b22be5a..8ce3c5e 100644
---- a/apps/text/public/case.html
++++ b/apps/text/public/case.html
@@@ -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">
ddiff --git a/apps/text/public/clean.html b/apps/text/public/clean.html
iindex abbda12..47ba5f0 100644
---- a/apps/text/public/clean.html
++++ b/apps/text/public/clean.html
@@@ -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">
ddiff --git a/apps/text/public/compare.html b/apps/text/public/compare.html
iindex d5b718b..c4387d1 100644
---- a/apps/text/public/compare.html
++++ b/apps/text/public/compare.html
@@@ -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; }
ddiff --git a/apps/text/public/count.html b/apps/text/public/count.html
iindex 32e1a53..8073109 100644
---- a/apps/text/public/count.html
++++ b/apps/text/public/count.html
@@@ -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">
ddiff --git a/apps/text/public/escape.html b/apps/text/public/escape.html
iindex 7a774e4..1296322 100644
---- a/apps/text/public/escape.html
++++ b/apps/text/public/escape.html
@@@ -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; }
ddiff --git a/apps/text/public/fancy.html b/apps/text/public/fancy.html
iindex 4701573..8151420 100644
---- a/apps/text/public/fancy.html
++++ b/apps/text/public/fancy.html
@@@ -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">
ddiff --git a/apps/text/public/ice.html b/apps/text/public/ice.html
iindex 3529f35..7498a70 100644
---- a/apps/text/public/ice.html
++++ b/apps/text/public/ice.html
@@@ -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%);
ddiff --git a/apps/text/public/index.html b/apps/text/public/index.html
iindex 63eb100..dcbb5b7 100644
---- a/apps/text/public/index.html
++++ b/apps/text/public/index.html
@@@ -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">
ddiff --git a/apps/text/public/json.html b/apps/text/public/json.html
iindex 686bdaa..da3108c 100644
---- a/apps/text/public/json.html
++++ b/apps/text/public/json.html
@@@ -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; }
ddiff --git a/apps/text/public/kaomoji.html b/apps/text/public/kaomoji.html
iindex 6dd6133..b31359e 100644
---- a/apps/text/public/kaomoji.html
++++ b/apps/text/public/kaomoji.html
@@@ -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;">
ddiff --git a/apps/text/public/logo-hub.html b/apps/text/public/logo-hub.html
iindex d1e395c..c0fbb09 100644
---- a/apps/text/public/logo-hub.html
++++ b/apps/text/public/logo-hub.html
@@@ -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">
ddiff --git a/apps/text/public/markdown.html b/apps/text/public/markdown.html
iindex ab0f250..43cda25 100644
---- a/apps/text/public/markdown.html
++++ b/apps/text/public/markdown.html
@@@ -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; }
ddiff --git a/apps/text/public/morse.html b/apps/text/public/morse.html
iindex a72e830..75353e2 100644
---- a/apps/text/public/morse.html
++++ b/apps/text/public/morse.html
@@@ -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">
ddiff --git a/apps/text/public/nickname.html b/apps/text/public/nickname.html
iindex 11a216b..62e9f52 100644
---- a/apps/text/public/nickname.html
++++ b/apps/text/public/nickname.html
@@@ -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">
ddiff --git a/apps/text/public/peso.html b/apps/text/public/peso.html
iindex 3b1a41f..83c958e 100644
---- a/apps/text/public/peso.html
++++ b/apps/text/public/peso.html
@@@ -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;
ddiff --git a/apps/text/public/reverse.html b/apps/text/public/reverse.html
iindex 6001dfc..bd3645c 100644
---- a/apps/text/public/reverse.html
++++ b/apps/text/public/reverse.html
@@@ -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">
ddiff --git a/apps/text/public/slang.html b/apps/text/public/slang.html
iindex c3cf227..46e0196 100644
---- a/apps/text/public/slang.html
++++ b/apps/text/public/slang.html
@@@ -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;
ddiff --git a/apps/text/public/slug.html b/apps/text/public/slug.html
iindex d0dded5..1ee7877 100644
---- a/apps/text/public/slug.html
++++ b/apps/text/public/slug.html
@@@ -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">
ddiff --git a/apps/text/public/sort.html b/apps/text/public/sort.html
iindex acd9f3e..859576a 100644
---- a/apps/text/public/sort.html
++++ b/apps/text/public/sort.html
@@@ -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">
ddiff --git a/apps/text/public/spanish.html b/apps/text/public/spanish.html
iindex 32ecdaf..5292ce5 100644
---- a/apps/text/public/spanish.html
++++ b/apps/text/public/spanish.html
@@@ -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%;
ddiff --git a/apps/text/public/symbols.html b/apps/text/public/symbols.html
iindex 8c0f086..8a9691e 100644
---- a/apps/text/public/symbols.html
++++ b/apps/text/public/symbols.html
@@@ -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;">
ddiff --git a/apps/text/public/thumbnail.html b/apps/text/public/thumbnail.html
iindex a28674f..50a87b7 100644
---- a/apps/text/public/thumbnail.html
++++ b/apps/text/public/thumbnail.html
@@@ -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; }
ddiff --git a/apps/text/public/title.html b/apps/text/public/title.html
iindex 84d7105..c95ed56 100644
---- a/apps/text/public/title.html
++++ b/apps/text/public/title.html
@@@ -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; }
ddiff --git a/apps/text/public/transparent.html b/apps/text/public/transparent.html
iindex 90c5571..50c6ac6 100644
---- a/apps/text/public/transparent.html
++++ b/apps/text/public/transparent.html
@@@ -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; }
ddiff --git a/apps/text/public/unicode.html b/apps/text/public/unicode.html
iindex 2d1e1d6..c4a6afe 100644
---- a/apps/text/public/unicode.html
++++ b/apps/text/public/unicode.html
@@@ -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; }
ddiff --git a/apps/text/public/watermark.html b/apps/text/public/watermark.html
iindex b3ac05b..7f3476b 100644
---- a/apps/text/public/watermark.html
++++ b/apps/text/public/watermark.html
@@@ -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; }
ddiff --git a/apps/text/public/wordmark.html b/apps/text/public/wordmark.html
iindex 727f50e..66973b7 100644
---- a/apps/text/public/wordmark.html
++++ b/apps/text/public/wordmark.html
@@@ -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; }
ddiff --git a/apps/text/public/zalgo.html b/apps/text/public/zalgo.html
iindex 298481f..eaa2bfb 100644
---- a/apps/text/public/zalgo.html
++++ b/apps/text/public/zalgo.html
@@@ -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">
ddiff --git a/apps/yt/public/index.html b/apps/yt/public/index.html
iindex d8a38ed..50b10b0 100644
---- a/apps/yt/public/index.html
++++ b/apps/yt/public/index.html
@@@ -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>
ddiff --git a/vendor/openvibe-shared/app-icon.js b/vendor/openvibe-shared/app-icon.js
iindex 96259c4..fe92006 100644
---- a/vendor/openvibe-shared/app-icon.js
++++ b/vendor/openvibe-shared/app-icon.js
@@@ -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 };
ddiff --git a/vendor/openvibe-shared/legal.js b/vendor/openvibe-shared/legal.js
iindex 24ffed0..c419fa0 100644
---- a/vendor/openvibe-shared/legal.js
++++ b/vendor/openvibe-shared/legal.js
@@@ -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>
ddiff --git a/vendor/openvibe-shared/theme-loader.js b/vendor/openvibe-shared/theme-loader.js
iindex 6a7a0eb..bd44091 100644
---- a/vendor/openvibe-shared/theme-loader.js
++++ b/vendor/openvibe-shared/theme-loader.js
@@@ -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();
}