diff options
| author | sillylaird <sillyfanboy@gmail.com> | 2026-09-03 00:33:59 +0000 |
|---|---|---|
| committer | sillylaird <sillyfanboy@gmail.com> | 2026-09-03 00:33:59 +0000 |
| commit | 898b52edcb47bcb3e9d6106e74ca73e74ea01e70 (patch) | |
| tree | 85c6ee5ad58b860144551184d4cf86b560c62b91 /assets/js/nav.js | |
| download | www-main.tar.gz www-main.zip | |
Diffstat (limited to 'assets/js/nav.js')
| -rw-r--r-- | assets/js/nav.js | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/assets/js/nav.js b/assets/js/nav.js new file mode 100644 index 0000000..e2fd6b6 --- /dev/null +++ b/assets/js/nav.js @@ -0,0 +1,177 @@ +/* ============================================================================ + Shared SillyLaird site nav — single source of truth. + + Drop this on any sibling site to get the same top nav as www.sillylaird.ca: + + <div id="sl-nav"></div> + <script defer src="https://www.sillylaird.ca/assets/js/nav.js"></script> + + The script injects the header markup, pulls in the shared stylesheet, and + then loads site.js (which wires the theme toggle, drawer menu, and language + menu). Edit the markup HERE and every site updates on next load. + + If #sl-nav is absent it replaces an existing <header class="site-header">, + else it prepends to <body>. Skips entirely on www itself (server-rendered). + ============================================================================ */ +(function () { + "use strict"; + + var WWW = "https://www.sillylaird.ca"; + // Bump when nav.css / site.js behavior for siblings changes — static assets + // are cached ~30d, so without this edits won't reach visitors. + var NAV_VER = "2026080401"; + + // Already server-rendered on www — don't double up. + if (location.hostname === "www.sillylaird.ca") return; + + var HEADER_HTML = + '<header class="site-header" style="position: fixed; top: 0; left: 0; right: 0;">' + + '<div class="header-inner">' + + '<div class="brand">' + + '<a href="' + WWW + '/" class="logo" aria-label="SillyLaird home">' + + '<img src="' + WWW + '/assets/img/lain.png" alt="SillyLaird logo" width="48" height="48" decoding="async" />' + + '</a>' + + '<div class="brand-text">' + + '<strong>SillyLaird</strong>' + + '<span>personal hub: links, logs, and vibes</span>' + + '</div>' + + '</div>' + + + '<div class="header-controls">' + + '<div class="protocol-pills" aria-label="Alternate network protocols">' + + '<a class="protocol-pill protocol-pill--gemini" href="gemini://sillylaird.ca" target="_blank" rel="noopener noreferrer" title="Open the Gemini capsule">Gemini</a>' + + '<a class="protocol-pill protocol-pill--gopher" href="gopher://sillylaird.ca" target="_blank" rel="noopener noreferrer" title="Open the Gopher hole">Gopher</a>' + + '<a class="protocol-pill protocol-pill--tor" href="http://xyb4zkpbnssz2kgiklg5vfbepbnblykgfvemwadvufwzfcrjny46q6qd.onion/" title="Open the Tor onion service">Tor</a>' + + '<a class="protocol-pill protocol-pill--i2p" href="http://sillylaird.i2p/" title="Open the I2P site">I2P</a>' + + '</div>' + + + '<a href="' + WWW + '/startpage/" class="header-button header-button--startpage" title="Open the power-user StartPage">StartPage</a>' + + '<a href="' + WWW + '/" class="header-button" title="Go to the homepage">Home</a>' + + + '<button class="header-button header-icon-button comfort-toggle" id="comfort-toggle" type="button" title="Toggle comfortable line spacing" aria-pressed="false" aria-label="Comfort spacing off">↕</button>' + + + '<button class="header-button header-icon-button" id="theme-toggle" type="button" title="Toggle light / dark colour theme" aria-label="Switch to light theme">☀</button>' + + + '<div class="lang">' + + '<div class="lang-toggle-wrapper">' + + '<button class="header-button header-icon-button" id="lang-toggle" type="button" title="Choose language" aria-haspopup="menu" aria-expanded="false">文<span class="visually-hidden">Language: <span id="current-lang">EN</span></span></button>' + + '</div>' + + '<div class="lang-menu" id="lang-options" role="menu" hidden>' + + '<a role="menuitem" href="' + WWW + '/?lang=en" hreflang="en" lang="en" data-lang="en" data-lang-href>English</a>' + + '<a role="menuitem" href="' + WWW + '/?lang=zh" hreflang="zh" lang="zh" data-lang="zh" data-lang-href>中文</a>' + + '<a role="menuitem" href="' + WWW + '/?lang=jp" hreflang="ja" lang="ja" data-lang="jp" data-lang-href>日本語</a>' + + '</div>' + + '</div>' + + + '<div class="menu-wrapper">' + + '<button id="menu-toggle" type="button" aria-label="Menu" aria-controls="site-nav" aria-expanded="false">☰</button>' + + '<nav id="site-nav" class="site-nav" aria-label="Main navigation" hidden>' + + '<ul class="nav-section" aria-label="Main">' + + '<li class="nav-label">Main</li>' + + '<li><a href="' + WWW + '/">Home</a></li>' + + '<li><a href="https://os.sillylaird.ca/" target="_blank" rel="noopener noreferrer">OS</a></li>' + + '<li><a href="' + WWW + '/now/">Now</a></li>' + + '<li><a href="' + WWW + '/motd/">MOTD</a></li>' + + '<li><a href="' + WWW + '/startpage/" class="nav-link--featured">StartPage ★</a></li>' + + '<li><a href="' + WWW + '/map/">Site Map</a></li>' + + '</ul>' + + '<ul class="nav-section" aria-label="Writing">' + + '<li class="nav-label">Writing</li>' + + '<li><a href="https://blog.sillylaird.ca/" target="_blank" rel="noopener noreferrer">Blog</a></li>' + + '<li><a href="' + WWW + '/journal/">Journal</a></li>' + + '<li><a href="' + WWW + '/changelog/">Changelog</a></li>' + + '<li><a href="https://diary.sillylaird.ca/bbs.php" target="_blank" rel="noopener noreferrer">Diary</a></li>' + + '<li><a href="https://guestbook.sillylaird.ca/" target="_blank" rel="noopener noreferrer">Guestbook</a></li>' + + '</ul>' + + '<ul class="nav-section" aria-label="Collections">' + + '<li class="nav-label">Collections</li>' + + '<li><a href="' + WWW + '/gaming/">Gaming</a></li>' + + '<li><a href="' + WWW + '/bookmarks/">Bookmarks</a></li>' + + '<li><a href="' + WWW + '/accounts/">Accounts</a></li>' + + '<li><a href="' + WWW + '/computers/">Computers</a></li>' + + '</ul>' + + '<ul class="nav-section" aria-label="Media">' + + '<li class="nav-label">Media</li>' + + '<li><a href="https://radio.sillylaird.ca/" target="_blank" rel="noopener noreferrer">Radio</a></li>' + + '<li><a href="https://live.sillylaird.ca/" target="_blank" rel="noopener noreferrer">Live</a></li>' + + '</ul>' + + '<ul class="nav-section" aria-label="Mail">' + + '<li class="nav-label">Mail</li>' + + '<li><a href="https://mail.google.com/mail/u/0/#inbox">Gmail</a></li>' + + '<li><a href="https://mail.sillylaird.ca/">Mail</a></li>' + + '<li><a href="http://xyb4zkpbnssz2kgiklg5vfbepbnblykgfvemwadvufwzfcrjny46q6qd.onion:8025/src/login.php">Mail@Tor</a></li>' + + '</ul>' + + '<ul class="nav-section" aria-label="Networks">' + + '<li class="nav-label">Networks</li>' + + '<li><a href="http://xyb4zkpbnssz2kgiklg5vfbepbnblykgfvemwadvufwzfcrjny46q6qd.onion/">Tor</a></li>' + + '<li><a href="http://sillylaird.i2p/">I2P</a></li>' + + '<li><a href="http://9p.sillylaird.ca" target="_blank" rel="noopener noreferrer">9p/9f</a></li>' + + '<li><a href="http://xp.sillylaird.ca" target="_blank" rel="noopener noreferrer">xp</a></li>' + + '<li><a href="gopher://sillylaird.ca" target="_blank" rel="noopener noreferrer">Gopher</a></li>' + + '<li><a href="gemini://sillylaird.ca" target="_blank" rel="noopener noreferrer">Gemini</a></li>' + + '</ul>' + + '<ul class="nav-section" aria-label="External">' + + '<li class="nav-label">External</li>' + + '<li><a href="https://www.heyaoi.net" target="_blank" rel="noopener noreferrer">Heyaoi</a></li>' + + '<li><a href="https://git.sillylaird.ca/" target="_blank" rel="noopener noreferrer">Git</a></li>' + + '<li><a href="https://cvs.sillylaird.ca/" target="_blank" rel="noopener noreferrer">CVS</a></li>' + + '</ul>' + + '</nav>' + + '</div>' + + '</div>' + + '</div>' + + '</header>'; + + // Ensure a stylesheet is present (idempotent by href). Prepended to the very + // top of <head> so the host page's own <style>/<link> (which come after) win + // on any shared selector. nav.css is already header-scoped (no base element + // rules), so it won't restyle the host page (e.g. diary's inline body colour). + function ensureCss(href) { + var links = document.getElementsByTagName("link"); + for (var i = 0; i < links.length; i++) { + if (links[i].rel === "stylesheet" && links[i].href.indexOf(href) > -1) return; + } + var l = document.createElement("link"); + l.rel = "stylesheet"; + l.href = href; + document.head.insertBefore(l, document.head.firstChild); + } + + function injectNav() { + var holder = document.getElementById("sl-nav"); + if (holder) { + holder.outerHTML = HEADER_HTML; + } else { + var existing = document.querySelector("header.site-header"); + if (existing) { + existing.outerHTML = HEADER_HTML; + } else { + document.body.insertAdjacentHTML("afterbegin", HEADER_HTML); + } + } + + // site.js binds the theme/menu/lang controls by id at execution time, so it + // must run AFTER the nav exists. Load it now unless the page already has it. + if (!document.querySelector('script[src*="/assets/js/site.js"]')) { + var s = document.createElement("script"); + s.src = WWW + "/assets/js/site.js?v=" + NAV_VER; + document.body.appendChild(s); + } + } + + ensureCss(WWW + "/assets/css/fonts.css"); + // If the host page already loads the full site.css (e.g. radio), its header + // rules are authoritative and identical to www — skip nav.css so the header is + // byte-for-byte the same as www and can never drift from the header-scoped subset. + var _hasSiteCss = false, _links = document.getElementsByTagName("link"); + for (var _i = 0; _i < _links.length; _i++) { + if (_links[_i].rel === "stylesheet" && _links[_i].href.indexOf("/assets/css/site.css") > -1) { _hasSiteCss = true; break; } + } + if (!_hasSiteCss) ensureCss(WWW + "/assets/css/nav.css?v=" + NAV_VER); + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", injectNav); + } else { + injectNav(); + } +})(); |
