aboutsummaryrefslogtreecommitdiffstats
path: root/assets/js/nav.js
diff options
context:
space:
mode:
Diffstat (limited to 'assets/js/nav.js')
-rw-r--r--assets/js/nav.js177
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();
+ }
+})();