zektyc/public/js/components.js
riricdev f16e6ddde1 feat: page portfolio de style terminal Linux présentant Riric (DotRiric)
- public/portfolio/ (FR + EN) : terminal Debian interactif (prompt dotriric@zektyc:~$, historique, commandes help/whoami/cat/neofetch/contact/sudo/exit/clear...)
- Corrige un bug CSP-critique : String.prototype.link faisait traiter toutes les lignes texte comme des liens (A undefined) ; makeRich exige désormais typeof item.link === 'string'
- Nav : lien Portfolio ajouté (components.js v=8, i18n nav.portfolio)
- Sitemaps FR/EN : /fr/portfolio et /en/portfolio
2026-09-08 12:29:49 +02:00

154 lines
7.9 KiB
JavaScript

(function () {
var SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 16" role="presentation" focusable="false"><path d="M5 4h22L5 12h22" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>';
var BRAND = '<span class="brand-mark" aria-hidden="true">' + SVG + '</span><span class="brand-name">Zektyc</span>';
var SUN = '<svg class="icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
var MOON = '<svg class="icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
var THEME_BTN = '<button type="button" class="theme-btn" aria-label="Theme">' + MOON + SUN + '</button>';
function path() { return location.pathname; }
function lang() { return /^\/en(\/|$)/.test(path()) ? 'en' : 'fr'; }
function isHome() { var p = path(); return p === '/' || p === '/index.html' || p === '/fr/' || p === '/fr/index.html' || p === '/en/' || p === '/en/index.html'; }
function buildHeader() {
var h = lang();
var l = h === 'fr' ? '/fr' : '/en';
var skip = h === 'fr' ? 'Aller au contenu' : 'Skip to content';
var html = '<a class="skip-link" href="#main">' + skip + '</a>';
html += '<a class="brand" href="' + (h === 'fr' ? '/fr/' : '/en/') + '">' + BRAND + '</a>';
html += '<input type="checkbox" id="nav-toggle" class="nav-toggle-input" />';
html += '<label for="nav-toggle" class="nav-burger" aria-label="Menu">';
html += '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16"/></svg>';
html += '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>';
html += '</label>';
html += '<nav class="site-nav" aria-label="Navigation">';
html += '<a href="' + l + '/blog" data-lang-path="/blog" data-i18n="nav.blog">Blog</a>';
html += '<a href="' + l + '/projets" data-lang-path="/projets" data-i18n="nav.projects">Projets</a>';
html += '<a href="' + l + '/equipe" data-lang-path="/equipe" data-i18n="nav.team">\u00c9quipe</a>';
html += '<a href="' + l + '/portfolio" data-lang-path="/portfolio" data-i18n="nav.portfolio">Portfolio</a>';
if (isHome()) {
html += '<a href="#principes" data-i18n="nav.principes">Nos principes</a>';
html += '<a href="#faq" data-i18n="nav.faq">FAQ</a>';
html += '<a href="#contact" data-i18n="nav.contact">Contact</a>';
}
html += '<div class="lang-switch" role="group" aria-label="Langue / Language">';
html += '<button type="button" data-lang="fr" class="lang-btn' + (h === 'fr' ? ' active' : '') + '">' + (h === 'fr' ? '<strong>FR</strong>' : 'FR') + '</button>';
html += '<button type="button" data-lang="en" class="lang-btn' + (h === 'en' ? ' active' : '') + '">' + (h === 'en' ? '<strong>EN</strong>' : 'EN') + '</button>';
html += '</div>';
html += THEME_BTN;
html += '</nav>';
return '<header class="site-header">' + html + '</header>';
}
function buildFooter() {
var h = lang();
var tos = h === 'fr' ? "Conditions d\u2019utilisation" : 'Terms of Service';
var priv = h === 'fr' ? 'Confidentialit\u00e9' : 'Privacy';
var lic = h === 'fr' ? 'Licence' : 'License';
var note = h === 'fr' ? 'Le site ne pose qu\'un cookie de pr\u00e9f\u00e9rence de langue, sans suivi.' : 'This site only sets a language-preference cookie, with no tracking.';
var tosHref = h === 'fr' ? '/fr/tos' : '/en/tos';
var privHref = h === 'fr' ? '/fr/privacy-policy' : '/en/privacy-policy';
var licHref = h === 'fr' ? '/fr/licence' : '/en/licence';
var html = '';
html += '<p>Zektyc \u00b7 France</p>';
html += '<p class="footer-note">' + note + '</p>';
html += '<nav class="footer-nav" aria-label="Legal">';
html += '<a rel="me" href="https://piaille.fr/@zektyc"><img src="/icons/mastodon.svg" alt="" width="14" height="14" class="footer-icon" /> Mastodon</a>';
html += '<span class="footer-sep" aria-hidden="true">\u00b7</span>';
html += '<a href="' + tosHref + '">' + tos + '</a>';
html += '<span class="footer-sep" aria-hidden="true">\u00b7</span>';
html += '<a href="' + privHref + '">' + priv + '</a>';
html += '<span class="footer-sep" aria-hidden="true">\u00b7</span>';
html += '<a href="' + licHref + '">' + lic + '</a>';
html += '<span class="footer-sep" aria-hidden="true">\u00b7</span>';
html += '<a href="/docs/maxspeed">API</a>';
html += '<span class="footer-sep" aria-hidden="true">\u00b7</span>';
html += '<a href="/.well-known/security.txt">security.txt</a>';
html += '<span class="footer-sep" aria-hidden="true">\u00b7</span>';
html += '<a href="http://l5cbkqmok4rnkhbtpltmtlf3pa5i5rfw23z2mtj5tugpo3quta3b5hqd.onion">Tor</a>';
html += '</nav>';
return '<footer class="site-footer">' + html + '</footer>';
}
function inject() {
var hs = document.getElementById('h');
var fs = document.getElementById('f');
if (hs) hs.outerHTML = buildHeader();
if (fs) fs.outerHTML = buildFooter();
var nav = document.getElementById('nav-toggle');
if (nav) {
document.addEventListener('click', function (e) {
if (e.target.closest && e.target.closest('.site-nav')) nav.checked = false;
});
}
}
function isTorBrowser() {
if (isOnion()) return true;
if (window.TorSensor) return true;
if (/TorBrowser|Tor\/|Tor Project/i.test(navigator.userAgent)) return true;
var gmt = new Date().getTimezoneOffset() === 0;
var letterboxed = !!(window.screen &&
window.screen.width === window.innerWidth &&
window.screen.height === window.innerHeight);
var noPlugins = !navigator.plugins || navigator.plugins.length === 0;
var vendor = navigator.vendor;
if (gmt && letterboxed) return true;
if (vendor === '' && gmt && noPlugins) return true;
return false;
}
function isOnion() {
return location.hostname.indexOf('.onion') !== -1;
}
function showTorToast() {
if (!isTorBrowser() || isOnion()) return;
var onionBase = 'http://l5cbkqmok4rnkhbtpltmtlf3pa5i5rfw23z2mtj5tugpo3quta3b5hqd.onion';
var path = location.pathname + location.search + location.hash;
var onionUrl = onionBase + path;
var toast = document.createElement('div');
toast.className = 'tor-toast';
toast.setAttribute('role', 'alert');
var h = lang();
var msg = h === 'fr'
? 'Vous utilisez Tor. Acc\u00e9dez \u00e0 la version .onion pour plus de s\u00e9curit\u00e9.'
: 'You are using Tor. Access the .onion version for better security.';
toast.innerHTML =
'<p>' + msg + '</p>' +
'<a href="' + onionUrl + '" class="tor-toast-btn">' + (h === 'fr' ? 'Ouvrir en .onion' : 'Open .onion') + '</a>' +
'<button type="button" class="tor-toast-close" aria-label="Fermer">&times;</button>';
document.body.appendChild(toast);
toast.querySelector('.tor-toast-close').addEventListener('click', function () {
toast.remove();
});
setTimeout(function () { toast.remove(); }, 30000);
}
function boot() {
inject();
showTorToast();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();