aboutsummaryrefslogtreecommitdiffstats
path: root/assets
diff options
context:
space:
mode:
authorsillylaird <sillyfanboy@gmail.com>2026-09-03 00:33:59 +0000
committersillylaird <sillyfanboy@gmail.com>2026-09-03 00:33:59 +0000
commit898b52edcb47bcb3e9d6106e74ca73e74ea01e70 (patch)
tree85c6ee5ad58b860144551184d4cf86b560c62b91 /assets
downloadwww-main.tar.gz
www-main.zip
import live www.sillylaird.ca webrootHEADmain
Diffstat (limited to '')
-rw-r--r--assets/css/fonts.css229
-rw-r--r--assets/css/inline-attrs.css31
-rw-r--r--assets/css/nav.css474
-rw-r--r--assets/css/pages/404.css12
-rw-r--r--assets/css/pages/50x.css9
-rw-r--r--assets/css/pages/admin.css392
-rw-r--r--assets/css/pages/articles-2023-04-10.css1
-rw-r--r--assets/css/pages/articles-2023-04-10_jp.css1
-rw-r--r--assets/css/pages/articles-2023-04-10_zh.css1
-rw-r--r--assets/css/pages/articles-runescape.css1
-rw-r--r--assets/css/pages/articles-runescape_jp.css1
-rw-r--r--assets/css/pages/articles-runescape_zh.css1
-rw-r--r--assets/css/pages/bookmarks.css85
-rw-r--r--assets/css/pages/changelog.css25
-rw-r--r--assets/css/pages/collection.css290
-rw-r--r--assets/css/pages/computers.css102
-rw-r--r--assets/css/pages/guestbook-form.css58
-rw-r--r--assets/css/pages/guestbook_jp.css79
-rw-r--r--assets/css/pages/guestbook_zh.css79
-rw-r--r--assets/css/pages/hitcounter.css7
-rw-r--r--assets/css/pages/journal-index.css3
-rw-r--r--assets/css/pages/journal-index_jp.css3
-rw-r--r--assets/css/pages/journal-index_zh.css3
-rw-r--r--assets/css/pages/links.css22
-rw-r--r--assets/css/pages/links_jp.css22
-rw-r--r--assets/css/pages/links_zh.css22
-rw-r--r--assets/css/pages/motd.css51
-rw-r--r--assets/css/pages/mstartpage-index.css341
-rw-r--r--assets/css/pages/vinyls.css14
-rw-r--r--assets/css/scroll-to-top.css162
-rw-r--r--assets/css/site.css4854
-rw-r--r--assets/css/skeleton.css63
-rw-r--r--assets/data/site-index.json23
-rw-r--r--assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2bin0 -> 18748 bytes
-rw-r--r--assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2bin0 -> 48256 bytes
-rw-r--r--assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2bin0 -> 18996 bytes
-rw-r--r--assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2bin0 -> 85068 bytes
-rw-r--r--assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2bin0 -> 25960 bytes
-rw-r--r--assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2bin0 -> 11232 bytes
-rw-r--r--assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2bin0 -> 10252 bytes
-rw-r--r--assets/fonts/jetbrains-mono-400.woff2bin0 -> 92380 bytes
-rw-r--r--assets/fonts/jetbrains-mono-700.woff2bin0 -> 94628 bytes
-rw-r--r--assets/fonts/source-code-pro-latin.woff2bin0 -> 22044 bytes
-rw-r--r--assets/img/2cows.gifbin0 -> 675 bytes
-rw-r--r--assets/img/Aeplaine.gifbin0 -> 2396 bytes
-rw-r--r--assets/img/L3.pngbin0 -> 75403 bytes
-rw-r--r--assets/img/The_Site_Banner.webpbin0 -> 258 bytes
-rw-r--r--assets/img/Vidlii6.pngbin0 -> 10105 bytes
-rw-r--r--assets/img/ae.gifbin0 -> 2396 bytes
-rw-r--r--assets/img/airvpn.pngbin0 -> 114108 bytes
-rw-r--r--assets/img/avatar.pngbin0 -> 44168 bytes
-rw-r--r--assets/img/babi.pngbin0 -> 125399 bytes
-rw-r--r--assets/img/buyvm.pngbin0 -> 4673 bytes
-rw-r--r--assets/img/canada.pngbin0 -> 15946 bytes
-rw-r--r--assets/img/ccp.svg1
-rw-r--r--assets/img/china.pngbin0 -> 28882 bytes
-rw-r--r--assets/img/cirno.jpgbin0 -> 106643 bytes
-rw-r--r--assets/img/cockbox.gifbin0 -> 1689 bytes
-rw-r--r--assets/img/code.pngbin0 -> 117000 bytes
-rw-r--r--assets/img/cornhub.pngbin0 -> 28930 bytes
-rw-r--r--assets/img/cryptostorm.pngbin0 -> 1712 bytes
-rw-r--r--assets/img/edramatica.pngbin0 -> 53309 bytes
-rw-r--r--assets/img/emacs.pngbin0 -> 12295 bytes
-rw-r--r--assets/img/eso-tilin-tilin.gifbin0 -> 306936 bytes
-rw-r--r--assets/img/expressvpn.pngbin0 -> 7332 bytes
-rw-r--r--assets/img/fastmail.pngbin0 -> 4221 bytes
-rw-r--r--assets/img/japan.pngbin0 -> 4752 bytes
-rw-r--r--assets/img/javascript.gifbin0 -> 1063 bytes
-rw-r--r--assets/img/jetbrain.pngbin0 -> 91236 bytes
-rw-r--r--assets/img/kansen.gifbin0 -> 16143 bytes
-rw-r--r--assets/img/l3-logo.pngbin0 -> 7488 bytes
-rw-r--r--assets/img/l3-transparent.pngbin0 -> 192531 bytes
-rw-r--r--assets/img/lain.gifbin0 -> 86641 bytes
-rw-r--r--assets/img/lain.pngbin0 -> 117611 bytes
-rw-r--r--assets/img/lolwut-button.gifbin0 -> 3515 bytes
-rw-r--r--assets/img/lolwut.pngbin0 -> 6375 bytes
-rw-r--r--assets/img/mothracompat.gifbin0 -> 1146 bytes
-rw-r--r--assets/img/multibox.pngbin0 -> 100218 bytes
-rw-r--r--assets/img/niceland.pngbin0 -> 16448 bytes
-rw-r--r--assets/img/nordvpn.svg1
-rw-r--r--assets/img/npp.svg339
-rw-r--r--assets/img/og-default.pngbin0 -> 53764 bytes
-rw-r--r--assets/img/openbgpd.gifbin0 -> 75238 bytes
-rw-r--r--assets/img/openbsd.pngbin0 -> 160416 bytes
-rw-r--r--assets/img/pia.svg54
-rw-r--r--assets/img/plan9.pngbin0 -> 14799 bytes
-rw-r--r--assets/img/plan9bunnysmwhite.jpgbin0 -> 3023 bytes
-rw-r--r--assets/img/playbilibili.pngbin0 -> 130709 bytes
-rw-r--r--assets/img/power36.gifbin0 -> 913 bytes
-rw-r--r--assets/img/ps2.jpgbin0 -> 192866 bytes
-rw-r--r--assets/img/rib_bar_red.pngbin0 -> 25218 bytes
-rw-r--r--assets/img/rib_bar_wh.pngbin0 -> 29772 bytes
-rw-r--r--assets/img/roundcube.pngbin0 -> 16149 bytes
-rw-r--r--assets/img/roundcube.svg11
-rw-r--r--assets/img/runescape.pngbin0 -> 203110 bytes
-rw-r--r--assets/img/runescape2.pngbin0 -> 14894 bytes
-rw-r--r--assets/img/sillylaird-88x31.pngbin0 -> 2970 bytes
-rw-r--r--assets/img/sillylaird.pngbin0 -> 2970 bytes
-rw-r--r--assets/img/soldierfront.pngbin0 -> 227194 bytes
-rw-r--r--assets/img/stepmania.pngbin0 -> 57868 bytes
-rw-r--r--assets/img/sublime_text.pngbin0 -> 38950 bytes
-rw-r--r--assets/img/ubuntu.jpgbin0 -> 53629 bytes
-rw-r--r--assets/img/ubuntu.pngbin0 -> 67264 bytes
-rw-r--r--assets/img/vidlii.pngbin0 -> 10105 bytes
-rw-r--r--assets/img/vpn-logo.svg54
-rw-r--r--assets/img/vscode.pngbin0 -> 64776 bytes
-rw-r--r--assets/img/windowserver.jpgbin0 -> 70847 bytes
-rw-r--r--assets/img/windowserver.pngbin0 -> 74764 bytes
-rw-r--r--assets/img/wtfpl.pngbin0 -> 23665 bytes
-rw-r--r--assets/js/includes.js33
-rw-r--r--assets/js/nav.js177
-rw-r--r--assets/js/pages/404.js5
-rw-r--r--assets/js/pages/50x.js5
-rw-r--r--assets/js/pages/admin-vibe.js71
-rw-r--r--assets/js/pages/admin.js714
-rw-r--r--assets/js/pages/guestbook-comments.js39
-rw-r--r--assets/js/pages/guestbook_jp.js91
-rw-r--r--assets/js/pages/guestbook_zh.js91
-rw-r--r--assets/js/pages/hitcounter.js26
-rw-r--r--assets/js/pages/latest.js10
-rw-r--r--assets/js/pages/mstartpage-index.js118
-rw-r--r--assets/js/pages/radio-nowplaying.js41
-rw-r--r--assets/js/pages/vibe-admin.js48
-rw-r--r--assets/js/pages/whatsleft.js16
-rw-r--r--assets/js/scroll-to-top.js180
-rw-r--r--assets/js/site.js1341
-rw-r--r--assets/js/visit-counter.js141
127 files changed, 11068 insertions, 0 deletions
diff --git a/assets/css/fonts.css b/assets/css/fonts.css
new file mode 100644
index 0000000..26ddd8a
--- /dev/null
+++ b/assets/css/fonts.css
@@ -0,0 +1,229 @@
+/* ==========================================================================
+ Font Declarations
+ Separated from site.css for faster initial CSS parsing.
+ ========================================================================== */
+
+/* --- Inter (Google Fonts, self-hosted woff2) --- */
+
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* cyrillic-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
+ unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
+}
+/* cyrillic */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+/* greek-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
+ unicode-range: U+1F00-1FFF;
+}
+/* greek */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
+ unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
+}
+/* vietnamese */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
+ unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
+}
+/* latin-ext */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+/* latin */
+@font-face {
+ font-family: 'Inter';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* --- JetBrains Mono (self-hosted; CSP-safe) --- */
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+ src: url(/assets/fonts/jetbrains-mono-400.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+@font-face {
+ font-family: 'JetBrains Mono';
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+ src: url(/assets/fonts/jetbrains-mono-700.woff2) format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+/* --- Source Code Pro (legacy fallback, self-hosted) --- */
+@font-face {
+ font-family: 'Source Code Pro';
+ font-style: normal;
+ font-weight: 400 700;
+ font-display: swap;
+ src: url(/assets/fonts/source-code-pro-latin.woff2) format('woff2');
+}
+
+/* --- Anonymous Pro (Google Fonts, external) --- */
+@import url('https://fonts.googleapis.com/css2?family=Anonymous+Pro:ital,wght@0,400;0,700;1,400;1,700&display=swap');
diff --git a/assets/css/inline-attrs.css b/assets/css/inline-attrs.css
new file mode 100644
index 0000000..2dce580
--- /dev/null
+++ b/assets/css/inline-attrs.css
@@ -0,0 +1,31 @@
+.s1da9facb4d{margin:0;}
+.s6e250fe32e{border:none;border-top:1px solid #caa;margin:1rem 0;}
+.s2a2536eebb{margin: 15px 0;}
+.sd078564312{border:1px solid #ffd; display:block; margin-bottom:6px;}
+.sd82634530e{background: transparent;
+ border:1px solid transparent;
+ color:#3a2a00;
+ padding:10px;
+ width:100%;
+ border-radius:4px;}
+.sefd85b92cb{display: flex; flex-direction: column; gap: 20px;}
+.s0b3fba4969{border: 1px solid var(--border); border-radius: var(--r);}
+.see3e7269f8{height:700px; border: 1px solid var(--border); border-radius: var(--r);}
+.s9572e08b37{width:150px;opacity:0.7;}
+.se7f7a3d62d{border:1px solid var(--border);border-radius:var(--r);padding:var(--space-md);background:var(--bg);overflow:auto;}
+.s06eeebc88f{max-width: 100%; height: auto;}
+.s386d82bd1c{height:1100px;}
+.s7e8ca45906{font-size: 50px;}
+.s0667eb5585{max-width:160px;border:1px solid var(--border);border-radius:var(--r);background:var(--bg);}
+.sed328f664c{min-height:360px;}
+.s63501a3337{border:1px solid var(--border);border-radius:var(--r);background:var(--bg);}
+.saf481e1471{width:100%;max-width:560px;height:315px;border:1px solid var(--border);border-radius:var(--r);}
+.sb83d4c5326{max-width:720px;margin:0 auto;border:1px solid var(--border);border-radius:var(--r);background:var(--bg);}
+.sd1a2ffa0b4{text-align:center;margin-top:var(--space-md);}
+.sdac4fe6c9b{text-align:center;}
+.sfed215d685{max-width:420px;margin:0 auto;border:1px solid var(--border);border-radius:var(--r);background:var(--bg);}
+.s16a8a8b00c{overflow:auto;}
+.s473e6bbfeb{width:100%;border-collapse:collapse;}
+.sf2e940dbbf{text-align:left;border-bottom:1px solid var(--border);padding:8px;}
+.s6f279abaab{padding:8px;border-bottom:1px dotted var(--border);}
+.s44cd1236df{padding:8px;}
diff --git a/assets/css/nav.css b/assets/css/nav.css
new file mode 100644
index 0000000..e89f92d
--- /dev/null
+++ b/assets/css/nav.css
@@ -0,0 +1,474 @@
+/* ==========================================================================
+ nav.css — header-only styles for the shared cross-site nav (nav.js).
+
+ This is a SCOPED subset of site.css: only the design tokens + header / menu
+ / language-dropdown rules. It deliberately omits site.css's base element
+ rules (body, p, a, h*, input, button, section…) so dropping the shared nav
+ onto a sibling site does NOT restyle that site's own content.
+
+ Keep header-related rules here in sync with site.css. See nav.js.
+ ========================================================================== */
+
+/* ---- Design tokens (from site.css :root). Scoped to the header (all nav
+ elements are descendants) so we never clobber a host site's own CSS vars. ---- */
+header.site-header {
+ --bg: #000000;
+ --ink: #8b0000;
+ --muted: #8b0000;
+ --accent: #a00000;
+ --gold: #8b0000;
+ --border: #3a0000;
+ --shadow: rgba(0, 0, 0, .40);
+ --glow-color: 160, 0, 0;
+ --glow-strong:
+ 0 0 0 1px rgba(var(--glow-color), .55),
+ 0 0 6px rgba(var(--glow-color), .65),
+ 0 0 18px rgba(var(--glow-color), .55),
+ 0 0 48px rgba(var(--glow-color), .35),
+ inset 0 0 12px rgba(var(--glow-color), .18);
+ --r: 6px;
+ --wrap: 960px;
+ --space-xs: 0.25rem;
+ --space-sm: 0.5rem;
+ --space-md: 0.75rem;
+ --space-lg: 1rem;
+ --space-xl: 1.35rem;
+ --space-2xl: 1.75rem;
+ --fs-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.85rem);
+ --fs-sm: clamp(0.8rem, 0.75rem + 0.25vw, 0.9rem);
+ --fs-lg: clamp(0.95rem, 0.9rem + 0.25vw, 1.05rem);
+ --fs-xl: clamp(1.05rem, 1rem + 0.25vw, 1.15rem);
+}
+
+/* Header should always look like www (Inter), not inherit the host body font. */
+header.site-header {
+ font-family: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
+ line-height: 1.6;
+}
+
+.header-button:hover,
+.header-button:focus-visible,
+#menu-toggle:hover,
+#menu-toggle:focus-visible {
+ text-shadow: none;
+}
+
+/* ---- Header ---- */
+header.site-header {
+ background: var(--bg);
+ border-bottom: 1px solid var(--border);
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+}
+
+/* Offset the host page so the fixed header doesn't overlap its content. */
+body:has(header.site-header) {
+ padding-top: var(--site-header-height, 76px);
+}
+
+.header-inner {
+ max-width: var(--wrap);
+ margin: 0 auto;
+ padding: var(--space-md) var(--space-lg);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-lg);
+ flex-wrap: wrap;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ min-width: 240px;
+}
+
+.logo {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--r);
+}
+
+.logo img {
+ height: 48px;
+ width: 48px;
+ object-fit: contain;
+ border: 2px solid var(--border);
+ border-radius: var(--r);
+ transition: border-color .25s ease, box-shadow .35s ease, transform .35s ease;
+}
+
+header.site-header .logo:hover img,
+header.site-header .logo:focus-visible img {
+ border-color: rgba(var(--glow-color), .9);
+ box-shadow: var(--glow-strong);
+ animation: logo-pulse 2.4s ease-in-out infinite;
+}
+
+@keyframes logo-pulse {
+ 0%, 100% {
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .55),
+ 0 0 6px rgba(var(--glow-color), .65),
+ 0 0 18px rgba(var(--glow-color), .55),
+ 0 0 48px rgba(var(--glow-color), .35),
+ inset 0 0 12px rgba(var(--glow-color), .18);
+ }
+ 50% {
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .8),
+ 0 0 10px rgba(var(--glow-color), .9),
+ 0 0 28px rgba(var(--glow-color), .75),
+ 0 0 64px rgba(var(--glow-color), .5),
+ inset 0 0 18px rgba(var(--glow-color), .28);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ header.site-header .logo:hover img,
+ header.site-header .logo:focus-visible img {
+ animation: none;
+ }
+}
+
+.brand-text {
+ display: flex;
+ flex-direction: column;
+ line-height: 1.2;
+}
+
+.brand-text strong {
+ font-size: var(--fs-lg);
+ color: var(--gold);
+ letter-spacing: .2px;
+}
+
+.brand-text span {
+ font-size: var(--fs-sm);
+ color: var(--muted);
+}
+
+.header-controls {
+ display: flex;
+ align-items: center;
+ gap: var(--space-sm);
+}
+
+.header-button {
+ font-size: var(--fs-sm);
+ border: 1px solid var(--border);
+ padding: 4px 8px;
+ border-radius: var(--r);
+ color: var(--gold);
+ text-decoration: none;
+ background: var(--bg);
+ cursor: pointer;
+ font-family: inherit;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ transition: background .15s, color .15s, border-color .15s, box-shadow .25s;
+}
+
+.header-button:hover {
+ background: var(--accent);
+ color: var(--ink);
+ border-color: rgba(var(--glow-color), .9);
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .4),
+ 0 0 12px rgba(var(--glow-color), .55),
+ 0 0 28px -4px rgba(var(--glow-color), .4);
+}
+
+nav.site-nav[hidden],
+.lang-menu[hidden] {
+ display: none !important;
+}
+
+.header-icon-button {
+ width: 36px;
+ min-width: 36px;
+ height: 36px;
+ padding: 0;
+ font-size: 1.05rem;
+ line-height: 1;
+}
+
+.visually-hidden {
+ position: absolute !important;
+ width: 1px; height: 1px;
+ padding: 0; margin: -1px;
+ overflow: hidden; clip: rect(0, 0, 0, 0);
+ white-space: nowrap; border: 0;
+}
+
+.protocol-pills {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 5px;
+}
+
+.protocol-pill {
+ --protocol-accent: var(--accent);
+ display: inline-flex;
+ align-items: center;
+ min-height: 34px;
+ padding: 5px 10px;
+ border: 1px solid var(--protocol-accent);
+ border-radius: 999px;
+ background: rgba(var(--glow-color), .06);
+ color: var(--protocol-accent);
+ font-family: "JetBrains Mono", "IBM Plex Mono", "Fira Code", "Source Code Pro", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ font-size: 0.88rem;
+ font-weight: 700;
+ line-height: 1.15;
+ letter-spacing: 0;
+ box-shadow: inset 0 0 10px rgba(var(--glow-color), .08);
+}
+
+.protocol-pill:hover,
+.protocol-pill:focus-visible {
+ background: var(--protocol-accent);
+ color: #050000;
+ text-shadow: none;
+ box-shadow:
+ 0 0 0 1px var(--protocol-accent),
+ 0 0 16px color-mix(in srgb, var(--protocol-accent) 65%, transparent);
+}
+
+.protocol-pill--gemini { --protocol-accent: #2fd7b5; }
+.protocol-pill--gopher { --protocol-accent: #d8a735; }
+.protocol-pill--tor { --protocol-accent: #a579d6; }
+.protocol-pill--i2p { --protocol-accent: #ff7b2c; }
+
+/* ---- Menu ---- */
+.menu-wrapper {
+ position: relative;
+}
+
+#menu-toggle {
+ width: 44px;
+ height: 44px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 22px;
+ line-height: 1;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--bg);
+ color: var(--ink);
+ padding: 0;
+ transition: transform .25s ease, background .15s, color .15s, border-color .15s, box-shadow .25s;
+}
+
+#menu-toggle:hover {
+ background: var(--accent);
+ color: var(--ink);
+ border-color: rgba(var(--glow-color), .9);
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .4),
+ 0 0 14px rgba(var(--glow-color), .6),
+ 0 0 32px -4px rgba(var(--glow-color), .4);
+}
+
+#menu-toggle.active {
+ transform: rotate(90deg);
+ background: var(--bg);
+ color: var(--ink);
+ border-color: var(--border);
+}
+
+#menu-toggle.active:hover {
+ background: var(--accent);
+ color: var(--ink);
+ border-color: var(--accent);
+}
+
+nav.site-nav {
+ position: absolute;
+ right: 0;
+ top: calc(100% + var(--space-xs));
+ background: var(--bg);
+ border: 1px solid var(--ink);
+ border-radius: var(--r);
+ padding: var(--space-md) var(--space-md);
+ min-width: 200px;
+ box-shadow: 0 10px 22px var(--shadow);
+ z-index: 1200;
+ max-height: calc(100vh - 80px);
+ overflow-y: auto;
+}
+
+nav.site-nav ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+nav.site-nav li {
+ margin: var(--space-sm) 0;
+}
+
+nav.site-nav a {
+ display: block;
+ padding: var(--space-xs) var(--space-xs);
+ transition: background .15s, color .15s;
+ color: var(--gold);
+ font-size: var(--fs-xl);
+ margin: 0;
+ text-decoration: none;
+}
+
+nav.site-nav a:hover {
+ background: var(--accent);
+ color: var(--ink);
+}
+
+nav.site-nav .nav-section {
+ margin-bottom: var(--space-sm);
+}
+
+nav.site-nav .nav-section:last-child {
+ margin-bottom: 0;
+}
+
+nav.site-nav .nav-section+.nav-section {
+ border-top: 1px solid var(--border);
+ padding-top: var(--space-sm);
+}
+
+nav.site-nav .nav-label {
+ font-size: var(--fs-xs);
+ font-weight: 600;
+ color: var(--muted);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: var(--space-xs);
+ padding: var(--space-xs);
+}
+
+/* ---- Language dropdown ---- */
+.lang {
+ position: relative;
+}
+
+.lang-toggle-wrapper {
+ display: inline-block;
+}
+
+.lang-menu {
+ position: absolute;
+ right: 0;
+ top: calc(100% + var(--space-xs));
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ box-shadow: 0 10px 22px var(--shadow);
+ min-width: 160px;
+ padding: var(--space-sm);
+ z-index: 1200;
+}
+
+.lang-menu a {
+ display: block;
+ padding: var(--space-sm) var(--space-sm);
+ color: var(--gold);
+ text-decoration: none;
+ transition: background .15s;
+}
+
+.lang-menu a:hover {
+ background: var(--accent);
+ color: var(--ink);
+}
+
+/* ---- Light-mode header (driven by body.light-mode from site.js) ---- */
+body.light-mode header.site-header {
+ --bg: #fff8f1;
+ --ink: #2a1212;
+ --muted: #7a4545;
+ --accent: #8b0000;
+ --gold: #8b0000;
+ --border: #e8c8c8;
+ --shadow: rgba(139, 0, 0, .12);
+ --glow-color: 139, 0, 0;
+ background: rgba(255, 248, 241, .96);
+}
+
+body.light-mode .header-button,
+body.light-mode #menu-toggle,
+body.light-mode nav.site-nav,
+body.light-mode .lang-menu {
+ --bg: #fff8f1;
+ --ink: #2a1212;
+ --muted: #7a4545;
+ --accent: #8b0000;
+ --gold: #8b0000;
+ --border: #e8c8c8;
+ background: #ffffff;
+}
+
+body.light-mode .header-button:hover,
+body.light-mode #menu-toggle:hover,
+body.light-mode nav.site-nav a:hover,
+body.light-mode .lang-menu a:hover {
+ background: #8b0000;
+ color: #ffffff;
+ text-shadow: none;
+}
+
+body.light-mode .protocol-pill {
+ background: #ffffff;
+}
+
+body.light-mode .protocol-pill:hover,
+body.light-mode .protocol-pill:focus-visible {
+ color: #ffffff;
+}
+
+/* ---- Responsive (header only) ---- */
+@media (max-width: 720px) {
+ .header-inner { gap: var(--space-sm); }
+ .brand { min-width: 0; flex: 1 1 auto; }
+ .header-controls {
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ gap: var(--space-sm);
+ row-gap: var(--space-xs);
+ }
+ .protocol-pills {
+ order: 4;
+ flex-basis: 100%;
+ justify-content: flex-end;
+ }
+ #lang-toggle { white-space: nowrap; }
+}
+
+@media (max-width: 600px) {
+ .brand-text span { display: none; }
+}
+
+@media (max-width: 520px) {
+ .header-inner { position: relative; padding: var(--space-sm) var(--space-md); }
+ nav.site-nav { min-width: 200px; max-width: calc(100vw - 2 * var(--space-md)); }
+ .lang-menu { right: auto; left: 0; max-width: calc(100vw - 32px); }
+ .header-button { padding: 4px 6px; }
+ .header-icon-button { width: 34px; min-width: 34px; height: 34px; padding: 0; }
+ .protocol-pills { justify-content: flex-start; }
+ .protocol-pill { min-height: 32px; padding-inline: 9px; font-size: 0.82rem; }
+}
+
+@media (max-width: 380px) {
+ .header-button { font-size: var(--fs-xs); padding: 3px 5px; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ #menu-toggle.active { transform: none; }
+}
diff --git a/assets/css/pages/404.css b/assets/css/pages/404.css
new file mode 100644
index 0000000..7939cc1
--- /dev/null
+++ b/assets/css/pages/404.css
@@ -0,0 +1,12 @@
+/* Minimal fallback if CSS fails to load */
+ body{margin:0;background:#ffd;color:#000;font-family:system-ui,Segoe UI,Arial,sans-serif;line-height:1.6;}
+ .wrap{max-width:960px;margin:0 auto;padding:1.35rem 1rem 2.5rem;}
+ .card{border:1px solid #caa;border-radius:6px;padding:1rem;background:#ffd;}
+ .top{display:flex;gap:12px;align-items:center;margin-bottom:1rem;}
+ .top img{width:48px;height:48px;border:2px solid #000;border-radius:6px;}
+ .top a:hover img{border-color:#d22;}
+ a{color:#d22;text-decoration:none;transition:0.1s;}
+ a:hover{background:#d22;color:#000 !important;}
+ .muted{color:#333;font-size:.9rem;}
+ .links{margin:1rem 0 0 0;}
+ .links a{display:inline-block;margin:0 .25rem .5rem 0;padding:2px 4px;border-radius:4px;}
diff --git a/assets/css/pages/50x.css b/assets/css/pages/50x.css
new file mode 100644
index 0000000..cc573de
--- /dev/null
+++ b/assets/css/pages/50x.css
@@ -0,0 +1,9 @@
+body{margin:0;background:#ffd;color:#000;font-family:system-ui,Segoe UI,Arial,sans-serif;line-height:1.6;}
+ .wrap{max-width:960px;margin:0 auto;padding:1.35rem 1rem 2.5rem;}
+ .card{border:1px solid #caa;border-radius:6px;padding:1rem;background:#ffd;}
+ .top{display:flex;gap:12px;align-items:center;margin-bottom:1rem;}
+ .top img{width:48px;height:48px;border:2px solid #000;border-radius:6px;}
+ .top a:hover img{border-color:#d22;}
+ a{color:#d22;text-decoration:none;transition:0.1s;}
+ a:hover{background:#d22;color:#000 !important;}
+ .muted{color:#333;font-size:.9rem;}
diff --git a/assets/css/pages/admin.css b/assets/css/pages/admin.css
new file mode 100644
index 0000000..6262433
--- /dev/null
+++ b/assets/css/pages/admin.css
@@ -0,0 +1,392 @@
+/* Unified admin panel — terminal theme helpers */
+
+.admin-tabs {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-xs);
+ margin: 0 0 var(--space-lg);
+ border-bottom: 1px solid var(--border);
+}
+
+.admin-tabs a {
+ padding: var(--space-sm) var(--space-md);
+ border: 1px solid var(--border);
+ border-bottom: none;
+ border-radius: var(--r) var(--r) 0 0;
+ color: var(--muted);
+ font-weight: 700;
+}
+
+.admin-tabs a.active {
+ color: var(--ink);
+ border-color: var(--accent);
+ background: #0a0000;
+}
+
+.admin-flash {
+ padding: var(--space-sm) var(--space-md);
+ border-radius: var(--r);
+ margin-bottom: var(--space-md);
+ border: 1px solid var(--border);
+}
+
+.admin-flash.ok { border-color: #2e7d32; }
+.admin-flash.err { border-color: var(--accent); }
+
+.admin-cols {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: var(--space-lg);
+ align-items: start;
+}
+
+@media (max-width: 800px) {
+ .admin-cols { grid-template-columns: 1fr; }
+}
+
+.panel {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ margin-bottom: var(--space-md);
+}
+
+.panel-head {
+ padding: var(--space-sm) var(--space-md);
+ border-bottom: 1px solid var(--border);
+ font-size: var(--fs-xs);
+ text-transform: uppercase;
+ letter-spacing: .07em;
+ color: var(--muted);
+}
+
+.panel-body { padding: var(--space-md); }
+
+.admin-field { margin-bottom: var(--space-md); }
+
+.admin-field label {
+ display: block;
+ margin-bottom: 4px;
+ font-size: var(--fs-sm);
+}
+
+.admin-field input,
+.admin-field textarea,
+.admin-field select {
+ width: 100%;
+ box-sizing: border-box;
+ background: #050000;
+ border: 1px solid var(--border);
+ color: #d00000;
+ padding: 10px;
+ border-radius: var(--r);
+ font: inherit;
+}
+
+.md-toolbar {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 4px;
+ margin-bottom: 6px;
+}
+
+.md-toolbar button {
+ padding: 6px 10px;
+ font-size: var(--fs-xs);
+}
+
+#md-preview {
+ background: #050000;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: 12px;
+ min-height: 200px;
+ overflow: auto;
+ line-height: 1.6;
+}
+
+.admin-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.admin-item {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-sm) var(--space-md);
+ margin-bottom: var(--space-sm);
+}
+
+.admin-item small {
+ color: var(--muted);
+ font-family: monospace;
+}
+
+.inline-form { display: inline; }
+
+.admin-checks {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ margin: 0 0 var(--space-md);
+}
+
+.admin-checks label {
+ font-size: var(--fs-sm);
+}
+
+.admin-motd-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-sm);
+ margin: 0 0 var(--space-md);
+}
+
+.motd-xmpp-preview {
+ margin: 0;
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ font-family: var(--font-mono, ui-monospace, monospace);
+ font-size: var(--fs-sm);
+ line-height: 1.55;
+ color: var(--ink);
+}
+
+.type-badge {
+ font-family: monospace;
+ padding: 1px 7px;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ color: var(--muted);
+}
+
+table.admin-table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+table.admin-table th,
+table.admin-table td {
+ border: 1px solid var(--border);
+ padding: 6px 8px;
+ text-align: left;
+ vertical-align: top;
+ font-size: var(--fs-sm);
+}
+
+.ratio-wrap {
+ position: relative;
+ padding-top: 56.25%;
+}
+
+.ratio-wrap iframe {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ border: 0;
+ border-radius: var(--r);
+}
+
+.preview-panel img,
+.preview-panel video,
+.preview-panel audio {
+ max-width: 100%;
+ border-radius: var(--r);
+ display: block;
+}
+
+/* Utility replacements for residual inline styles */
+.admin-section-title {
+ font-size: var(--fs-lg);
+ margin: var(--space-2xl) 0 var(--space-md);
+}
+
+.admin-section-label {
+ font-size: var(--fs-sm);
+ text-transform: uppercase;
+ letter-spacing: .07em;
+ color: var(--muted);
+ margin: var(--space-xl) 0 var(--space-sm);
+}
+
+.admin-stack-gap {
+ margin-bottom: var(--space-2xl);
+}
+
+.admin-inline-note {
+ font-size: var(--fs-sm);
+ color: var(--accent);
+}
+
+.admin-search-input {
+ padding: 8px;
+ background: #050000;
+ border: 1px solid var(--border);
+ color: #d00000;
+ border-radius: var(--r);
+}
+
+body.light-mode .admin-tabs a.active {
+ background: #fff4f4;
+}
+
+body.light-mode .admin-field input,
+body.light-mode .admin-field textarea,
+body.light-mode .admin-field select,
+body.light-mode #md-preview,
+body.light-mode .admin-search-input {
+ background: #fff;
+ color: #2a1212;
+}
+
+/* ---- Traffic analytics ---- */
+.traffic-live-bar {
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ margin: 0 0 var(--space-md);
+ font-family: var(--font-mono, ui-monospace, monospace);
+ font-size: var(--fs-xs);
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--muted);
+}
+.traffic-live-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: #39ff14;
+ box-shadow: 0 0 8px rgba(57, 255, 20, 0.7);
+ animation: traffic-live-pulse 1.6s ease-in-out infinite;
+}
+.traffic-live-bar--ok .traffic-live-dot {
+ background: #39ff14;
+}
+.traffic-live-bar--err .traffic-live-dot {
+ background: #ff4d4d;
+ box-shadow: 0 0 8px rgba(255, 77, 77, 0.55);
+ animation: none;
+}
+.traffic-live-bar--err #traffic-live-status {
+ color: #ff6b6b;
+}
+@keyframes traffic-live-pulse {
+ 0%, 100% { opacity: 1; transform: scale(1); }
+ 50% { opacity: 0.45; transform: scale(0.85); }
+}
+body.light-mode .traffic-live-dot {
+ background: #1a7a12;
+ box-shadow: none;
+}
+body.light-mode .traffic-live-bar--err .traffic-live-dot {
+ background: #c0392b;
+}
+
+.traffic-kpi-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+ gap: var(--space-sm);
+ margin: 0 0 var(--space-lg);
+}
+.traffic-kpi-val.traffic-kpi-flash {
+ animation: traffic-kpi-pop 0.55s ease;
+}
+@keyframes traffic-kpi-pop {
+ 0% { transform: scale(1); }
+ 35% { transform: scale(1.12); filter: brightness(1.35); }
+ 100% { transform: scale(1); }
+}
+.traffic-kpi {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-md);
+ background: #0a0000;
+ text-align: center;
+}
+.traffic-kpi-val {
+ display: block;
+ font-family: var(--font-mono, ui-monospace, monospace);
+ font-size: 1.6rem;
+ font-weight: 700;
+ color: #39ff14;
+ text-shadow: 0 0 8px rgba(57, 255, 20, .25);
+ line-height: 1.2;
+}
+.traffic-kpi-label {
+ display: block;
+ margin-top: 4px;
+ font-size: var(--fs-xs);
+ text-transform: uppercase;
+ letter-spacing: .06em;
+ color: var(--muted);
+}
+.traffic-kpi--bots {
+ border-color: rgba(255, 159, 26, 0.45);
+}
+.traffic-kpi--bots .traffic-kpi-val {
+ color: #ff9f1a;
+ text-shadow: 0 0 8px rgba(255, 159, 26, .3);
+}
+.traffic-row--bot {
+ opacity: 0.78;
+}
+.traffic-badge--bot {
+ background: rgba(255, 159, 26, 0.18);
+ color: #ff9f1a;
+ border: 1px solid rgba(255, 159, 26, 0.4);
+}
+body.light-mode .traffic-kpi--bots .traffic-kpi-val {
+ color: #a65c00;
+ text-shadow: none;
+}
+body.light-mode .traffic-badge--bot {
+ background: rgba(166, 92, 0, 0.1);
+ color: #a65c00;
+ border-color: rgba(166, 92, 0, 0.35);
+}
+.traffic-chart-panel .panel-body {
+ padding-bottom: var(--space-sm);
+}
+.traffic-chart {
+ display: block;
+ width: 100%;
+ max-width: 100%;
+ height: auto;
+ background: #050000;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+}
+.traffic-chart-legend {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ margin-top: var(--space-sm);
+ font-size: var(--fs-xs);
+ color: var(--muted);
+ font-family: var(--font-mono, ui-monospace, monospace);
+}
+.traffic-swatch {
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ border-radius: 2px;
+ margin-right: 4px;
+ vertical-align: -1px;
+}
+.traffic-swatch--hits { background: #39ff14; box-shadow: 0 0 6px rgba(57,255,20,.4); }
+.traffic-swatch--uniq { background: #5b9dff; box-shadow: 0 0 6px rgba(91,157,255,.35); }
+.traffic-swatch--bots { background: #ff9f1a; box-shadow: 0 0 6px rgba(255,159,26,.4); }
+.traffic-chart-row { margin-bottom: var(--space-md); }
+body.light-mode .traffic-swatch--bots { background: #a65c00; box-shadow: none; }
+
+body.light-mode .traffic-kpi {
+ background: #f8fbfd;
+}
+body.light-mode .traffic-kpi-val {
+ color: #1a7a12;
+ text-shadow: none;
+}
+body.light-mode .traffic-chart {
+ background: #f4f7fa;
+}
diff --git a/assets/css/pages/articles-2023-04-10.css b/assets/css/pages/articles-2023-04-10.css
new file mode 100644
index 0000000..1818de4
--- /dev/null
+++ b/assets/css/pages/articles-2023-04-10.css
@@ -0,0 +1 @@
+kbd{ border:1px solid var(--border); border-radius:4px; padding:0 6px; background: #fff; }
diff --git a/assets/css/pages/articles-2023-04-10_jp.css b/assets/css/pages/articles-2023-04-10_jp.css
new file mode 100644
index 0000000..1818de4
--- /dev/null
+++ b/assets/css/pages/articles-2023-04-10_jp.css
@@ -0,0 +1 @@
+kbd{ border:1px solid var(--border); border-radius:4px; padding:0 6px; background: #fff; }
diff --git a/assets/css/pages/articles-2023-04-10_zh.css b/assets/css/pages/articles-2023-04-10_zh.css
new file mode 100644
index 0000000..1818de4
--- /dev/null
+++ b/assets/css/pages/articles-2023-04-10_zh.css
@@ -0,0 +1 @@
+kbd{ border:1px solid var(--border); border-radius:4px; padding:0 6px; background: #fff; }
diff --git a/assets/css/pages/articles-runescape.css b/assets/css/pages/articles-runescape.css
new file mode 100644
index 0000000..1818de4
--- /dev/null
+++ b/assets/css/pages/articles-runescape.css
@@ -0,0 +1 @@
+kbd{ border:1px solid var(--border); border-radius:4px; padding:0 6px; background: #fff; }
diff --git a/assets/css/pages/articles-runescape_jp.css b/assets/css/pages/articles-runescape_jp.css
new file mode 100644
index 0000000..1818de4
--- /dev/null
+++ b/assets/css/pages/articles-runescape_jp.css
@@ -0,0 +1 @@
+kbd{ border:1px solid var(--border); border-radius:4px; padding:0 6px; background: #fff; }
diff --git a/assets/css/pages/articles-runescape_zh.css b/assets/css/pages/articles-runescape_zh.css
new file mode 100644
index 0000000..1818de4
--- /dev/null
+++ b/assets/css/pages/articles-runescape_zh.css
@@ -0,0 +1 @@
+kbd{ border:1px solid var(--border); border-radius:4px; padding:0 6px; background: #fff; }
diff --git a/assets/css/pages/bookmarks.css b/assets/css/pages/bookmarks.css
new file mode 100644
index 0000000..7cb6ccf
--- /dev/null
+++ b/assets/css/pages/bookmarks.css
@@ -0,0 +1,85 @@
+.bookmarks-title {
+ margin-bottom: var(--space-md);
+}
+
+.tree {
+ list-style-type: none;
+ padding-left: 0;
+ margin: 0;
+}
+
+.tree ul {
+ list-style-type: none;
+ padding-left: 28px;
+ margin: 5px 0;
+}
+
+.tree li {
+ margin: 4px 0;
+ position: relative;
+}
+
+.tree summary {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ padding: 8px 12px;
+ transition: background-color 0.2s ease;
+ font-weight: 600;
+ outline: none;
+ user-select: none;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+}
+
+.tree summary:hover {
+ background-color: #2a1010;
+ border-color: var(--accent);
+}
+
+.tree summary::-webkit-details-marker {
+ display: none;
+}
+
+.tree summary::before {
+ content: ">";
+ color: var(--ink);
+ margin-right: 10px;
+ font-size: 1rem;
+ transition: all 0.2s ease;
+ width: 20px;
+ text-align: left;
+ font-weight: 700;
+}
+
+.tree details[open]>summary::before {
+ content: ">";
+ transform: rotate(90deg);
+}
+
+.tree a {
+ color: var(--ink);
+ text-decoration: none;
+ display: inline-flex;
+ align-items: center;
+ padding: 6px 6px;
+ border-radius: 4px;
+}
+
+.tree a:hover {
+ background: var(--accent);
+ color: #000 !important;
+}
+
+
+.tree .navbar-file a {
+ color: var(--muted);
+}
+
+.tree .navbar-file a::before {
+ content: "->";
+ color: var(--muted);
+ margin-right: 10px;
+ font-size: 0.9rem;
+} \ No newline at end of file
diff --git a/assets/css/pages/changelog.css b/assets/css/pages/changelog.css
new file mode 100644
index 0000000..18633fd
--- /dev/null
+++ b/assets/css/pages/changelog.css
@@ -0,0 +1,25 @@
+.changelog-list{ list-style: none; padding: 0; margin: 0; }
+
+.changelog-entry{
+ padding: var(--space-md) 0;
+ border-bottom: 1px dotted rgba(0,0,0,.25);
+}
+
+.changelog-entry:last-child{ border-bottom: none; }
+
+.changelog-entry time{ font-weight: 700; color: var(--ink); white-space: nowrap; display: block; margin-bottom: var(--space-xs); }
+
+.changelog-entry .text{ color: var(--muted); }
+
+.changelog-more{
+ display: inline-block;
+ margin-top: var(--space-xs);
+ font-size: 13px;
+ white-space: nowrap;
+}
+
+hr {
+ border: none;
+ border-top: 1px dotted var(--border);
+ margin: var(--space-lg) 0;
+}
diff --git a/assets/css/pages/collection.css b/assets/css/pages/collection.css
new file mode 100644
index 0000000..8f3c81a
--- /dev/null
+++ b/assets/css/pages/collection.css
@@ -0,0 +1,290 @@
+/* ==========================================================================
+ Game collection catalog
+ Layout pattern: personal small-web inventory (photo | name :: model | specs)
+ Visual system: SillyLaird crimson / mono — not a third-party theme clone.
+ ========================================================================== */
+
+body.gc-page {
+ font-family: var(--font-mono);
+}
+
+.gc-wrap {
+ max-width: 52rem;
+}
+
+/* Big centered page title (catalog feel, site colors) */
+.gc-title {
+ font-family: var(--font-mono);
+ font-size: clamp(1.75rem, 1.2rem + 3vw, 2.75rem);
+ font-weight: 700;
+ text-align: center;
+ color: var(--ink);
+ letter-spacing: 0.02em;
+ margin: var(--space-md) 0 var(--space-sm);
+ line-height: 1.15;
+}
+
+/* COMPUTERS / PHONES ⁜ WISHLIST style menu */
+.gc-menu {
+ text-align: center;
+ margin: 0 0 var(--space-2xl);
+ font-size: var(--fs-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--muted);
+ line-height: 1.8;
+}
+
+.gc-menu a {
+ color: var(--muted);
+ text-decoration: none;
+ padding: 0 2px;
+}
+
+.gc-menu a:hover,
+.gc-menu a:focus-visible {
+ color: var(--accent-bright);
+}
+
+.gc-menu a.gc-menu-active,
+.gc-menu a#gc-main {
+ color: var(--ink);
+ font-style: italic;
+}
+
+.gc-menu-sep {
+ margin: 0 1px;
+ color: var(--muted);
+}
+
+.gc-menu-diamond {
+ margin: 0 0.45rem;
+ color: var(--border);
+ font-style: normal;
+}
+
+.gc-page-blurb {
+ text-align: center;
+ color: var(--muted);
+ font-size: var(--fs-sm);
+ margin: 0 auto var(--space-xl);
+ max-width: 34rem;
+}
+
+.gc-count {
+ text-align: center;
+ color: var(--muted);
+ font-size: var(--fs-xs);
+ margin: 0 0 var(--space-lg);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+/* Table-like catalog stack (centered inventory) */
+.gc-catalog {
+ display: table;
+ width: 100%;
+ margin: 0 auto;
+ border-collapse: separate;
+ border-spacing: 0 var(--space-md);
+}
+
+.gc-row {
+ display: table-row;
+}
+
+.gc-pic,
+.gc-body {
+ display: table-cell;
+ vertical-align: middle;
+}
+
+.gc-pic {
+ width: 1%;
+ padding-right: var(--space-lg);
+ white-space: nowrap;
+}
+
+.gc-pic a {
+ display: inline-block;
+ line-height: 0;
+}
+
+.gc-pic img {
+ display: block;
+ width: auto;
+ max-width: 13rem;
+ max-height: 10rem;
+ height: auto;
+ border: 1px solid var(--border);
+ background: var(--surface-deep);
+}
+
+.gc-pic-empty {
+ width: 10rem;
+ height: 7rem;
+ border: 1px dashed var(--border);
+ background:
+ radial-gradient(ellipse at 30% 20%, color-mix(in srgb, var(--accent) 12%, transparent), transparent 55%),
+ var(--surface-deep);
+ color: var(--muted);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: var(--fs-sm);
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ opacity: 0.85;
+}
+
+.gc-body {
+ width: 99%;
+ padding: 2px 0;
+}
+
+/* Large display name: left :: right */
+.gc-name {
+ font-family: var(--font-mono);
+ font-size: clamp(1.15rem, 1rem + 0.9vw, 1.65rem);
+ font-weight: 700;
+ color: var(--gold);
+ margin: 0 0 var(--space-xs);
+ line-height: 1.2;
+ word-break: break-word;
+}
+
+.gc-name a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.gc-name a:hover,
+.gc-name a:focus-visible {
+ color: var(--accent-bright);
+ text-decoration: underline;
+}
+
+.gc-colon {
+ color: var(--muted);
+ font-weight: 400;
+ margin: 0 0.28em;
+}
+
+.gc-note {
+ margin: 0 0 var(--space-xs);
+ font-size: var(--fs-sm);
+ color: var(--muted);
+ font-style: italic;
+}
+
+.gc-note em {
+ font-style: italic;
+}
+
+/* Inline “icon + value” specs (like arch / cpu / ram lines) */
+.gc-specs {
+ margin: 0;
+ font-size: var(--fs-sm);
+ color: var(--ink);
+ line-height: 1.7;
+}
+
+.gc-spec {
+ display: inline;
+ white-space: nowrap;
+ margin-right: 0.65rem;
+}
+
+.gc-spec-ico {
+ display: inline-block;
+ min-width: 1.1em;
+ text-align: center;
+ font-size: 0.75em;
+ font-weight: 700;
+ color: var(--bg);
+ background: var(--ink);
+ border-radius: 2px;
+ padding: 0 0.28em;
+ margin-right: 0.25em;
+ vertical-align: 0.05em;
+ line-height: 1.35;
+}
+
+.gc-spec-val {
+ color: var(--ink);
+}
+
+a.gc-spec-val {
+ color: var(--accent-bright);
+ text-decoration: none;
+}
+
+a.gc-spec-val:hover,
+a.gc-spec-val:focus-visible {
+ text-decoration: underline;
+}
+
+.gc-empty {
+ text-align: center;
+ color: var(--muted);
+ font-size: var(--fs-sm);
+ border: 1px dashed var(--border);
+ padding: var(--space-xl);
+ margin: var(--space-lg) auto;
+ max-width: 28rem;
+}
+
+.gc-footer-hint {
+ text-align: center;
+ margin: var(--space-2xl) 0 var(--space-md);
+ font-size: var(--fs-xs);
+ color: var(--muted);
+}
+
+.gc-footer-hint a {
+ color: var(--muted);
+}
+
+/* Mobile: stack photo above body (table → block) */
+@media (max-width: 640px) {
+ .gc-catalog,
+ .gc-row,
+ .gc-pic,
+ .gc-body {
+ display: block;
+ width: 100%;
+ }
+
+ .gc-row {
+ margin-bottom: var(--space-xl);
+ padding-bottom: var(--space-lg);
+ border-bottom: 1px solid var(--border);
+ }
+
+ .gc-pic {
+ padding-right: 0;
+ margin-bottom: var(--space-sm);
+ }
+
+ .gc-pic img,
+ .gc-pic-empty {
+ max-width: 100%;
+ width: min(100%, 16rem);
+ }
+
+ .gc-spec {
+ display: inline-block;
+ margin-bottom: 0.15rem;
+ }
+}
+
+/* Light mode: keep readable contrast */
+body.light-mode .gc-spec-ico {
+ color: #fff;
+ background: var(--ink);
+}
+
+body.light-mode .gc-pic img,
+body.light-mode .gc-pic-empty {
+ background: #f6f0f0;
+}
diff --git a/assets/css/pages/computers.css b/assets/css/pages/computers.css
new file mode 100644
index 0000000..2b0ff38
--- /dev/null
+++ b/assets/css/pages/computers.css
@@ -0,0 +1,102 @@
+.machine{
+ border: 1px solid var(--ink);
+ padding: var(--space-md);
+ margin-top: var(--space-md);
+ background: var(--bg);
+}
+
+.machine-info{ font-size: var(--fs-base); }
+
+.machine-title{
+ color: var(--gold);
+ font-weight: 700;
+ font-size: var(--fs-lg);
+ margin-bottom: var(--space-xs);
+}
+
+.machine--notes {
+ border-color: color-mix(in srgb, var(--accent-cyan) 42%, var(--border));
+ background: linear-gradient(90deg, color-mix(in srgb, var(--accent-cyan) 7%, var(--bg)), var(--bg) 45%);
+}
+
+.colon-nav {
+ font-size: var(--fs-base);
+ margin-top: var(--space-sm);
+}
+
+.colon-nav strong {
+ color: var(--gold);
+}
+
+.machine-row {
+ display: flex;
+ gap: var(--space-md);
+ align-items: flex-start;
+ flex-wrap: wrap;
+}
+
+.machine-row .machine-thumb {
+ flex: 0 0 auto;
+ width: 96px;
+ height: 96px;
+ object-fit: contain;
+ border: 1px solid var(--border);
+ background: var(--bg);
+ padding: var(--space-xs);
+}
+
+.machine-row .machine-facts {
+ flex: 1 1 240px;
+}
+
+.machine-title--sub {
+ margin-top: var(--space-sm);
+}
+
+.placeholder-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+ gap: var(--space-md);
+}
+
+.placeholder-box {
+ border: 1px dashed var(--border);
+ border-radius: var(--r);
+ padding: var(--space-md);
+ text-align: center;
+ color: var(--muted);
+ font-size: var(--fs-sm);
+ min-height: 120px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.rs-shot {
+ margin: 0;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ overflow: hidden;
+ background: var(--bg);
+}
+
+.rs-shot img {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+.rs-shot figcaption {
+ padding: var(--space-xs) var(--space-sm);
+ font-size: var(--fs-sm);
+}
+
+.rs-era-banner {
+ display: block;
+ width: 100%;
+ max-height: 220px;
+ object-fit: cover;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ margin: var(--space-sm) 0 var(--space-md);
+}
diff --git a/assets/css/pages/guestbook-form.css b/assets/css/pages/guestbook-form.css
new file mode 100644
index 0000000..8ff7d9f
--- /dev/null
+++ b/assets/css/pages/guestbook-form.css
@@ -0,0 +1,58 @@
+body {
+ background: #0a0a0a !important;
+ margin: 0;
+ padding: 10px;
+ font-family: "JetBrains Mono", "IBM Plex Mono", "Fira Code", "Source Code Pro", monospace;
+ color: #e0e0e0 !important;
+ overflow-y: auto;
+ overflow-x: hidden;
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+}
+body::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+}
+
+form {
+ max-width: 900px;
+ margin: 0 auto 20px;
+ background: #0a0a0a !important;
+}
+
+form input,
+form textarea {
+ background: #111 !important;
+ border: 1px solid #333 !important;
+ color: #e0e0e0 !important;
+ font: inherit;
+ box-sizing: border-box;
+ margin-bottom: 10px;
+ width: 100%;
+ padding: 8px;
+}
+
+form input::placeholder,
+form textarea::placeholder {
+ color: #666 !important;
+}
+
+form input:focus,
+form textarea:focus {
+ outline: none;
+ border-color: #ff4444 !important;
+}
+
+form button {
+ background: #ff4444 !important;
+ border: none !important;
+ padding: 12px 18px;
+ border-radius: 4px;
+ cursor: pointer;
+ font: inherit;
+ color: #fff !important;
+ font-weight: bold;
+}
+form button:hover {
+ background: #cc0000 !important;
+}
diff --git a/assets/css/pages/guestbook_jp.css b/assets/css/pages/guestbook_jp.css
new file mode 100644
index 0000000..c4da6e4
--- /dev/null
+++ b/assets/css/pages/guestbook_jp.css
@@ -0,0 +1,79 @@
+.anonymous {
+ color: var(--accent) !important;
+}
+
+.error {
+ color: var(--accent);
+}
+
+.captcha {
+ font-weight: 700;
+ font-size: 20px;
+}
+
+form {
+ display: grid;
+ gap: var(--space-sm);
+ max-width: 640px;
+}
+
+label {
+ font-weight: 600;
+}
+
+input[type="text"],
+textarea {
+ width: 100%;
+ padding: 10px;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--bg);
+ color: var(--accent) !important;
+ font: inherit;
+}
+
+textarea {
+ min-height: 120px;
+ resize: vertical;
+}
+
+.btn-row {
+ display: flex;
+ gap: var(--space-sm);
+ flex-wrap: wrap;
+}
+
+button {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--bg);
+ padding: 8px 12px;
+ cursor: pointer;
+ font: inherit;
+}
+
+button:hover {
+ background: #fdd;
+ border-color: var(--accent);
+}
+
+.entry {
+ border-top: 1px dotted rgba(0, 0, 0, .25);
+ padding-top: var(--space-md);
+ margin-top: var(--space-md);
+}
+
+.entry h4 {
+ margin: 0 0 var(--space-xs) 0;
+}
+
+.entry p {
+ margin: 0;
+ color: var(--muted);
+}
+
+#captcha-canvas {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: #fff;
+} \ No newline at end of file
diff --git a/assets/css/pages/guestbook_zh.css b/assets/css/pages/guestbook_zh.css
new file mode 100644
index 0000000..c4da6e4
--- /dev/null
+++ b/assets/css/pages/guestbook_zh.css
@@ -0,0 +1,79 @@
+.anonymous {
+ color: var(--accent) !important;
+}
+
+.error {
+ color: var(--accent);
+}
+
+.captcha {
+ font-weight: 700;
+ font-size: 20px;
+}
+
+form {
+ display: grid;
+ gap: var(--space-sm);
+ max-width: 640px;
+}
+
+label {
+ font-weight: 600;
+}
+
+input[type="text"],
+textarea {
+ width: 100%;
+ padding: 10px;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--bg);
+ color: var(--accent) !important;
+ font: inherit;
+}
+
+textarea {
+ min-height: 120px;
+ resize: vertical;
+}
+
+.btn-row {
+ display: flex;
+ gap: var(--space-sm);
+ flex-wrap: wrap;
+}
+
+button {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--bg);
+ padding: 8px 12px;
+ cursor: pointer;
+ font: inherit;
+}
+
+button:hover {
+ background: #fdd;
+ border-color: var(--accent);
+}
+
+.entry {
+ border-top: 1px dotted rgba(0, 0, 0, .25);
+ padding-top: var(--space-md);
+ margin-top: var(--space-md);
+}
+
+.entry h4 {
+ margin: 0 0 var(--space-xs) 0;
+}
+
+.entry p {
+ margin: 0;
+ color: var(--muted);
+}
+
+#captcha-canvas {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: #fff;
+} \ No newline at end of file
diff --git a/assets/css/pages/hitcounter.css b/assets/css/pages/hitcounter.css
new file mode 100644
index 0000000..1c52da1
--- /dev/null
+++ b/assets/css/pages/hitcounter.css
@@ -0,0 +1,7 @@
+.counter {
+ display: grid;
+ gap: var(--space-sm);
+ justify-items: center;
+ text-align: center;
+ }
+ .big { font-size: 3rem; font-weight: 800; color: var(--gold); }
diff --git a/assets/css/pages/journal-index.css b/assets/css/pages/journal-index.css
new file mode 100644
index 0000000..02e8c42
--- /dev/null
+++ b/assets/css/pages/journal-index.css
@@ -0,0 +1,3 @@
+.entry { margin: var(--space-md) 0; }
+ .entry time { font-weight: 700; color: var(--ink); }
+ .entry p { margin: var(--space-xs) 0 0 0; color: var(--muted); }
diff --git a/assets/css/pages/journal-index_jp.css b/assets/css/pages/journal-index_jp.css
new file mode 100644
index 0000000..02e8c42
--- /dev/null
+++ b/assets/css/pages/journal-index_jp.css
@@ -0,0 +1,3 @@
+.entry { margin: var(--space-md) 0; }
+ .entry time { font-weight: 700; color: var(--ink); }
+ .entry p { margin: var(--space-xs) 0 0 0; color: var(--muted); }
diff --git a/assets/css/pages/journal-index_zh.css b/assets/css/pages/journal-index_zh.css
new file mode 100644
index 0000000..02e8c42
--- /dev/null
+++ b/assets/css/pages/journal-index_zh.css
@@ -0,0 +1,3 @@
+.entry { margin: var(--space-md) 0; }
+ .entry time { font-weight: 700; color: var(--ink); }
+ .entry p { margin: var(--space-xs) 0 0 0; color: var(--muted); }
diff --git a/assets/css/pages/links.css b/assets/css/pages/links.css
new file mode 100644
index 0000000..d0a6c8b
--- /dev/null
+++ b/assets/css/pages/links.css
@@ -0,0 +1,22 @@
+.buttons img { image-rendering: auto; }
+ .buttons a { padding: 0; background: transparent; }
+ .buttons a:hover { background: transparent; }
+/* Link hub: card-based grouping. Mirrors .start-here inset card. */
+.link-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
+ gap: var(--space-md);
+ margin: var(--space-md) 0 var(--space-lg);
+}
+.link-card {
+ border: 1px solid var(--border);
+ border-left: 3px solid var(--accent);
+ border-radius: var(--r);
+ padding: var(--space-md) var(--space-lg);
+ background: linear-gradient(180deg, rgba(var(--glow-color), .04), transparent 70%);
+}
+.link-card-title { margin: 0 0 var(--space-sm) !important; font-size: var(--fs-lg); }
+.link-card-list {
+ list-style: none; padding: 0; margin: 0;
+ display: grid; gap: var(--space-xs); line-height: 1.5;
+}
diff --git a/assets/css/pages/links_jp.css b/assets/css/pages/links_jp.css
new file mode 100644
index 0000000..d0a6c8b
--- /dev/null
+++ b/assets/css/pages/links_jp.css
@@ -0,0 +1,22 @@
+.buttons img { image-rendering: auto; }
+ .buttons a { padding: 0; background: transparent; }
+ .buttons a:hover { background: transparent; }
+/* Link hub: card-based grouping. Mirrors .start-here inset card. */
+.link-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
+ gap: var(--space-md);
+ margin: var(--space-md) 0 var(--space-lg);
+}
+.link-card {
+ border: 1px solid var(--border);
+ border-left: 3px solid var(--accent);
+ border-radius: var(--r);
+ padding: var(--space-md) var(--space-lg);
+ background: linear-gradient(180deg, rgba(var(--glow-color), .04), transparent 70%);
+}
+.link-card-title { margin: 0 0 var(--space-sm) !important; font-size: var(--fs-lg); }
+.link-card-list {
+ list-style: none; padding: 0; margin: 0;
+ display: grid; gap: var(--space-xs); line-height: 1.5;
+}
diff --git a/assets/css/pages/links_zh.css b/assets/css/pages/links_zh.css
new file mode 100644
index 0000000..d0a6c8b
--- /dev/null
+++ b/assets/css/pages/links_zh.css
@@ -0,0 +1,22 @@
+.buttons img { image-rendering: auto; }
+ .buttons a { padding: 0; background: transparent; }
+ .buttons a:hover { background: transparent; }
+/* Link hub: card-based grouping. Mirrors .start-here inset card. */
+.link-cards {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
+ gap: var(--space-md);
+ margin: var(--space-md) 0 var(--space-lg);
+}
+.link-card {
+ border: 1px solid var(--border);
+ border-left: 3px solid var(--accent);
+ border-radius: var(--r);
+ padding: var(--space-md) var(--space-lg);
+ background: linear-gradient(180deg, rgba(var(--glow-color), .04), transparent 70%);
+}
+.link-card-title { margin: 0 0 var(--space-sm) !important; font-size: var(--fs-lg); }
+.link-card-list {
+ list-style: none; padding: 0; margin: 0;
+ display: grid; gap: var(--space-xs); line-height: 1.5;
+}
diff --git a/assets/css/pages/motd.css b/assets/css/pages/motd.css
new file mode 100644
index 0000000..8a30bad
--- /dev/null
+++ b/assets/css/pages/motd.css
@@ -0,0 +1,51 @@
+/* MOTD — same inner-page language as Computers / Journal / ChangeLog. */
+
+.motd-current {
+ border: 1px solid var(--ink);
+ padding: var(--space-md);
+ margin-top: var(--space-md);
+ background: var(--bg);
+}
+
+.motd-heading {
+ color: var(--gold);
+ font-weight: 700;
+ font-size: var(--fs-lg);
+ margin: 0 0 var(--space-xs);
+}
+
+.motd-current .muted {
+ margin: 0;
+}
+
+.motd-body {
+ margin: var(--space-sm) 0 0;
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ max-width: 68ch;
+ line-height: 1.65;
+}
+
+.entry {
+ margin: var(--space-md) 0;
+}
+
+.entry time {
+ font-weight: 700;
+ color: var(--ink);
+ display: block;
+ margin-bottom: var(--space-xs);
+}
+
+.entry p {
+ margin: 0;
+ color: var(--muted);
+ white-space: pre-wrap;
+ overflow-wrap: anywhere;
+ max-width: 68ch;
+}
+
+.entry p strong {
+ color: var(--ink);
+ margin-right: 0.35em;
+}
diff --git a/assets/css/pages/mstartpage-index.css b/assets/css/pages/mstartpage-index.css
new file mode 100644
index 0000000..02f0e9b
--- /dev/null
+++ b/assets/css/pages/mstartpage-index.css
@@ -0,0 +1,341 @@
+:root {
+ --main-text-color: #ddd;
+ --border-color: var(--border, #2a1010);
+ --search-input-color: black;
+ --link-color: #FF0000;
+ --background-color: #000;
+ --menu-background: #111;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ background-color: var(--background-color);
+ font-family: 'Source Code Pro', "MS Gothic", IPAMonaGothic, IPAGothic, "BIZ UDGothic", HGGothicM, "Noto Sans Mono CJK JP", "Noto Sans Mono", monospace;
+ font-size: 14px;
+ color: var(--main-text-color);
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
+ overflow-x: hidden;
+}
+
+a {
+ color: var(--link-color);
+ background-color: transparent;
+ text-decoration: none;
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+/* Layout Overrides for Unified Header */
+.header-inner {
+ margin: 0 !important;
+ /* Left aligned */
+ padding-left: 10px !important;
+}
+
+/* Main menu fallback (if any) */
+.main-menu {}
+
+.main-menu.show {
+ display: flex;
+}
+
+.main-menu a {
+ padding: 10px;
+ border-bottom: 1px solid #333;
+}
+
+.main-menu a:last-child {
+ border-bottom: none;
+}
+
+/* Search box */
+.search-container {
+ width: 100%;
+ padding: 10px;
+ background-color: var(--background-color);
+ margin-bottom: 15px;
+}
+
+.search-form {
+ display: flex;
+ flex-direction: column;
+}
+
+.search-input {
+ color: var(--link-color);
+ background-color: #3A3A3A;
+ border: 1px solid var(--link-color);
+ padding: 12px;
+ margin-bottom: 10px;
+ text-align: center;
+ border-radius: 4px;
+ font-size: 16px;
+ /* Larger for touch */
+}
+
+input[type="submit"] {
+ color: var(--link-color);
+ background-color: black;
+ padding: 12px;
+ border: 1px solid var(--link-color);
+ cursor: pointer;
+ border-radius: 4px;
+ font-weight: bold;
+ font-size: 16px;
+}
+
+/* Content sections */
+.content-section {
+ margin-bottom: 20px;
+ padding: 0 10px;
+}
+
+.section-title {
+ color: var(--link-color);
+ font-size: 1.2em;
+ border-bottom: 1px solid var(--border-color);
+ padding-bottom: 5px;
+ margin-bottom: 10px;
+}
+
+.link-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 10px;
+}
+
+.link-item {
+ padding: 10px;
+ background-color: #1a1a1a;
+ border: 1px solid #333;
+ border-radius: 4px;
+ text-align: center;
+}
+
+/* Gallery items for mobile */
+.gallery-container {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ gap: 15px;
+ padding: 10px;
+}
+
+.gallery-item {
+ border: 2px ridge var(--border-color);
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.gallery-item:hover {
+ border: 2px ridge var(--link-color);
+}
+
+.gallery-item img {
+ width: 100%;
+ height: auto;
+ display: block;
+}
+
+.gallery-desc {
+ padding: 8px;
+ text-align: center;
+ font-size: 0.9em;
+ color: var(--link-color);
+ background-color: #1a1a1a;
+}
+
+/* Date and time */
+.datetime {
+ padding: 10px;
+ text-align: center;
+ margin: 15px 0;
+}
+
+#realtime-date,
+#realtime-clock {
+ color: var(--link-color);
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
+ margin: 5px 0;
+}
+
+/* Footer */
+.footer {
+ padding: 15px;
+ text-align: center;
+ border-top: 1px solid #333;
+ margin-top: 20px;
+}
+
+.footer-links {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 10px;
+}
+
+.footer-links a {
+ padding: 5px;
+}
+
+/* Collapsible sections */
+.collapsible {
+ background-color: #1a1a1a;
+ color: var(--link-color);
+ cursor: pointer;
+ padding: 12px;
+ width: 100%;
+ border: 1px solid var(--border-color);
+ text-align: left;
+ outline: none;
+ font-size: 1em;
+ font-weight: bold;
+ margin-bottom: 5px;
+ border-radius: 4px;
+}
+
+.active,
+.collapsible:hover {
+ background-color: #222;
+}
+
+.collapsible-content {
+ padding: 0 10px;
+ display: none;
+ overflow: hidden;
+ background-color: #1a1a1a;
+ margin-bottom: 10px;
+ border-radius: 0 0 4px 4px;
+}
+
+/* XXX Verification Modal */
+.xxx-modal {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.9);
+ z-index: 1000;
+ justify-content: center;
+ align-items: center;
+}
+
+.xxx-modal-content {
+ background-color: var(--menu-background);
+ padding: 20px;
+ border-radius: 8px;
+ border: 2px solid var(--link-color);
+ text-align: center;
+ max-width: 90%;
+ width: 350px;
+}
+
+.xxx-logo {
+ width: 80px;
+ height: 80px;
+ margin: 0 auto 15px;
+ background-color: #333;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 24px;
+ color: var(--link-color);
+ border: 2px solid var(--link-color);
+}
+
+.xxx-buttons {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 20px;
+}
+
+.xxx-button {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: bold;
+}
+
+.xxx-confirm {
+ background-color: var(--link-color);
+ color: white;
+}
+
+.xxx-deny {
+ background-color: #f44336;
+ color: white;
+}
+
+/* XXX Section */
+.xxx-section {
+ margin: 20px 0;
+ text-align: center;
+}
+
+.xxx-toggle {
+ padding: 12px 20px;
+ background-color: #333;
+ color: var(--link-color);
+ border: 1px solid var(--link-color);
+ border-radius: 4px;
+ cursor: pointer;
+ font-weight: bold;
+ margin-bottom: 15px;
+}
+
+.xxx-list {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ background-color: var(--menu-background);
+ border: 1px solid var(--link-color);
+ border-radius: 8px;
+ padding: 10px;
+ max-width: 200px;
+ display: none;
+ z-index: 999;
+}
+
+.xxx-list-title {
+ color: var(--link-color);
+ font-weight: bold;
+ margin-bottom: 8px;
+ text-align: center;
+ border-bottom: 1px solid #444;
+ padding-bottom: 5px;
+}
+
+.xxx-list-item {
+ margin: 5px 0;
+ text-align: center;
+}
+
+/* Responsive adjustments */
+@media (min-width: 768px) {
+ body {
+ font-size: 16px;
+ max-width: 800px;
+ margin: 0 auto;
+ }
+
+ .link-grid {
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+ }
+
+ .gallery-container {
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+ }
+} \ No newline at end of file
diff --git a/assets/css/pages/vinyls.css b/assets/css/pages/vinyls.css
new file mode 100644
index 0000000..ecf8b4b
--- /dev/null
+++ b/assets/css/pages/vinyls.css
@@ -0,0 +1,14 @@
+.vinyl-grid{
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: var(--space-md);
+ margin-top: var(--space-md);
+}
+
+.vinyl-grid img{
+ width: 100%;
+ height: auto;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--bg);
+}
diff --git a/assets/css/scroll-to-top.css b/assets/css/scroll-to-top.css
new file mode 100644
index 0000000..e538251
--- /dev/null
+++ b/assets/css/scroll-to-top.css
@@ -0,0 +1,162 @@
+/* ==========================================================================
+ scroll-to-top.css — floating "back to top" control
+ Loaded by assets/js/scroll-to-top.js on www and sibling *.sillylaird.ca hosts.
+ Self-contained so sibling pages that do not load site.css still look right.
+ Selectors are more specific than site.css's `a:hover` so the global link
+ glow / scale does not leak onto the control.
+ ========================================================================== */
+
+a.scroll-to-top {
+ --stt-bg: var(--bg, #000000);
+ --stt-ink: var(--gold, #8b0000);
+ --stt-border: var(--border, #3a0000);
+ --stt-hover: var(--accent-cyan, #8b0000);
+ position: fixed;
+ right: max(0.85rem, env(safe-area-inset-right, 0px));
+ bottom: max(1.1rem, env(safe-area-inset-bottom, 0px));
+ z-index: 10050;
+ box-sizing: border-box;
+ width: 2.75rem;
+ height: 2.75rem;
+ margin: 0;
+ padding: 0.2rem 0 0.15rem;
+ display: inline-flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 0.05rem;
+ border: 1px solid var(--stt-border);
+ border-radius: 6px;
+ background: var(--stt-bg);
+ color: var(--stt-ink);
+ font-family: "JetBrains Mono", "IBM Plex Mono", "Source Code Pro", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+ font-size: 0.58rem;
+ font-weight: 700;
+ letter-spacing: 0.14em;
+ line-height: 1;
+ text-decoration: none;
+ text-transform: uppercase;
+ text-shadow: none;
+ box-shadow:
+ 0 0 0 1px rgba(160, 0, 0, .18),
+ 0 0 14px -2px rgba(160, 0, 0, .4),
+ inset 0 0 10px rgba(160, 0, 0, .12);
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transform: translateY(10px);
+ transition:
+ opacity .2s ease,
+ visibility .2s ease,
+ transform .2s ease,
+ color .15s ease,
+ border-color .15s ease,
+ box-shadow .25s ease,
+ background-color .15s ease;
+ touch-action: manipulation;
+ -webkit-tap-highlight-color: transparent;
+}
+
+a.scroll-to-top.is-visible {
+ opacity: 1;
+ visibility: visible;
+ pointer-events: auto;
+ transform: none;
+}
+
+a.scroll-to-top .scroll-to-top-caret {
+ font-size: 0.85rem;
+ letter-spacing: 0;
+ line-height: 1;
+ transform: translateY(1px);
+}
+
+a.scroll-to-top .scroll-to-top-label {
+ font-size: 0.52rem;
+ letter-spacing: 0.16em;
+}
+
+a.scroll-to-top:hover,
+a.scroll-to-top:focus-visible {
+ background: var(--stt-bg);
+ color: var(--stt-hover);
+ text-decoration: none;
+ text-shadow:
+ 0 0 6px rgba(255, 0, 0, .85),
+ 0 0 14px rgba(139, 0, 0, .45);
+ border-color: rgba(255, 0, 0, .75);
+ border-radius: 6px;
+ transform: none;
+ box-shadow:
+ 0 0 0 1px rgba(255, 0, 0, .45),
+ 0 0 16px rgba(139, 0, 0, .4),
+ 0 0 32px -6px rgba(139, 0, 0, .35),
+ inset 0 0 10px rgba(139, 0, 0, .08);
+}
+
+a.scroll-to-top:focus-visible {
+ outline: 2px solid var(--stt-hover);
+ outline-offset: 2px;
+}
+
+a.scroll-to-top:active {
+ transform: translateY(1px);
+}
+
+/* Light theme — www uses body.light-mode; blog uses data-blog-theme */
+body.light-mode a.scroll-to-top,
+html[data-blog-theme="light"] a.scroll-to-top {
+ --stt-bg: #fff8f1;
+ --stt-ink: #8b0000;
+ --stt-border: #e8c8c8;
+ --stt-hover: #5a0000;
+ box-shadow:
+ 0 1px 0 rgba(255, 255, 255, .95) inset,
+ 0 8px 22px rgba(139, 0, 0, .16);
+}
+
+body.light-mode a.scroll-to-top:hover,
+body.light-mode a.scroll-to-top:focus-visible,
+html[data-blog-theme="light"] a.scroll-to-top:hover,
+html[data-blog-theme="light"] a.scroll-to-top:focus-visible {
+ background: #ffffff;
+ color: #5a0000;
+ text-shadow: none;
+ border-color: #8b0000;
+ box-shadow:
+ 0 0 0 1px rgba(139, 0, 0, .35),
+ 0 8px 22px rgba(139, 0, 0, .22);
+}
+
+/* When the visit-counter has no footer to live in, it docks bottom-right.
+ Lift it so the two chips do not sit on top of each other. */
+body.has-scroll-to-top #site-visit-counter[data-stt-docked="1"] {
+ bottom: 4.65rem;
+}
+
+@media (max-width: 520px) {
+ a.scroll-to-top {
+ width: 2.55rem;
+ height: 2.55rem;
+ right: max(0.65rem, env(safe-area-inset-right, 0px));
+ bottom: max(0.85rem, env(safe-area-inset-bottom, 0px));
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ a.scroll-to-top,
+ a.scroll-to-top.is-visible,
+ a.scroll-to-top:hover,
+ a.scroll-to-top:focus-visible,
+ a.scroll-to-top:active {
+ transition: none;
+ transform: none;
+ }
+}
+
+@media print {
+ a.scroll-to-top,
+ .footer-back-to-top {
+ display: none !important;
+ }
+}
diff --git a/assets/css/site.css b/assets/css/site.css
new file mode 100644
index 0000000..f71e2d7
--- /dev/null
+++ b/assets/css/site.css
@@ -0,0 +1,4854 @@
+/* ==========================================================================
+ site.css — SillyLaird global stylesheet
+ Sections: Variables, Reset, Layout, Typography, Header, Navigation,
+ Components, Pages, Footer, Utilities, Responsive, Theme
+ ========================================================================== */
+
+/* ==========================================================================
+ 1. Variables
+ ========================================================================== */
+:root {
+ --bg: #000000;
+ --surface-deep: #050505;
+ --ink: #8b0000;
+ --muted: #a87070;
+ --accent: #a00000;
+ --accent-bright: #ff1a1a;
+ --gold: #8b0000;
+ --border: #3a0000;
+ --shadow: rgba(0, 0, 0, .40);
+ --neon-cyan: #ff1a1a;
+ --neon-green: #39ff14;
+ --accent-cyan: #8b0000;
+ --accent-pink: #a00000;
+ --accent-magenta: #8b0000;
+ --accent-purple: #5a0000;
+ --font-mono: "JetBrains Mono", "IBM Plex Mono", "Fira Code", "Source Code Pro", ui-monospace, SFMono-Regular, "Cascadia Code", Menlo, Consolas, monospace;
+ --lh-body: 1.72;
+ --lh-comfort: 1.88;
+ --font-prose: Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, "Times New Roman", serif;
+ --link-glow: 255, 0, 0;
+ --btn-glow: 139, 0, 0;
+
+ /* Crimson glow stack — layered for atmospheric depth */
+ --glow-color: 160, 0, 0;
+ --glow-soft:
+ 0 0 0 1px rgba(var(--glow-color), .18),
+ 0 0 12px -2px rgba(var(--glow-color), .35),
+ 0 0 36px -8px rgba(var(--glow-color), .25);
+ --glow-strong:
+ 0 0 0 1px rgba(var(--glow-color), .55),
+ 0 0 6px rgba(var(--glow-color), .65),
+ 0 0 18px rgba(var(--glow-color), .55),
+ 0 0 48px rgba(var(--glow-color), .35),
+ inset 0 0 12px rgba(var(--glow-color), .18);
+
+ --wrap: 960px;
+ --r: 6px;
+
+ /* Unified spacing scale */
+ --space-xs: 0.25rem;
+ --space-sm: 0.5rem;
+ --space-md: 0.75rem;
+ --space-lg: 1rem;
+ --space-xl: 1.35rem;
+ --space-2xl: 1.75rem;
+ --space-3xl: 2.5rem;
+
+ /* Fluid typography scale (clamp: min, preferred, max) */
+ --fs-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.85rem);
+ --fs-sm: clamp(0.8rem, 0.75rem + 0.25vw, 0.9rem);
+ --fs-base: clamp(0.9rem, 0.85rem + 0.25vw, 1rem);
+ --fs-lg: clamp(0.95rem, 0.9rem + 0.25vw, 1.05rem);
+ --fs-xl: clamp(1.05rem, 1rem + 0.25vw, 1.15rem);
+ --fs-2xl: clamp(1.3rem, 1.15rem + 0.75vw, 1.5rem);
+ --fs-3xl: clamp(1.5rem, 1.3rem + 1vw, 1.8rem);
+}
+
+/* ==========================================================================
+ 2. Reset
+ ========================================================================== */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ height: 100%;
+}
+
+body {
+ margin: 0;
+}
+
+/* ==========================================================================
+ 3. Base / Typography
+ ========================================================================== */
+body {
+ font-family: "Inter", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
+ line-height: var(--lh-body);
+ background: var(--bg);
+ color: var(--ink);
+ font-size: var(--fs-base);
+ overflow-x: hidden;
+ transition: background-color .35s ease, color .35s ease;
+}
+
+body.theme-transitioning,
+body.theme-transitioning *,
+body.theme-transitioning *::before,
+body.theme-transitioning *::after {
+ transition: background-color .35s ease, color .35s ease, border-color .35s ease, box-shadow .35s ease !important;
+}
+
+p {
+ margin: var(--space-sm) 0;
+ line-height: 1.6;
+}
+
+p:first-child {
+ margin-top: 0;
+}
+
+p:last-child {
+ margin-bottom: 0;
+}
+
+img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+}
+
+video {
+ display: block;
+}
+
+a {
+ color: var(--accent);
+ text-decoration: none;
+ transition: color .15s ease, background .15s ease, text-shadow .25s ease, box-shadow .25s ease;
+}
+
+a:hover,
+a:focus-visible {
+ background: color-mix(in srgb, var(--accent-cyan) 18%, var(--accent));
+ color: #ffd0d0;
+ text-shadow:
+ 0 0 4px rgba(var(--link-glow), .95),
+ 0 0 12px rgba(var(--link-glow), .7),
+ 0 0 24px rgba(var(--link-glow), .45);
+ box-shadow:
+ 0 0 0 1px rgba(var(--link-glow), .55),
+ 0 0 10px rgba(var(--link-glow), .5),
+ 0 0 24px -4px rgba(var(--link-glow), .4);
+ border-radius: 2px;
+ transform: scale(1.02);
+}
+
+/* External links get a persistent subtle glow as an affordance */
+a[href^="http"]:not([href*="sillylaird.ca"]):hover,
+a[href^="https"]:not([href*="sillylaird.ca"]):focus-visible {
+ text-shadow:
+ 0 0 6px rgba(var(--glow-color), 1),
+ 0 0 16px rgba(var(--glow-color), .75),
+ 0 0 32px rgba(var(--glow-color), .5);
+}
+
+/* Buttons styled as links shouldn't pick up the link box-shadow halo on top of their own */
+.header-button:hover,
+.header-button:focus-visible,
+#menu-toggle:hover,
+#menu-toggle:focus-visible {
+ text-shadow: none;
+}
+
+h1 {
+ margin: 0 0 var(--space-md) 0;
+ color: var(--gold);
+ font-weight: 700;
+ font-size: var(--fs-2xl);
+ letter-spacing: .2px;
+ line-height: 1.2;
+}
+
+h2 {
+ margin: 0 0 var(--space-md) 0;
+ color: var(--gold);
+ font-size: var(--fs-xl);
+ font-weight: 800;
+ letter-spacing: .1px;
+ line-height: 1.3;
+}
+
+blockquote {
+ margin: var(--space-md) 0 0 0;
+ padding-left: var(--space-md);
+ border-left: 3px solid var(--border);
+ color: var(--muted);
+ font-style: italic;
+ font-size: var(--fs-sm);
+ line-height: 1.6;
+}
+
+.muted {
+ color: var(--muted);
+ font-size: var(--fs-sm);
+}
+
+.footer-colophon {
+ max-width: 46rem;
+ margin: var(--space-sm) auto 0;
+ color: var(--muted);
+ font-size: var(--fs-xs);
+}
+
+/* Comic Sans headers (intentional retro style) */
+h1 {
+ font-family: 'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif;
+ font-size: clamp(1.5rem, 1.3rem + 1vw, 1.875rem);
+ padding-left: 40px;
+}
+
+pre {
+ font-family: 'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif;
+ font-size: 12px;
+ padding-left: 40px;
+}
+
+/* ==========================================================================
+ 4. Accessibility
+ ========================================================================== */
+.skip-link {
+ position: absolute;
+ left: -999px;
+ top: 0;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ padding: .5rem .75rem;
+ border-radius: var(--r);
+ z-index: 2000;
+}
+
+.skip-link:focus {
+ left: 1rem;
+ top: 1rem;
+}
+
+:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+/* ==========================================================================
+ 5. Layout
+ ========================================================================== */
+.wrap {
+ max-width: var(--wrap);
+ margin: 0 auto;
+ padding: var(--space-xl) var(--space-lg) var(--space-3xl);
+}
+
+.stack {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-xl);
+}
+
+.stack-small {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+}
+
+section {
+ position: relative;
+ margin: var(--space-xl) 0;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-lg);
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .06), transparent 70%),
+ var(--bg);
+ box-shadow: var(--glow-soft);
+ transition: box-shadow .35s ease, border-color .35s ease, transform .2s ease;
+}
+
+section:focus-within {
+ border-color: rgba(var(--glow-color), .28);
+}
+
+.site-status-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-sm);
+ align-items: center;
+ color: var(--muted);
+ font-size: var(--fs-sm);
+}
+
+.home-status-row {
+ margin: 0 0 var(--space-md);
+}
+
+.site-status-row span,
+.site-status-row a {
+ display: inline-flex;
+ align-items: center;
+ min-height: 28px;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: 2px var(--space-sm);
+ background: #070000;
+}
+
+.footer-status-row {
+ justify-content: center;
+}
+
+.featured-links {
+ display: grid;
+ grid-template-columns: repeat(6, minmax(0, 1fr));
+ gap: var(--space-sm);
+ margin: calc(-1 * var(--space-sm)) 0 var(--space-xl);
+}
+
+.featured-links a {
+ min-height: 42px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-sm);
+ background: #070000;
+ text-align: center;
+ font-size: var(--fs-sm);
+ font-weight: 700;
+}
+
+#contact {
+ padding: var(--space-md);
+}
+
+#contact blockquote,
+#contact .audio-wrap {
+ margin-top: var(--space-sm);
+}
+
+#contact .contact-list {
+ margin-top: var(--space-sm);
+}
+
+section:hover {
+ border-color: rgba(var(--glow-color), .55);
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .35),
+ 0 0 18px -4px rgba(var(--glow-color), .45),
+ 0 0 48px -8px rgba(var(--glow-color), .30);
+}
+
+/* ==========================================================================
+ 6. Header
+ ========================================================================== */
+header.site-header,
+footer.site-footer {
+ background: var(--bg);
+ border-bottom: 1px solid var(--border);
+}
+
+header.site-header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+}
+
+body:has(header.site-header) {
+ padding-top: var(--site-header-height, 76px);
+}
+
+.header-inner {
+ max-width: var(--wrap);
+ margin: 0 auto;
+ padding: var(--space-md) var(--space-lg);
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-lg);
+ flex-wrap: wrap;
+}
+
+.brand {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ min-width: 240px;
+}
+
+.logo {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--r);
+}
+
+.logo img {
+ height: 48px;
+ width: 48px;
+ object-fit: contain;
+ border: 2px solid var(--border);
+ border-radius: var(--r);
+ transition: border-color .25s ease, box-shadow .35s ease, transform .35s ease;
+}
+
+header.site-header .logo:hover img,
+header.site-header .logo:focus-visible img {
+ border-color: rgba(var(--glow-color), .9);
+ box-shadow: var(--glow-strong);
+ animation: logo-pulse 2.4s ease-in-out infinite;
+}
+
+@keyframes logo-pulse {
+ 0%, 100% {
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .55),
+ 0 0 6px rgba(var(--glow-color), .65),
+ 0 0 18px rgba(var(--glow-color), .55),
+ 0 0 48px rgba(var(--glow-color), .35),
+ inset 0 0 12px rgba(var(--glow-color), .18);
+ }
+ 50% {
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .8),
+ 0 0 10px rgba(var(--glow-color), .9),
+ 0 0 28px rgba(var(--glow-color), .75),
+ 0 0 64px rgba(var(--glow-color), .5),
+ inset 0 0 18px rgba(var(--glow-color), .28);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ header.site-header .logo:hover img,
+ header.site-header .logo:focus-visible img {
+ animation: none;
+ }
+}
+
+.brand-text {
+ display: flex;
+ flex-direction: column;
+ line-height: 1.2;
+}
+
+.brand-text strong {
+ font-size: var(--fs-lg);
+ color: var(--gold);
+ letter-spacing: .2px;
+}
+
+.brand-text span {
+ font-size: var(--fs-sm);
+ color: var(--muted);
+}
+
+.header-controls {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+}
+
+.header-button {
+ font-size: var(--fs-sm);
+ border: 1px solid var(--border);
+ padding: 4px 8px;
+ border-radius: var(--r);
+ color: var(--gold);
+ text-decoration: none;
+ background: var(--bg);
+ cursor: pointer;
+ font-family: inherit;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ transition: background .15s, color .15s, border-color .15s, box-shadow .25s;
+}
+
+.header-button:hover {
+ background: var(--accent);
+ color: var(--ink);
+ border-color: rgba(var(--glow-color), .9);
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .4),
+ 0 0 12px rgba(var(--glow-color), .55),
+ 0 0 28px -4px rgba(var(--glow-color), .4);
+}
+
+/* ==========================================================================
+ 7. Navigation Menu
+ ========================================================================== */
+.menu-wrapper {
+ position: relative;
+}
+
+#menu-toggle {
+ width: 44px;
+ height: 44px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 22px;
+ line-height: 1;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--bg);
+ color: var(--ink);
+ padding: 0;
+}
+
+#menu-toggle {
+ transition: transform .25s ease, background .15s, color .15s, border-color .15s, box-shadow .25s;
+}
+
+#menu-toggle:hover {
+ background: var(--accent);
+ color: var(--ink);
+ border-color: rgba(var(--glow-color), .9);
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .4),
+ 0 0 14px rgba(var(--glow-color), .6),
+ 0 0 32px -4px rgba(var(--glow-color), .4);
+}
+
+#menu-toggle.active {
+ transform: rotate(90deg);
+ background: var(--bg);
+ color: var(--ink);
+ border-color: var(--border);
+}
+
+#menu-toggle.active:hover {
+ background: var(--accent);
+ color: var(--ink);
+ border-color: var(--accent);
+}
+
+nav.site-nav {
+ position: absolute;
+ right: 0;
+ top: calc(100% + var(--space-xs));
+ background: var(--bg);
+ border: 1px solid var(--ink);
+ border-radius: var(--r);
+ padding: var(--space-md) var(--space-md);
+ min-width: 200px;
+ box-shadow: 0 10px 22px var(--shadow);
+ z-index: 1200;
+ max-height: calc(100vh - 80px);
+ overflow-y: auto;
+}
+
+nav.site-nav ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+nav.site-nav li {
+ margin: var(--space-sm) 0;
+}
+
+nav.site-nav a {
+ display: block;
+ padding: var(--space-xs) var(--space-xs);
+ transition: background .15s, color .15s;
+ color: var(--gold);
+ font-size: var(--fs-xl);
+ margin: 0;
+}
+
+nav.site-nav a:hover {
+ background: var(--accent);
+ color: var(--ink);
+}
+
+nav.site-nav .nav-section {
+ margin-bottom: var(--space-sm);
+}
+
+nav.site-nav .nav-section:last-child {
+ margin-bottom: 0;
+}
+
+nav.site-nav .nav-section+.nav-section {
+ border-top: 1px solid var(--border);
+ padding-top: var(--space-sm);
+}
+
+nav.site-nav .nav-label {
+ font-size: var(--fs-xs);
+ font-weight: 600;
+ color: var(--muted);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: var(--space-xs);
+ padding: var(--space-xs);
+}
+
+/* Language dropdown */
+.lang {
+ position: relative;
+}
+
+.lang-toggle-wrapper {
+ display: inline-block;
+}
+
+.lang-menu {
+ position: absolute;
+ right: 0;
+ top: calc(100% + var(--space-xs));
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ box-shadow: 0 10px 22px var(--shadow);
+ min-width: 160px;
+ padding: var(--space-sm);
+ z-index: 1200;
+}
+
+.lang-menu a {
+ display: block;
+ padding: var(--space-sm) var(--space-sm);
+ color: var(--gold);
+ transition: background .15s;
+}
+
+.lang-menu a:hover {
+ background: var(--accent);
+ color: var(--ink);
+}
+
+/* ==========================================================================
+ 8. Components — Cards, Lists, Tiles, Embeds
+ ========================================================================== */
+
+/* Changelog */
+.changelog-title {
+ color: var(--gold);
+ margin-bottom: var(--space-sm);
+}
+
+.changelog-date {
+ font-size: var(--fs-3xl);
+ font-weight: 700;
+ color: var(--muted);
+ margin: var(--space-sm) 0 var(--space-lg) 0;
+ line-height: 1.1;
+}
+
+/* Video */
+.video-wrap {
+ margin: var(--space-md) 0 0 0;
+}
+
+.video-wrap video {
+ width: 100%;
+ height: auto;
+ max-height: 80vh;
+ background: #000;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+}
+
+/* Contact list */
+.contact-list {
+ list-style: none;
+ padding: 0;
+ margin: var(--space-md) 0 0 0;
+}
+
+.contact-list li {
+ margin: var(--space-sm) 0;
+ line-height: 1.6;
+}
+
+.contact-address {
+ font-style: normal;
+}
+
+/* Iframe loading overlay */
+.iframe-clip {
+ position: relative;
+}
+
+.iframe-clip iframe {
+ position: relative;
+ z-index: 1;
+}
+
+.iframe-clip.is-loading::before {
+ content: "loading...";
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(2, 2, 2, 0.94);
+ color: var(--neon-green);
+ font-family: var(--font-mono);
+ font-size: var(--fs-sm);
+ text-shadow: 0 0 8px rgba(57, 255, 20, .45);
+ z-index: 2;
+}
+
+.iframe-clip.is-loading::after {
+ content: "_";
+ position: absolute;
+ top: 50%;
+ left: calc(50% + 2.8em);
+ transform: translateY(-50%);
+ color: var(--accent-cyan);
+ font-family: var(--font-mono);
+ font-size: var(--fs-sm);
+ font-weight: 700;
+ animation: retro-cursor-blink 1s step-end infinite;
+ z-index: 3;
+}
+
+@keyframes retro-cursor-blink {
+ 50% { opacity: 0; }
+}
+
+/* Friends grid */
+.friends-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+ gap: var(--space-lg);
+ margin: var(--space-lg) 0 0 0;
+ padding: 0;
+ list-style: none;
+}
+
+.friends-list a {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 44px;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+
+ background: var(--surface-deep);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-sm) var(--space-md);
+ color: var(--accent);
+ transition:
+ background .2s ease,
+ color .2s ease,
+ border-color .2s ease,
+ box-shadow .25s ease,
+ transform .2s ease;
+}
+
+.friends-list a:hover,
+.friends-list a:focus-visible {
+ background: color-mix(in srgb, var(--accent-cyan) 14%, var(--accent));
+ color: #ffd0d0;
+ border-color: var(--accent-cyan);
+ transform: translateY(-2px) scale(1.03);
+ box-shadow:
+ inset 0 0 16px rgba(var(--link-glow), .12),
+ 0 0 0 1px rgba(var(--link-glow), .45),
+ 0 0 14px rgba(var(--link-glow), .35);
+}
+
+.network-subsection .friends-list a:hover,
+.network-subsection .friends-list a:focus-visible {
+ background: color-mix(in srgb, var(--section-neon) 16%, var(--bg));
+ border-color: var(--section-neon);
+ color: #ffd0d0;
+ box-shadow:
+ inset 0 0 18px rgba(var(--section-glow), .14),
+ 0 0 0 1px rgba(var(--section-glow), .5),
+ 0 0 16px rgba(var(--section-glow), .38);
+}
+
+/* Tiles */
+.tile-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
+ gap: var(--space-lg);
+ list-style: none;
+ padding: 0;
+ margin: var(--space-lg) 0 0 0;
+}
+
+.tile-grid a {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ max-width: 100%;
+ box-sizing: border-box;
+ height: 64px;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-sm);
+ transition: background .15s, border-color .15s, box-shadow .15s;
+}
+
+.tile-grid a:hover {
+ background: var(--accent);
+ color: var(--ink);
+ border-color: var(--accent);
+ box-shadow: 0 1px 0 var(--accent) inset;
+}
+
+.tile-grid img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ aspect-ratio: auto;
+}
+
+/* Reserve tile box height to avoid CLS regardless of intrinsic img dims. */
+.tile-grid li.tile--wide a {
+ aspect-ratio: 5 / 2;
+}
+
+.tile-grid a:has(.vendor-logo):not(:hover),
+.tile-grid a.vendor-link:not(:hover) {
+ background: transparent;
+ border-color: #5a0000;
+}
+.tile-grid .vendor-logo {
+ padding: 8px;
+ box-sizing: border-box;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.tile-grid .vendor-logo svg {
+ max-width: 70%;
+ max-height: 70%;
+ width: auto;
+ height: auto;
+ display: block;
+}
+
+.tile-grid li.tile--wide a {
+ height: 128px;
+}
+
+.tile-grid--three {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+.tile-grid--three li.tile--wide {
+ grid-column: auto;
+}
+@media (max-width: 640px) {
+ .tile-grid--three {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* Embeds */
+.embed-frame {
+ display: block;
+ width: 100%;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: #000;
+ margin: var(--space-md) 0 0 0;
+}
+
+.guestbook-form-frame {
+ height: 450px;
+ min-height: 450px;
+}
+
+.iframe-clip {
+ position: relative;
+ overflow: hidden;
+ border-radius: var(--r);
+ border: 1px solid var(--border);
+ background: #000;
+}
+
+.iframe-clip>iframe {
+ display: block;
+ width: 100%;
+ height: 100%;
+ border: 0;
+ background: #000;
+ color-scheme: dark;
+}
+.guestbook-form-frame {
+ height: 500px;
+ min-height: 500px;
+}
+
+#guestbook-comments-iframe {
+ height: 500px;
+ width: 100%;
+ border: 0;
+ background: #000;
+ color-scheme: dark;
+}
+
+#guestbook-form-iframe {
+ height: 500px;
+ width: 100%;
+ border: 0;
+ background: #000;
+ color-scheme: dark;
+}
+
+.changelog-frame {
+ min-height: 360px;
+}
+
+/* Audio */
+.audio-wrap {
+ margin: var(--space-md) 0 0 0;
+}
+
+audio {
+ width: 100%;
+ max-width: 400px;
+}
+
+/* Forms */
+form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-md);
+}
+
+label {
+ display: block;
+ font-size: var(--fs-sm);
+ font-weight: 600;
+ color: var(--gold);
+}
+
+input[type="text"],
+input[type="email"],
+input[type="url"],
+input[type="password"],
+input[type="search"],
+textarea,
+select {
+ width: 100%;
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-sm) var(--space-md);
+ color: var(--ink);
+ font-family: inherit;
+ font-size: var(--fs-base);
+ transition: border-color .15s, box-shadow .15s;
+}
+
+input:focus,
+textarea:focus,
+select:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 2px rgba(139, 0, 0, 0.2);
+}
+
+textarea {
+ resize: vertical;
+ min-height: 100px;
+}
+
+input::placeholder,
+textarea::placeholder {
+ color: var(--muted);
+ opacity: 0.6;
+}
+
+button[type="submit"],
+button.button-primary {
+ background: var(--accent);
+ color: #e0e0e0;
+ border: 1px solid var(--accent);
+ padding: var(--space-sm) var(--space-lg);
+ border-radius: var(--r);
+ cursor: pointer;
+ font-weight: 700;
+ font-family: inherit;
+ font-size: var(--fs-base);
+ transition: background .15s, transform .1s;
+}
+
+button[type="submit"]:hover,
+button.button-primary:hover {
+ background: var(--ink);
+ color: var(--bg);
+}
+
+button:not(.logo):not(.reset-button):not(.header-button):not([type="submit"]):not(.button-primary) {
+ background: var(--bg);
+ color: var(--ink);
+ border: 1px solid var(--border);
+ padding: var(--space-sm) var(--space-lg);
+ border-radius: var(--r);
+ cursor: pointer;
+ font-family: inherit;
+ transition: background .15s, color .15s;
+}
+
+button:not(.logo):not(.reset-button):not(.header-button):not([type="submit"]):not(.button-primary):hover {
+ background: var(--accent);
+ color: #e0e0e0;
+}
+
+button[type="submit"]:active {
+ transform: translateY(1px);
+}
+
+.stack {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-md);
+}
+
+.field-group {
+ display: block;
+ width: 100%;
+}
+
+/* Guestbook Specific */
+.captcha-wrap {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-sm);
+ align-items: flex-start;
+}
+
+.captcha-img {
+ border-radius: var(--r);
+ border: 1px solid var(--border);
+ background: var(--bg);
+}
+
+.guestbook-entry {
+ background: var(--bg);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-md);
+ margin-bottom: var(--space-md);
+ transition: border-color .15s, box-shadow .15s;
+}
+
+.guestbook-entry:hover {
+ border-color: var(--accent);
+}
+
+.guestbook-entry:last-child {
+ margin-bottom: 0;
+}
+
+.guestbook-entry strong {
+ color: var(--gold);
+ display: block;
+ font-size: var(--fs-sm);
+ margin-bottom: var(--space-xs);
+}
+
+.guestbook-entry time {
+ display: block;
+ font-size: var(--fs-xs);
+ color: var(--muted);
+ margin-bottom: var(--space-sm);
+}
+
+.guestbook-entry p {
+ margin: 0;
+ line-height: 1.6;
+ font-family: inherit;
+ color: var(--ink) !important;
+}
+
+.anonymous {
+ color: var(--ink) !important;
+}
+
+/* Blog + changelog lists */
+#blog-list,
+#changelog-list {
+ margin: var(--space-md) 0 0 0;
+}
+
+#blog-list>div:first-child,
+#changelog-list>div:first-child {
+ margin-bottom: var(--space-md);
+}
+
+#blog-list ol,
+#changelog-list ol {
+ margin: var(--space-md) 0 0 0;
+ padding-left: var(--space-xl);
+}
+
+#blog-list li,
+#changelog-list li {
+ margin: 0 0 var(--space-md) 0;
+ line-height: 1.6;
+}
+
+#blog-list .muted,
+#changelog-list .muted {
+ font-size: var(--fs-sm);
+ margin-top: var(--space-xs);
+}
+
+#blog-list .blog-more {
+ margin-top: var(--space-xs);
+}
+
+#changelog-list .changelog-body {
+ margin-top: var(--space-xs);
+ white-space: pre-line;
+}
+
+#changelog-list time {
+ color: var(--accent);
+}
+
+/* Reset Button */
+.reset-button {
+ font-size: var(--fs-sm);
+ border: 1px solid var(--border);
+ padding: 4px 8px;
+ border-radius: var(--r);
+ color: var(--ink);
+ background: var(--bg);
+ cursor: pointer;
+ font-family: inherit;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ transition: background .15s, color .15s, border-color .15s;
+}
+
+.reset-button:hover {
+ background: var(--accent);
+ color: var(--ink);
+ border-color: var(--accent);
+}
+
+.category-block {
+ margin: var(--space-xl) 0;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--bg);
+ overflow: hidden;
+}
+
+.category-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--space-md) var(--space-lg);
+ border-bottom: 1px solid var(--border);
+ background: rgba(139, 0, 0, 0.05);
+}
+
+.category-header h2 {
+ margin: 0;
+ font-size: var(--fs-xl);
+}
+
+.category-content {
+ padding: var(--space-lg);
+}
+
+.mini-button {
+ padding: 4px 12px;
+ font-size: var(--fs-xs);
+ background: var(--bg);
+ border: 1px solid var(--border);
+ color: var(--ink);
+ border-radius: var(--r);
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.mini-button:hover {
+ background: var(--accent);
+ color: var(--ink);
+}
+
+/* Last.fm Widget */
+.lastfm-inner-box {
+ border: 1px solid var(--border);
+ padding: var(--space-md);
+ background: rgba(139, 0, 0, 0.03);
+ border-radius: var(--r);
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.3);
+}
+
+.lastfm-link-hint {
+ font-size: var(--fs-xs);
+ text-align: left;
+ margin-top: var(--space-md);
+ padding-top: var(--space-xs);
+ border-top: 1px dotted var(--border);
+}
+
+.lastfm-text-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.lastfm-text-list li {
+ margin-bottom: var(--space-xs);
+ line-height: 1.4;
+}
+
+.lastfm-text-list li strong {
+ color: var(--accent);
+}
+
+.lastfm-track {
+ display: flex;
+ align-items: center;
+ gap: var(--space-md);
+ padding: var(--space-sm);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: rgba(139, 0, 0, 0.05);
+ width: 100%;
+ transition: background 0.2s, transform 0.2s;
+ text-decoration: none;
+ min-height: 80px;
+}
+
+.lastfm-track:hover {
+ background: rgba(139, 0, 0, 0.1);
+ transform: translateY(-2px);
+ border-color: var(--accent);
+}
+
+.lastfm-image-wrap {
+ flex-shrink: 0;
+ width: 64px;
+ height: 64px;
+ border-radius: var(--r);
+ overflow: hidden;
+ border: 1px solid var(--border);
+}
+
+.lastfm-image-wrap img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.lastfm-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ min-width: 0; /* Prevents overflow */
+}
+
+.lastfm-status {
+ font-size: var(--fs-xs);
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ color: var(--muted);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-bottom: 2px;
+}
+
+.lastfm-status.is-playing {
+ color: var(--gold);
+}
+
+.lastfm-status.is-playing::after {
+ content: "";
+ width: 8px;
+ height: 8px;
+ background: var(--gold);
+ border-radius: 50%;
+ display: inline-block;
+ animation: pulse-red 1.5s infinite;
+}
+
+@keyframes pulse-red {
+ 0% { box-shadow: 0 0 0 0 rgba(178, 34, 34, 0.7); }
+ 70% { box-shadow: 0 0 0 6px rgba(178, 34, 34, 0); }
+ 100% { box-shadow: 0 0 0 0 rgba(178, 34, 34, 0); }
+}
+
+.lastfm-title {
+ font-weight: 700;
+ color: var(--gold);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: var(--fs-lg);
+}
+
+.lastfm-artist {
+ font-size: var(--fs-sm);
+ color: var(--ink);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.lastfm-error-msg {
+ display: none;
+ font-size: var(--fs-xs);
+ color: var(--muted);
+ padding: var(--space-md);
+ border: 1px dashed var(--border);
+ border-radius: var(--r);
+ text-align: center;
+ width: 100%;
+}
+
+/* ==========================================================================
+ 9. Page-specific (from legacy style.css)
+ ========================================================================== */
+.button1 {
+ background-color: black;
+ font-family: Comic Sans MS;
+ font-weight: 300;
+}
+
+.member {
+ float: left;
+ width: calc(20% - 2%);
+ margin: 1% 1% 45px 1%;
+}
+
+.name {
+ bottom: 0px;
+}
+
+.member img {
+ width: 50%;
+ display: block;
+}
+
+ul.flowxl {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0;
+}
+
+ul.flowxl>li {
+ list-style-type: none;
+ text-align: center;
+ width: var(--xiconsize);
+ min-width: var(--xiconsize);
+ max-width: var(--xiconsize);
+ max-height: calc(var(--xiconsize) + 60px);
+ padding: 2px;
+ margin: 0.125rem;
+ overflow: hidden;
+ font-size: 1rem;
+ line-height: 1;
+}
+
+ul.flowxl>li>a {
+ display: block;
+ line-height: 1;
+ font-size: 1.2rem;
+}
+
+ul.flowxl li>a>img,
+ul.flowxl li>img {
+ display: block;
+ border-radius: 9px;
+ width: auto;
+ max-width: var(--xiconsize);
+ max-height: var(--xiconsize);
+ overflow: hidden;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+ul.flowxl li>a>img[src$=".svg"] {
+ max-width: 170px;
+ max-height: 170px;
+}
+
+.country-img {
+ height: 12.5rem;
+ width: 250px;
+ object-fit: contain;
+ max-width: 100%;
+}
+
+.countries {
+ flex-wrap: wrap;
+ gap: 10px;
+ justify-content: center;
+}
+
+.vpn-logo {
+ width: 200px;
+ max-width: 100%;
+ height: auto;
+ object-fit: contain;
+}
+
+/* ==========================================================================
+ 10. Footer
+ ========================================================================== */
+footer.site-footer {
+ position: relative;
+ border-top: 1px solid var(--border);
+ border-bottom: none;
+}
+
+footer.site-footer::after {
+ content: '';
+ display: block;
+ height: 4px;
+ background:
+ repeating-linear-gradient(
+ 90deg,
+ transparent,
+ transparent 3px,
+ rgba(var(--link-glow), .25) 3px,
+ rgba(var(--link-glow), .25) 4px
+ ),
+ linear-gradient(90deg, var(--neon-green), var(--accent-cyan), var(--accent-purple), var(--accent-pink));
+ opacity: 0.45;
+ box-shadow: 0 -1px 12px rgba(var(--link-glow), .15);
+}
+
+.site-footer .footer-inner::after {
+ content: '> connection closed — EOF_';
+ display: block;
+ margin-top: var(--space-md);
+ padding-top: var(--space-sm);
+ border-top: 1px dashed color-mix(in srgb, var(--border) 70%, transparent);
+ font-family: var(--font-mono);
+ font-size: var(--fs-xs);
+ letter-spacing: 0.06em;
+ color: var(--muted);
+ opacity: 0.5;
+ animation: footer-cursor-hint 2.5s step-end infinite;
+}
+
+@keyframes footer-cursor-hint {
+ 50% { opacity: 0.35; }
+}
+
+.footer-inner {
+ max-width: var(--wrap);
+ margin: 0 auto;
+ padding: var(--space-lg) var(--space-lg) var(--space-2xl);
+ color: var(--muted);
+ font-size: var(--fs-sm);
+ line-height: 1.6;
+}
+
+.badge {
+ display: inline-flex;
+ margin-top: var(--space-sm);
+ border-radius: var(--r);
+ padding: var(--space-xs);
+ border: 1px solid var(--border);
+ background: #2a0000;
+ transition: border-color .15s;
+}
+
+.badge:hover {
+ border-color: var(--accent);
+}
+
+.eff-badge {
+ background: #000000 !important;
+ border-color: #4a0000 !important;
+ box-shadow: none !important;
+}
+
+.tile-grid a.eff-badge:hover,
+.tile-grid a.eff-badge:focus-visible {
+ background: #000000 !important;
+ border-color: #a00000 !important;
+ box-shadow: none !important;
+}
+
+.eff-badge:focus-visible {
+ outline: 2px solid #2a52ff !important;
+}
+
+.tile-grid a.eff-badge:hover img,
+.tile-grid a.eff-badge:focus-visible img {
+ box-shadow: none !important;
+}
+
+.eff-badge img {
+ opacity: .95;
+ filter: drop-shadow(0 1px 0 rgba(0, 0, 0, .55));
+}
+
+body.light-mode .eff-badge {
+ background: #ffffff !important;
+ border-color: #78c8ff !important;
+ box-shadow:
+ 0 0 0 1px rgba(120, 200, 255, .25),
+ 0 0 16px rgba(120, 200, 255, .32);
+}
+
+body.light-mode .tile-grid a.eff-badge:hover,
+body.light-mode .tile-grid a.eff-badge:focus-visible,
+body.light-mode .badge.eff-badge:hover,
+body.light-mode .badge.eff-badge:focus-visible {
+ background: var(--accent) !important;
+ border-color: var(--accent) !important;
+}
+
+body.light-mode .eff-badge img {
+ content: url("/assets/img/rib_bar_wh.png");
+ filter: drop-shadow(0 1px 0 rgba(0, 38, 64, .35));
+}
+
+.badge.eff-badge img {
+ width: 150px;
+ max-width: 100%;
+ height: auto;
+ aspect-ratio: 572 / 120;
+}
+
+.tile-grid .eff-badge img {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+}
+
+/* ==========================================================================
+ 11. Responsive
+ ========================================================================== */
+
+/* Long URLs / emails / quotes never blow out the viewport */
+.contact-list,
+.contact-list a,
+blockquote,
+.lastfm-link-hint,
+.vibe-preview-link,
+#blog-list a,
+#changelog-list a {
+ overflow-wrap: anywhere;
+ word-break: break-word;
+}
+
+/* Tables become horizontally scrollable rather than overflowing */
+table {
+ display: block;
+ max-width: 100%;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+}
+
+/* Long <pre> blocks wrap on narrow screens */
+pre {
+ white-space: pre-wrap;
+ word-break: break-word;
+}
+
+/* Tablet: fix tile-grid gaps */
+@media (min-width: 601px) and (max-width: 900px) {
+ .tile-grid {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ }
+
+ .friends-list {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (max-width: 720px) {
+ /* Header: let controls wrap cleanly under brand */
+ .header-inner {
+ gap: var(--space-sm);
+ }
+
+ .brand {
+ min-width: 0;
+ flex: 1 1 auto;
+ }
+
+ .header-controls {
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ gap: var(--space-sm);
+ row-gap: var(--space-xs);
+ }
+
+ /* Tighten the language button's label so it fits next to the others */
+ #lang-toggle {
+ white-space: nowrap;
+ }
+}
+
+@media (max-width: 600px) {
+ .wrap {
+ padding: var(--space-lg) var(--space-md) var(--space-2xl);
+ }
+
+ section {
+ padding: var(--space-md);
+ }
+
+ /* h1 / pre had a 40px left indent that wastes space on phones */
+ h1,
+ pre {
+ padding-left: 0;
+ }
+
+ .home-status-row {
+ margin-left: 0;
+ }
+
+ .featured-links {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .tile-grid {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ }
+
+ .tile-grid li {
+ margin-bottom: 1rem;
+ text-align: center;
+ width: 100%;
+ }
+
+ .tile-grid a {
+ width: 100%;
+ }
+
+ .contact-list {
+ padding-left: 20px;
+ }
+
+ .friends-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ /* Hide the subtitle line on tight screens so the brand fits on one line */
+ .brand-text span {
+ display: none;
+ }
+
+ /* Iframes: drop the desktop fixed heights so they're not absurd on phones */
+ .guestbook-form-frame,
+ #guestbook-form-iframe,
+ #guestbook-comments-iframe {
+ height: 420px;
+ min-height: 420px;
+ }
+}
+
+@media (max-width: 520px) {
+ header.site-header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ }
+
+ .header-inner {
+ position: relative;
+ padding: var(--space-sm) var(--space-md);
+ }
+
+ nav.site-nav {
+ min-width: 200px;
+ max-width: calc(100vw - 2 * var(--space-md));
+ }
+
+ .lang-menu {
+ right: auto;
+ left: 0;
+ max-width: calc(100vw - 32px);
+ }
+
+ #menu-toggle {
+ display: inline-flex;
+ transition: transform .25s ease;
+ }
+
+ #menu-toggle.active {
+ transform: rotate(90deg);
+ }
+
+ /* Last.fm row: stack image + info if title would clip */
+ .lastfm-track {
+ gap: var(--space-sm);
+ }
+
+ .lastfm-image-wrap {
+ width: 48px;
+ height: 48px;
+ }
+
+ /* Header buttons: keep tap targets but trim padding */
+ .header-button {
+ padding: 4px 6px;
+ }
+}
+
+@media (max-width: 380px) {
+ /* Very narrow phones: shrink header buttons further so all wrap cleanly */
+ .header-button {
+ font-size: var(--fs-xs);
+ padding: 3px 5px;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ * {
+ transition: none !important;
+ scroll-behavior: auto !important;
+ }
+
+ #menu-toggle.active {
+ transform: none;
+ }
+}
+
+/* ==========================================================================
+ 12. Theme: High Contrast (dark)
+ ========================================================================== */
+[data-theme="high-contrast"] {
+ --bg: #000000;
+ --ink: #ffffff;
+ --muted: #ccc;
+ --accent: #ff0000;
+ --gold: #ff4d4d;
+ --border: #fff;
+ --shadow: rgba(255, 255, 255, .12);
+}
+
+[data-theme="high-contrast"] a {
+ color: #ff0000;
+ text-decoration: underline;
+}
+
+[data-theme="high-contrast"] a:hover {
+ background: #ff0000;
+ color: #000;
+}
+
+[data-theme="high-contrast"] nav.site-nav a {
+ color: #fff;
+}
+
+[data-theme="high-contrast"] .friends-list a {
+ color: #ff0000;
+}
+
+/* ==========================================================================
+ 13. Theme: Light Mode
+ ========================================================================== */
+body.light-mode {
+ --bg: #fff8f1;
+ --surface: #fffdf9;
+ --surface-soft: #faf3ea;
+ --surface-tint: #f5ebe0;
+ --ink: #2a1212;
+ --muted: #7a4545;
+ --accent: #8b0000;
+ --gold: #8b0000;
+ --border: #d9c9b8;
+ --shadow: rgba(92, 72, 52, .12);
+ --glow-color: 139, 0, 0;
+ --link-glow: 139, 0, 0;
+ --btn-glow: 139, 0, 0;
+ --glow-soft:
+ 0 1px 0 rgba(255, 255, 255, .95) inset,
+ 0 8px 26px rgba(139, 0, 0, .12);
+ --glow-strong:
+ 0 0 0 1px rgba(var(--glow-color), .45),
+ 0 10px 30px rgba(139, 0, 0, .18);
+ color-scheme: light;
+ background: #fff8f1;
+}
+
+body.light-mode header.site-header,
+body.light-mode footer.site-footer,
+body.light-mode .header-search {
+ background: rgba(255, 248, 241, .96);
+}
+
+body.light-mode section,
+body.light-mode .category-block,
+body.light-mode .guestbook-entry,
+body.light-mode .captcha-img {
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .18), transparent 72%),
+ var(--surface);
+}
+
+body.light-mode .site-status-row span,
+body.light-mode .site-status-row a,
+body.light-mode .featured-links a,
+body.light-mode .friends-list a,
+body.light-mode .tile-grid a,
+body.light-mode .header-button,
+body.light-mode #menu-toggle,
+body.light-mode nav.site-nav,
+body.light-mode .lang-menu,
+body.light-mode input[type="text"],
+body.light-mode input[type="email"],
+body.light-mode input[type="url"],
+body.light-mode input[type="password"],
+body.light-mode input[type="search"],
+body.light-mode textarea,
+body.light-mode select,
+body.light-mode button:not(.logo):not(.reset-button):not(.header-button):not([type="submit"]):not(.button-primary),
+body.light-mode .reset-button,
+body.light-mode .mini-button {
+ background: var(--surface);
+}
+
+body.light-mode .category-header,
+body.light-mode .lastfm-inner-box,
+body.light-mode .lastfm-track {
+ background: var(--surface-soft);
+}
+
+body.light-mode .lastfm-track:hover,
+body.light-mode .featured-links a:hover,
+body.light-mode .friends-list a:hover,
+body.light-mode .tile-grid a:hover,
+body.light-mode .header-button:hover,
+body.light-mode #menu-toggle:hover,
+body.light-mode nav.site-nav a:hover,
+body.light-mode .lang-menu a:hover,
+body.light-mode button:not(.logo):not(.reset-button):not(.header-button):not([type="submit"]):not(.button-primary):hover,
+body.light-mode .reset-button:hover,
+body.light-mode .mini-button:hover,
+body.light-mode a:hover,
+body.light-mode a:focus-visible {
+ background: var(--accent);
+ color: #ffffff;
+ text-shadow: none;
+}
+
+body.light-mode button[type="submit"],
+body.light-mode button.button-primary {
+ color: #ffffff;
+}
+
+body.light-mode button[type="submit"]:hover,
+body.light-mode button.button-primary:hover {
+ color: #ffffff;
+}
+
+body.light-mode .badge {
+ background: var(--surface-tint);
+}
+
+body.light-mode .video-wrap video,
+body.light-mode .embed-frame,
+body.light-mode .iframe-clip,
+body.light-mode .iframe-clip>iframe,
+body.light-mode #guestbook-comments-iframe,
+body.light-mode #guestbook-form-iframe {
+ background: var(--surface);
+ color-scheme: light;
+}
+
+body.light-mode .iframe-clip.is-loading::before {
+ background: rgba(248, 253, 255, 0.92);
+}
+
+body.light-mode .tile-grid a:has(.vendor-logo):not(:hover),
+body.light-mode .tile-grid a.vendor-link:not(:hover) {
+ background: var(--surface);
+ border-color: var(--border);
+}
+
+body.light-mode .unified-search,
+body.light-mode .search-engine-toggle,
+body.light-mode .search-engine-menu,
+body.light-mode .unified-search .unified-search-input {
+ background: var(--surface);
+}
+
+body.light-mode .search-engine-toggle:hover,
+body.light-mode .search-engine-menu li:hover,
+body.light-mode .search-engine-menu li[aria-selected="true"] {
+ background: var(--accent);
+ color: #ffffff;
+}
+
+body.light-mode .unified-search .unified-search-input::placeholder {
+ color: rgba(85, 113, 127, 0.72);
+}
+.guestbook-error { color: var(--ink) !important; margin-bottom: 20px; }
+
+.vibe-preview-media { max-width: 100%; }
+.vibe-preview-audio { width: 100%; }
+.vibe-audio-full { width: 100%; }
+.vibe-preview-link { font-size: var(--fs-sm); font-family: monospace; color: var(--accent); word-break: break-all; }
+
+/* ==========================================================================
+ Global Header Search (unified search w/ engine picker)
+ ========================================================================== */
+.header-search {
+ border-top: 1px solid var(--border);
+ border-bottom: 1px solid var(--border);
+ background: var(--bg);
+ padding: var(--space-sm) var(--space-lg);
+ width: 100%;
+}
+
+.header-search-inner {
+ max-width: var(--wrap);
+ margin: 0 auto;
+ width: 100%;
+}
+
+.gh-search-form {
+ width: 100%;
+ margin: 0;
+}
+
+.header-search .unified-search {
+ display: flex;
+ align-items: stretch;
+ width: 100%;
+ max-width: none;
+ margin: 0;
+}
+
+.unified-search {
+ display: flex;
+ align-items: stretch;
+ width: 100%;
+ max-width: 584px;
+ margin: 0 auto;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: #000;
+ position: relative;
+ transition: box-shadow .25s ease, border-color .25s ease;
+}
+
+.unified-search:hover,
+.unified-search:focus-within {
+ border-color: rgba(var(--glow-color), .8);
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .35),
+ 0 0 14px rgba(var(--glow-color), .55),
+ 0 0 36px -6px rgba(var(--glow-color), .35);
+}
+
+.search-engine-picker {
+ position: relative;
+ display: flex;
+}
+
+.search-engine-toggle {
+ background: #000;
+ color: var(--ink);
+ border: none;
+ border-right: 1px solid var(--border);
+ font-family: inherit;
+ font-weight: bold;
+ font-size: 0.95rem;
+ padding: 0 14px;
+ cursor: pointer;
+ min-width: 60px;
+ transition: background .15s, color .15s;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+}
+
+.search-engine-toggle:hover {
+ background: var(--accent);
+ color: #000;
+}
+
+.search-engine-caret {
+ font-size: 0.7rem;
+ opacity: 0.8;
+}
+
+.search-engine-menu {
+ position: absolute;
+ top: calc(100% + 2px);
+ left: 0;
+ min-width: 200px;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background: #000;
+ border: 1px solid rgba(var(--glow-color), .7);
+ border-radius: var(--r);
+ z-index: 1300;
+ text-align: left;
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .35),
+ 0 0 18px rgba(var(--glow-color), .55),
+ 0 10px 28px var(--shadow);
+}
+
+.search-engine-menu[hidden] {
+ display: none;
+}
+
+.search-engine-menu li {
+ padding: 8px 12px;
+ color: var(--ink);
+ cursor: pointer;
+ font-family: inherit;
+ font-size: 0.9rem;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ border-bottom: 1px solid var(--border);
+}
+
+.search-engine-menu li:last-child {
+ border-bottom: none;
+}
+
+.search-engine-menu li:hover,
+.search-engine-menu li[aria-selected="true"] {
+ background: var(--accent);
+ color: #000;
+}
+
+.search-engine-menu .engine-icon {
+ font-weight: bold;
+ min-width: 28px;
+ text-align: center;
+}
+
+.unified-search .unified-search-input {
+ border: none;
+ border-radius: 0;
+ max-width: none;
+ flex: 1;
+ width: auto;
+ margin: 0;
+ padding: 8px 12px;
+ background: #000;
+ color: var(--ink);
+ font-family: inherit;
+ font-size: 0.95rem;
+ outline: none;
+ text-align: left;
+}
+
+.unified-search .unified-search-input::placeholder {
+ color: rgba(139, 0, 0, 0.55);
+}
+
+.unified-search .unified-search-input:focus {
+ box-shadow: none;
+}
+
+@media (max-width: 600px) {
+ .header-search { padding: var(--space-xs) var(--space-sm); }
+ .search-engine-toggle { min-width: 52px; padding: 0 10px; font-size: 0.85rem; }
+ /* >=16px stops iOS Safari auto-zooming (and reflowing) the page on focus */
+ .unified-search .unified-search-input { font-size: 16px; padding: 7px 10px; }
+}
+
+/* ==========================================================================
+ Mobile nav drawer (<=600px)
+ Turn the absolute dropdown into a full-height right-edge sheet with
+ a backdrop, larger tap targets, and a body scroll lock while open.
+ ========================================================================== */
+@media (max-width: 600px) {
+ /* Hide redundant/secondary header buttons; primary nav lives in the drawer */
+ .header-controls > a.header-button {
+ display: none;
+ }
+ /* Keep brand-text legible but tighten on narrow screens */
+ .brand-text span { display: none; }
+
+ .header-controls {
+ gap: var(--space-xs);
+ }
+
+ /* Bigger, easier-to-hit hamburger */
+ #menu-toggle {
+ width: 48px;
+ height: 48px;
+ font-size: 26px;
+ }
+
+ /* Convert dropdown into right-edge sheet */
+ nav.site-nav {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: auto;
+ width: min(86vw, 360px);
+ max-width: none;
+ min-width: 0;
+ max-height: 100vh;
+ max-height: 100dvh;
+ height: 100vh;
+ height: 100dvh; /* exclude mobile toolbar so bottom nav items aren't hidden */
+ border-radius: 0;
+ border: 0;
+ border-left: 1px solid var(--ink);
+ padding: calc(var(--space-md) + 56px) var(--space-md) var(--space-lg);
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ transform: translateX(100%);
+ transition: transform .25s ease;
+ z-index: 1300;
+ box-shadow: -8px 0 24px var(--shadow);
+ }
+
+ nav.site-nav:not([hidden]) {
+ transform: translateX(0);
+ }
+
+ /* Closed drawers should be truly hidden; opening is immediate, then the
+ sheet transform handles the visible state. */
+ nav.site-nav[hidden] {
+ display: none !important;
+ pointer-events: none;
+ transform: translateX(100%);
+ }
+
+ nav.site-nav a {
+ font-size: 1.05rem;
+ padding: 12px 10px;
+ border-radius: 6px;
+ }
+
+ nav.site-nav li {
+ margin: 2px 0;
+ }
+
+ /* Backdrop using ::before on body when menu is open */
+ body:has(#menu-toggle[aria-expanded="true"])::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.55);
+ z-index: 1250;
+ backdrop-filter: blur(2px);
+ animation: vibe-backdrop-in .2s ease;
+ }
+
+ /* Lock body scroll while drawer open */
+ body:has(#menu-toggle[aria-expanded="true"]) {
+ overflow: hidden;
+ }
+
+ @keyframes vibe-backdrop-in {
+ from { opacity: 0; }
+ to { opacity: 1; }
+ }
+}
+
+@media (max-width: 600px) and (prefers-reduced-motion: reduce) {
+ nav.site-nav { transition: none; }
+ body:has(#menu-toggle[aria-expanded="true"])::before { animation: none; }
+}
+
+/* ==========================================================================
+ Polish: subtle hovers + typography rhythm
+ ========================================================================== */
+html {
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
+}
+
+/* Breathing room between top-level sections */
+main.wrap.stack > section {
+ margin-bottom: var(--space-2xl);
+}
+main.wrap.stack > section:last-child {
+ margin-bottom: 0;
+}
+
+/* Better paragraph rhythm and measure */
+main p {
+ line-height: 1.65;
+ max-width: 68ch;
+}
+main p + p {
+ margin-top: var(--space-sm);
+}
+
+/* Heading micro-spacing: more above, tight below */
+main section > h2 {
+ margin-top: var(--space-lg);
+ margin-bottom: var(--space-sm);
+ scroll-margin-top: 80px;
+}
+main section > h2 + p,
+main section > h2 + .muted {
+ margin-top: 0;
+}
+
+/* Anchored section targets briefly glow */
+main section:target > h2 {
+ text-shadow:
+ 0 0 6px rgba(var(--glow-color), .8),
+ 0 0 18px rgba(var(--glow-color), .4);
+}
+
+/* List items: subtle hover lift */
+.friends-list li,
+.recent-list li {
+ transition: transform .15s ease, padding-left .15s ease;
+}
+.friends-list li:hover,
+.recent-list li:hover {
+ transform: translateX(2px);
+}
+
+.recent-list {
+ list-style: decimal;
+ padding-left: 1.5em;
+ margin: var(--space-sm) 0 0;
+}
+.recent-list li {
+ margin: var(--space-sm) 0;
+ line-height: 1.5;
+}
+.recent-list li > .muted {
+ font-size: 0.85em;
+ margin-top: 2px;
+}
+.recent-list li > div:not(.muted) {
+ margin-top: 2px;
+ opacity: 0.9;
+}
+.recent-list li > .changelog-more {
+ display: inline-block;
+ margin-top: 4px;
+ font-size: 0.85em;
+ white-space: nowrap;
+}
+
+/* Tile-grid images: gentle scale + glow on hover */
+.tile-grid li {
+ transition: transform .2s ease;
+}
+.tile-grid a img,
+.tile-grid a .vendor-logo {
+ transition: transform .2s ease, filter .2s ease, box-shadow .2s ease;
+}
+.tile-grid a:hover img,
+.tile-grid a:focus-visible img,
+.tile-grid a:hover .vendor-logo,
+.tile-grid a:focus-visible .vendor-logo {
+ transform: translateY(-2px);
+ filter: brightness(1.08);
+ box-shadow: 0 6px 18px -6px rgba(var(--glow-color), .6);
+}
+/* Tile hover should not inherit the global link halo */
+.tile-grid a:hover,
+.tile-grid a:focus-visible {
+ background: transparent;
+ text-shadow: none;
+ box-shadow: none;
+ transform: translateY(-2px) scale(1.03);
+}
+
+.network-subsection .tile-grid a:hover,
+.network-subsection .tile-grid a:focus-visible {
+ border-color: var(--section-neon);
+ box-shadow:
+ inset 0 0 20px rgba(var(--section-glow), .12),
+ 0 0 0 1px rgba(var(--section-glow), .45),
+ 0 0 18px rgba(var(--section-glow), .32) !important;
+}
+
+.network-subsection#games .tile-grid a:hover,
+.network-subsection#games .tile-grid a:focus-visible {
+ --section-neon: var(--neon-green);
+ --section-glow: 57, 255, 20;
+}
+
+/* Featured nav links: tighter, more clickable */
+.featured-links {
+ gap: var(--space-md) var(--space-lg);
+}
+.featured-links a {
+ transition: transform .15s ease, color .15s ease, background .15s ease, box-shadow .25s ease;
+}
+.featured-links a:hover,
+.featured-links a:focus-visible {
+ transform: translateY(-1px);
+}
+
+/* Contact list: a touch more breathing room */
+.contact-list li {
+ margin: 4px 0;
+ line-height: 1.55;
+}
+
+/* Blockquote: pull-quote feel */
+blockquote {
+ padding: var(--space-sm) var(--space-md);
+ border-radius: 4px;
+ background: rgba(var(--glow-color), .04);
+}
+
+/* Network section: combined Friends + Games + Countries + Sponsors */
+.network-section .network-subsection {
+ margin-top: var(--space-2xl);
+ padding: var(--space-lg) var(--space-md) var(--space-xl);
+ border-top: 1px dashed var(--border);
+ border-radius: var(--r);
+ background: linear-gradient(180deg, rgba(var(--glow-color), .03), transparent 85%);
+}
+
+.network-section .network-subsection:first-of-type {
+ margin-top: var(--space-md);
+ padding-top: var(--space-md);
+ border-top: 0;
+}
+
+.network-section .network-subsection .tile-grid,
+.network-section .network-subsection .friends-groups {
+ margin-top: var(--space-md);
+}
+
+.network-subsection#friends { --section-neon: var(--accent-cyan); --section-glow: var(--link-glow); }
+.network-subsection#games { --section-neon: var(--neon-green); --section-glow: 57, 255, 20; }
+.network-subsection#countries { --section-neon: var(--accent-purple); --section-glow: 179, 136, 255; }
+.network-subsection#buttons { --section-neon: var(--accent-pink); --section-glow: 255, 45, 149; }
+
+.network-subsection .network-subtitle::before {
+ content: '▸ ';
+ color: var(--section-neon, var(--gold));
+ text-shadow: 0 0 8px color-mix(in srgb, var(--section-neon, var(--gold)) 50%, transparent);
+}
+
+.network-subtitle {
+ margin: 0 0 var(--space-md);
+ font-size: 1.05rem;
+ font-weight: 700;
+ color: var(--gold);
+ letter-spacing: 0.3px;
+ scroll-margin-top: 80px;
+ text-shadow: 0 0 10px rgba(var(--glow-color), .25);
+}
+
+/* Friends section: grouped columns */
+.friends-groups {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ gap: var(--space-2xl) var(--space-xl);
+ margin-top: var(--space-md);
+}
+.friends-group-title {
+ font-size: 0.85rem;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ opacity: 0.7;
+ margin: 0 0 var(--space-xs);
+ font-weight: 600;
+}
+.friends-group .friends-list {
+ margin: 0;
+ padding-left: 1.1em;
+}
+
+/* Recent updates block under the welcome paragraph */
+.home-recent-updates {
+ margin-top: var(--space-lg);
+ padding: var(--space-md) var(--space-lg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ background: rgba(var(--glow-color), .04);
+}
+.home-recent-updates-title {
+ margin: 0 0 var(--space-sm);
+ font-size: var(--fs-md, 1rem);
+ text-transform: uppercase;
+ letter-spacing: 0.8px;
+ opacity: 0.8;
+}
+.home-recent-updates .recent-list {
+ margin: 0;
+}
+.home-recent-updates .recent-list strong {
+ font-weight: 600;
+}
+
+/* Code inline rhythm */
+code, kbd, samp, pre {
+ font-family: var(--font-mono);
+ font-feature-settings: "calt" 1, "liga" 1;
+ font-size: 0.92em;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .friends-list li,
+ .recent-list li,
+ .featured-links a,
+ .tile-grid li,
+ .tile-grid a img,
+ .tile-grid a .vendor-logo {
+ transition: none;
+ transform: none !important;
+ }
+}
+
+/* ==========================================================================
+ 2026-05 polish pass: visual hierarchy, vibe container, mobile tap targets
+ ========================================================================== */
+
+/* NOTE: every <section> is already a bordered card (see global `section`
+ rule), so no extra dividers/borders are needed here — that would double the
+ border. Just give the vibe embed a little depth. */
+.vibe-section > h2 {
+ margin-top: 0;
+}
+.vibe-embed {
+ box-shadow: 0 8px 24px -8px rgba(0, 0, 0, .6);
+}
+
+/* Last.fm / radio fallback links should read as real links, not muted noise. */
+#lastfm-list a,
+#radio-nowplaying a {
+ font-weight: 600;
+}
+
+/* Mobile: larger spacing + tap targets so the link-dense layout is usable
+ on small screens (feedback: cramped on mobile). */
+@media (max-width: 600px) {
+ .featured-links a,
+ nav.site-nav a {
+ min-height: 44px;
+ display: flex;
+ align-items: center;
+ }
+ .contact-list a {
+ min-height: 44px;
+ display: inline-flex;
+ align-items: center;
+ }
+ .contact-list li {
+ margin: var(--space-sm) 0;
+ line-height: 1.5;
+ }
+}
+
+/* ==========================================================================
+ 2026-05 polish pass 2: items 6-13
+ ========================================================================== */
+
+/* Screen-reader-only utility (#13 accessibility). */
+.visually-hidden {
+ position: absolute !important;
+ width: 1px; height: 1px;
+ padding: 0; margin: -1px;
+ overflow: hidden; clip: rect(0, 0, 0, 0);
+ white-space: nowrap; border: 0;
+}
+
+/* (#9) Typography: slightly larger base on desktop + clearer heading weight
+ hierarchy so headings differ at a glance. */
+@media (min-width: 900px) {
+ body { font-size: clamp(1rem, 0.95rem + 0.25vw, 1.06rem); }
+}
+main p, main li { line-height: 1.7; }
+main h1 { font-weight: 800; letter-spacing: -0.01em; }
+main h2 { font-weight: 700; }
+main h3, main h4 { font-weight: 600; }
+
+/* (#8) "Where to start" + site search. */
+.start-here {
+ border: 1px solid var(--border);
+ border-left: 3px solid var(--accent);
+ border-radius: var(--r);
+ padding: var(--space-md) var(--space-lg);
+ margin: var(--space-md) 0 var(--space-lg);
+ background: linear-gradient(180deg, rgba(var(--glow-color), .04), transparent 70%);
+}
+.start-here-title { margin-top: 0 !important; font-size: var(--fs-xl); }
+.start-here-list {
+ list-style: none; padding: 0; margin: 0 0 var(--space-md);
+ display: grid; gap: var(--space-xs);
+}
+.start-here-list li { line-height: 1.5; }
+
+.site-search { display: flex; align-items: stretch; gap: 0; flex-wrap: wrap; }
+.site-search input[type="search"] {
+ flex: 1 1 12rem; min-width: 0;
+ padding: var(--space-sm) var(--space-md);
+ border: 1px solid var(--accent); background: #000; color: var(--ink, #eee);
+ font: inherit;
+}
+.site-search .header-button { border-radius: 0; }
+
+/* Search engine picker (mirrors /startpage/, scoped vars for site.css). */
+.search-engine-picker { position: relative; display: flex; }
+.search-engine-toggle {
+ background: #000; color: var(--accent); border: 1px solid var(--accent);
+ border-right: none; font-family: inherit; font-weight: bold; font-size: 1rem;
+ padding: 0 14px; cursor: pointer; min-width: 58px;
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
+ transition: .15s;
+}
+.search-engine-toggle:hover { background: var(--accent); color: #000; }
+.search-engine-caret { font-size: 0.7rem; opacity: 0.8; }
+.search-engine-menu {
+ position: absolute; top: calc(100% + 2px); left: 0; min-width: 180px;
+ margin: 0; padding: 0; list-style: none; background: #000;
+ border: 1px solid var(--accent); border-radius: var(--r); z-index: 50;
+ text-align: left; box-shadow: 0 0 10px rgba(var(--glow-color), .4);
+}
+.search-engine-menu[hidden] { display: none; }
+.search-engine-menu li {
+ padding: 8px 12px; color: var(--accent); cursor: pointer;
+ font-family: inherit; font-size: 0.9rem; display: flex; align-items: center; gap: 10px;
+}
+.search-engine-menu li:hover,
+.search-engine-menu li[aria-selected="true"] { background: var(--accent); color: #000; }
+.search-engine-menu .engine-icon { font-weight: bold; min-width: 1.6em; }
+
+/* (#11) YouTube facade — fills the 16:9 .vibe-embed, click to load. */
+.vibe-facade {
+ position: absolute; inset: 0; width: 100%; height: 100%;
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
+ gap: var(--space-sm); cursor: pointer; border: 0;
+ color: var(--neon-green); font-family: var(--font-mono); text-align: center;
+ background:
+ radial-gradient(ellipse at 50% 38%, rgba(var(--link-glow), .1), transparent 58%),
+ radial-gradient(ellipse at 50% 100%, rgba(255, 45, 149, .08), transparent 55%),
+ linear-gradient(180deg, #0c0000 0%, #020000 100%);
+ transition: filter .2s ease, box-shadow .25s ease;
+ box-shadow: inset 0 0 0 1px rgba(var(--link-glow), .18);
+}
+.vibe-facade::before {
+ content: '▌ VIDEO // STANDBY';
+ position: absolute;
+ top: var(--space-sm);
+ left: var(--space-md);
+ font-size: 0.68rem;
+ letter-spacing: 0.12em;
+ color: var(--accent-cyan);
+ opacity: 0.75;
+ text-shadow: 0 0 8px rgba(var(--link-glow), .35);
+}
+.vibe-facade:hover, .vibe-facade:focus-visible {
+ filter: brightness(1.12);
+ outline: none;
+ box-shadow:
+ inset 0 0 0 1px rgba(var(--link-glow), .45),
+ 0 0 20px rgba(var(--link-glow), .22);
+}
+.vibe-facade-play {
+ font-size: 1.65rem; line-height: 1; color: var(--accent-cyan);
+ width: 68px; height: 68px; display: flex; align-items: center; justify-content: center;
+ border-radius: 50%;
+ border: 2px solid var(--accent-cyan);
+ background: rgba(0, 0, 0, .72);
+ box-shadow:
+ 0 0 16px rgba(var(--link-glow), .45),
+ 0 0 32px rgba(255, 45, 149, .18),
+ inset 0 0 14px rgba(var(--link-glow), .12);
+ transition: transform .2s ease, box-shadow .25s ease;
+}
+.vibe-facade:hover .vibe-facade-play,
+.vibe-facade:focus-visible .vibe-facade-play {
+ transform: scale(1.06);
+ box-shadow:
+ 0 0 22px rgba(var(--link-glow), .55),
+ 0 0 40px rgba(224, 64, 251, .28),
+ inset 0 0 18px rgba(var(--link-glow), .18);
+}
+.vibe-facade-text {
+ font-weight: 700;
+ color: #ffd0d0;
+ text-shadow: 0 0 10px rgba(var(--link-glow), .25);
+}
+.vibe-facade-text small {
+ display: block;
+ font-weight: 400;
+ color: var(--muted);
+ font-size: .78em;
+ margin-top: 4px;
+}
+/* Pause overlay: same menu, dimmed so the paused frame shows behind it. */
+.vibe-facade-paused {
+ z-index: 2;
+ background:
+ radial-gradient(circle at 50% 45%, rgba(255,0,0,.18), transparent 60%),
+ rgba(0,0,0,.55);
+}
+.vibe-facade-paused[hidden] { display: none; }
+/* Compact pause overlay for the (short) native audio control bar. */
+.vibe-audio-wrap { position: relative; }
+.vibe-facade-compact {
+ flex-direction: row;
+ gap: var(--space-xs);
+ border-radius: 6px;
+}
+.vibe-facade-compact .vibe-facade-play {
+ width: 34px; height: 34px; font-size: 1.2rem;
+}
+.vibe-facade-compact .vibe-facade-text { font-size: .9em; }
+.vibe-facade-compact .vibe-facade-text small { display: none; }
+
+/* Curated link panel (xahlee.info .lpanelxl style), shown on non-home pages.
+ Docks fixed on the RIGHT (≥1101px) so startpage/search chrome on the left
+ stay clear. --lpanel-w / --lpanel-inset live on body for startpage overrides. */
+body:has(.lpanelxl) {
+ --lpanel-w: 13.75rem; /* ~220px */
+ --lpanel-inset: calc(var(--lpanel-w) + (2 * var(--space-lg)));
+}
+.lpanelxl {
+ margin: var(--space-2xl) 0 0;
+ padding: var(--space-md);
+ max-width: 22rem;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ background: linear-gradient(180deg, rgba(var(--glow-color), .10), var(--bg));
+ font-size: var(--fs-sm);
+ box-sizing: border-box;
+}
+.lpanelxl h3 {
+ margin: 0 0 var(--space-sm);
+ padding-bottom: var(--space-xs);
+ font-size: var(--fs-sm);
+ font-family: var(--font-mono);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ font-weight: 700;
+ color: var(--gold, var(--accent));
+ border-bottom: 1px solid var(--border);
+}
+.lpanelxl ul { list-style: none; margin: 0; padding: 0; }
+.lpanelxl li + li { border-top: 1px dotted var(--border); }
+.lpanelxl a {
+ display: block;
+ padding: 0.4rem 0.35rem;
+ color: var(--accent);
+ text-decoration: none;
+ line-height: 1.35;
+ font-family: var(--font-mono);
+}
+.lpanelxl a:hover, .lpanelxl a:focus-visible {
+ background: rgba(160, 0, 0, .15);
+ text-decoration: underline;
+ outline: none;
+}
+.emojixl { font-size: 1.15em; font-style: normal; }
+
+/* Light-mode polish to match blog.sillylaird.ca's Explore box. */
+body.light-mode .lpanelxl {
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .18), transparent 72%),
+ var(--surface);
+ border-color: var(--border);
+ box-shadow: 0 8px 26px rgba(139, 0, 0, .12);
+}
+body.light-mode .lpanelxl h3 {
+ color: var(--gold);
+ border-bottom-color: var(--border);
+}
+body.light-mode .lpanelxl li + li { border-top-color: var(--border); }
+body.light-mode .lpanelxl a { color: var(--accent); }
+body.light-mode .lpanelxl a:hover { background: var(--accent); color: #ffffff; }
+
+/* Dock Explore as a fixed RIGHT sidebar (≥1101px). Below that, in-flow above footer. */
+@media (min-width: 1101px) {
+ .lpanelxl {
+ position: fixed;
+ top: calc(var(--site-header-height, 76px) + var(--space-md));
+ right: var(--space-lg);
+ left: auto;
+ width: var(--lpanel-w);
+ max-width: none;
+ margin: 0;
+ max-height: calc(100vh - var(--site-header-height, 76px) - (2 * var(--space-md)));
+ overflow-y: auto;
+ overflow-x: hidden;
+ scrollbar-width: thin;
+ z-index: 50;
+ }
+
+ /* Collapse the empty in-flow wrapper that only holds the fixed panel. */
+ body:has(.lpanelxl) .wrap:has(> .lpanelxl:only-child) {
+ margin: 0;
+ padding: 0;
+ max-width: none;
+ height: 0;
+ overflow: visible;
+ }
+
+ /* Keep main content left-aligned; reserve space on the right for the rail.
+ Homepage has no .lpanelxl (uses .home-sidebar instead). */
+ body:has(.lpanelxl) main#main,
+ body:has(.lpanelxl) main.wrap {
+ margin-left: 0;
+ margin-right: var(--lpanel-inset);
+ max-width: calc(100% - var(--lpanel-inset));
+ box-sizing: border-box;
+ }
+ body:has(.lpanelxl) footer.site-footer {
+ margin-left: 0;
+ margin-right: var(--lpanel-inset);
+ }
+ body:has(.lpanelxl) footer.site-footer .footer-inner {
+ margin-left: auto;
+ margin-right: 0;
+ }
+ body:has(.lpanelxl) header.site-header {
+ margin-left: 0;
+ margin-right: var(--lpanel-inset);
+ }
+ body:has(.lpanelxl) header.site-header .header-inner,
+ body:has(.lpanelxl) .header-search-inner {
+ margin-left: auto;
+ margin-right: 0;
+ }
+}
+
+/* Mid laptops: slightly tighter rail so content still breathes. */
+@media (min-width: 1101px) and (max-width: 1280px) {
+ body:has(.lpanelxl) { --lpanel-w: 12.5rem; }
+}
+
+/* (#12) Footer personality. */
+.footer-quote {
+ margin: var(--space-sm) 0 0; padding: var(--space-xs) var(--space-md);
+ border-left: 2px solid var(--accent); font-style: italic; opacity: .9;
+ min-height: 1.5em;
+}
+.footer-joke { margin-top: var(--space-xs); font-size: var(--fs-sm); opacity: .7; }
+.footer-back-to-top {
+ margin: 0 0 var(--space-sm);
+ font-family: var(--font-mono);
+ font-size: var(--fs-xs);
+ letter-spacing: 0.08em;
+}
+.footer-back-to-top a {
+ color: var(--muted);
+}
+.footer-back-to-top a:hover,
+.footer-back-to-top a:focus-visible {
+ color: var(--accent-cyan);
+}
+
+/* Floating control: full rules live in scroll-to-top.css (loaded by the
+ network-wide script). These overrides exist so site.css's global `a:hover`
+ glow/scale cannot leak onto the button while that file is in flight. */
+a.scroll-to-top:hover,
+a.scroll-to-top:focus-visible {
+ background: var(--bg);
+ color: var(--accent-cyan);
+ text-decoration: none;
+ transform: none;
+ border-radius: var(--r);
+}
+.footer-badges {
+ display: flex; flex-wrap: wrap; gap: var(--space-sm);
+ align-items: center; margin-top: var(--space-sm);
+}
+.own-button img {
+ image-rendering: pixelated; border: 1px solid var(--border);
+ transition: transform .15s ease, box-shadow .15s ease;
+}
+.own-button:hover img {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px -4px rgba(var(--glow-color), .7);
+}
+
+/* Homepage Blog + Changelog — two columns side by side (stacks on narrow screens). */
+.updates-groups {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: var(--space-lg) var(--space-xl);
+ margin-top: var(--space-sm);
+}
+
+.updates-group > h3.network-subtitle { margin-top: 0; }
+
+/* 2026-06: Last.fm "Music" nested inside the Current Vibe card. */
+.vibe-music {
+ margin-top: var(--space-lg);
+ padding-top: var(--space-md);
+ border-top: 1px solid var(--border);
+}
+.vibe-music > h3.network-subtitle { margin-top: 0; }
+
+/* 2026-06: structural homepage pass. */
+nav.site-nav[hidden],
+.lang-menu[hidden] {
+ display: none !important;
+}
+
+.header-controls {
+ gap: var(--space-sm);
+}
+
+.header-icon-button {
+ width: 36px;
+ min-width: 36px;
+ height: 36px;
+ padding: 0;
+ font-size: 1.05rem;
+ line-height: 1;
+ transition:
+ background .2s ease,
+ color .2s ease,
+ border-color .2s ease,
+ box-shadow .25s ease,
+ transform .2s ease;
+}
+
+.header-icon-button:hover,
+.header-icon-button:focus-visible {
+ transform: scale(1.06);
+}
+
+.protocol-pills {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 5px;
+}
+
+.protocol-pill {
+ --protocol-accent: var(--accent);
+ display: inline-flex;
+ align-items: center;
+ min-height: 34px;
+ padding: 5px 10px;
+ border: 1px solid var(--protocol-accent);
+ border-radius: 999px;
+ background: rgba(var(--glow-color), .06);
+ color: var(--protocol-accent);
+ font-family: var(--font-mono);
+ font-size: 0.88rem;
+ font-weight: 700;
+ line-height: 1.15;
+ letter-spacing: 0.02em;
+ box-shadow: inset 0 0 10px rgba(var(--glow-color), .08);
+ transition:
+ background .2s ease,
+ color .2s ease,
+ border-color .2s ease,
+ box-shadow .25s ease,
+ transform .15s ease;
+}
+
+.protocol-pill:hover,
+.protocol-pill:focus-visible {
+ background: var(--protocol-accent);
+ color: #050000;
+ text-shadow: none;
+ transform: translateY(-1px);
+ box-shadow:
+ 0 0 0 1px var(--protocol-accent),
+ 0 0 16px color-mix(in srgb, var(--protocol-accent) 65%, transparent);
+}
+
+.protocol-pill--gemini { --protocol-accent: #2fd7b5; }
+.protocol-pill--gopher { --protocol-accent: #d8a735; }
+.protocol-pill--tor { --protocol-accent: #a579d6; }
+.protocol-pill--i2p { --protocol-accent: #ff7b2c; }
+
+.protocol-pill--gemini::before { content: '◇ '; opacity: .85; }
+.protocol-pill--gopher::before { content: '🕳 '; font-size: .9em; }
+.protocol-pill--tor::before { content: '◈ '; opacity: .85; }
+.protocol-pill--i2p::before { content: '◎ '; opacity: .85; }
+
+.home-wrap {
+ max-width: min(1180px, calc(100vw - 2rem));
+ padding-bottom: var(--space-lg);
+}
+
+/* Homepage footer: align the footer-inner with the main content column
+ (offset past the left sidebar) so it starts/ends where the page content does.
+ Below 1100px the home-layout stacks, so footer already lines up. */
+@media (min-width: 1101px) {
+ body:has(.home-wrap) footer.site-footer .footer-inner,
+ body:has(.home-wrap) header.site-header .header-inner,
+ body:has(.home-wrap) .header-search-inner {
+ max-width: min(1180px, calc(100vw - 2rem));
+ padding-left: calc(220px + var(--space-lg) + var(--space-xl));
+ padding-right: var(--space-lg);
+ }
+}
+
+.home-layout {
+ display: grid;
+ grid-template-columns: minmax(180px, 220px) minmax(0, 1fr);
+ gap: var(--space-xl);
+ align-items: start;
+}
+
+.home-content {
+ min-width: 0;
+}
+
+.home-content section,
+.home-content [id] {
+ scroll-margin-top: calc(var(--site-header-height, 76px) + var(--space-lg));
+}
+
+.home-sidebar {
+ position: sticky;
+ top: calc(var(--site-header-height, 76px) + var(--space-lg));
+ max-height: calc(100vh - var(--site-header-height, 76px) - (2 * var(--space-lg)));
+ overflow-y: auto;
+ scrollbar-width: thin;
+ scrollbar-color: color-mix(in srgb, var(--border) 80%, transparent) transparent;
+ opacity: 1;
+ font-size: var(--fs-sm);
+ color: var(--ink);
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-md);
+}
+
+.home-sidebar::-webkit-scrollbar {
+ width: 6px;
+}
+
+.home-sidebar::-webkit-scrollbar-thumb {
+ background: color-mix(in srgb, var(--border) 85%, transparent);
+ border-radius: 3px;
+}
+
+.home-side-nav {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-md) var(--space-md) var(--space-sm);
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .07), transparent 72%),
+ color-mix(in srgb, var(--bg) 88%, #1a0000);
+ box-shadow: var(--glow-soft);
+}
+
+.home-side-nav h2 {
+ margin: 0 0 var(--space-sm);
+ padding-bottom: var(--space-xs);
+ border-bottom: 1px solid color-mix(in srgb, var(--border) 80%, transparent);
+ font-family: var(--font-mono);
+ font-size: var(--fs-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ font-weight: 700;
+ color: var(--gold);
+ opacity: 1;
+}
+
+.home-side-nav a {
+ display: block;
+ padding: 0.45rem 0.4rem;
+ margin: 0 -0.25rem;
+ border-radius: calc(var(--r) - 2px);
+ border-top: 0;
+ color: var(--ink);
+ font-size: var(--fs-sm);
+ font-family: var(--font-mono);
+ line-height: 1.35;
+ text-decoration: none;
+ transition: background 0.12s ease, color 0.12s ease;
+}
+
+.home-side-nav a + a {
+ border-top: 1px solid color-mix(in srgb, var(--border) 45%, transparent);
+}
+
+.home-side-nav a:hover,
+.home-side-nav a:focus-visible {
+ background: color-mix(in srgb, var(--accent) 18%, transparent);
+ color: var(--accent-bright, var(--accent));
+ outline: none;
+}
+
+.home-side-nav a:first-of-type {
+ border-top: 0;
+}
+
+.home-sidebar-widget,
+.home-vibe-widget,
+.home-changelog-widget {
+ margin-top: 0;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-md);
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .1), transparent 72%),
+ #080000;
+ box-shadow: var(--glow-soft);
+ font-size: var(--fs-sm);
+}
+
+.home-sidebar-widget h3,
+.home-vibe-widget h3,
+.home-changelog-widget h3 {
+ margin: 0 0 var(--space-sm);
+ padding-bottom: var(--space-xs);
+ border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
+ font-family: var(--font-mono);
+ font-size: var(--fs-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ font-weight: 700;
+ color: var(--gold);
+}
+
+.home-sidebar-widget p,
+.home-vibe-widget p,
+.home-changelog-widget p {
+ margin: 0 0 var(--space-sm);
+ color: color-mix(in srgb, var(--ink) 88%, var(--muted));
+ font-family: var(--font-prose, inherit);
+ font-size: var(--fs-sm);
+ line-height: 1.55;
+ max-height: none;
+ overflow: visible;
+}
+
+.home-sidebar-widget a,
+.home-vibe-widget a,
+.home-changelog-widget a {
+ display: inline-block;
+ font-weight: 700;
+ color: var(--accent-bright, var(--accent));
+ line-height: 1.4;
+}
+
+.home-sidebar-widget a:hover,
+.home-vibe-widget a:hover,
+.home-changelog-widget a:hover {
+ text-decoration: underline;
+}
+
+.home-sidebar-widget time,
+.home-vibe-widget time,
+.home-changelog-widget time {
+ display: block;
+ margin-top: var(--space-sm);
+ font-family: var(--font-mono);
+ font-size: var(--fs-xs);
+ color: var(--muted);
+ opacity: 1;
+ letter-spacing: 0.02em;
+}
+
+.start-card-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: var(--space-sm);
+}
+
+.start-card {
+ min-height: 132px;
+ display: grid;
+ grid-template-rows: auto auto 1fr;
+ gap: 3px;
+ align-content: start;
+ padding: var(--space-md);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background:
+ linear-gradient(180deg, rgba(var(--glow-color), .05), transparent 70%),
+ #050000;
+ color: var(--accent);
+ transition: transform .2s ease, border-color .2s ease, box-shadow .25s ease;
+ box-shadow: var(--glow-soft);
+}
+
+.start-card:hover,
+.start-card:focus-visible {
+ transform: translateY(-2px) scale(1.03);
+ background: #070000;
+ color: #e0e0e0;
+ border-color: var(--card-accent, var(--accent));
+ text-shadow: none;
+ box-shadow:
+ inset 0 0 24px color-mix(in srgb, var(--card-accent, var(--accent)) 18%, transparent),
+ 0 0 0 1px color-mix(in srgb, var(--card-accent, var(--accent)) 50%, transparent),
+ 0 0 16px color-mix(in srgb, var(--card-accent, var(--accent)) 40%, transparent),
+ 0 8px 28px -8px rgba(0, 0, 0, .65);
+}
+
+.start-card--now { --card-accent: var(--neon-cyan); }
+.start-card--blog { --card-accent: var(--accent-bright); }
+.start-card--journal { --card-accent: var(--neon-green); }
+.start-card--gaming { --card-accent: #c77dff; }
+.start-card--projects { --card-accent: var(--accent-pink); }
+
+.start-card--now,
+.start-card--blog,
+.start-card--journal,
+.start-card--gaming,
+.start-card--projects {
+ border-color: color-mix(in srgb, var(--card-accent) 35%, var(--border));
+}
+
+.start-card--now:hover,
+.start-card--blog:hover,
+.start-card--journal:hover,
+.start-card--gaming:hover,
+.start-card--projects:hover {
+ border-color: var(--card-accent);
+ box-shadow:
+ 0 0 0 1px color-mix(in srgb, var(--card-accent) 55%, transparent),
+ 0 0 12px color-mix(in srgb, var(--card-accent) 45%, transparent),
+ 0 0 28px color-mix(in srgb, var(--card-accent) 25%, transparent);
+}
+
+.start-card-icon {
+ font-family: var(--font-mono);
+ font-size: 1.35rem;
+ line-height: 1;
+ color: var(--card-accent, var(--gold));
+ text-shadow: 0 0 8px color-mix(in srgb, var(--card-accent, var(--gold)) 50%, transparent);
+}
+
+.start-card strong {
+ font-size: var(--fs-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+}
+
+.start-card span:last-child {
+ font-size: var(--fs-sm);
+ color: var(--muted);
+ line-height: 1.35;
+}
+
+.contact-darkweb {
+ margin-top: var(--space-sm);
+ border-top: 1px dotted var(--border);
+ padding-top: var(--space-sm);
+}
+
+.contact-darkweb summary {
+ cursor: pointer;
+ color: var(--accent);
+ font-family: var(--font-mono);
+ font-size: var(--fs-sm);
+}
+
+.contact-list--compact {
+ margin-top: var(--space-xs);
+}
+
+.contact-list--compact li {
+ font-size: var(--fs-sm);
+}
+
+.country-links .country-card {
+ flex-direction: column;
+ gap: var(--space-xs);
+ font-weight: 700;
+}
+
+.country-links .country-emoji {
+ display: block;
+ height: 2.25rem;
+ width: auto;
+ border-radius: 3px;
+ /* Keep light flags (Japan) visible on light surfaces */
+ box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 25%, transparent);
+}
+
+.network-section .network-subsection {
+ border-top-style: solid;
+}
+
+body.light-mode .protocol-pill {
+ background: #ffffff;
+}
+
+body.light-mode .protocol-pill:hover,
+body.light-mode .protocol-pill:focus-visible {
+ color: #ffffff;
+}
+
+body.light-mode .home-side-nav,
+body.light-mode .home-sidebar-widget,
+body.light-mode .home-vibe-widget,
+body.light-mode .home-changelog-widget,
+body.light-mode .home-protocol-nav,
+body.light-mode .start-card {
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .16), transparent 72%),
+ var(--surface);
+}
+
+body.light-mode .home-side-nav a {
+ color: var(--ink);
+}
+
+body.light-mode .home-side-nav a:hover,
+body.light-mode .home-side-nav a:focus-visible {
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
+ color: var(--accent);
+}
+
+body.light-mode .home-sidebar-widget p,
+body.light-mode .home-vibe-widget p,
+body.light-mode .home-changelog-widget p {
+ color: color-mix(in srgb, var(--ink) 92%, var(--muted));
+}
+
+@media (max-width: 1100px) {
+ .home-wrap {
+ max-width: var(--wrap);
+ }
+
+ .home-layout {
+ display: block;
+ }
+
+ .home-sidebar {
+ position: static;
+ max-height: none;
+ overflow: visible;
+ margin: var(--space-lg) 0;
+ gap: var(--space-md);
+ }
+
+ .home-side-nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--space-xs) var(--space-sm);
+ align-items: center;
+ padding: var(--space-md);
+ }
+
+ .home-side-nav h2 {
+ margin: 0 var(--space-sm) 0 0;
+ padding-bottom: 0;
+ border-bottom: 0;
+ width: 100%;
+ }
+
+ .home-side-nav a {
+ border-top: 0;
+ padding: 0.35rem 0.55rem;
+ margin: 0;
+ border: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
+ border-radius: var(--r);
+ background: color-mix(in srgb, var(--bg) 70%, transparent);
+ }
+
+ .home-side-nav a + a {
+ border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
+ }
+}
+
+@media (max-width: 900px) {
+ .protocol-pills {
+ order: 4;
+ flex-basis: 100%;
+ justify-content: flex-end;
+ }
+
+ .start-card-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 600px) {
+ .header-icon-button {
+ width: 34px;
+ min-width: 34px;
+ height: 34px;
+ }
+
+ .protocol-pills {
+ justify-content: flex-start;
+ }
+
+ .protocol-pill {
+ min-height: 32px;
+ padding-inline: 9px;
+ font-size: 0.82rem;
+ }
+
+ .start-card-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .start-card {
+ min-height: 0;
+ }
+}
+
+/* ==========================================================================
+ 2026-07: visual polish pass — hero, textures, badges, panels
+ ========================================================================== */
+
+/* Subtle ambient texture (CSS-only, no image budget hit). */
+body::before {
+ content: '';
+ pointer-events: none;
+ position: fixed;
+ inset: 0;
+ z-index: -1;
+ opacity: 0.55;
+ background:
+ radial-gradient(ellipse 120% 80% at 50% -15%, rgba(160, 0, 0, .07), transparent 52%),
+ radial-gradient(ellipse 85% 65% at 100% 100%, rgba(var(--link-glow), .035), transparent 48%),
+ radial-gradient(ellipse 75% 55% at 0% 85%, rgba(179, 136, 255, .045), transparent 42%);
+}
+
+body.light-mode::before {
+ opacity: 0.5;
+ background:
+ radial-gradient(ellipse at 15% 0%, rgba(210, 180, 140, .18), transparent 50%),
+ radial-gradient(ellipse at 85% 100%, rgba(139, 0, 0, .08), transparent 45%),
+ repeating-linear-gradient(
+ -4deg,
+ transparent,
+ transparent 3px,
+ rgba(120, 90, 60, .04) 3px,
+ rgba(120, 90, 60, .04) 4px
+ );
+}
+
+/* Section titles with accent underline */
+.section-accent-title {
+ position: relative;
+ padding-bottom: var(--space-sm);
+ margin-bottom: var(--space-md) !important;
+ font-size: var(--fs-2xl);
+ font-weight: 800;
+ letter-spacing: 0.02em;
+ text-shadow:
+ 0 0 14px rgba(var(--glow-color), .35),
+ 0 1px 0 rgba(0, 0, 0, .75);
+}
+
+.section-accent-title::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 3px;
+ margin-top: var(--space-xs);
+ border-radius: 2px;
+ background: linear-gradient(90deg, var(--neon-green), var(--accent-cyan), var(--accent-magenta));
+ box-shadow: 0 0 10px color-mix(in srgb, var(--neon-green) 50%, transparent);
+ transition: width .55s cubic-bezier(.4, 0, .2, 1), box-shadow .35s ease;
+}
+
+.section-accent-title.is-drawn::after {
+ width: 3.5rem;
+ animation: neon-underline-pulse 3.5s ease-in-out infinite;
+}
+
+.section-accent-title:hover::after,
+.section-accent-title:focus-within::after {
+ width: 5.5rem;
+ box-shadow:
+ 0 0 8px color-mix(in srgb, var(--neon-green) 55%, transparent),
+ 0 0 18px color-mix(in srgb, var(--accent-cyan) 45%, transparent);
+}
+
+/* Panel heading underlines (sidebar Index / Alt nets / Current vibe / Changelog,
+ and the .lpanelxl panels) used to be a flat red rule. They now carry the same
+ green -> red bar as .section-accent-title / .network-subtitle, expanding on
+ hover. The faint full-width divider stays underneath, matching how
+ .network-subtitle pairs a dotted border with its bar. */
+.home-side-nav h2,
+.home-sidebar-widget h3,
+.home-vibe-widget h3,
+.home-changelog-widget h3,
+.home-protocol-nav h3,
+.lpanelxl h3 {
+ position: relative;
+}
+
+.home-side-nav h2::after,
+.home-sidebar-widget h3::after,
+.home-vibe-widget h3::after,
+.home-changelog-widget h3::after,
+.home-protocol-nav h3::after,
+.lpanelxl h3::after {
+ content: '';
+ display: block;
+ width: 2.5rem;
+ height: 2px;
+ margin-top: var(--space-xs);
+ border-radius: 1px;
+ background: linear-gradient(90deg, var(--neon-green), var(--accent-cyan), var(--accent-magenta));
+ box-shadow: 0 0 8px color-mix(in srgb, var(--neon-green) 45%, transparent);
+ transition: width .5s cubic-bezier(.4, 0, .2, 1), box-shadow .35s ease;
+}
+
+.home-side-nav:hover h2::after,
+.home-sidebar-widget:hover h3::after,
+.home-vibe-widget:hover h3::after,
+.home-changelog-widget:hover h3::after,
+.home-protocol-nav:hover h3::after,
+.lpanelxl:hover h3::after {
+ width: 4.5rem;
+ box-shadow:
+ 0 0 8px color-mix(in srgb, var(--neon-green) 55%, transparent),
+ 0 0 18px color-mix(in srgb, var(--accent-cyan) 45%, transparent);
+}
+
+@keyframes neon-underline-pulse {
+ 0%, 100% {
+ box-shadow: 0 0 8px color-mix(in srgb, var(--neon-green) 45%, transparent);
+ }
+ 50% {
+ box-shadow:
+ 0 0 12px color-mix(in srgb, var(--accent-cyan) 55%, transparent),
+ 0 0 22px color-mix(in srgb, var(--accent-magenta) 25%, transparent);
+ }
+}
+
+body.light-mode .section-accent-title::after,
+body.light-mode .network-subtitle::after {
+ background: linear-gradient(90deg, var(--accent), #5eb8d9);
+ box-shadow: none;
+}
+
+body.light-mode .home-side-nav h2::after,
+body.light-mode .home-sidebar-widget h3::after,
+body.light-mode .home-vibe-widget h3::after,
+body.light-mode .home-changelog-widget h3::after,
+body.light-mode .home-protocol-nav h3::after,
+body.light-mode .lpanelxl h3::after {
+ background: linear-gradient(90deg, var(--accent), #5eb8d9);
+ box-shadow: none;
+}
+
+/* Homepage hero */
+.home-hero {
+ position: relative;
+ margin-bottom: var(--space-lg);
+ padding: var(--space-lg) var(--space-xl);
+ border-radius: var(--r);
+ border: 1px solid color-mix(in srgb, var(--border) 65%, transparent);
+ background: var(--surface-deep);
+ overflow: hidden;
+}
+
+.home-hero::before {
+ content: '';
+ pointer-events: none;
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ z-index: 0;
+ opacity: 0.55;
+ background:
+ radial-gradient(ellipse 115% 85% at 50% 48%, transparent 38%, rgba(0, 0, 0, .5) 100%),
+ repeating-radial-gradient(circle at 17% 29%, rgba(255, 255, 255, .018) 0 1px, transparent 1px 3px),
+ repeating-radial-gradient(circle at 73% 61%, rgba(var(--link-glow), .012) 0 1px, transparent 1px 4px);
+}
+
+.home-hero::after {
+ content: '';
+ pointer-events: none;
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ background: radial-gradient(ellipse at center, transparent 42%, rgba(0, 0, 0, .62) 100%);
+ z-index: 0;
+}
+
+.home-hero > * {
+ position: relative;
+ z-index: 1;
+}
+
+.home-hero-brand {
+ display: flex;
+ align-items: flex-start;
+ gap: var(--space-lg);
+ margin-bottom: var(--space-md);
+}
+
+.home-hero-avatar-wrap {
+ position: relative;
+ flex-shrink: 0;
+ line-height: 0;
+}
+
+.home-hero-avatar {
+ display: block;
+ width: 72px;
+ height: 72px;
+ max-width: 72px;
+ object-fit: contain;
+ border: 2px solid color-mix(in srgb, var(--accent-cyan) 35%, var(--border));
+ border-radius: var(--r);
+ background: #070000;
+ image-rendering: pixelated;
+ box-shadow:
+ 0 0 0 1px rgba(var(--glow-color), .35),
+ 0 0 6px rgba(var(--link-glow), .55),
+ 0 0 16px rgba(224, 64, 251, .35),
+ 0 0 32px rgba(var(--glow-color), .45),
+ 0 0 48px -8px rgba(179, 136, 255, .25),
+ inset 0 0 14px rgba(var(--link-glow), .12);
+ filter: drop-shadow(0 0 4px rgba(var(--link-glow), .35));
+ animation: avatar-idle-pulse 2.2s ease-in-out infinite;
+ transition: transform .25s ease, box-shadow .35s ease, border-color .25s ease, filter .25s ease;
+}
+
+@keyframes avatar-idle-pulse {
+ 0%, 100% {
+ filter: drop-shadow(0 0 4px rgba(var(--link-glow), .32)) brightness(1);
+ }
+ 50% {
+ filter: drop-shadow(0 0 9px rgba(224, 64, 251, .42)) brightness(1.06) hue-rotate(8deg);
+ }
+}
+
+.home-hero-brand:hover .home-hero-avatar,
+.home-hero-avatar-wrap:hover .home-hero-avatar {
+ transform: translateY(-2px) rotate(-2deg) scale(1.04);
+ border-color: var(--accent-cyan);
+ filter: drop-shadow(0 0 8px rgba(var(--link-glow), .6));
+ animation: avatar-glitch .45s steps(2) 1;
+ box-shadow:
+ 0 0 0 1px rgba(var(--link-glow), .65),
+ 0 0 10px rgba(var(--link-glow), .75),
+ 0 0 22px rgba(224, 64, 251, .5),
+ 0 0 40px rgba(var(--glow-color), .55),
+ 0 0 60px -6px rgba(179, 136, 255, .4),
+ inset 0 0 20px rgba(var(--link-glow), .18);
+}
+
+.home-hero-text {
+ min-width: 0;
+ flex: 1;
+}
+
+.home-hero h1 {
+ padding-left: 0;
+ margin-bottom: var(--space-xs);
+ font-weight: 800;
+ text-shadow:
+ 0 0 12px rgba(var(--glow-color), .45),
+ 0 0 28px rgba(179, 136, 255, .2),
+ 0 1px 0 rgba(0, 0, 0, .8);
+ transition: text-shadow .25s ease;
+}
+
+.home-hero h1:hover {
+ animation: title-glitch .4s steps(2) 1;
+ text-shadow:
+ 0 0 14px rgba(var(--link-glow), .5),
+ 0 0 24px rgba(224, 64, 251, .35),
+ 2px 0 0 rgba(255, 45, 149, .5),
+ -2px 0 0 rgba(var(--link-glow), .5);
+}
+
+.home-tagline {
+ margin: 0;
+ font-family: var(--font-mono);
+ font-size: var(--fs-base);
+ font-weight: 600;
+ color: var(--neon-green);
+ letter-spacing: 0.04em;
+ min-height: 1.6em;
+ text-shadow: 0 0 10px rgba(57, 255, 20, .28);
+}
+
+.home-tagline-text {
+ display: inline;
+ animation: tagline-glitch-in .85s ease-out both;
+ animation-delay: .25s;
+}
+
+.home-tagline-text::after {
+ content: '█';
+ margin-left: 3px;
+ font-size: .92em;
+ animation: tagline-cursor 1s step-end infinite;
+ color: var(--accent-cyan);
+ text-shadow: 0 0 8px rgba(var(--link-glow), .65);
+}
+
+@keyframes tagline-cursor {
+ 0%, 49% { opacity: 1; }
+ 50%, 100% { opacity: 0; }
+}
+
+@keyframes tagline-glitch-in {
+ 0% {
+ opacity: 0;
+ transform: translateX(-6px);
+ filter: hue-rotate(60deg) saturate(1.4);
+ }
+ 25% {
+ opacity: .85;
+ transform: translateX(3px);
+ }
+ 45% {
+ transform: translateX(-2px);
+ filter: none;
+ }
+ 100% {
+ opacity: 1;
+ transform: none;
+ }
+}
+
+@keyframes avatar-glitch {
+ 0%, 100% { clip-path: inset(0 0 0 0); }
+ 33% { clip-path: inset(12% 0 58% 0); filter: hue-rotate(25deg) drop-shadow(0 0 8px rgba(var(--link-glow), .6)); }
+ 66% { clip-path: inset(48% 0 8% 0); filter: hue-rotate(-15deg) drop-shadow(0 0 8px rgba(var(--link-glow), .6)); }
+}
+
+@keyframes title-glitch {
+ 0%, 100% { transform: none; }
+ 25% { transform: translate(-2px, 1px); }
+ 50% { transform: translate(2px, -1px); }
+ 75% { transform: translate(-1px, 0); }
+}
+
+body.light-mode .home-tagline {
+ color: var(--accent);
+}
+
+body.light-mode .home-tagline-text::after {
+ color: #8b0000;
+}
+
+.home-welcome {
+ max-width: 62ch;
+}
+
+.home-content > section > p,
+.home-content .muted {
+ max-width: 65ch;
+}
+
+.home-content > section + section {
+ margin-top: var(--space-2xl);
+}
+
+/* Start cards — larger icons in badge circles */
+.start-card-grid {
+ gap: var(--space-md);
+}
+
+.start-card {
+ min-height: 148px;
+ padding: var(--space-lg);
+ gap: var(--space-xs);
+}
+
+.start-card-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.5rem;
+ height: 2.5rem;
+ font-size: 1.35rem;
+ border-radius: 50%;
+ background: color-mix(in srgb, var(--card-accent, var(--gold)) 12%, transparent);
+ border: 1px solid color-mix(in srgb, var(--card-accent, var(--gold)) 35%, var(--border));
+ text-shadow: none;
+ transition: transform .2s ease, box-shadow .25s ease;
+}
+
+.start-card:hover .start-card-icon,
+.start-card:focus-visible .start-card-icon {
+ transform: scale(1.08);
+ box-shadow: 0 0 14px color-mix(in srgb, var(--card-accent, var(--gold)) 45%, transparent);
+}
+
+.start-card strong {
+ font-size: var(--fs-base);
+}
+
+/* Updates panels — card + timeline feel */
+.updates-panel {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-md) var(--space-lg);
+ background:
+ linear-gradient(180deg, rgba(var(--glow-color), .05), transparent 65%),
+ #050000;
+ box-shadow: var(--glow-soft);
+ transition: border-color .25s ease, box-shadow .25s ease;
+}
+
+.updates-panel:hover {
+ border-color: rgba(var(--glow-color), .35);
+}
+
+.updates-panel--changelog .recent-list li {
+ border-left-color: var(--neon-cyan);
+}
+
+body.light-mode .updates-panel {
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .14), transparent 72%),
+ var(--surface);
+}
+
+.home-content .recent-list li {
+ margin: var(--space-md) 0;
+ padding: var(--space-sm) var(--space-md);
+ padding-left: var(--space-md);
+ border-left: 3px solid var(--accent);
+ border-radius: 0 var(--r) var(--r) 0;
+ background: rgba(var(--glow-color), .04);
+ transition: transform .15s ease, background .15s ease, border-color .15s ease;
+}
+
+.home-content .recent-list li:hover {
+ transform: translateX(4px);
+ background: rgba(var(--glow-color), .08);
+ border-left-color: var(--neon-green);
+}
+
+/* Guestbook card panels */
+.guestbook-panels {
+ gap: var(--space-lg);
+}
+
+.guestbook-panel {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-md);
+ background:
+ linear-gradient(180deg, rgba(var(--glow-color), .04), transparent 70%),
+ #050000;
+ box-shadow: var(--glow-soft);
+}
+
+.guestbook-panel-head {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ gap: var(--space-sm);
+ margin-bottom: var(--space-sm);
+ padding-bottom: var(--space-sm);
+ border-bottom: 1px dashed var(--border);
+}
+
+.guestbook-panel-title {
+ margin: 0;
+ font-size: var(--fs-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.6px;
+ color: var(--gold);
+}
+
+body.light-mode .guestbook-panel,
+body.light-mode .updates-panel {
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .12), transparent 72%),
+ var(--surface);
+}
+
+/* Unified link-badge styling for friends + countries */
+.friends-list a,
+.country-links .country-card,
+.tile-grid a {
+ border-radius: var(--r);
+ transition:
+ background .2s ease,
+ color .2s ease,
+ border-color .2s ease,
+ box-shadow .25s ease,
+ transform .2s ease;
+}
+
+.friends-list a:hover,
+.friends-list a:focus-visible,
+.country-links .country-card:hover,
+.country-links .country-card:focus-visible {
+ transform: translateY(-2px);
+ box-shadow:
+ 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent),
+ 0 4px 14px -4px rgba(var(--glow-color), .55);
+}
+
+.country-links .country-card {
+ min-height: 96px;
+ background:
+ linear-gradient(180deg, rgba(var(--glow-color), .06), transparent 70%),
+ var(--bg);
+}
+
+body.light-mode .country-links .country-card {
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .14), transparent 72%),
+ var(--surface);
+}
+
+/* Network grids — uniform scan-friendly layout */
+.network-section .tile-grid {
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: var(--space-md);
+}
+
+.network-section .tile-grid--three {
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+}
+
+.network-subtitle {
+ font-size: var(--fs-lg);
+ padding-bottom: var(--space-xs);
+ border-bottom: 1px dotted var(--border);
+ position: relative;
+}
+
+.network-subtitle::after {
+ content: '';
+ display: block;
+ width: 0;
+ height: 2px;
+ margin-top: var(--space-xs);
+ border-radius: 1px;
+ /* Same green -> red sweep as .section-accent-title, so every expanding bar on
+ the page reads as one family. The lead stop is always --neon-green: the
+ per-subsection --section-neon values for friends (--accent-cyan #8b0000),
+ countries (--accent-purple #5a0000) and buttons (--accent-pink #a00000) are
+ all dark reds, so leading with them left those three bars red-only. */
+ background: linear-gradient(90deg, var(--neon-green), var(--accent-cyan), var(--accent-magenta));
+ box-shadow: 0 0 8px color-mix(in srgb, var(--neon-green) 45%, transparent);
+ transition: width .5s cubic-bezier(.4, 0, .2, 1), box-shadow .35s ease;
+}
+
+.network-subtitle.is-drawn::after {
+ width: 4.5rem;
+}
+
+.network-subtitle:hover::after,
+.network-subtitle:focus-within::after {
+ width: 6.5rem;
+ box-shadow:
+ 0 0 8px color-mix(in srgb, var(--neon-green) 55%, transparent),
+ 0 0 18px color-mix(in srgb, var(--accent-cyan) 45%, transparent);
+}
+
+/* Footer small-web badge */
+.footer-smallweb {
+ margin: var(--space-sm) 0 0;
+ font-family: var(--font-mono);
+ font-size: var(--fs-xs);
+ letter-spacing: 0.04em;
+ color: var(--muted);
+ opacity: 0.85;
+}
+
+.footer-smallweb::before {
+ content: '◈ ';
+ color: var(--neon-cyan);
+}
+
+body.light-mode .footer-smallweb::before {
+ color: var(--accent);
+}
+
+/* Light mode: stronger link contrast on protocol pills */
+body.light-mode .protocol-pill {
+ background: var(--surface);
+ color: color-mix(in srgb, var(--protocol-accent) 85%, #2a1212);
+ border-color: color-mix(in srgb, var(--protocol-accent) 55%, var(--border));
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .home-tagline-text::after {
+ animation: none;
+ }
+
+ .home-hero-brand:hover .home-hero-avatar,
+ .home-hero-avatar-wrap:hover .home-hero-avatar,
+ .start-card:hover .start-card-icon,
+ .protocol-pill:hover,
+ .header-icon-button:hover {
+ transform: none;
+ animation: none;
+ }
+}
+
+@media (max-width: 600px) {
+ .home-hero {
+ padding: var(--space-md) var(--space-lg);
+ }
+
+ .home-hero-brand {
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ }
+
+ .home-hero-avatar {
+ width: min(64px, 20vw);
+ height: min(64px, 20vw);
+ max-width: 64px;
+ object-fit: contain;
+ }
+
+ .home-hero h1 {
+ font-size: var(--fs-xl);
+ }
+
+ .home-tagline-text {
+ white-space: normal;
+ }
+}
+
+/* ==========================================================================
+ 2026-07 polish pass — comfort spacing, retro loaders, terminal cursor
+ ========================================================================== */
+
+/* Comfortable line spacing toggle (site-wide + startpage) */
+.header-icon-button.comfort-toggle {
+ width: 36px;
+ min-width: 36px;
+ height: 36px;
+ padding: 0;
+ font-size: 1rem;
+ line-height: 1;
+}
+
+.comfort-toggle {
+ padding: 5px 10px;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--surface-deep, #050505);
+ color: var(--accent);
+ font-family: var(--font-mono);
+ font-size: 0.78rem;
+ font-weight: 600;
+ line-height: 1.2;
+ cursor: pointer;
+ transition:
+ background .2s ease,
+ color .2s ease,
+ border-color .2s ease,
+ box-shadow .25s ease,
+ transform .15s ease;
+}
+
+.comfort-toggle:hover,
+.comfort-toggle:focus-visible {
+ background: var(--accent);
+ color: #ffd0d0;
+ outline: none;
+ transform: scale(1.04);
+ box-shadow: 0 0 12px rgba(var(--link-glow), .35);
+}
+
+.comfort-toggle[aria-pressed="true"] {
+ border-color: var(--neon-green);
+ color: var(--neon-green);
+ box-shadow: 0 0 12px rgba(57, 255, 20, .28);
+}
+
+body.comfort-spacing,
+body.startpage-comfort {
+ line-height: var(--lh-comfort);
+}
+
+body.comfort-spacing p,
+body.comfort-spacing .home-content section > p,
+body.comfort-spacing .home-welcome,
+body.comfort-spacing .card-body,
+body.startpage-comfort .card-body {
+ line-height: var(--lh-comfort);
+}
+
+body.comfort-spacing .friends-list,
+body.comfort-spacing .card-links,
+body.startpage-comfort .card-links {
+ gap: calc(var(--space-lg) + 4px);
+}
+
+body.comfort-spacing .friends-list a,
+body.comfort-spacing .card-links a,
+body.startpage-comfort .card-links a {
+ line-height: 1.7;
+ padding-block: 6px;
+}
+
+body.light-mode .comfort-toggle {
+ background: var(--surface);
+ color: var(--accent);
+ border-color: var(--border);
+}
+
+body.light-mode .comfort-toggle:hover {
+ background: var(--accent);
+ color: #fff;
+}
+
+body.light-mode .comfort-toggle[aria-pressed="true"] {
+ color: #1a6b8f;
+ border-color: var(--accent);
+}
+
+/* Retro terminal loading indicator */
+.retro-loading {
+ font-family: var(--font-mono);
+ font-size: var(--fs-sm);
+ color: var(--neon-green);
+ text-shadow: 0 0 6px rgba(57, 255, 20, .4);
+}
+
+.retro-loading::after {
+ content: '_';
+ margin-left: 2px;
+ color: var(--accent-cyan);
+ animation: retro-cursor-blink 1s step-end infinite;
+}
+
+/* Button / pill hover — magenta glow (distinct from link cyan) */
+button:hover,
+button:focus-visible,
+.header-button:hover,
+.header-button:focus-visible,
+.reset-button:hover,
+.reset-button:focus-visible,
+.button-primary:hover,
+.button-primary:focus-visible,
+.mini-button:hover,
+.mini-button:focus-visible,
+.protocol-pill:hover,
+.protocol-pill:focus-visible {
+ box-shadow:
+ 0 0 0 1px rgba(var(--btn-glow), .5),
+ 0 0 14px rgba(var(--btn-glow), .45),
+ 0 0 28px -6px rgba(var(--btn-glow), .3);
+}
+
+.protocol-pill:hover,
+.protocol-pill:focus-visible {
+ transform: translateY(-2px) scale(1.04);
+}
+
+/* Dark mode: deeper terminal atmosphere */
+body:not(.light-mode)::before {
+ opacity: 0.45;
+ background:
+ repeating-linear-gradient(
+ 0deg,
+ rgba(0, 0, 0, 0.09) 0,
+ rgba(0, 0, 0, 0.09) 1px,
+ transparent 1px,
+ transparent 3px
+ );
+}
+
+/* Network section: even grid columns, more breathing room */
+.network-section .tile-grid {
+ grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
+ gap: var(--space-lg);
+}
+
+.network-section .tile-grid--three {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+}
+
+@media (max-width: 720px) {
+ .network-section .tile-grid--three {
+ grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
+ }
+}
+
+/* Vibe / YouTube embed loading overlay */
+.vibe-embed {
+ position: relative;
+}
+
+.vibe-embed.is-loading::before {
+ content: "loading...";
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(2, 2, 2, 0.92);
+ color: var(--neon-green);
+ font-family: var(--font-mono);
+ font-size: var(--fs-sm);
+ text-shadow: 0 0 8px rgba(57, 255, 20, .45);
+ z-index: 2;
+}
+
+.vibe-embed.is-loading::after {
+ content: "_";
+ position: absolute;
+ top: 50%;
+ left: calc(50% + 2.8em);
+ transform: translateY(-50%);
+ color: var(--accent-cyan);
+ font-family: var(--font-mono);
+ font-weight: 700;
+ animation: retro-cursor-blink 1s step-end infinite;
+ z-index: 3;
+}
+
+body.light-mode .vibe-embed.is-loading::before {
+ background: rgba(255, 248, 241, 0.94);
+ color: var(--accent);
+ text-shadow: none;
+}
+
+/* Terminal block cursor on fine-pointer devices */
+@media (hover: hover) and (pointer: fine) {
+ a,
+ button,
+ summary,
+ .start-card,
+ .protocol-pill,
+ .comfort-toggle,
+ .header-button,
+ .reset-button,
+ .ctx-item,
+ .search-engine-menu li,
+ label[for],
+ input[type="checkbox"],
+ input[type="radio"],
+ select {
+ cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='20' viewBox='0 0 16 20'%3E%3Crect x='6' y='0' width='4' height='14' fill='%23ff1a1a'/%3E%3Crect x='4' y='14' width='8' height='4' fill='%238b0000'/%3E%3C/svg%3E") 8 10, pointer;
+ }
+}
+
+/* Light mode: soft paper grain (no scanlines). */
+body.light-mode::after {
+ content: '';
+ pointer-events: none;
+ position: fixed;
+ inset: 0;
+ z-index: -2;
+ opacity: 0.35;
+ background:
+ radial-gradient(ellipse at 20% 10%, rgba(210, 180, 140, .12), transparent 55%),
+ radial-gradient(ellipse at 80% 90%, rgba(139, 0, 0, .06), transparent 50%);
+ animation: none;
+}
+
+/* Start cards: persistent inner edge glow */
+.start-card {
+ box-shadow:
+ var(--glow-soft),
+ inset 0 0 0 1px rgba(var(--glow-color), .08);
+}
+
+.start-card strong {
+ text-shadow: 0 0 8px rgba(var(--glow-color), .2);
+}
+
+body.light-mode .home-hero {
+ background:
+ radial-gradient(ellipse at 20% 0%, rgba(139, 0, 0, .12), transparent 55%),
+ radial-gradient(ellipse at 80% 100%, rgba(210, 180, 140, .1), transparent 50%),
+ var(--surface);
+ border-color: var(--border);
+}
+
+body.light-mode .home-hero::after {
+ background: radial-gradient(ellipse at center, transparent 50%, rgba(92, 72, 52, .08) 100%);
+}
+
+body.light-mode .home-hero h1 {
+ text-shadow: 0 1px 0 rgba(255, 255, 255, .8);
+}
+
+body.light-mode .section-accent-title {
+ text-shadow: none;
+}
+
+/* ==========================================================================
+ 2026-07 content & UX pass — widgets, search, tiles, easter egg
+ ========================================================================== */
+
+.header-button--startpage {
+ font-weight: 700;
+ border-color: color-mix(in srgb, var(--accent-cyan) 45%, var(--border));
+ color: var(--accent-cyan);
+}
+
+.header-button--startpage:hover,
+.header-button--startpage:focus-visible {
+ background: color-mix(in srgb, var(--accent-cyan) 18%, var(--accent));
+ color: #ffd0d0;
+}
+
+.nav-link--featured {
+ font-weight: 700;
+ color: var(--accent-cyan) !important;
+}
+
+.site-search-hits {
+ list-style: none;
+ margin: var(--space-xs) 0 0;
+ padding: 0;
+ width: 100%;
+ max-width: none;
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background: var(--surface-deep, #050505);
+ box-shadow: var(--glow-soft);
+ overflow: hidden;
+}
+
+.site-search-hits li a {
+ display: block;
+ padding: 8px 12px;
+ border-top: 1px dotted var(--border);
+}
+
+.site-search-hits li:first-child a {
+ border-top: 0;
+}
+
+.media-widgets {
+ display: grid;
+ gap: var(--space-md);
+ margin-bottom: var(--space-md);
+}
+
+@media (min-width: 640px) {
+ .media-widgets {
+ grid-template-columns: 1fr 1fr;
+ }
+}
+
+.lastfm-now-widget,
+.radio-now-widget {
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ padding: var(--space-md);
+ background: linear-gradient(180deg, rgba(var(--glow-color), .05), transparent 70%), var(--surface-deep, #050505);
+ min-height: 5.5rem;
+}
+
+.lastfm-track--featured {
+ margin: 0;
+}
+
+.media-subheading {
+ margin: var(--space-md) 0 var(--space-xs);
+ font-size: var(--fs-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--gold);
+}
+
+.media-profile-link {
+ margin-bottom: var(--space-sm);
+}
+
+.guestbook-panel--comments {
+ order: -1;
+}
+
+.guestbook-comments-inline {
+ gap: var(--space-sm);
+ max-height: 28rem;
+ overflow-y: auto;
+}
+
+.guestbook-panel--form summary {
+ cursor: pointer;
+ font-weight: 700;
+ color: var(--gold);
+ padding: var(--space-sm) 0;
+}
+
+.guestbook-panel--form[open] summary {
+ margin-bottom: var(--space-sm);
+}
+
+.tile-grid--labeled .tile-card {
+ flex-direction: column;
+ gap: var(--space-xs);
+ min-height: 7.5rem;
+ padding: var(--space-sm);
+ background: var(--surface-deep, #050505);
+}
+
+.tile-grid--labeled .tile-label {
+ font-family: var(--font-mono);
+ font-size: var(--fs-xs);
+ font-weight: 700;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--accent-cyan);
+}
+
+.tile-card--game:hover .tile-label {
+ color: var(--neon-green);
+}
+
+.tile-card--button:hover .tile-label {
+ color: var(--accent-pink);
+}
+
+.network-subsection#games .tile-card {
+ border-color: color-mix(in srgb, var(--neon-green) 25%, var(--border));
+}
+
+.network-subsection#buttons .tile-card {
+ border-color: color-mix(in srgb, var(--accent-pink) 25%, var(--border));
+}
+
+.home-hero-avatar-wrap {
+ border: 0;
+ padding: 0;
+ background: transparent;
+ cursor: pointer;
+ border-radius: var(--r);
+}
+
+.home-hero-avatar-wrap.is-lain-pulse .home-hero-avatar {
+ animation: avatar-glitch .5s steps(2) 1;
+}
+
+.lain-toast {
+ position: fixed;
+ left: 50%;
+ bottom: 1.5rem;
+ transform: translateX(-50%) translateY(12px);
+ z-index: 10001;
+ padding: 10px 16px;
+ border: 1px solid var(--accent-cyan);
+ border-radius: var(--r);
+ background: rgba(2, 2, 2, .92);
+ color: var(--neon-green);
+ font-family: var(--font-mono);
+ font-size: var(--fs-sm);
+ box-shadow: 0 0 18px rgba(var(--link-glow), .35);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity .25s ease, transform .25s ease;
+}
+
+.lain-toast.is-visible {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+}
+
+.site-visit-counter {
+ margin: var(--space-sm) 0 0;
+ font-family: var(--font-mono);
+ font-size: var(--fs-xs);
+ letter-spacing: 0.08em;
+ color: var(--muted);
+}
+
+.site-visit-counter-digits {
+ color: var(--neon-green);
+ text-shadow: 0 0 6px rgba(57, 255, 20, .35);
+}
+
+body.light-mode .site-search-hits,
+body.light-mode .lastfm-now-widget,
+body.light-mode .radio-now-widget {
+ background: var(--surface);
+}
+
+@media (max-width: 600px) {
+ .header-controls > a.header-button--startpage {
+ display: inline-flex !important;
+ font-size: 0.78rem;
+ padding-inline: 8px;
+ }
+}
+
+/* ==========================================================================
+ 2026-07 polish pass II — glows, BBS guestbook, placeholders
+ ========================================================================== */
+
+.mono-terminal {
+ font-family: var(--font-mono);
+ font-feature-settings: "calt" 1, "liga" 1, "zero" 1;
+ font-variant-ligatures: contextual;
+}
+
+.home-tagline,
+.protocol-pill,
+.tile-label,
+.footer-smallweb,
+.site-visit-counter,
+.retro-loading,
+.guestbook-entry strong,
+.network-subtitle,
+code,
+pre,
+kbd {
+ font-feature-settings: "calt" 1, "liga" 1, "zero" 1;
+ font-variant-ligatures: contextual;
+}
+
+#theme-toggle {
+ transition: box-shadow .35s ease, transform .15s ease, background .2s ease;
+}
+
+#theme-toggle:hover,
+#theme-toggle:focus-visible {
+ box-shadow:
+ 0 0 10px rgba(255, 200, 80, .5),
+ 0 0 22px rgba(255, 45, 149, .28);
+}
+
+body.light-mode #theme-toggle {
+ box-shadow: 0 0 8px rgba(160, 0, 0, .32);
+}
+
+body.light-mode .home-hero-avatar {
+ filter: sepia(.12) contrast(1.04) saturate(.92);
+ animation: avatar-idle-pulse-light 2.4s ease-in-out infinite;
+}
+
+@keyframes avatar-idle-pulse-light {
+ 0%, 100% { filter: sepia(.12) contrast(1.04) saturate(.92); }
+ 50% { filter: sepia(.06) contrast(1.08) saturate(1.05) hue-rotate(-6deg); }
+}
+
+body.light-mode .home-hero-brand:hover .home-hero-avatar,
+body.light-mode .home-hero-avatar-wrap:hover .home-hero-avatar {
+ filter: contrast(1.1) drop-shadow(0 0 6px rgba(47, 111, 145, .45));
+ animation: none;
+}
+
+.protocol-pill {
+ box-shadow:
+ inset 0 0 10px rgba(var(--glow-color), .08),
+ 0 0 0 0 transparent;
+}
+
+.protocol-pill:hover,
+.protocol-pill:focus-visible {
+ box-shadow:
+ 0 0 0 1px var(--protocol-accent),
+ 0 0 8px color-mix(in srgb, var(--protocol-accent) 55%, transparent),
+ 0 0 16px color-mix(in srgb, var(--protocol-accent) 35%, transparent);
+}
+
+.lastfm-now-widget,
+.radio-now-widget {
+ transition: border-color .2s ease, box-shadow .25s ease;
+}
+
+.lastfm-now-widget:hover,
+.lastfm-now-widget:focus-within,
+.radio-now-widget:hover,
+.radio-now-widget:focus-within {
+ border-color: color-mix(in srgb, var(--accent-cyan) 55%, var(--border));
+ box-shadow:
+ 0 0 8px rgba(var(--link-glow), .32),
+ inset 0 0 14px rgba(var(--link-glow), .06);
+}
+
+.lastfm-track:hover,
+.lastfm-track:focus-visible {
+ box-shadow: 0 0 8px rgba(var(--link-glow), .22);
+}
+
+.guestbook-comments-inline .guestbook-entry {
+ border-left: 3px solid var(--accent-cyan);
+ background:
+ linear-gradient(135deg, rgba(var(--link-glow), .04), transparent 55%),
+ repeating-linear-gradient(
+ -3deg,
+ transparent,
+ transparent 4px,
+ rgba(255, 255, 255, .012) 4px,
+ rgba(255, 255, 255, .012) 5px
+ ),
+ var(--bg);
+ font-family: var(--font-mono);
+}
+
+.guestbook-comments-inline .guestbook-entry strong {
+ font-family: var(--font-mono);
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ font-size: var(--fs-xs);
+ color: var(--accent-cyan);
+ text-shadow: 0 0 6px rgba(var(--link-glow), .25);
+}
+
+.guestbook-comments-inline .guestbook-entry time {
+ font-family: var(--font-mono);
+ font-size: 0.72rem;
+ opacity: 0.75;
+}
+
+.guestbook-comments-inline .guestbook-entry:hover {
+ border-color: var(--accent-cyan);
+ box-shadow:
+ 0 0 8px rgba(var(--link-glow), .28),
+ inset 0 0 0 1px rgba(var(--link-glow), .12);
+}
+
+.guestbook-comments-inline .guestbook-entry p {
+ font-family: inherit;
+ color: var(--ink) !important;
+}
+
+.footer-heart {
+ display: inline-block;
+ color: var(--accent-pink);
+ text-shadow: 0 0 8px rgba(255, 45, 149, .45);
+ animation: footer-heart-pulse 1.8s ease-in-out infinite;
+}
+
+@keyframes footer-heart-pulse {
+ 0%, 100% { transform: scale(1); opacity: 1; }
+ 35% { transform: scale(1.12); opacity: .92; }
+ 55% { transform: scale(.96) translateX(1px); opacity: .85; filter: hue-rotate(20deg); }
+ 70% { transform: scale(1.04); opacity: 1; }
+}
+
+.home-content > section + section {
+ position: relative;
+}
+
+.home-content > section + section::before {
+ content: '';
+ display: block;
+ height: 1px;
+ margin: calc(-1 * var(--space-xl)) 0 var(--space-xl);
+ background: linear-gradient(
+ 90deg,
+ transparent,
+ rgba(var(--link-glow), .22) 20%,
+ rgba(57, 255, 20, .18) 50%,
+ rgba(224, 64, 251, .15) 80%,
+ transparent
+ );
+ opacity: 0;
+ transform: scaleX(0);
+ transform-origin: left center;
+ transition: opacity .4s ease, transform .55s cubic-bezier(.4, 0, .2, 1);
+}
+
+.home-content > section.is-drawn + section::before,
+.home-content > section + section.is-drawn::before {
+ opacity: 1;
+ transform: scaleX(1);
+}
+
+body.light-mode .guestbook-comments-inline .guestbook-entry {
+ background:
+ linear-gradient(135deg, rgba(139, 0, 0, .07), transparent 55%),
+ repeating-linear-gradient(
+ -3deg,
+ transparent,
+ transparent 4px,
+ rgba(92, 72, 52, .04) 4px,
+ rgba(92, 72, 52, .04) 5px
+ ),
+ var(--surface);
+ border-left-color: color-mix(in srgb, var(--accent) 55%, #8b0000);
+}
+
+body.light-mode .guestbook-comments-inline .guestbook-entry strong {
+ color: color-mix(in srgb, var(--accent) 80%, #3a0000);
+ text-shadow: none;
+}
+
+@media (max-width: 600px) {
+ .home-wrap {
+ max-width: calc(100vw - 1rem);
+ padding-left: var(--space-sm);
+ padding-right: var(--space-sm);
+ }
+
+ .home-sidebar {
+ margin: var(--space-md) 0;
+ }
+
+ .home-side-nav {
+ padding: var(--space-sm);
+ gap: var(--space-xs) var(--space-sm);
+ }
+
+ .network-section .tile-grid,
+ .network-section .tile-grid--three {
+ gap: var(--space-sm);
+ }
+
+ .guestbook-comments-inline {
+ max-height: 22rem;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .section-accent-title::after,
+ .retro-loading::after,
+ .iframe-clip.is-loading::after,
+ .home-tagline-text,
+ .home-tagline-text::after,
+ .site-footer .footer-inner::after,
+ .footer-heart,
+ .home-hero-avatar {
+ animation: none;
+ }
+
+ .section-accent-title.is-drawn::after,
+ .network-subtitle.is-drawn::after {
+ width: 3.5rem;
+ }
+
+ /* Panel heading bars: keep them at rest, no grow-on-hover. */
+ .home-side-nav h2::after,
+ .home-sidebar-widget h3::after,
+ .home-vibe-widget h3::after,
+ .home-changelog-widget h3::after,
+ .home-protocol-nav h3::after,
+ .lpanelxl h3::after,
+ .home-side-nav:hover h2::after,
+ .home-sidebar-widget:hover h3::after,
+ .home-vibe-widget:hover h3::after,
+ .home-changelog-widget:hover h3::after,
+ .home-protocol-nav:hover h3::after,
+ .lpanelxl:hover h3::after {
+ width: 2.5rem;
+ transition: none;
+ }
+
+ .network-subtitle:hover::after,
+ .network-subtitle:focus-within::after {
+ width: 4.5rem;
+ transition: none;
+ }
+
+ .home-hero h1:hover,
+ .home-hero-brand:hover .home-hero-avatar,
+ .home-hero-avatar-wrap:hover .home-hero-avatar {
+ animation: none;
+ }
+
+ a:hover,
+ a:focus-visible,
+ .tile-grid a:hover,
+ .friends-list a:hover,
+ .start-card:hover {
+ transform: none;
+ }
+}
+
+/* ==========================================================================
+ 2026-07 polish pass III — prose, protocol nav, mobile accordions
+ ========================================================================== */
+
+.muted {
+ line-height: var(--lh-body);
+}
+
+.home-content .muted,
+#blog-list .muted,
+#changelog-list .muted,
+.recent-list .muted {
+ color: var(--muted);
+ opacity: 0.92;
+}
+
+body.light-mode .home-content .muted,
+body.light-mode #blog-list .muted,
+body.light-mode #changelog-list .muted {
+ color: var(--muted);
+ opacity: 1;
+}
+
+.home-content > section + section {
+ margin-top: var(--space-3xl);
+}
+
+.home-content > section + section::before {
+ margin: calc(-1 * var(--space-3xl) + var(--space-lg)) 0 var(--space-lg);
+}
+
+#blog-list,
+#blog-list li,
+#blog-list p,
+#changelog-list li,
+.updates-panel .recent-list li,
+.updates-panel .recent-list p,
+.start-card span:not(.start-card-icon) {
+ font-family: var(--font-prose);
+ line-height: var(--lh-comfort);
+}
+
+#blog-list h3,
+#blog-list strong,
+.updates-panel h3,
+.updates-panel .network-subtitle,
+.section-accent-title,
+.network-subtitle,
+.start-card strong {
+ font-family: var(--font-mono);
+}
+
+/* Sidebar protocol navigation */
+.home-protocol-nav {
+ margin-top: 0;
+ padding: var(--space-md);
+ border: 1px solid var(--border);
+ border-radius: var(--r);
+ background:
+ radial-gradient(ellipse at top, rgba(var(--glow-color), .07), transparent 72%),
+ color-mix(in srgb, var(--bg) 88%, #1a0000);
+ box-shadow: var(--glow-soft);
+}
+
+.home-protocol-nav h3 {
+ margin: 0 0 var(--space-sm);
+ padding-bottom: var(--space-xs);
+ border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent);
+ font-family: var(--font-mono);
+ font-size: var(--fs-sm);
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ font-weight: 700;
+ color: var(--gold);
+}
+
+.protocol-pills--sidebar {
+ flex-direction: column;
+ align-items: stretch;
+ gap: 8px;
+}
+
+.protocol-pills--sidebar .protocol-pill {
+ justify-content: center;
+ min-height: 36px;
+ font-size: var(--fs-sm);
+ font-weight: 600;
+}
+
+/* Network subsections — collapsible on mobile */
+.network-collapsible > .network-subsection-summary {
+ list-style: none;
+ cursor: pointer;
+}
+
+.network-collapsible > .network-subsection-summary::-webkit-details-marker {
+ display: none;
+}
+
+.network-collapsible > .network-subsection-summary::marker {
+ content: '';
+}
+
+.network-collapsible > .network-subsection-summary {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: var(--space-sm);
+}
+
+.network-collapsible > .network-subsection-summary .network-subtitle {
+ flex: 1;
+ margin-bottom: 0;
+}
+
+.network-collapsible > .network-subsection-summary::after {
+ content: '▼';
+ flex-shrink: 0;
+ margin-top: 0.35rem;
+ font-size: 0.65rem;
+ opacity: 0.55;
+ transition: transform .2s ease;
+}
+
+.network-collapsible[open] > .network-subsection-summary::after {
+ transform: rotate(180deg);
+}
+
+.network-collapsible > .network-subsection-body {
+ margin-top: var(--space-md);
+}
+
+.network-subsection#friends .friends-list a {
+ display: inline-block;
+ padding: 5px 10px;
+ margin: 2px 0;
+ border: 1px solid color-mix(in srgb, var(--section-neon, var(--border)) 25%, var(--border));
+ border-radius: var(--r);
+ background: linear-gradient(180deg, rgba(var(--section-glow, var(--glow-color)), .06), transparent 80%);
+}
+
+body.light-mode .vibe-facade {
+ color: var(--accent);
+ background:
+ radial-gradient(ellipse at 50% 38%, rgba(139, 0, 0, .12), transparent 58%),
+ linear-gradient(180deg, #f5ebe0, var(--surface));
+ box-shadow: inset 0 0 0 1px var(--border);
+}
+
+body.light-mode .vibe-facade::before {
+ color: var(--accent);
+ text-shadow: none;
+}
+
+body.light-mode .vibe-facade-play {
+ color: var(--accent);
+ border-color: var(--accent);
+ background: var(--surface);
+ box-shadow: 0 4px 16px var(--shadow);
+}
+
+body.light-mode .vibe-facade-text {
+ color: var(--ink);
+ text-shadow: none;
+}
+
+@media (min-width: 601px) {
+ .network-collapsible > .network-subsection-summary {
+ pointer-events: none;
+ cursor: default;
+ }
+
+ .network-collapsible > .network-subsection-summary::after {
+ display: none;
+ }
+}
+
+@media (max-width: 600px) {
+ .home-protocol-nav {
+ display: none;
+ }
+
+ .network-collapsible {
+ padding: var(--space-md);
+ }
+
+ .network-collapsible:not([open]) > .network-subsection-body {
+ display: none;
+ }
+}
diff --git a/assets/css/skeleton.css b/assets/css/skeleton.css
new file mode 100644
index 0000000..9e86e6c
--- /dev/null
+++ b/assets/css/skeleton.css
@@ -0,0 +1,63 @@
+/* ==========================================================================
+ skeleton.css — Skeleton loading placeholders
+ Shimmering blocks that match the dark aesthetic of the site.
+ ========================================================================== */
+
+@keyframes skeleton-shimmer {
+ 0% { background-position: -200px 0; }
+ 100% { background-position: calc(200px + 100%) 0; }
+}
+
+.skeleton-line {
+ display: block;
+ height: 0.9em;
+ margin: var(--space-sm, 0.5rem) 0;
+ border-radius: var(--r, 6px);
+ background: linear-gradient(
+ 90deg,
+ var(--border, #2a1010) 0px,
+ #3a1818 40px,
+ var(--border, #2a1010) 80px
+ );
+ background-size: 200px 100%;
+ animation: skeleton-shimmer 1.4s ease-in-out infinite;
+}
+
+.skeleton-line:first-child { width: 60%; }
+.skeleton-line:nth-child(2) { width: 80%; }
+.skeleton-line:nth-child(3) { width: 45%; }
+.skeleton-line:nth-child(4) { width: 70%; }
+
+.skeleton-block {
+ display: block;
+ height: 3rem;
+ margin: var(--space-sm, 0.5rem) 0;
+ border-radius: var(--r, 6px);
+ background: linear-gradient(
+ 90deg,
+ var(--border, #2a1010) 0px,
+ #3a1818 40px,
+ var(--border, #2a1010) 80px
+ );
+ background-size: 200px 100%;
+ animation: skeleton-shimmer 1.4s ease-in-out infinite;
+}
+
+/* High-contrast theme skeleton */
+[data-theme="high-contrast"] .skeleton-line,
+[data-theme="high-contrast"] .skeleton-block {
+ background: linear-gradient(
+ 90deg,
+ #ddd 0px,
+ #eee 40px,
+ #ddd 80px
+ );
+ background-size: 200px 100%;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .skeleton-line,
+ .skeleton-block {
+ animation: none;
+ }
+}
diff --git a/assets/data/site-index.json b/assets/data/site-index.json
new file mode 100644
index 0000000..b7d7978
--- /dev/null
+++ b/assets/data/site-index.json
@@ -0,0 +1,23 @@
+[
+ {"title": "Home", "url": "/", "tags": "home hub"},
+ {"title": "StartPage", "url": "/startpage/", "tags": "startpage bookmarks terminal"},
+ {"title": "Now", "url": "/now/", "tags": "now current"},
+ {"title": "MOTD", "url": "/motd/", "tags": "motd xmpp waifu notice"},
+ {"title": "Site Map", "url": "/map/", "tags": "map sitemap navigation"},
+ {"title": "Journal", "url": "/journal/", "tags": "journal diary"},
+ {"title": "Guestbook", "url": "/guestbook.php", "tags": "guestbook comments"},
+ {"title": "Gaming hub", "url": "/gaming/", "tags": "games gaming"},
+ {"title": "RuneScape", "url": "/gaming/runescape/", "tags": "runescape gaming"},
+ {"title": "StepMania", "url": "/gaming/stepmania/", "tags": "rhythm gaming"},
+ {"title": "SpecialForce", "url": "/gaming/specialforce/", "tags": "gaming fps"},
+ {"title": "Multiboxing", "url": "/gaming/multibox/", "tags": "mmo multibox gaming"},
+ {"title": "Bookmarks", "url": "/bookmarks/", "tags": "links bookmarks"},
+ {"title": "Accounts", "url": "/accounts/", "tags": "accounts"},
+ {"title": "Computers", "url": "/computers/", "tags": "hardware computers"},
+ {"title": "Changelog", "url": "/changelog/", "tags": "updates changelog"},
+ {"title": "Links", "url": "/links.php", "tags": "links friends webring"},
+ {"title": "Blog", "url": "https://blog.sillylaird.ca/", "tags": "blog writing external"},
+ {"title": "Radio", "url": "https://radio.sillylaird.ca/", "tags": "radio music stream"},
+ {"title": "Gemini capsule", "url": "gemini://sillylaird.ca", "tags": "gemini small web"},
+ {"title": "Gopher hole", "url": "gopher://sillylaird.ca", "tags": "gopher small web"}
+] \ No newline at end of file
diff --git a/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2 b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2
new file mode 100644
index 0000000..d750914
--- /dev/null
+++ b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2
Binary files differ
diff --git a/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2 b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2
new file mode 100644
index 0000000..d15208d
--- /dev/null
+++ b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2
Binary files differ
diff --git a/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2 b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2
new file mode 100644
index 0000000..024f077
--- /dev/null
+++ b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2
Binary files differ
diff --git a/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2 b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2
new file mode 100644
index 0000000..479d010
--- /dev/null
+++ b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2
Binary files differ
diff --git a/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2 b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2
new file mode 100644
index 0000000..de83a9c
--- /dev/null
+++ b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2
Binary files differ
diff --git a/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2 b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2
new file mode 100644
index 0000000..6e7141f
--- /dev/null
+++ b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2
Binary files differ
diff --git a/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2 b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2
new file mode 100644
index 0000000..a40c469
--- /dev/null
+++ b/assets/fonts/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2
Binary files differ
diff --git a/assets/fonts/jetbrains-mono-400.woff2 b/assets/fonts/jetbrains-mono-400.woff2
new file mode 100644
index 0000000..66c5467
--- /dev/null
+++ b/assets/fonts/jetbrains-mono-400.woff2
Binary files differ
diff --git a/assets/fonts/jetbrains-mono-700.woff2 b/assets/fonts/jetbrains-mono-700.woff2
new file mode 100644
index 0000000..81c5a21
--- /dev/null
+++ b/assets/fonts/jetbrains-mono-700.woff2
Binary files differ
diff --git a/assets/fonts/source-code-pro-latin.woff2 b/assets/fonts/source-code-pro-latin.woff2
new file mode 100644
index 0000000..bc303f5
--- /dev/null
+++ b/assets/fonts/source-code-pro-latin.woff2
Binary files differ
diff --git a/assets/img/2cows.gif b/assets/img/2cows.gif
new file mode 100644
index 0000000..0fcc49b
--- /dev/null
+++ b/assets/img/2cows.gif
Binary files differ
diff --git a/assets/img/Aeplaine.gif b/assets/img/Aeplaine.gif
new file mode 100644
index 0000000..b7a958a
--- /dev/null
+++ b/assets/img/Aeplaine.gif
Binary files differ
diff --git a/assets/img/L3.png b/assets/img/L3.png
new file mode 100644
index 0000000..09a5f7c
--- /dev/null
+++ b/assets/img/L3.png
Binary files differ
diff --git a/assets/img/The_Site_Banner.webp b/assets/img/The_Site_Banner.webp
new file mode 100644
index 0000000..1d1ddc4
--- /dev/null
+++ b/assets/img/The_Site_Banner.webp
Binary files differ
diff --git a/assets/img/Vidlii6.png b/assets/img/Vidlii6.png
new file mode 100644
index 0000000..540c3a5
--- /dev/null
+++ b/assets/img/Vidlii6.png
Binary files differ
diff --git a/assets/img/ae.gif b/assets/img/ae.gif
new file mode 100644
index 0000000..b7a958a
--- /dev/null
+++ b/assets/img/ae.gif
Binary files differ
diff --git a/assets/img/airvpn.png b/assets/img/airvpn.png
new file mode 100644
index 0000000..c110b12
--- /dev/null
+++ b/assets/img/airvpn.png
Binary files differ
diff --git a/assets/img/avatar.png b/assets/img/avatar.png
new file mode 100644
index 0000000..dbe8101
--- /dev/null
+++ b/assets/img/avatar.png
Binary files differ
diff --git a/assets/img/babi.png b/assets/img/babi.png
new file mode 100644
index 0000000..1168016
--- /dev/null
+++ b/assets/img/babi.png
Binary files differ
diff --git a/assets/img/buyvm.png b/assets/img/buyvm.png
new file mode 100644
index 0000000..7fc9c71
--- /dev/null
+++ b/assets/img/buyvm.png
Binary files differ
diff --git a/assets/img/canada.png b/assets/img/canada.png
new file mode 100644
index 0000000..a38a30e
--- /dev/null
+++ b/assets/img/canada.png
Binary files differ
diff --git a/assets/img/ccp.svg b/assets/img/ccp.svg
new file mode 100644
index 0000000..67c23b9
--- /dev/null
+++ b/assets/img/ccp.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="900" height="600" viewBox="-15 -11 144 96" fill="#ff0"><path d="M-15-11h144v96h-144z" fill="#ee1c25"/><path d="M4 14l7.923966-7.923966A5.315073 5.315073 0 0 0 17 5l2.5 2.5-4 4L33 29l-4 4-17.5-17.5-3 3zm.5 8.5a16.62077 16.62077 0 0 0 12 5.12077A11.12077 11.12077 0 0 0 27.62077 16.5 16.62077 16.62077 0 0 0 17 1a16 16 0 0 1 0 32 18 18 0 0 1-14.979984-8.020016zM3 29l2-2 2 2-2 2z"/><circle cx="3.5" cy="30.5" r="2.5"/></svg> \ No newline at end of file
diff --git a/assets/img/china.png b/assets/img/china.png
new file mode 100644
index 0000000..8a300a1
--- /dev/null
+++ b/assets/img/china.png
Binary files differ
diff --git a/assets/img/cirno.jpg b/assets/img/cirno.jpg
new file mode 100644
index 0000000..9b9c372
--- /dev/null
+++ b/assets/img/cirno.jpg
Binary files differ
diff --git a/assets/img/cockbox.gif b/assets/img/cockbox.gif
new file mode 100644
index 0000000..e1669b3
--- /dev/null
+++ b/assets/img/cockbox.gif
Binary files differ
diff --git a/assets/img/code.png b/assets/img/code.png
new file mode 100644
index 0000000..04f7ed1
--- /dev/null
+++ b/assets/img/code.png
Binary files differ
diff --git a/assets/img/cornhub.png b/assets/img/cornhub.png
new file mode 100644
index 0000000..52c09f3
--- /dev/null
+++ b/assets/img/cornhub.png
Binary files differ
diff --git a/assets/img/cryptostorm.png b/assets/img/cryptostorm.png
new file mode 100644
index 0000000..b7ac51b
--- /dev/null
+++ b/assets/img/cryptostorm.png
Binary files differ
diff --git a/assets/img/edramatica.png b/assets/img/edramatica.png
new file mode 100644
index 0000000..565045e
--- /dev/null
+++ b/assets/img/edramatica.png
Binary files differ
diff --git a/assets/img/emacs.png b/assets/img/emacs.png
new file mode 100644
index 0000000..8cfb15f
--- /dev/null
+++ b/assets/img/emacs.png
Binary files differ
diff --git a/assets/img/eso-tilin-tilin.gif b/assets/img/eso-tilin-tilin.gif
new file mode 100644
index 0000000..0f5925a
--- /dev/null
+++ b/assets/img/eso-tilin-tilin.gif
Binary files differ
diff --git a/assets/img/expressvpn.png b/assets/img/expressvpn.png
new file mode 100644
index 0000000..765c580
--- /dev/null
+++ b/assets/img/expressvpn.png
Binary files differ
diff --git a/assets/img/fastmail.png b/assets/img/fastmail.png
new file mode 100644
index 0000000..9b66d49
--- /dev/null
+++ b/assets/img/fastmail.png
Binary files differ
diff --git a/assets/img/japan.png b/assets/img/japan.png
new file mode 100644
index 0000000..adad280
--- /dev/null
+++ b/assets/img/japan.png
Binary files differ
diff --git a/assets/img/javascript.gif b/assets/img/javascript.gif
new file mode 100644
index 0000000..40dde39
--- /dev/null
+++ b/assets/img/javascript.gif
Binary files differ
diff --git a/assets/img/jetbrain.png b/assets/img/jetbrain.png
new file mode 100644
index 0000000..71794ac
--- /dev/null
+++ b/assets/img/jetbrain.png
Binary files differ
diff --git a/assets/img/kansen.gif b/assets/img/kansen.gif
new file mode 100644
index 0000000..186ee1a
--- /dev/null
+++ b/assets/img/kansen.gif
Binary files differ
diff --git a/assets/img/l3-logo.png b/assets/img/l3-logo.png
new file mode 100644
index 0000000..c3e0cb6
--- /dev/null
+++ b/assets/img/l3-logo.png
Binary files differ
diff --git a/assets/img/l3-transparent.png b/assets/img/l3-transparent.png
new file mode 100644
index 0000000..38fc0b3
--- /dev/null
+++ b/assets/img/l3-transparent.png
Binary files differ
diff --git a/assets/img/lain.gif b/assets/img/lain.gif
new file mode 100644
index 0000000..43ff64a
--- /dev/null
+++ b/assets/img/lain.gif
Binary files differ
diff --git a/assets/img/lain.png b/assets/img/lain.png
new file mode 100644
index 0000000..1614204
--- /dev/null
+++ b/assets/img/lain.png
Binary files differ
diff --git a/assets/img/lolwut-button.gif b/assets/img/lolwut-button.gif
new file mode 100644
index 0000000..4fe9974
--- /dev/null
+++ b/assets/img/lolwut-button.gif
Binary files differ
diff --git a/assets/img/lolwut.png b/assets/img/lolwut.png
new file mode 100644
index 0000000..397e98d
--- /dev/null
+++ b/assets/img/lolwut.png
Binary files differ
diff --git a/assets/img/mothracompat.gif b/assets/img/mothracompat.gif
new file mode 100644
index 0000000..d904672
--- /dev/null
+++ b/assets/img/mothracompat.gif
Binary files differ
diff --git a/assets/img/multibox.png b/assets/img/multibox.png
new file mode 100644
index 0000000..4d65b97
--- /dev/null
+++ b/assets/img/multibox.png
Binary files differ
diff --git a/assets/img/niceland.png b/assets/img/niceland.png
new file mode 100644
index 0000000..644fa2b
--- /dev/null
+++ b/assets/img/niceland.png
Binary files differ
diff --git a/assets/img/nordvpn.svg b/assets/img/nordvpn.svg
new file mode 100644
index 0000000..b6bbf42
--- /dev/null
+++ b/assets/img/nordvpn.svg
@@ -0,0 +1 @@
+<svg width="512" height="111.16" viewBox="0 0 512 111.16" xmlns="http://www.w3.org/2000/svg"><path d="M499.86 37.684c0-3.322 2.692-6.07 6.07-6.07s6.071 2.748 6.071 6.07c0 3.379-2.691 6.07-6.07 6.07s-6.071-2.748-6.071-6.07zm1.145 0c0 2.749 2.177 4.983 4.926 4.983 2.691 0 4.925-2.234 4.925-4.983 0-2.749-2.234-4.983-4.925-4.983-2.75 0-4.926 2.234-4.926 4.983zm4.01 3.436h-1.432v-6.93h2.749c1.374 0 2.462.688 2.462 2.177 0 1.03-.573 1.66-1.317 2.004l1.432 2.75h-1.546l-1.26-2.52h-1.089zm1.202-5.555h-1.203v1.718h1.203c.516 0 1.088-.23 1.088-.86 0-.686-.572-.858-1.088-.858zm-290.53 49.94-24.74-37.17v37.17h-11.054V31.613h12.771l23.366 35.164V31.613h11.053v53.892zm36.653-37.57c-11.168 0-20.159 8.304-20.159 19.3 0 11.11 8.877 19.128 20.045 19.128 11.111 0 20.102-8.247 20.102-19.358-.057-10.996-8.934-19.071-19.987-19.071zm-.114 28.52c-5.04 0-9.106-3.836-9.106-9.449 0-5.498 4.009-9.335 8.991-9.335 5.04 0 9.106 3.837 9.106 9.335.057 5.613-3.952 9.45-8.991 9.45zm47.878-27.718v10.538c-1.432-.286-2.979-.286-3.437-.286-5.154 0-8.762 3.608-8.762 10.423v16.093h-10.538V48.737h10.194v5.383h.114c2.005-4.18 5.728-5.784 9.507-5.784.86 0 2.177.171 2.921.4zm30.754-17.124v21.362h-.115c-3.78-4.467-8.877-4.868-10.996-4.868-10.538 0-17.639 9.679-17.639 19.014 0 9.908 6.815 19.128 17.468 19.128 2.463 0 7.96-.344 11.454-5.384h.115v4.697h10.423v-53.95zm-8.82 44.843c-5.04 0-9.106-3.837-9.106-9.45 0-5.498 4.066-9.278 9.106-9.278s9.106 3.78 9.106 9.278c-.057 5.613-4.066 9.45-9.106 9.45zm42.323 9.049-19.53-53.892h12.085l13.974 40.949 14.03-40.95h11.856l-19.587 53.893zm57.042-53.892h-20.045v53.892h11.168V66.49h8.419c11.282 0 19.7-5.727 19.7-17.64 0-11.396-8.475-17.237-19.242-17.237zm-.916 24.512h-7.961V42.15h7.903c4.64 0 8.992 1.775 8.992 7.044 0 4.983-4.525 6.93-8.934 6.93zm61.566 29.38-24.798-37.17v37.17h-10.996V31.613h12.77l23.367 35.164V31.613h11.053v53.892z" fill="#2c3038"/><path d="M13.287 111.11C4.926 99.599 0 85.395 0 69.99 0 31.332 31.27.005 69.87.005c38.6 0 69.87 31.327 69.87 70.042 0 15.349-4.925 29.552-13.287 41.12L92.892 56.531l-3.264 5.498 3.264 15.234-23.08-39.517-14.203 24.111 3.322 15.406-12.084-20.675z" fill="#4580ff"/></svg> \ No newline at end of file
diff --git a/assets/img/npp.svg b/assets/img/npp.svg
new file mode 100644
index 0000000..410e19a
--- /dev/null
+++ b/assets/img/npp.svg
@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 606.6 524.8" style="enable-background:new 0 0 606.6 524.8;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#231F20;}
+ .st1{fill:#90E59A;}
+ .st2{fill:#5CCE74;}
+ .st3{fill:#75D66F;}
+ .st4{fill:#8FE772;}
+ .st5{fill:#A9F37E;}
+ .st6{fill:#87DE71;}
+ .st7{fill:none;}
+ .st8{fill:#FBFDFC;}
+ .st9{fill:#FFFFFF;}
+ .st10{fill:#97EB74;}
+ .st11{fill:#84CB71;}
+ .st12{fill:#6CB960;}
+ .st13{fill:#77C66C;}
+ .st14{fill:#D07B84;}
+ .st15{fill:#FEFEFE;}
+ .st16{fill:#E1CB86;}
+ .st17{fill:#DDC377;}
+ .st18{fill:#DCC576;}
+ .st19{fill:#F5D776;}
+ .st20{fill:#959D64;}
+</style>
+<g id="Calque_2_-_copie_5">
+ <path d="M400.3,437.6 M438.5,397.9 M133.3,148.1V49c0-23.9,16.5-39.8,40.1-39.8h237.7c7.8,0,14.5,2.3,20,7.9l60.1,61.3
+ c5.5,5.7,7.7,13.2,7.7,21.1v189.3l9.4-0.1v-194c0-8.3-3.2-16.1-9-22L435.9,9.1c-5.8-5.8-13.8-9.1-22-9.1H169.4
+ c-24.8,0-45,20.2-45,45v107.4L133.3,148.1z M124.4,348v131.9c0,24.8,20.2,45,45,45h57.2l0-9.2h-53.2c-23.5,0-40.1-16-40.1-39.8
+ V352.2L124.4,348z M448,401.9 M403,446.9"/>
+</g>
+<g id="Calque_3">
+ <path d="M476.9,87.4h-55.4V29.5c0-4.3,0-7.7-4.2-7.7c-4.3,0-4.2,3.5-4.2,7.7v58.6c0,4.3,3.4,7.7,7.7,7.7h56.1
+ c4.3,0,7.7,0.1,7.7-4.2S481.1,87.4,476.9,87.4z"/>
+</g>
+<g id="Calque_6">
+ <g>
+ <path class="st0" d="M271.9,363.3c-13.8,0-16.7,5.9-16.7,10.8v61.1c0,4.9,2.9,10.8,16.7,10.8c13.8,0,16.7-5.9,16.7-10.8v-61.1
+ C288.6,369.2,285.7,363.3,271.9,363.3z M264,377.1c0-4.8,2.4-6.9,7.9-6.9c5.5,0,7.9,2.1,7.9,6.9v55.2c0,4.8-2.4,6.9-7.9,6.9
+ c-5.5,0-7.9-2.1-7.9-6.9V377.1z"/>
+ <polygon class="st0" points="314.4,304.2 305.5,304.2 305.5,353.5 299.6,353.5 299.6,360.4 305.5,360.4 305.5,455.9 314.4,455.9
+ 314.4,360.4 328.2,360.4 328.2,353.5 314.4,353.5 "/>
+ <path class="st0" d="M348.9,363.3c-13.8,0-16.7,5.9-16.7,10.8v61.1c0,4.9,2.9,10.8,16.7,10.8c13.8,0,16.7-5.9,16.7-10.8v-9.8h-8.9
+ v6.9c0,4.8-2.4,6.9-7.9,6.9c-5.5,0-7.9-2.1-7.9-6.9v-16.7c2.2,0.6,4.8,0.9,7.9,0.9c13.8,0,16.7-5.9,16.7-10.8v-31.5
+ C365.6,369.2,362.7,363.3,348.9,363.3z M341,377.1c0-4.8,2.4-6.9,7.9-6.9c5.5,0,7.9,2.1,7.9,6.9v25.6c0,4.8-2.4,6.9-7.9,6.9
+ c-5.5,0-7.9-2.1-7.9-6.9V377.1z"/>
+ <path class="st0" d="M395.9,363.3c-13.8,0-16.7,5.9-16.7,10.8v109.3h8.9v-38.3c2.2,0.6,4.8,0.9,7.9,0.9c13.8,0,16.7-5.9,16.7-10.8
+ v-61.1C412.7,369.2,409.8,363.3,395.9,363.3z M388.1,377.1c0-4.8,2.4-6.9,7.9-6.9c5.5,0,7.9,2.1,7.9,6.9v55.2
+ c0,4.8-2.4,6.9-7.9,6.9c-5.5,0-7.9-2.1-7.9-6.9V377.1z"/>
+ <path class="st0" d="M443,363.3c-13.8,0-16.7,5.9-16.7,10.8v9.8h8.9v-6.9c0-4.8,2.4-6.9,7.9-6.9c5.5,0,7.9,2.1,7.9,6.9v16.7
+ c-2.2-0.6-4.8-0.9-7.9-0.9c-13.8,0-16.7,5.9-16.7,10.8v31.5c0,4.9,2.9,10.8,16.7,10.8c13.8,0,16.7-5.9,16.7-10.8v-61.1
+ C459.8,369.2,456.8,363.3,443,363.3z M435.1,406.6c0-4.8,2.4-6.9,7.9-6.9c5.5,0,7.9,2.1,7.9,6.9v25.6c0,4.8-2.4,6.9-7.9,6.9
+ c-5.5,0-7.9-2.1-7.9-6.9V406.6z"/>
+ <path class="st0" d="M498.7,364.2c-2.2-0.6-4.8-0.9-7.9-0.9c-13.8,0-16.7,5.9-16.7,10.8v61.1c0,4.9,2.9,10.8,16.7,10.8
+ c13.8,0,16.7-5.9,16.7-10.8v-131h-8.9V364.2z M483,377.1c0-4.8,2.4-6.9,7.9-6.9c5.5,0,7.9,2.1,7.9,6.9v55.2c0,4.8-2.4,6.9-7.9,6.9
+ c-5.5,0-7.9-2.1-7.9-6.9V377.1z"/>
+ <polygon class="st0" points="530.3,363.3 521.4,363.3 521.4,383 511.5,383 511.5,389.9 521.4,389.9 521.4,409.6 530.3,409.6
+ 530.3,389.9 540.1,389.9 540.1,383 530.3,383 "/>
+ <polygon class="st0" points="562.8,383 562.8,363.3 553.9,363.3 553.9,383 544,383 544,389.9 553.9,389.9 553.9,409.6
+ 562.8,409.6 562.8,389.9 572.6,389.9 572.6,383 "/>
+ </g>
+</g>
+<g id="Calque_7">
+</g>
+<g id="Calque_9">
+ <path class="st0" d="M234.4,304.2c-13.8,0-16.7,5.9-16.7,10.8v185.5h8.9V318c0-4.8,2.4-6.9,7.9-6.9s7.9,2.1,7.9,6.9v128h8.9v-131
+ C251.2,310.1,248.3,304.2,234.4,304.2z"/>
+</g>
+<g id="Layer_1">
+ <g>
+ <path class="st1" d="M129.4,159.4c8.5-8.4,20.7-10.7,31.8-14.2c12.7-0.7,25.4,3.2,36.9,8.5c5,2,4.7,7.9,5.8,12.4
+ c-3.6,0.8-7.2,1.5-10.8,2.4c1.6-2.5,3.1-4.9,4.6-7.4c-0.1-2.4-2.9-3.3-4.3-5c-1.9,1.3-3.8,2.6-5.7,3.9c-1.3-1.1-2.6-2.2-3.9-3.3
+ c-3.5,0.5-7.3,1-10.6-0.9c-5.6-2.5-11.6-4.4-17.8-3.8c-2.8,1.2-5,3.4-7.3,5.3c0-1,0.1-3,0.1-4c-1.3,1.5-2.6,3-3.8,4.6
+ c-8.6,0.9-12.6,9.8-10.7,17.5c-1.8,1.4-3.7,2.9-5.5,4.3c2-3.9,3.7-8.2,1.4-12.4c-6.4,1.9-6.9,8.9-9.8,13.9
+ c-3,5.5-8.1,9.7-10.5,15.5c-0.2,3.2,0.3,6.3,0.6,9.5c-2.4-0.7-4.8-1.3-7.1-2C108.8,187.9,116.1,171.1,129.4,159.4L129.4,159.4z"/>
+ <path class="st2" d="M148.1,157.4c2.4-1.9,4.5-4.2,7.3-5.3c6.2-0.6,12.2,1.3,17.8,3.8c-0.8,3.5-2,6.9-3.6,10.1
+ c-4.8-2.5-9.6-5.2-14.1-8.1c0.9,1.8,1.8,3.6,2.7,5.4c-1,1.4-2.1,2.7-3.1,4.1c-2-2.6-4-5.2-5.9-7.9c-0.7,3.9-3.6,6.8-7.6,4.6
+ c0.9-2.1,1.8-4.1,2.7-6.1c1.2-1.6,2.5-3.1,3.8-4.6C148.2,154.3,148.1,156.3,148.1,157.4L148.1,157.4z"/>
+ <path class="st3" d="M173.2,155.8c3.3,1.9,7,1.4,10.6,0.9c1.3,1.1,2.6,2.2,3.9,3.3c1.9-1.3,3.8-2.6,5.7-3.9c1.3,1.7,4.2,2.6,4.3,5
+ c-1.4,2.5-3,5-4.6,7.4c-0.9,0.2-2.7,0.6-3.5,0.9c1.8,2.2,3.6,4.4,5.4,6.5c-7.6,0.4-13.1-4.8-18.9-8.6c-5.3,0.8-9.1,6.5-14.6,7.1
+ c-2.7-1.2-5.2-2.8-7.7-4.4c-0.9,2.9-1.5,5.9-3.2,8.4c-2.5,1.7-5.7,1.7-8.6,2.5c0.1,1.3,0.3,2.7,0.4,4c-3.1-3-6.9-5.8-11.3-3.8
+ c3.2,3.3-1.6,4.6-3.8,6.1c-4.3,2.4-9.1,5.5-9.6,10.9c-1.2,3.1,0.1,7.9-3.4,9.6c-4.1,1.1-8.3,0.4-12.4,0.3c0.3-1,0.8-2.9,1.1-3.9
+ c2.4,0.7,4.8,1.3,7.1,2c-0.4-3.2-0.9-6.3-0.6-9.5c2.4-5.8,7.5-10,10.5-15.5c2.9-5,3.4-12,9.8-13.9c2.3,4.2,0.6,8.5-1.4,12.4
+ c1.8-1.5,3.6-2.9,5.5-4.3c-1.9-7.7,2.1-16.6,10.7-17.5c-0.9,2-1.8,4.1-2.7,6.1c3.9,2.2,6.9-0.7,7.6-4.6c1.9,2.7,3.9,5.3,5.9,7.9
+ c1-1.4,2-2.7,3.1-4.1c-0.9-1.8-1.8-3.6-2.7-5.4c4.6,2.9,9.3,5.5,14.1,8.1C171.2,162.8,172.4,159.4,173.2,155.8 M133.3,219.4
+ c2.2,0.4,4.3,0.8,6.5,1.3c-2.5,6.7-6,13.5-5.6,20.9c1.3,4.8,5.3,8.4,8.3,12.3c-4.4,1.3-10.5,0.3-13.5,4.4
+ c-1.8,8,5.5,12.6,10.2,17.5c-6.8-2.5-14.4-6.1-16.8-13.6c3.3-4.1,7.4-7.4,11.7-10.6C127.9,242.2,125.2,228.4,133.3,219.4
+ M202.6,236.3c1.6,1.4,3.3,2.8,4.9,4.1c0.2,2.7,0.4,5.5,0.5,8.2C205.2,245,202.7,241.1,202.6,236.3 M245.2,238.4
+ c1.7-2.4,4.6-2.2,6.4-0.1c3.4,4.4,5.3,9.6,7.6,14.6c2.9,0.2,5.8,0.4,8.6,0.6c-5.7,2.1-11.1,5.3-11.9,11.9
+ c-2.4-3.4,1.1-6.5,2.5-9.6C255.2,249.1,247.4,245.7,245.2,238.4 M163.6,242.7l1.7-0.9c0.3,4.5,2.4,9.7-0.9,13.5
+ c-3.3-0.3-6.5-0.5-9.7-1.1C159,251.4,162.2,247.9,163.6,242.7 M208,248.7c2.4,3.2,5.1,6,8.1,8.6c3.2-3.7,10.3-9.6,13.2-2.6
+ c-4.8,2.3-10.8,4-12.4,9.8c0,0.2-0.1,0.5-0.1,0.7c0.7,5.1,2.6,10,4.1,14.9C209.9,273,216.5,257.7,208,248.7 M267.8,250.1
+ c4.7,0.5,9.7,1.6,13.3,5.1c-4.5-0.3-9.1-0.1-13.3-1.7C267.8,252.7,267.8,251,267.8,250.1 M256,275.9c5.7,2.5,11.3,5.2,17.3,7.2
+ c3.4-1.4,8.2-8.9,11-3.4c-4.4,4-10.5,8.1-16.7,6.7C262.8,284.1,259.7,279.5,256,275.9 M97.3,276.8c1.1,0.2,3.2,0.5,4.4,0.6
+ c4.8,11.1,5,24,11.6,34.4c3.8,6.2,6.9,12.7,10.5,19c3.8,6.5,10.1,10.9,16,15.4c12.6,9.3,33.4,7.9,42.3-5.8
+ c5.7-12.2-1.4-25.1-6.3-36.3c-8.4-13.2-30.3-3.2-29.7,11.2c-2.5-14.3,15.8-26.4,28-19.1c8.7,5.6,11.1,16.2,14.5,25.3
+ c4,10.9-1.9,23.8-12.2,28.6c-8,5.1-17.8,3.1-26.7,3.1c-12.2-0.6-20.1-10.5-28-18.4c-7.7-9.4-12.7-20.6-18.7-31.2
+ C99.8,295.1,98,286,97.3,276.8 M162.1,313.4c1.4,1.6,2.5,3.4,3.9,5.1c-3.4,4.4-8.9,4.2-13.8,3.5c-2-2.2-4.1-4.4-6.1-6.6
+ c4.8,2.9,10.2,8.8,16.2,5.1C162.1,318.1,162.1,315.8,162.1,313.4L162.1,313.4z"/>
+ <path class="st4" d="M161.3,174.4c5.5-0.6,9.3-6.3,14.6-7.1c5.9,3.9,11.4,9,18.9,8.6c-1.8-2.2-3.6-4.4-5.4-6.5
+ c0.9-0.2,2.7-0.7,3.5-0.9c3.6-0.9,7.2-1.6,10.8-2.4c0.9,3.4,1.8,6.9,2.9,10.3c-1.7,1.7-3.4,3.5-5.2,5.2
+ c3.7,5.1,7.1,10.5,10.8,15.6c7.7-2.1,12.6,5.3,16,11.1c-6.9-0.4-14.7-0.5-20.1-5.5c-3.3-3-6.5-6.1-10-9c-2.1,0.6-4.2,1.2-6.3,1.8
+ c-1.7-4.1-2.7-9.5-7.9-10.5c0.4,2.1,0.9,4.1,1.3,6.2c-3.1,1.8-6.5,2.6-10,1.6c1.4-2.9,2.9-5.7,4.4-8.6c-4.3-0.4-6.8,3.3-9.2,6.2
+ c-0.5-1.5-1-3-1.4-4.5c-1.6-0.2-3.1-0.6-4.7-0.7c-2.3,0.5-3.2,3.8-5.7,3.9c-4.1-0.4-8-2.1-12.2-2.3c-5,0.7-9.7,2.9-14.6,4.3
+ c-1.6-1.3-3.3-2.6-4.9-3.9c2.2-1.6,7.1-2.8,3.8-6.1c4.5-2,8.2,0.8,11.3,3.8c-0.1-1.3-0.3-2.7-0.4-4c2.9-0.7,6-0.8,8.6-2.5
+ c1.7-2.5,2.3-5.6,3.2-8.4C156.1,171.6,158.6,173.2,161.3,174.4L161.3,174.4z"/>
+ <path class="st5" d="M201.5,181.6c1.7-1.7,3.4-3.5,5.2-5.2c1.7,5.1,3.9,10.3,8.2,13.7c7.2,5.5,14.7,11.5,23.7,13.6
+ c8,1.5,16.8,0.6,23.7-4c6-5.4,8.8-13.3,15-18.5c5.8,2.9,12.3,4.2,17.9,7.5c5.8,3.7,8.6,10.3,12.1,16c-3.8,0.7-7.5,1.5-11.3,2.3
+ c1.4,2.4,2.9,4.9,4.4,7.3c-1,1.5-2,3-2.9,4.6c-1.9-3-3.7-6-5.5-9c-4.1,1-8,2.6-11.5,4.8c0.4,1.9,0.8,3.8,1.2,5.7
+ c-8.8-4.5-18.2,0-26.1,4.4c-2.4,1.3-4.9,2.8-7.3,4.1c-5-2.7-9.9-5.5-15.2-7.6c-2.1,3.4-4.6,6.7-7.1,9.9c-0.9-2.6-1.9-5.2-2.7-7.9
+ c-0.9,0.1-2.7,0.4-3.6,0.5c-0.4,3.7-0.3,7.4,0.2,11.1c-2.4-0.3-4.9-0.6-7.3-0.9c1.1-5,2.4-9.9,3.4-15c-1.2,0.1-3.7,0.3-4.9,0.4
+ c-4.1,6.3-4.3,13.9-3.5,21.2c-1.7-1.4-3.3-2.8-4.9-4.1c0.2-5.2,0.9-10.3,1.3-15.5c-5.4-2.3-7.6,3.9-8.6,7.9c-3-1-6-1.9-8.9-3
+ c-6.7-2.1-11.1-8-17.6-10.6c-3.4-1.4-5.9-4.2-8.5-6.7c-0.3,1-0.9,3-1.1,4c2.6,4.4,2.6,10.1-2.5,12.8c-1.6-0.2-4.8-0.6-6.5-0.9
+ c5.4,5.5,13.3,6.5,20.4,8.4c-1,0.7-3,2.1-4,2.8c-6.8-2.2-14.2-2.4-20.6-5.9c-0.2-2.6-0.5-5.3-0.7-7.9c-1.9-0.4-3.7-0.7-5.6-1.1
+ c-2.2-0.4-4.3-0.8-6.5-1.3c-1.6-1.9-3.4-5.2-6.3-3.5c-2.3,2.1-4.2,4.6-6.4,6.9c-1.3-1.8-2.7-3.6-4-5.4c-9.8,1.2-9,13-15.3,18.3
+ c-4,2.9-3.8,8.4-7.5,11.6c-0.5-4.5-0.8-8.9-0.3-13.4c2-7.9,5.5-15.3,7.5-23.3c0.2-0.6,0.5-1.9,0.7-2.6c4.1,0.2,8.4,0.8,12.4-0.3
+ c3.5-1.7,2.1-6.5,3.4-9.6c0.6-5.4,5.3-8.5,9.6-10.9c1.6,1.3,3.3,2.6,4.9,3.9c4.9-1.5,9.6-3.6,14.6-4.3c4.2,0.2,8.1,1.9,12.2,2.3
+ c2.5-0.1,3.3-3.4,5.7-3.9c1.6,0.1,3.1,0.5,4.7,0.7c0.5,1.5,1,3,1.4,4.5c2.5-2.9,4.9-6.6,9.2-6.2c-1.5,2.9-2.9,5.7-4.4,8.6
+ c3.5,1,6.9,0.1,10-1.6c-0.5-2.1-0.9-4.1-1.3-6.2c5.2,0.9,6.2,6.3,7.9,10.5c2.1-0.6,4.2-1.2,6.3-1.8c3.4,2.9,6.7,6,10,9
+ c5.4,5,13.2,5.1,20.1,5.5c-3.3-5.7-8.3-13.2-16-11.1C208.7,192.1,205.2,186.7,201.5,181.6L201.5,181.6z"/>
+ <path class="st6" d="M296.1,207c3.8-0.8,7.5-1.5,11.3-2.3c1.2,2.6,2.2,5.2,3.2,7.8c-10.4,10.4-25.7,13.1-39.4,16.3
+ c-5.7,1.7-11-1.3-15.7-4c7.9-4.3,17.3-8.9,26.1-4.4c-0.4-1.9-0.8-3.8-1.2-5.7c3.6-2.2,7.5-3.8,11.5-4.8c1.8,3,3.7,6,5.5,9
+ c0.9-1.5,1.9-3.1,2.9-4.6C299,211.9,297.5,209.4,296.1,207L296.1,207z"/>
+ <path class="st7" d="M166,318.5c-1.4-1.6-2.5-3.5-3.9-5.1c0,2.4,0.1,4.7,0.2,7.1c-6,3.7-11.4-2.2-16.2-5.1c2,2.2,4.1,4.3,6.1,6.6
+ C157.1,322.7,162.6,322.9,166,318.5z"/>
+ <path class="st7" d="M116.9,261.7c-3.7-4.3-10-2.3-15-3c-5.4,0.3-14-0.9-16.1,5.7c9.8,0.3,19.6,0.4,29.4,0.9
+ C115.7,264.4,116.5,262.6,116.9,261.7z"/>
+ <path class="st7" d="M227.3,248.1c5.4,8.8,16,7.7,24.9,7.5c-3.7-3.5-8.2-6.2-11.3-10.3c-1.8-1.7-2.5-5.1-5.3-5.4
+ c-5.4,0.7-10.8,1.5-16.3,1.3H219c-1.4,0-4.3,0-5.7,0c-0.3,3.3-0.3,6.6-0.3,9.9c1.3,0.7,4,2.1,5.3,2.9
+ C220.1,250.5,223.7,249.1,227.3,248.1z"/>
+ <path class="st7" d="M82.3,258.6l-0.4-1.1c-0.2,0.4-0.5,1.2-0.6,1.6l-1.1,1.3c-0.2,0.2-0.6,0.7-0.7,0.9c-2.6,2.2-2.1,5.6-2.4,8.6
+ c0.4,0.7,1.3,2.2,1.7,3c4.5,2.7,3.6-7.2,0.5-7.4c1.9-2.3,4-4.5,4.6-7.5L82.3,258.6z"/>
+ <path class="st7" d="M97.3,276.8c0.7,9.1,2.5,18.3,5.6,26.9c6,10.5,11,21.7,18.7,31.2c8,7.9,15.9,17.8,28,18.4
+ c8.9,0,18.8,2,26.7-3.1c10.3-4.8,16.2-17.7,12.2-28.6c-3.4-9-5.9-19.7-14.5-25.3c-12.2-7.3-30.5,4.8-28,19.1
+ c-0.6-14.4,21.3-24.4,29.7-11.2c4.9,11.1,11.9,24.1,6.3,36.3c-9,13.7-29.7,15.1-42.3,5.8c-5.9-4.5-12.2-8.9-16-15.4
+ c-3.6-6.2-6.8-12.8-10.5-19c-6.5-10.5-6.8-23.3-11.6-34.4C100.5,277.3,98.4,277,97.3,276.8z"/>
+ <path class="st7" d="M88.2,269.7c0.3,0.7,1,2.1,1.4,2.8c10.7,1.1,21.4,0.3,32.1,0.3c-0.4-0.5-1.2-1.4-1.7-1.9
+ C109.4,270.5,98.8,268,88.2,269.7z"/>
+ <path class="st7" d="M251.6,238.3c-1.8-2.1-4.7-2.3-6.4,0.1c2.2,7.2,10,10.7,13.2,17.4c-1.4,3-4.9,6.2-2.5,9.6
+ c0.9-6.5,6.3-9.8,11.9-11.9c-2.9-0.2-5.8-0.4-8.6-0.6C256.9,247.9,254.9,242.7,251.6,238.3z"/>
+ <path class="st7" d="M69.8,256.7c-1.1,2.5-2.4,5.1-3.7,7.5c1.1,3.2,2.8,6.2,4.8,8.9c0.6-0.3,1.8-0.8,2.3-1c-4.3-5-0.9-11,1.9-15.8
+ C73.3,256.6,71.5,256.7,69.8,256.7z"/>
+ <path class="st7" d="M281.1,255.2c-3.5-3.4-8.4-4.5-13.2-5c0,0-0.1,0-0.1,0c0,0.9,0,2.5,0,3.3C272,255.1,276.6,254.9,281.1,255.2z
+ "/>
+ <path class="st7" d="M127.6,249.9c-4.1-6.5-5.5-14.1-3-21.5c-1.8,0.5-3.6,0.9-5.4,1.3c-4,7.1-9.4,14.4-8.2,23
+ C116.6,252.7,122.4,252.6,127.6,249.9z"/>
+ <path class="st7" d="M66.2,254.4c-3.4,0.6-6.8,0.7-10.2,0.2c-2,3.5-4.6,6.7-8.2,8.7c-3.6,2.2-2,7.3-1.4,10.7
+ c6.4,1.2,13,0.8,19.5,1.1c-1.6-3.7-3.8-7.2-4.7-11.1C60.7,259.9,66,258.3,66.2,254.4z"/>
+ <path class="st7" d="M153.1,247.3c0.4-2.3,1-4.7,1.5-7c-3.7-3.5-9.3-3.4-13.9-4.7c-1.3,6.3,0.1,12.6,3.8,17.8
+ C147.2,251.3,150,249,153.1,247.3z"/>
+ <path class="st7" d="M285.2,269.2c-9.8,1.7-19.8,0.5-29.6,1.8c-10.5-2.1-21.2-2.4-31.9-1.6c0.1,1,0.2,3,0.3,4
+ c10.5-0.7,21.2,0.4,31.6-1.8c9.1,2,18.4,1.2,27.6,0.6c4.7-0.5,9,2.4,13.6,1.9C297.7,267.7,289.2,269.5,285.2,269.2z"/>
+ <path class="st7" d="M262.6,261.1c-0.4,0.7-1.2,2.2-1.6,3c5.9,1.8,12.2,0.7,18.2,1.5c7.8,1.1,16.2,2,23.5-1.7
+ c-2.5-1.6-4.8-4-7.8-4.6C284.1,258.7,273.3,260.1,262.6,261.1z"/>
+ <path class="st7" d="M273.3,283.1c-5.9-2-11.6-4.6-17.3-7.2c3.7,3.6,6.8,8.2,11.5,10.5c6.2,1.4,12.3-2.7,16.7-6.7
+ C281.5,274.2,276.7,281.7,273.3,283.1z"/>
+ <path class="st7" d="M232.4,260.6c-2.4,0.6-7.6-0.2-7,3.9c6.8,0.4,13.7,0.4,20.5,0.2c2.8,0.1,4-1.3,3.7-4.2
+ C243.9,260.5,238.1,260.2,232.4,260.6z"/>
+ <path class="st7" d="M312.1,259c-0.3,2.4-0.5,4.8-0.7,7.2c-1,0.6-2.9,1.8-3.8,2.5c0,2.2-0.2,4.5-0.3,6.7c8.3-0.2,15.9-3.7,24-5.3
+ c1.3-2.6,0.5-5.5,0.5-8.3C325.1,261.2,318.8,258.7,312.1,259z"/>
+ <path class="st7" d="M165.3,241.9l-1.7,0.9c-1.4,5.2-4.6,8.7-8.9,11.5c3.2,0.6,6.4,0.8,9.7,1.1
+ C167.7,251.5,165.6,246.3,165.3,241.9z"/>
+ <path class="st7" d="M206.4,256.5c-1.9-4.7-3.7-9.9-7.8-13.1c-8.7,0.4-17.4,0.1-26.1,0.4c-1.1,4.1-2.5,8-3.3,12.2
+ C181.5,257.1,194,256.6,206.4,256.5z"/>
+ <path class="st7" d="M216.9,264.5c1.6-5.8,7.6-7.5,12.4-9.8c-3-7.1-10-1.1-13.2,2.6c-2.4-2.1-4.6-4.4-6.6-6.8
+ c6.2,9.1,1.2,23,11.4,29.7c-1.5-4.9-3.3-9.8-4.1-14.9L216.9,264.5z"/>
+ <path class="st7" d="M155.1,264.6c6-1.8,11.7,2.2,17.7,2.2c11.5,0.3,23,0.3,34.5-0.2c-0.1-1.3-0.3-4-0.4-5.3
+ c-15.3,0-31.1-2.3-46,2c-4.2-0.1-8.1-2-12.2-2.7c-2.8-3.8-10.1-1.1-14.7-2.2c0.7,2.1,1.3,4.3,1.9,6.4c3.7,0.1,7.5,0.2,11.2,0.2
+ C149.5,267.6,152.5,264.8,155.1,264.6z"/>
+ <path class="st7" d="M141.5,270.7c0.9,1.2,1.8,2.5,2.7,3.7c3.6,0.1,7.3,0.2,10.9,0.4c17.3,0.1,34.5,0.4,51.8-0.2
+ c0.3-0.6,0.9-1.7,1.3-2.2c-17.7-1.2-35.4-1.4-53.1-1.7C150.6,270.8,146.1,270.8,141.5,270.7z"/>
+ <path class="st8" d="M220.5,183.6l-0.2,1.2c8.6,8.6,21.4,13.9,33.4,10.4c4.6-2.5,9.2-5.6,11.4-10.5c7.5-15.5,8.8-33.8,3.7-50.2
+ c-4.6-3.3-9-7.1-11.7-12.1c-5.5-7.6-14.6-15.6-24.6-12c-10.2,1.4-17.4,10.4-20.2,19.8c-3.8,12-5.1,25.6,0.1,37.4
+ C213.4,173.5,214.5,180.8,220.5,183.6z M239.7,154.1c2.2,6.5-1.4,15.5-9.4,15.6c-3.6-2.5-8.6-4.8-7.8-10
+ C221.1,150.1,234.1,150.4,239.7,154.1z"/>
+ <path class="st8" d="M307,191.9c4.6-4.9,10.2-8.7,15.1-13.2c8.4-12.3,15.5-28.6,9.1-43.4c-2.2-9.8-13.6-13.2-22.5-12.1
+ c-11.7,2-24.6,6.2-30.8,17.3c0.2,10.8,1.9,21.6-0.3,32.3C289,175.8,301.4,180.8,307,191.9z M315.4,146.4c2.3-0.5,4.7-1.1,7-1.8
+ c2.6,4,2.5,9.7,1.1,14.1c-4.6,4.5-11.2-0.1-13.1-5C312.1,151.2,313.8,148.8,315.4,146.4z"/>
+ <path class="st0" d="M267.8,250.1C267.8,250.2,267.8,250.2,267.8,250.1C267.9,250.2,267.9,250.2,267.8,250.1
+ C267.9,250.2,267.9,250.2,267.8,250.1z"/>
+ <path class="st7" d="M307,191.9c4.6-4.9,10.2-8.7,15.1-13.2c8.4-12.3,15.5-28.6,9.1-43.4c-2.2-9.8-13.6-13.2-22.5-12.1
+ c-11.7,2-24.6,6.2-30.8,17.3c0.2,10.8,1.9,21.6-0.3,32.3C289,175.8,301.4,180.8,307,191.9z M315.4,146.4c2.3-0.5,4.7-1.1,7-1.8
+ c2.6,4,2.5,9.7,1.1,14.1c-4.6,4.5-11.2-0.1-13.1-5C312.1,151.2,313.8,148.8,315.4,146.4z"/>
+ <path class="st7" d="M115.3,265.3c0.4-0.9,1.2-2.7,1.6-3.6c-3.7-4.3-10-2.3-15-3c-4.2,0.3-10.2-0.4-13.7,2.3v3.5
+ C97.2,264.7,106.3,264.9,115.3,265.3z"/>
+ <path class="st7" d="M124.6,228.4c-1.8,0.5-3.6,0.9-5.4,1.3c-4,7.1-9.4,14.4-8.2,23c5.7,0,11.5-0.1,16.6-2.8
+ C123.5,243.4,122.1,235.8,124.6,228.4z"/>
+ <path class="st7" d="M129,258.2c3-4.1,9-3.1,13.5-4.4c-3-3.9-7.1-7.4-8.3-12.3c-0.4-7.3,3.1-14.1,5.6-20.9
+ c1.9,0.4,3.7,0.7,5.6,1.1c0.3,2.6,0.5,5.2,0.7,7.9c6.4,3.5,13.8,3.7,20.6,5.9c9.4,3.1,19.5,2,29-0.2c-0.2-2.2-0.3-4.4-0.3-6.6
+ c1-4,3.2-10.2,8.6-7.9c-0.5,5.2-1.1,10.3-1.3,15.5c0.1,4.7,2.5,8.7,5.4,12.3c-0.1-2.7-0.3-5.5-0.5-8.2c-0.8-7.2-0.6-14.9,3.5-21.2
+ c1.2-0.1,3.7-0.3,4.9-0.4c-1,5-2.3,10-3.4,15c2.4,0.3,4.9,0.6,7.3,0.9c9.8,1.1,19.7-1.4,28.4-6c2.5-1.3,4.9-2.7,7.3-4.1
+ c4.7,2.8,10,5.7,15.7,4c13.7-3.2,29-5.9,39.4-16.3c-0.8-2.1-1.7-4.2-2.6-6.3c-0.2-0.5-0.4-1-0.6-1.5c-3.4-5.7-6.2-12.3-12.1-16
+ c-5.6-3.3-12.1-4.7-17.9-7.5c-6.2,5.2-9.1,13.1-15,18.5c-6.9,4.6-15.7,5.5-23.7,4c-9.1-2.1-16.5-8.1-23.7-13.6
+ c-4.4-3.4-6.6-8.6-8.2-13.7c-1-3.4-2-6.8-2.9-10.3c-1.1-4.4-0.8-10.3-5.8-12.4c-0.2-0.1-0.4-0.2-0.6-0.3
+ c-11.3-5.2-23.7-9-36.3-8.3c-11,3.4-23.2,5.8-31.8,14.2c-13.3,11.7-20.7,28.4-26.6,44.8c-0.3,1-0.8,2.9-1.1,3.9
+ c-0.2,0.7-0.5,1.9-0.7,2.6c-2,7.9-5.5,15.4-7.5,23.3c-0.5,4.5-0.2,9,0.3,13.4c0,0.8,0.1,2.5,0.2,3.4c2.7,0,5.4,0.1,8.1,0.2
+ c5.6-9.7,9.2-21,18.5-28.1c2.2-2.2,4.1-4.7,6.4-6.9c2.9-1.6,4.8,1.7,6.3,3.5c-8.1,9-5.4,22.8,0.9,32.1c-4.2,3.1-8.4,6.4-11.7,10.6
+ c2.3,7.5,9.9,11.1,16.8,13.6C134.5,270.8,127.2,266.2,129,258.2z"/>
+ <path class="st7" d="M198.5,243.4c-8.7,0.4-17.4,0.1-26.1,0.4c-1.1,4.1-2.5,8-3.3,12.2c12.4,1.1,24.9,0.7,37.3,0.5
+ C204.5,251.8,202.7,246.6,198.5,243.4z"/>
+ <path class="st7" d="M166,318.5c-1.4-1.6-2.5-3.5-3.9-5.1c0,2.4,0.1,4.7,0.2,7.1c-6,3.7-11.4-2.2-16.2-5.1c2,2.2,4.1,4.3,6.1,6.6
+ C157.1,322.7,162.6,322.9,166,318.5z"/>
+ <path class="st7" d="M121.7,272.8c-0.4-0.5-1.2-1.4-1.7-1.9c-10.6-0.4-21.2-2.9-31.8-1.2c0.3,0.7,1,2.1,1.4,2.8
+ C100.2,273.6,111,272.8,121.7,272.8z"/>
+ <path class="st7" d="M97.3,276.8c0.7,9.1,2.5,18.3,5.6,26.9c6,10.5,11,21.7,18.7,31.2c8,7.9,15.9,17.8,28,18.4
+ c8.9,0,18.8,2,26.7-3.1c10.3-4.8,16.2-17.7,12.2-28.6c-3.4-9-5.9-19.7-14.5-25.3c-12.2-7.3-30.5,4.8-28,19.1
+ c-0.6-14.4,21.3-24.4,29.7-11.2c4.9,11.1,11.9,24.1,6.3,36.3c-9,13.7-29.7,15.1-42.3,5.8c-5.9-4.5-12.2-8.9-16-15.4
+ c-3.6-6.2-6.8-12.8-10.5-19c-6.5-10.5-6.8-23.3-11.6-34.4C100.5,277.3,98.4,277,97.3,276.8z"/>
+ <path class="st7" d="M232.4,260.6c-2.4,0.6-7.6-0.2-7,3.9c6.8,0.4,13.7,0.4,20.5,0.2c2.8,0.1,4-1.3,3.7-4.2
+ C243.9,260.5,238.1,260.2,232.4,260.6z"/>
+ <path class="st7" d="M240.9,245.3c-1.8-1.7-2.5-5.1-5.3-5.4c-5.4,0.7-10.8,1.5-16.3,1.3H219c-1.4,0-4.3,0-5.7,0
+ c-0.3,3.3-0.3,6.6-0.3,9.9c1.3,0.7,4,2.1,5.3,2.9c1.9-3.5,5.5-4.9,9.1-5.9c5.4,8.8,16,7.7,24.9,7.5
+ C248.5,252.1,244,249.3,240.9,245.3z"/>
+ <path class="st7" d="M220.5,183.6l-0.2,1.2c8.6,8.6,21.4,13.9,33.4,10.4c4.6-2.5,9.2-5.6,11.4-10.5c7.5-15.5,8.8-33.8,3.7-50.2
+ c-4.6-3.3-9-7.1-11.7-12.1c-5.5-7.6-14.6-15.6-24.6-12c-10.2,1.4-17.4,10.4-20.2,19.8c-3.8,12-5.1,25.6,0.1,37.4
+ C213.4,173.5,214.5,180.8,220.5,183.6z M239.7,154.1c2.2,6.5-1.4,15.5-9.4,15.6c-3.6-2.5-8.6-4.8-7.8-10
+ C221.1,150.1,234.1,150.4,239.7,154.1z"/>
+ <path class="st7" d="M262.6,261.1c-0.4,0.7-1.2,2.2-1.6,3c5.9,1.8,12.2,0.7,18.2,1.5c7.8,1.1,16.2,2,23.5-1.7
+ c-2.5-1.6-4.8-4-7.8-4.6C284.1,258.7,273.3,260.1,262.6,261.1z"/>
+ <path class="st7" d="M285.2,269.2c-9.8,1.7-19.8,0.5-29.6,1.8c-10.5-2.1-21.2-2.4-31.9-1.6c0.1,1,0.2,3,0.3,4
+ c10.5-0.7,21.2,0.4,31.6-1.8c9.1,2,18.4,1.2,27.6,0.6c4.7-0.5,9,2.4,13.6,1.9C297.7,267.7,289.2,269.5,285.2,269.2z"/>
+ <path class="st7" d="M273.3,283.1c-5.9-2-11.6-4.6-17.3-7.2c3.7,3.6,6.8,8.2,11.5,10.5c6.2,1.4,12.3-2.7,16.7-6.7
+ C281.5,274.2,276.7,281.7,273.3,283.1z"/>
+ <path class="st7" d="M312.1,259c-0.3,2.4-0.5,4.8-0.7,7.2c-1,0.6-2.9,1.8-3.8,2.5c0,2.2-0.2,4.5-0.3,6.7c8.3-0.2,15.9-3.7,24-5.3
+ c1.3-2.6,0.5-5.5,0.5-8.3C325.1,261.2,318.8,258.7,312.1,259z"/>
+ <path class="st7" d="M251.6,238.3c-1.8-2.1-4.7-2.3-6.4,0.1c2.2,7.2,10,10.7,13.2,17.4c-1.4,3-4.9,6.2-2.5,9.6
+ c0.9-6.5,6.3-9.8,11.9-11.9c-2.9-0.2-5.8-0.4-8.6-0.6C256.9,247.9,254.9,242.7,251.6,238.3z"/>
+ <path class="st7" d="M155.1,264.6c6-1.8,11.7,2.2,17.7,2.2c11.5,0.3,23,0.3,34.5-0.2c-0.1-1.3-0.3-4-0.4-5.3
+ c-15.3,0-31.1-2.3-46,2c-4.2-0.1-8.1-2-12.2-2.7c-2.8-3.8-10.1-1.1-14.7-2.2c0.7,2.1,1.3,4.3,1.9,6.4c3.7,0.1,7.5,0.2,11.2,0.2
+ C149.5,267.6,152.5,264.8,155.1,264.6z"/>
+ <path class="st7" d="M141.5,270.7c0.9,1.2,1.8,2.5,2.7,3.7c3.6,0.1,7.3,0.2,10.9,0.4c17.3,0.1,34.5,0.4,51.8-0.2
+ c0.3-0.6,0.9-1.7,1.3-2.2c-17.7-1.2-35.4-1.4-53.1-1.7C150.6,270.8,146.1,270.8,141.5,270.7z"/>
+ <path class="st7" d="M144.4,253.5c2.8-2.2,5.6-4.4,8.6-6.1c0.4-2.3,1-4.7,1.5-7c-3.7-3.5-9.3-3.4-13.9-4.7
+ C139.4,241.9,140.8,248.2,144.4,253.5z"/>
+ <path class="st7" d="M154.7,254.2c3.2,0.6,6.4,0.8,9.7,1.1c3.3-3.8,1.2-9,0.9-13.5l-1.7,0.9C162.2,247.9,159,251.4,154.7,254.2z"
+ />
+ <path class="st7" d="M216.9,264.5c1.6-5.8,7.6-7.5,12.4-9.8c-3-7.1-10-1.1-13.2,2.6c-2.4-2.1-4.6-4.4-6.6-6.8
+ c6.2,9.1,1.2,23,11.4,29.7c-1.5-4.9-3.3-9.8-4.1-14.9L216.9,264.5z"/>
+ <path class="st9" d="M69.8,256.7c-1.1,2.5-2.4,5.1-3.7,7.5c1.1,3.2,2.8,6.2,4.8,8.9c0.6-0.3,1.8-0.8,2.3-1c-4.3-5-0.9-11,1.9-15.8
+ C73.3,256.6,71.5,256.7,69.8,256.7z"/>
+ <path class="st9" d="M82.3,258.6l-0.4-1.1c-0.2,0.4-0.5,1.2-0.6,1.6l-1.1,1.3c-0.2,0.2-0.6,0.7-0.7,0.9c-2.6,2.2-2.1,5.6-2.4,8.6
+ c0.4,0.7,1.3,2.2,1.7,3c4.5,2.7,3.6-7.2,0.5-7.4c1.9-2.3,4-4.5,4.6-7.5L82.3,258.6z"/>
+ <path class="st0" d="M230.3,169.7c8,0,11.5-9.1,9.4-15.6c-5.6-3.7-18.5-4-17.2,5.5C221.7,164.8,226.7,167.2,230.3,169.7z"/>
+ <path class="st0" d="M323.5,158.6c1.4-4.4,1.5-10.1-1.1-14.1c-2.3,0.7-4.6,1.3-7,1.8c-1.7,2.4-3.3,4.8-5,7.2
+ C312.3,158.5,318.8,163.1,323.5,158.6z"/>
+ <path class="st0" d="M319.1,255.3c-10.3-1.8-20.7-0.1-31-0.3c-4.6-3.1-9.1-6.7-14.8-7.1c-1.8,0.8-3.6,1.5-5.4,2.2
+ c4.7,0.5,9.6,1.6,13.2,5c-4.5-0.3-9.1-0.1-13.3-1.7c-5.7,2.1-11.1,5.3-11.9,11.9c-2.4-3.4,1.1-6.5,2.5-9.6
+ c-3.2-6.7-10.9-10.2-13.2-17.4c1.7-2.4,4.6-2.2,6.4-0.1c3.4,4.4,5.3,9.6,7.6,14.6c2.9,0.2,5.8,0.4,8.6,0.6c0-0.8,0-2.5,0-3.3
+ c-1.4,0.6-2.9,1.2-4.3,1.9c-2.5-5.5-4.6-11.1-6.4-16.8c3.4,0.8,6.7,2,10.1,2.7c15.6-2.4,32.3-5.5,44.5-16.3
+ c2.6-2.6,7.2-4.9,6.4-9.2c-0.3-2.2-1-4.3-2-6.2c-1.7-3.6-4.1-6.9-5.6-10.7c3.5-1.3,7.1-2.6,10.5-4.2
+ c12.2-11.9,18.3-28.9,19.6-45.7c-1.2-8.9-3.7-18.7-10.6-25c-12.6-6.3-27.4-4.3-40.2,0.3c-5.8,2.3-10,7-14.9,10.8
+ c-3.9-0.9-6.3-5.4-8.9-8.4c-5-8.5-11.8-16.3-21.1-20c-7.8-0.3-16.7-2.4-23.4,2.6c-3.4,2.2-7.2,4.2-9.6,7.7
+ c-5.4,10.1-8.5,21.3-9.4,32.6c-1.7-0.7-3.4-1.3-5.1-2c-11.9-4.4-24.3-7.4-37-7c-8.8,1.7-17.4,4.3-25.6,8
+ c-16.7,8.7-27.3,25.7-33.5,43c-3.8,7.6-6.5,15.6-8,23.9c-1.4,7.5-4.3,14.8-4.7,22.5c0,5.2,0.4,10.4,0.3,15.6
+ c-0.2,0.1-0.5,0.3-0.7,0.4c-5.4,3-11.8,2.9-17.8,3.9c-4.5-0.1-8.8-2.3-13.3-1c0,0-0.1,0-0.1,0c-4.8,2.3-9.9,3.6-14.7,5.8
+ c-1.1,6.8-2.2,16.2,6,18.8c2.9,0,5.9,0,8.8,0c10.4,0,20.7,0.1,31.1,0.2c0.8,0,1.6,0,2.4,0c2.5,13,5,26.4,11.9,37.9
+ c8.1,13.1,16.1,26.8,28.4,36.4c11.8,7.9,26.7,8.2,40.2,5.8c5.1-0.6,8.7-4.6,13.3-6.5c2.8-1.7,7-2.1,8-5.7
+ c9.2-15,2.2-33.5-6.2-47.2c-5.3-9.4-16.9-11.3-26.7-10.9c-5.8,0.9-9.4,5.8-14.1,8.8c-5.1,3.1-10.1,9.7-6.2,15.7
+ c3.3,6.3,7.4,14.7,15.8,14.2c5.3-0.6,12.3-1.8,14.6-7.3c1-6.7-6.9-9.9-12.2-11.1c0.3,3.3,0.6,6.5,0.9,9.8
+ c-2.7-1.9-5.5-3.7-8.3-5.6c1.7-8.8,11.5-8.6,18.3-11.3c6.9,10.7,14.1,24.5,8.5,37.3c-6.5,8.1-18.2,7.1-27.2,5
+ c-11.8-1.8-19.7-12.4-24.9-22.3c-7.9-13.7-17.5-28.4-15.8-44.9c5.5-0.1,11.1-0.5,16.5,0.1c5.3,1.2,10,5.8,15.7,3.6
+ c12.2-3.3,24.9-1.3,37.4-1.5c9.8-0.5,19.7-1,29.5-0.4c3.5,1,5.5,5.3,9.6,5c4.7,1.6,7.4-3.3,10.9-5.3c7.9-0.3,15.9-0.5,23.7,0
+ c3.7,6.5,9.3,13.5,17.3,14.5c9.7,0.4,18-6.5,23.2-14.1c6.9,1.6,14.3,3.7,21,0.3c14.5-6.1,30.5-6.1,45.4-10.4c0-1.1,0-3.2,0-4.3
+ C347.4,256.5,332.6,258.7,319.1,255.3z M252.2,255.6c-8.9,0.2-19.5,1.3-24.9-7.5c-3.6,1-7.2,2.4-9.1,5.9c-1.3-0.7-4-2.2-5.3-2.9
+ c0-3.3,0-6.6,0.3-9.9c1.4,0,4.3,0,5.7,0h0.3c5.5,0.3,10.9-0.6,16.3-1.3c2.8,0.3,3.5,3.7,5.3,5.4
+ C244,249.3,248.5,252.1,252.2,255.6z M245.9,264.7c-6.9,0.2-13.7,0.2-20.5-0.2c-0.7-4.1,4.5-3.3,7-3.9c5.8-0.4,11.5-0.1,17.3-0.1
+ C249.9,263.4,248.7,264.8,245.9,264.7z M277.9,140.6c6.2-11.1,19.1-15.3,30.8-17.3c8.9-1.1,20.2,2.3,22.5,12.1
+ c6.4,14.7-0.7,31-9.1,43.4c-4.9,4.6-10.5,8.3-15.1,13.2c-5.6-11.2-18-16.2-29.4-19C279.7,162.2,278.1,151.3,277.9,140.6z
+ M212.3,130.2c2.8-9.4,10-18.4,20.2-19.8c10-3.6,19.2,4.4,24.6,12c2.8,5.1,7.1,8.8,11.7,12.1c5.1,16.4,3.8,34.7-3.7,50.2
+ c-2.2,5-6.9,8-11.4,10.5c-12.1,3.5-24.8-1.8-33.4-10.4l0.2-1.2c-6.1-2.8-7.1-10.2-8.2-16C207.2,155.8,208.5,142.2,212.3,130.2z
+ M57.1,274.9c-3.6-0.1-7.1-0.3-10.7-0.9c-0.6-3.4-2.1-8.4,1.4-10.7c3.6-2,6.1-5.2,8.2-8.7c0.4,0.1,0.7,0.1,1.1,0.1
+ c3,0.4,6.1,0.3,9.1-0.3c-0.3,3.9-5.6,5.6-4.9,9.6c0.9,3.9,3.1,7.4,4.7,11.1C63,275,60.1,274.9,57.1,274.9z M73.2,272.2
+ c-0.6,0.3-1.8,0.8-2.3,1c-2-2.8-3.6-5.7-4.8-8.9c1.4-2.4,2.6-5,3.7-7.5c1.7-0.1,3.5-0.2,5.2-0.4C72.3,261.2,68.9,267.1,73.2,272.2
+ z M78.6,272.8c-0.4-0.7-1.3-2.2-1.7-3c0.3-3-0.2-6.4,2.4-8.6c0.2-0.2,0.5-0.6,0.7-0.9l1.1-1.3c0.2-0.4,0.5-1.2,0.6-1.6l0.4,1.1
+ l1.4-0.6c-0.5,3-2.7,5.2-4.6,7.5C82.3,265.6,83.1,275.5,78.6,272.8z M85.9,264.4c0.5-1.5,1.3-2.6,2.3-3.4c3.5-2.7,9.5-2,13.7-2.3
+ c5,0.7,11.2-1.3,15,3c-0.4,0.9-1.2,2.7-1.6,3.6c-9-0.4-18.1-0.6-27.1-0.8C87.4,264.5,86.7,264.5,85.9,264.4z M127.6,249.9
+ c-5.2,2.7-11,2.8-16.6,2.8c-1.2-8.6,4.2-15.9,8.2-23c1.8-0.4,3.6-0.8,5.4-1.3C122.1,235.8,123.5,243.4,127.6,249.9z M113.2,311.9
+ c3.8,6.2,6.9,12.7,10.5,19c3.8,6.5,10.1,10.9,16,15.4c12.6,9.3,33.4,7.9,42.3-5.8c5.7-12.2-1.4-25.1-6.3-36.3
+ c-8.4-13.2-30.3-3.2-29.7,11.2c4.8,2.9,10.2,8.8,16.2,5.1c-0.1-2.3-0.2-4.7-0.2-7.1c1.4,1.6,2.5,3.4,3.9,5.1
+ c-3.4,4.4-8.9,4.2-13.8,3.5c-2-2.2-4.1-4.4-6.1-6.6c-2.5-14.3,15.8-26.4,28-19.1c8.7,5.6,11.1,16.2,14.5,25.3
+ c4,10.9-1.9,23.8-12.2,28.6c-8,5.1-17.8,3.1-26.7,3.1c-12.2-0.6-20.1-10.5-28-18.4c-7.7-9.4-12.7-20.6-18.7-31.2
+ c-3.1-8.6-4.9-17.8-5.6-26.9c1.1,0.2,3.2,0.5,4.4,0.6C106.4,288.6,106.7,301.4,113.2,311.9z M89.6,272.5c-0.4-0.7-1-2.1-1.4-2.8
+ c10.6-1.7,21.2,0.8,31.8,1.2c0.4,0.5,1.2,1.4,1.7,1.9C111,272.8,100.2,273.6,89.6,272.5z M122.5,262.1c3.3-4.1,7.4-7.4,11.7-10.6
+ c-6.2-9.3-9-23.1-0.9-32.1c-1.6-1.9-3.4-5.2-6.3-3.5c-2.3,2.1-4.2,4.6-6.4,6.9c-9.3,7.1-12.9,18.4-18.5,28.1
+ c-2.7-0.1-5.4-0.2-8.1-0.2c0-0.8-0.1-2.5-0.2-3.4c-0.5-4.5-0.8-8.9-0.3-13.4c2-7.9,5.5-15.3,7.5-23.3c0.2-0.6,0.5-1.9,0.7-2.6
+ c0.3-1,0.8-2.9,1.1-3.9c6-16.3,13.4-33,26.6-44.8c8.5-8.4,20.7-10.7,31.8-14.2c12.5-0.7,25,3.1,36.3,8.3c0.2,0.1,0.4,0.2,0.6,0.3
+ c5,2,4.7,7.9,5.8,12.4c0.9,3.4,1.8,6.9,2.9,10.3c1.7,5.1,3.9,10.3,8.2,13.7c7.2,5.5,14.7,11.5,23.7,13.6c8,1.5,16.8,0.6,23.7-4
+ c6-5.4,8.8-13.3,15-18.5c5.8,2.9,12.3,4.2,17.9,7.5c5.8,3.7,8.6,10.3,12.1,16c0.2,0.5,0.4,1,0.6,1.5c0.9,2.1,1.7,4.2,2.6,6.3
+ c-10.4,10.4-25.7,13.1-39.4,16.3c-5.7,1.7-11-1.3-15.7-4c-2.4,1.3-4.9,2.8-7.3,4.1c-8.6,4.6-18.6,7.1-28.4,6
+ c-2.4-0.3-4.9-0.6-7.3-0.9c1.1-5,2.4-9.9,3.4-15c-1.2,0.1-3.7,0.3-4.9,0.4c-4.1,6.3-4.3,13.9-3.5,21.2c0.2,2.7,0.4,5.5,0.5,8.2
+ c-2.9-3.6-5.3-7.6-5.4-12.3c0.2-5.2,0.9-10.3,1.3-15.5c-5.4-2.3-7.6,3.9-8.6,7.9c0,2.2,0.2,4.4,0.3,6.6c-9.5,2.2-19.6,3.3-29,0.2
+ c-6.8-2.2-14.2-2.4-20.6-5.9c-0.2-2.6-0.5-5.3-0.7-7.9c-1.9-0.4-3.7-0.7-5.6-1.1c-2.5,6.7-6,13.5-5.6,20.9
+ c1.3,4.8,5.3,8.4,8.3,12.3c-4.4,1.3-10.5,0.3-13.5,4.4c-1.8,8,5.5,12.6,10.2,17.5C132.4,273.2,124.8,269.6,122.5,262.1z
+ M206.4,256.5c-12.4,0.2-24.9,0.6-37.3-0.5c0.8-4.1,2.2-8.1,3.3-12.2c8.7-0.3,17.4,0,26.1-0.4
+ C202.7,246.6,204.5,251.8,206.4,256.5z M164.4,255.3c-3.3-0.3-6.5-0.5-9.7-1.1c4.3-2.9,7.5-6.4,8.9-11.5l1.7-0.9
+ C165.6,246.3,167.7,251.5,164.4,255.3z M154.6,240.3c-0.6,2.3-1.1,4.6-1.5,7c-3.1,1.7-5.9,4-8.6,6.1c-3.6-5.3-5-11.5-3.8-17.8
+ C145.3,237,150.9,236.8,154.6,240.3z M136,264.9c-0.6-2.1-1.2-4.3-1.9-6.4c4.6,1.1,11.9-1.6,14.7,2.2c4.1,0.7,8,2.6,12.2,2.7
+ c14.9-4.4,30.7-2,46-2c0.1,1.3,0.3,4,0.4,5.3c-11.5,0.4-23,0.5-34.5,0.2c-6,0-11.7-4-17.7-2.2c-2.6,0.2-5.6,3-7.9,0.6
+ C143.4,265.1,139.7,265,136,264.9z M206.9,274.6c-17.3,0.6-34.5,0.3-51.8,0.2c-3.6-0.1-7.3-0.3-10.9-0.4c-0.9-1.2-1.8-2.5-2.7-3.7
+ c4.5,0,9,0.1,13.6,0c17.7,0.2,35.4,0.5,53.1,1.7C207.8,273,207.2,274.1,206.9,274.6z M209.5,250.4c2,2.5,4.2,4.7,6.6,6.8
+ c3.2-3.7,10.3-9.6,13.2-2.6c-4.8,2.3-10.8,4-12.4,9.8l-0.1,0.7c0.7,5.1,2.6,10,4.1,14.9C210.6,273.4,215.7,259.6,209.5,250.4z
+ M267.6,286.4c-4.8-2.3-7.8-6.9-11.5-10.5c5.7,2.5,11.3,5.2,17.3,7.2c3.4-1.4,8.2-8.9,11-3.4C279.9,283.7,273.8,287.8,267.6,286.4
+ z M296.8,274.1c-4.6,0.6-8.9-2.3-13.6-1.9c-9.2,0.6-18.5,1.4-27.6-0.6c-10.4,2.2-21.1,1.2-31.6,1.8c-0.1-1-0.2-3-0.3-4
+ c10.7-0.9,21.4-0.5,31.9,1.6c9.8-1.3,19.8-0.1,29.6-1.8C289.2,269.5,297.7,267.7,296.8,274.1z M279.2,265.6
+ c-6-0.8-12.3,0.3-18.2-1.5c0.4-0.7,1.2-2.2,1.6-3c10.7-0.9,21.4-2.4,32.3-1.8c3,0.6,5.3,3,7.8,4.6
+ C295.4,267.5,287,266.6,279.2,265.6z M331.3,270.1c-8.1,1.5-15.7,5.1-24,5.3c0.2-2.2,0.3-4.5,0.3-6.7c0.9-0.6,2.8-1.9,3.8-2.5
+ c0.2-2.4,0.5-4.8,0.7-7.2c6.7-0.3,13,2.2,19.6,2.9C331.8,264.5,332.6,267.5,331.3,270.1z"/>
+ <path class="st10" d="M160.4,208.5c2.6,2.5,5.1,5.3,8.5,6.7c6.4,2.5,10.9,8.5,17.6,10.6c-0.6,1-1.2,2.1-1.8,3.1
+ c-9.1-2.9-18.8-0.7-27.9-3.6c5.1-2.7,5.1-8.4,2.5-12.8C159.5,211.5,160.1,209.5,160.4,208.5L160.4,208.5z"/>
+ <path class="st11" d="M101.2,235.7c6.3-5.3,5.5-17.1,15.3-18.3c1.4,1.8,2.7,3.6,4,5.4c-9.3,7.1-12.9,18.4-18.5,28.1
+ c-2.7-0.1-5.4-0.2-8.1-0.2c0-0.8-0.1-2.5-0.2-3.4C97.4,244.1,97.2,238.6,101.2,235.7L101.2,235.7z"/>
+ <path class="st12" d="M233,221.3c5.3,2.1,10.2,4.9,15.2,7.6c-8.6,4.6-18.6,7.1-28.4,6c-0.5-3.7-0.6-7.4-0.2-11.1
+ c0.9-0.1,2.7-0.3,3.6-0.5c0.9,2.6,1.8,5.2,2.7,7.9C228.4,228,230.8,224.7,233,221.3L233,221.3z"/>
+ <path class="st13" d="M150.3,224.4c1.6,0.2,4.8,0.6,6.5,0.9c9.1,2.9,18.8,0.7,27.9,3.6c0.6-1,1.2-2.1,1.8-3.1c3,1.1,5.9,2,8.9,3
+ c0,2.2,0.2,4.4,0.3,6.6c-9.5,2.2-19.6,3.3-29,0.2c1-0.7,3-2.1,4-2.8C163.6,230.9,155.7,229.9,150.3,224.4L150.3,224.4z"/>
+ <path class="st14" d="M56.1,254.6c3.4,0.5,6.8,0.5,10.2-0.2c-0.3,3.9-5.6,5.6-4.9,9.6c0.9,3.9,3.1,7.4,4.7,11.1
+ c-6.5-0.3-13.1,0.1-19.5-1.1c-0.6-3.4-2.1-8.4,1.4-10.7C51.5,261.2,54,258.1,56.1,254.6L56.1,254.6z"/>
+ <path class="st15" d="M312.1,259c6.7-0.3,13,2.2,19.6,2.9c0,2.7,0.8,5.7-0.5,8.3c-8.1,1.5-15.7,5.1-24,5.3
+ c0.2-2.2,0.3-4.5,0.3-6.7c0.9-0.6,2.8-1.9,3.8-2.5C311.6,263.8,311.9,261.4,312.1,259L312.1,259z"/>
+ <path class="st16" d="M134.1,258.5c4.6,1.1,11.9-1.6,14.7,2.2c-0.8-0.1-2.4-0.3-3.2-0.4c0.5,1.6,1,3.3,1.6,4.9
+ c-3.7,0-7.5-0.2-11.2-0.2C135.4,262.8,134.7,260.6,134.1,258.5 M88.2,269.7c10.6-1.7,21.2,0.8,31.8,1.2c0.4,0.5,1.2,1.4,1.7,1.9
+ c-10.7,0-21.5,0.8-32.1-0.3C89.2,271.8,88.5,270.4,88.2,269.7L88.2,269.7z"/>
+ <path class="st17" d="M85.9,264.4c2-6.6,10.6-5.4,16.1-5.7c5,0.7,11.2-1.3,15,3c-0.4,0.9-1.2,2.7-1.6,3.6
+ C105.5,264.8,95.7,264.7,85.9,264.4 M155.1,270.8c17.7,0.2,35.4,0.5,53.1,1.7c-0.3,0.6-1,1.7-1.3,2.2c-17.3,0.6-34.5,0.3-51.8,0.2
+ C155.1,273.8,155.1,271.8,155.1,270.8L155.1,270.8z"/>
+ <path class="st18" d="M145.6,260.3c0.8,0.1,2.4,0.3,3.2,0.4c4.1,0.7,8,2.6,12.2,2.7c14.9-4.4,30.7-2,46-2c0.1,1.3,0.3,4,0.4,5.3
+ c-11.5,0.4-23,0.5-34.5,0.2c-6,0-11.7-4-17.7-2.2c-2.6,0.2-5.6,3-7.9,0.6C146.6,263.5,146.1,261.9,145.6,260.3 M141.5,270.7
+ c4.5,0,9,0.1,13.6,0c0,1,0,3.1,0,4.1c-3.6-0.1-7.3-0.3-10.9-0.4C143.3,273.2,142.4,272,141.5,270.7L141.5,270.7z"/>
+ <path class="st19" d="M225.4,264.5c-0.7-4.1,4.5-3.3,7-3.9c5.8-0.4,11.5-0.1,17.3-0.1c0.3,2.9-0.9,4.3-3.7,4.2
+ C239.1,264.9,232.2,264.9,225.4,264.5 M262.6,261.1c10.7-0.9,21.4-2.4,32.3-1.8c3,0.6,5.3,3,7.8,4.6c-7.3,3.6-15.6,2.8-23.5,1.7
+ c-6-0.8-12.3,0.3-18.2-1.5C261.4,263.3,262.2,261.8,262.6,261.1 M223.7,269.5c10.7-0.9,21.4-0.5,31.9,1.6
+ c9.8-1.3,19.8-0.1,29.6-1.8c4.1,0.3,12.6-1.5,11.6,4.9c-4.6,0.6-8.9-2.3-13.6-1.9c-9.2,0.6-18.5,1.4-27.6-0.6
+ c-10.4,2.2-21.1,1.2-31.6,1.8C223.9,272.5,223.8,270.5,223.7,269.5L223.7,269.5z"/>
+ <path class="st20" d="M216.8,265.2c0-0.2,0.1-0.5,0.1-0.7L216.8,265.2L216.8,265.2z"/>
+ </g>
+</g>
+</svg>
diff --git a/assets/img/og-default.png b/assets/img/og-default.png
new file mode 100644
index 0000000..9f453aa
--- /dev/null
+++ b/assets/img/og-default.png
Binary files differ
diff --git a/assets/img/openbgpd.gif b/assets/img/openbgpd.gif
new file mode 100644
index 0000000..5a7c67d
--- /dev/null
+++ b/assets/img/openbgpd.gif
Binary files differ
diff --git a/assets/img/openbsd.png b/assets/img/openbsd.png
new file mode 100644
index 0000000..0937cfc
--- /dev/null
+++ b/assets/img/openbsd.png
Binary files differ
diff --git a/assets/img/pia.svg b/assets/img/pia.svg
new file mode 100644
index 0000000..08933a4
--- /dev/null
+++ b/assets/img/pia.svg
@@ -0,0 +1,54 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="149.338" height="47.5" viewBox="0 0 149.338 47.5">
+ <defs>
+ <clipPath id="clip-path">
+ <rect id="Rectangle_2855" data-name="Rectangle 2855" width="31.71" height="17.053" fill="#fff" stroke="#696b73" stroke-width="1"/>
+ </clipPath>
+ <clipPath id="clip-path-2">
+ <rect id="Rectangle_2851" data-name="Rectangle 2851" width="72.882" height="50.425" fill="none"/>
+ </clipPath>
+ <linearGradient id="linear-gradient" x1="0.497" y1="0.034" x2="0.501" y2="0.994" gradientUnits="objectBoundingBox">
+ <stop offset="0" stop-color="#56b14e"/>
+ <stop offset="0.539" stop-color="#4a9e41"/>
+ <stop offset="1" stop-color="#357c2a"/>
+ </linearGradient>
+ <linearGradient id="linear-gradient-2" x1="0.5" y1="0.969" x2="0.5" y2="0.011" gradientUnits="objectBoundingBox">
+ <stop offset="0" stop-color="#4a9e41"/>
+ <stop offset="1" stop-color="#56b14e"/>
+ </linearGradient>
+ <linearGradient id="linear-gradient-3" x1="0.517" y1="0.858" x2="0.496" y2="0.179" gradientUnits="objectBoundingBox">
+ <stop offset="0" stop-color="#56b14e"/>
+ <stop offset="0.3" stop-color="#4a9e41"/>
+ <stop offset="0.355" stop-color="#45973c"/>
+ <stop offset="0.523" stop-color="#3c8732"/>
+ <stop offset="0.717" stop-color="#367e2b"/>
+ <stop offset="1" stop-color="#357c2a"/>
+ </linearGradient>
+ <linearGradient id="linear-gradient-4" x1="0.517" y1="0.858" x2="0.496" y2="0.179" gradientUnits="objectBoundingBox">
+ <stop offset="0" stop-color="#56b14e" stop-opacity="0.153"/>
+ <stop offset="1" stop-color="#9bf893" stop-opacity="0.263"/>
+ </linearGradient>
+ </defs>
+ <g id="Light-bg-horizontal-web" transform="translate(-672.501 -2161)">
+ <g id="Mask_Group_11" data-name="Mask Group 11" transform="translate(673 2191.447)" clip-path="url(#clip-path)">
+ <g id="Group_37087" data-name="Group 37087" transform="translate(-19.452 -9.999)">
+ <g id="Group_37086" data-name="Group 37086" transform="translate(0)" clip-path="url(#clip-path-2)">
+ <image id="Rectangle_2850" data-name="Rectangle 2850" width="69.123" height="44.53" xlink:href="data:image/png;base64,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"/>
+ </g>
+ </g>
+ </g>
+ <path id="Path_28986" data-name="Path 28986" d="M150.331,275.306h-.489a12.711,12.711,0,0,0-12.711,12.711v3.411a1.031,1.031,0,0,0,1.031,1.031h2.743a1.031,1.031,0,0,0,1.031-1.031v-3.621a7.959,7.959,0,0,1,7.959-7.959h.384a7.959,7.959,0,0,1,7.959,7.959v3.621a1.031,1.031,0,0,0,1.031,1.031h2.742a1.031,1.031,0,0,0,1.031-1.031v-3.411A12.711,12.711,0,0,0,150.331,275.306Z" transform="translate(538.921 1885.694)" fill="url(#linear-gradient)"/>
+ <path id="Subtraction_31" data-name="Subtraction 31" d="M25.3,27.448H21.736a3.133,3.133,0,0,1-2.8-1.718H12.4a3.133,3.133,0,0,1-2.8,1.718H6.04a3.139,3.139,0,0,1-2.87-1.872A4.289,4.289,0,0,1,0,21.442V5.145A4.252,4.252,0,0,1,.588,2.977,3.016,3.016,0,0,1,3.593,0H9.362a2.971,2.971,0,0,1,2.1.854h8.82A2.964,2.964,0,0,1,22.368,0h5.775a3.01,3.01,0,0,1,3.006,3.006v.017a4.3,4.3,0,0,1,.559,2.123v16.3a4.275,4.275,0,0,1-3.582,4.227A3.15,3.15,0,0,1,25.3,27.448ZM15.849,6.227a3.644,3.644,0,0,0-.4.021,3.509,3.509,0,0,0-1.565,6.392.532.532,0,0,1,.232.525l-.864,5.85a.557.557,0,0,0,.131.443.546.546,0,0,0,.411.187H17.91a.546.546,0,0,0,.411-.187.557.557,0,0,0,.131-.443l-.864-5.85a.556.556,0,0,1,.231-.532,3.506,3.506,0,0,0-1.971-6.406Z" transform="translate(673.001 2176.169)" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1" fill="url(#linear-gradient-2)"/>
+ <path id="Path_28993" data-name="Path 28993" d="M13.381-11.211H5.8L4.272-6.455a2.8,2.8,0,0,1-.591.187,3.862,3.862,0,0,1-.746.062A2.2,2.2,0,0,1,1.66-6.548a1.162,1.162,0,0,1-.5-1.026,2.421,2.421,0,0,1,.093-.668q.093-.326.249-.731.4-1.306,1.135-3.373t1.555-4.368q.824-2.3,1.663-4.524t1.461-3.809a3.882,3.882,0,0,1,.964-.358,5.255,5.255,0,0,1,1.337-.171,4.9,4.9,0,0,1,1.617.249,1.5,1.5,0,0,1,.964.871q.684,1.772,1.492,4.026t1.617,4.648q.808,2.394,1.555,4.695T18.2-6.921a2.147,2.147,0,0,1-.731.513,2.607,2.607,0,0,1-1.073.2,1.825,1.825,0,0,1-1.213-.326,2.435,2.435,0,0,1-.622-1.1ZM9.588-22.684q-.311.839-.684,1.9t-.777,2.223q-.4,1.166-.808,2.363t-.777,2.316h6.125q-.4-1.244-.824-2.5t-.808-2.41L10.3-20.958q-.342-1.01-.56-1.726Zm25.588-.435a1.539,1.539,0,0,1-.3.933,1.8,1.8,0,0,1-.7.591,13.35,13.35,0,0,0-1.85-.995,6.446,6.446,0,0,0-2.627-.466,6.524,6.524,0,0,0-2.5.466,5.4,5.4,0,0,0-1.959,1.368,6.389,6.389,0,0,0-1.29,2.239,9.329,9.329,0,0,0-.466,3.078,7.367,7.367,0,0,0,1.741,5.317,6.1,6.1,0,0,0,4.6,1.8,7.279,7.279,0,0,0,2.643-.435,9.3,9.3,0,0,0,1.928-.995,2.4,2.4,0,0,1,.637.622,1.539,1.539,0,0,1,.3.933,1.5,1.5,0,0,1-.233.808,2.166,2.166,0,0,1-.762.684,7.315,7.315,0,0,1-1.865.777,10.942,10.942,0,0,1-2.985.342,11,11,0,0,1-3.7-.606A8.194,8.194,0,0,1,22.8-8.491,8.576,8.576,0,0,1,20.8-11.569a11.77,11.77,0,0,1-.731-4.337,11.488,11.488,0,0,1,.746-4.291,9,9,0,0,1,2.005-3.109,8.35,8.35,0,0,1,2.938-1.881,9.989,9.989,0,0,1,3.575-.637,11.381,11.381,0,0,1,2.41.233,8.094,8.094,0,0,1,1.834.606,3.576,3.576,0,0,1,1.181.871A1.548,1.548,0,0,1,35.176-23.119Zm17.535,0a1.539,1.539,0,0,1-.3.933,1.8,1.8,0,0,1-.7.591,13.35,13.35,0,0,0-1.85-.995,6.446,6.446,0,0,0-2.627-.466,6.524,6.524,0,0,0-2.5.466,5.4,5.4,0,0,0-1.959,1.368,6.389,6.389,0,0,0-1.29,2.239,9.329,9.329,0,0,0-.466,3.078,7.367,7.367,0,0,0,1.741,5.317,6.1,6.1,0,0,0,4.6,1.8,7.279,7.279,0,0,0,2.643-.435,9.3,9.3,0,0,0,1.928-.995,2.4,2.4,0,0,1,.637.622,1.539,1.539,0,0,1,.3.933,1.5,1.5,0,0,1-.233.808,2.166,2.166,0,0,1-.762.684,7.315,7.315,0,0,1-1.865.777,10.942,10.942,0,0,1-2.985.342,11,11,0,0,1-3.7-.606,8.194,8.194,0,0,1-2.985-1.834,8.576,8.576,0,0,1-2.005-3.078,11.77,11.77,0,0,1-.731-4.337,11.488,11.488,0,0,1,.746-4.291,9,9,0,0,1,2.005-3.109,8.35,8.35,0,0,1,2.938-1.881,9.989,9.989,0,0,1,3.575-.637,11.381,11.381,0,0,1,2.41.233,8.1,8.1,0,0,1,1.834.606,3.576,3.576,0,0,1,1.181.871A1.548,1.548,0,0,1,52.711-23.119ZM58.557-6.455a1.766,1.766,0,0,1-1.306-.5,1.766,1.766,0,0,1-.5-1.306V-23.586a1.714,1.714,0,0,1,.5-1.29,1.8,1.8,0,0,1,1.306-.482H68.848a1.569,1.569,0,0,1,.264.529,2.45,2.45,0,0,1,.109.746,1.555,1.555,0,0,1-.326,1.057,1.117,1.117,0,0,1-.886.373H60.049v5.161h7.866a1.568,1.568,0,0,1,.264.529,2.45,2.45,0,0,1,.109.746,1.484,1.484,0,0,1-.326,1.057,1.167,1.167,0,0,1-.886.342H60.049v5.659H69a2.549,2.549,0,0,1,.249.529,2.168,2.168,0,0,1,.124.746,1.544,1.544,0,0,1-.326,1.073,1.141,1.141,0,0,1-.886.358ZM78.672-8.786a4.339,4.339,0,0,0,2.891-.808,2.63,2.63,0,0,0,.933-2.083,2.381,2.381,0,0,0-.715-1.8,5.625,5.625,0,0,0-2.27-1.15l-2.332-.715a12.845,12.845,0,0,1-1.974-.793A6.106,6.106,0,0,1,73.7-17.2a4.064,4.064,0,0,1-.948-1.43,5.168,5.168,0,0,1-.326-1.912,4.637,4.637,0,0,1,1.819-3.871,7.825,7.825,0,0,1,4.928-1.415,12.955,12.955,0,0,1,2.363.2,8.256,8.256,0,0,1,1.881.56,3.841,3.841,0,0,1,1.244.839,1.5,1.5,0,0,1,.451,1.042,1.591,1.591,0,0,1-.264.917,2.331,2.331,0,0,1-.637.637,7.037,7.037,0,0,0-2.036-1.01,8.759,8.759,0,0,0-2.783-.42,4.408,4.408,0,0,0-2.643.684,2.161,2.161,0,0,0-.933,1.834,1.913,1.913,0,0,0,.653,1.508,5.742,5.742,0,0,0,2.145,1.01l1.8.529a10.052,10.052,0,0,1,4.057,2.13,4.8,4.8,0,0,1,1.446,3.684,5.375,5.375,0,0,1-.482,2.285A5.048,5.048,0,0,1,84.051-7.6,6.333,6.333,0,0,1,81.8-6.455a10.693,10.693,0,0,1-3.062.4A12.7,12.7,0,0,1,76.123-6.3a9.228,9.228,0,0,1-2.036-.653,4.034,4.034,0,0,1-1.321-.933A1.671,1.671,0,0,1,72.3-9a1.569,1.569,0,0,1,.373-1.057,2.411,2.411,0,0,1,.871-.653,8.435,8.435,0,0,0,2.036,1.306A7.069,7.069,0,0,0,78.672-8.786Zm16.509,0a4.339,4.339,0,0,0,2.891-.808,2.63,2.63,0,0,0,.933-2.083,2.381,2.381,0,0,0-.715-1.8,5.625,5.625,0,0,0-2.27-1.15l-2.332-.715a12.844,12.844,0,0,1-1.974-.793A6.105,6.105,0,0,1,90.207-17.2a4.064,4.064,0,0,1-.948-1.43,5.168,5.168,0,0,1-.326-1.912,4.637,4.637,0,0,1,1.819-3.871,7.825,7.825,0,0,1,4.928-1.415,12.955,12.955,0,0,1,2.363.2,8.256,8.256,0,0,1,1.881.56,3.841,3.841,0,0,1,1.244.839,1.5,1.5,0,0,1,.451,1.042,1.591,1.591,0,0,1-.264.917,2.332,2.332,0,0,1-.637.637,7.038,7.038,0,0,0-2.036-1.01,8.759,8.759,0,0,0-2.783-.42,4.408,4.408,0,0,0-2.643.684,2.161,2.161,0,0,0-.933,1.834,1.913,1.913,0,0,0,.653,1.508,5.743,5.743,0,0,0,2.145,1.01l1.8.529a10.052,10.052,0,0,1,4.057,2.13,4.8,4.8,0,0,1,1.446,3.684,5.375,5.375,0,0,1-.482,2.285A5.047,5.047,0,0,1,100.56-7.6a6.333,6.333,0,0,1-2.254,1.15,10.692,10.692,0,0,1-3.062.4A12.7,12.7,0,0,1,92.632-6.3,9.228,9.228,0,0,1,90.6-6.952a4.034,4.034,0,0,1-1.321-.933A1.671,1.671,0,0,1,88.808-9a1.569,1.569,0,0,1,.373-1.057,2.411,2.411,0,0,1,.871-.653,8.435,8.435,0,0,0,2.036,1.306A7.069,7.069,0,0,0,95.182-8.786Z" transform="translate(712.511 2209.225)" fill="#1f212d"/>
+ <path id="Path_28995" data-name="Path 28995" transform="translate(759.331 2178.4)" fill="#3ab54a"/>
+ <g id="Group_37089" data-name="Group 37089" transform="translate(713.673 2167.364)">
+ <path id="Path_28994" data-name="Path 28994" d="M3.084-6.72v3.3q-.092.031-.246.069a1.392,1.392,0,0,1-.338.038q-.676,0-.676-.568v-8.168a.529.529,0,0,1,.123-.376.847.847,0,0,1,.384-.207,5.471,5.471,0,0,1,1.082-.2q.637-.061,1.236-.061a4.134,4.134,0,0,1,2.848.821,2.859,2.859,0,0,1,.9,2.219,2.956,2.956,0,0,1-.921,2.28,4.071,4.071,0,0,1-2.84.852ZM4.558-7.764a3.12,3.12,0,0,0,1.9-.507,1.83,1.83,0,0,0,.676-1.566,1.776,1.776,0,0,0-.653-1.528,3.092,3.092,0,0,0-1.865-.484q-.415,0-.821.038t-.729.1v3.946Zm6.387,4.345a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.66,0-.66-.553v-5.2a1.044,1.044,0,0,1,.131-.56,1.419,1.419,0,0,1,.438-.407,3.953,3.953,0,0,1,1.082-.453,5.709,5.709,0,0,1,1.543-.192q1.228,0,1.228.614a.848.848,0,0,1-.046.284.716.716,0,0,1-.123.223q-.154-.031-.4-.061a3.957,3.957,0,0,0-.491-.031,4.768,4.768,0,0,0-1.244.146,3.9,3.9,0,0,0-.891.345Zm5.942,0a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.66,0-.66-.553v-6.587a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.645,0,.645.568Zm-1.413-9.3a.741.741,0,0,1,.223-.545.779.779,0,0,1,.576-.223.754.754,0,0,1,.568.223.755.755,0,0,1,.215.545.755.755,0,0,1-.215.545.754.754,0,0,1-.568.223.779.779,0,0,1-.576-.223A.741.741,0,0,1,15.474-12.723Zm9.12,2.1a.626.626,0,0,1,.422.138.5.5,0,0,1,.161.4,2.526,2.526,0,0,1-.123.591q-.123.422-.322,1T24.28-7.227q-.253.691-.537,1.374T23.174-4.54q-.284.63-.53,1.09a.719.719,0,0,1-.269.107,1.739,1.739,0,0,1-.407.046q-.568,0-.737-.322-.138-.261-.353-.722t-.461-1.044q-.246-.583-.507-1.244T19.4-7.941q-.246-.653-.461-1.259t-.353-1.082a.766.766,0,0,1,.292-.246.818.818,0,0,1,.368-.092.591.591,0,0,1,.422.138,1.252,1.252,0,0,1,.269.461l.952,2.748q.092.261.23.637t.284.768q.146.392.292.76t.238.63H22q.614-1.551,1.144-3.055t.929-2.963A.985.985,0,0,1,24.594-10.62Zm4.591,6.433a3.677,3.677,0,0,0,1.067-.131,2.49,2.49,0,0,0,.606-.253V-6.8l-1.9.2a2.39,2.39,0,0,0-1.182.361.975.975,0,0,0-.384.837,1.034,1.034,0,0,0,.445.9A2.351,2.351,0,0,0,29.185-4.187Zm-.015-6.495a3.4,3.4,0,0,1,2.119.6,2.226,2.226,0,0,1,.783,1.889v3.639a.857.857,0,0,1-.131.514,1.293,1.293,0,0,1-.392.33,4,4,0,0,1-.975.376,5.426,5.426,0,0,1-1.39.161,3.783,3.783,0,0,1-2.234-.568,1.9,1.9,0,0,1-.791-1.643,1.818,1.818,0,0,1,.66-1.528,3.577,3.577,0,0,1,1.858-.637l2.18-.215v-.43A1.323,1.323,0,0,0,30.4-9.315a1.994,1.994,0,0,0-1.244-.353,3.908,3.908,0,0,0-1.213.184,6.722,6.722,0,0,0-1.013.415,1.463,1.463,0,0,1-.223-.246.512.512,0,0,1-.1-.307.558.558,0,0,1,.107-.353,1.044,1.044,0,0,1,.338-.261,3.783,3.783,0,0,1,.952-.33A5.569,5.569,0,0,1,29.17-10.681Zm7.569,7.493A2.937,2.937,0,0,1,35-3.665a1.807,1.807,0,0,1-.668-1.581v-7.293a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.645,0,.645.553v1.766h2.518a1.167,1.167,0,0,1,.092.207.848.848,0,0,1,.046.284q0,.522-.461.522h-2.2v3.992a1.006,1.006,0,0,0,.345.867,1.625,1.625,0,0,0,.975.253,2.144,2.144,0,0,0,.583-.092A2.442,2.442,0,0,0,38-4.509a1.032,1.032,0,0,1,.169.23.653.653,0,0,1,.077.322.632.632,0,0,1-.4.553A2.355,2.355,0,0,1,36.739-3.189Zm3.7-3.025A2.378,2.378,0,0,0,43.08-4.2a3.038,3.038,0,0,0,1.167-.215,3.415,3.415,0,0,0,.829-.461.653.653,0,0,1,.368.583.6.6,0,0,1-.192.415,1.724,1.724,0,0,1-.522.353,3.562,3.562,0,0,1-.768.246,4.582,4.582,0,0,1-.944.092A3.965,3.965,0,0,1,40.2-4.156a3.678,3.678,0,0,1-1.036-2.825,4.463,4.463,0,0,1,.253-1.558A3.343,3.343,0,0,1,40.125-9.7a3.042,3.042,0,0,1,1.082-.729,3.626,3.626,0,0,1,1.366-.253,3.441,3.441,0,0,1,1.274.23,2.919,2.919,0,0,1,1.006.653,3.033,3.033,0,0,1,.66,1,3.285,3.285,0,0,1,.238,1.267.645.645,0,0,1-.154.484.72.72,0,0,1-.43.177Zm2.119-3.47a2.039,2.039,0,0,0-1.551.653,2.726,2.726,0,0,0-.645,1.865l4.222-.583a2.256,2.256,0,0,0-.591-1.39A1.878,1.878,0,0,0,42.558-9.683Z" transform="translate(-1.825 13.491)" fill="#1f212d"/>
+ <path id="Path_28992" data-name="Path 28992" d="M3.089-3.187a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.676,0-.676-.568v-8.813a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.66,0,.66.568Zm7.462-4.591a1.54,1.54,0,0,0-.484-1.259,1.975,1.975,0,0,0-1.3-.4,3.549,3.549,0,0,0-1.09.154,4.042,4.042,0,0,0-.814.353v5.742a1.919,1.919,0,0,1-.23.069A1.392,1.392,0,0,1,6.3-3.08q-.66,0-.66-.553V-8.868a.979.979,0,0,1,.131-.53A1.383,1.383,0,0,1,6.221-9.8,4.573,4.573,0,0,1,7.28-10.25a5.142,5.142,0,0,1,1.489-.2,3.492,3.492,0,0,1,2.2.63,2.357,2.357,0,0,1,.806,1.981v4.652a1.847,1.847,0,0,1-.238.069,1.42,1.42,0,0,1-.33.038q-.66,0-.66-.553Zm5.957,4.821a2.937,2.937,0,0,1-1.743-.476A1.807,1.807,0,0,1,14.1-5.014v-7.293a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.645,0,.645.553V-10.1h2.518a1.168,1.168,0,0,1,.092.207.848.848,0,0,1,.046.284q0,.522-.461.522h-2.2v3.992a1.006,1.006,0,0,0,.345.867,1.625,1.625,0,0,0,.975.253,2.144,2.144,0,0,0,.583-.092,2.442,2.442,0,0,0,.537-.215,1.032,1.032,0,0,1,.169.23.653.653,0,0,1,.077.322.632.632,0,0,1-.4.553A2.355,2.355,0,0,1,16.508-2.957Zm3.716-3.025A2.378,2.378,0,0,0,22.865-3.97a3.038,3.038,0,0,0,1.167-.215,3.416,3.416,0,0,0,.829-.461.653.653,0,0,1,.368.583.6.6,0,0,1-.192.415,1.724,1.724,0,0,1-.522.353,3.562,3.562,0,0,1-.768.246,4.582,4.582,0,0,1-.944.092,3.965,3.965,0,0,1-2.817-.967,3.678,3.678,0,0,1-1.036-2.825A4.463,4.463,0,0,1,19.2-8.308a3.343,3.343,0,0,1,.706-1.159,3.042,3.042,0,0,1,1.082-.729,3.626,3.626,0,0,1,1.366-.253,3.441,3.441,0,0,1,1.274.23,2.919,2.919,0,0,1,1.006.653,3.033,3.033,0,0,1,.66,1A3.285,3.285,0,0,1,25.536-7.3a.645.645,0,0,1-.154.484.72.72,0,0,1-.43.177Zm2.119-3.47a2.039,2.039,0,0,0-1.551.653,2.726,2.726,0,0,0-.645,1.865l4.222-.583a2.256,2.256,0,0,0-.591-1.39A1.878,1.878,0,0,0,22.342-9.451Zm6.2,6.264a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.66,0-.66-.553v-5.2a1.044,1.044,0,0,1,.131-.56,1.419,1.419,0,0,1,.438-.407,3.953,3.953,0,0,1,1.082-.453,5.709,5.709,0,0,1,1.543-.192q1.228,0,1.228.614a.848.848,0,0,1-.046.284.716.716,0,0,1-.123.223q-.154-.031-.4-.061a3.958,3.958,0,0,0-.491-.031,4.768,4.768,0,0,0-1.244.146,3.9,3.9,0,0,0-.891.345Zm9.412-4.591a1.54,1.54,0,0,0-.484-1.259,1.975,1.975,0,0,0-1.3-.4,3.549,3.549,0,0,0-1.09.154,4.042,4.042,0,0,0-.814.353v5.742a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.66,0-.66-.553V-8.868a.979.979,0,0,1,.131-.53,1.383,1.383,0,0,1,.453-.407,4.574,4.574,0,0,1,1.059-.445,5.142,5.142,0,0,1,1.489-.2,3.492,3.492,0,0,1,2.2.63,2.357,2.357,0,0,1,.806,1.981v4.652a1.847,1.847,0,0,1-.238.069,1.42,1.42,0,0,1-.33.038q-.66,0-.66-.553Zm4.407,1.8A2.378,2.378,0,0,0,45-3.97a3.038,3.038,0,0,0,1.167-.215A3.415,3.415,0,0,0,47-4.646a.653.653,0,0,1,.368.583.6.6,0,0,1-.192.415,1.724,1.724,0,0,1-.522.353,3.562,3.562,0,0,1-.768.246,4.582,4.582,0,0,1-.944.092,3.965,3.965,0,0,1-2.817-.967,3.678,3.678,0,0,1-1.036-2.825,4.463,4.463,0,0,1,.253-1.558,3.343,3.343,0,0,1,.706-1.159,3.042,3.042,0,0,1,1.082-.729,3.626,3.626,0,0,1,1.366-.253,3.441,3.441,0,0,1,1.274.23,2.919,2.919,0,0,1,1.006.653,3.033,3.033,0,0,1,.66,1A3.285,3.285,0,0,1,47.676-7.3a.645.645,0,0,1-.154.484.72.72,0,0,1-.43.177Zm2.119-3.47a2.039,2.039,0,0,0-1.551.653,2.726,2.726,0,0,0-.645,1.865l4.222-.583a2.256,2.256,0,0,0-.591-1.39A1.878,1.878,0,0,0,44.483-9.451Zm7.4,6.495a2.937,2.937,0,0,1-1.743-.476,1.807,1.807,0,0,1-.668-1.581v-7.293a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.645,0,.645.553V-10.1h2.518a1.168,1.168,0,0,1,.092.207.848.848,0,0,1,.046.284q0,.522-.461.522H50.7v3.992a1.006,1.006,0,0,0,.345.867,1.625,1.625,0,0,0,.975.253,2.144,2.144,0,0,0,.583-.092,2.442,2.442,0,0,0,.537-.215,1.032,1.032,0,0,1,.169.23.653.653,0,0,1,.077.322.632.632,0,0,1-.4.553A2.355,2.355,0,0,1,51.883-2.957Z" transform="translate(47.793 13.259)" fill="#1e811f"/>
+ </g>
+ <path id="Path_28981" data-name="Path 28981" d="M229.379,330.53a1.442,1.442,0,1,0-1.442,1.442,1.441,1.441,0,0,0,1.442-1.442m5.675-1.441a1.442,1.442,0,1,0,1.441,1.441,1.442,1.442,0,0,0-1.441-1.441m-1.555,3.824a2.994,2.994,0,0,1-4.008,0,.374.374,0,0,0-.507.549,3.666,3.666,0,0,0,5.025,0,.374.374,0,1,0-.509-.549" transform="translate(457.359 1840.705)" fill="#56b14e"/>
+ <g id="Group_37088" data-name="Group 37088" transform="translate(683.976 2181.027)">
+ <path id="Subtraction_32" data-name="Subtraction 32" d="M6.934,16.167H2.821a1.93,1.93,0,0,1-1.9-2.2l.8-5.377A4.876,4.876,0,0,1,4.328.028,4.395,4.395,0,0,1,4.881,0,4.876,4.876,0,0,1,8.04,8.589l.8,5.377a1.922,1.922,0,0,1-1.9,2.2ZM4.885,1.37a3.864,3.864,0,0,0-.4.024A3.507,3.507,0,0,0,2.912,7.782a.521.521,0,0,1,.231.525L2.28,14.162a.55.55,0,0,0,.542.627H6.934a.557.557,0,0,0,.417-.192.545.545,0,0,0,.125-.435L6.613,8.307a.555.555,0,0,1,.237-.532,3.505,3.505,0,0,0-1.965-6.4Z" transform="translate(0 0.002)" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1" fill="url(#linear-gradient-3)"/>
+ <path id="Subtraction_30" data-name="Subtraction 30" d="M6.934,16.167H2.821a1.93,1.93,0,0,1-1.9-2.2l.8-5.377A4.876,4.876,0,0,1,4.328.028,4.395,4.395,0,0,1,4.881,0,4.876,4.876,0,0,1,8.04,8.589l.8,5.377a1.922,1.922,0,0,1-1.9,2.2ZM4.884,1.37a3.848,3.848,0,0,0-.4.024A3.507,3.507,0,0,0,2.912,7.782a.523.523,0,0,1,.231.525L2.28,14.162A.546.546,0,0,0,2.4,14.6a.556.556,0,0,0,.417.192H6.934a.55.55,0,0,0,.542-.627L6.613,8.307a.547.547,0,0,1,.231-.532,3.5,3.5,0,0,0-1.96-6.4Z" transform="translate(0 0)" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1" fill="url(#linear-gradient-4)"/>
+ </g>
+ <path id="Path_34" data-name="Path 34" d="M812.553,358.932a2.441,2.441,0,0,1,.178-.931,2.256,2.256,0,0,1,.5-.762,2.456,2.456,0,0,1,.779-.518,2.718,2.718,0,0,1,2.016,0,2.5,2.5,0,0,1,.789.519,2.307,2.307,0,0,1,.513.762,2.484,2.484,0,0,1,0,1.861,2.349,2.349,0,0,1-.513.766,2.4,2.4,0,0,1-.789.519,2.783,2.783,0,0,1-2.016,0,2.316,2.316,0,0,1-1.282-1.285A2.442,2.442,0,0,1,812.553,358.932Zm.542,0a2.091,2.091,0,0,0,.141.771,1.8,1.8,0,0,0,.39.611,1.769,1.769,0,0,0,.609.4,2.236,2.236,0,0,0,1.587,0,1.785,1.785,0,0,0,.611-.4,1.805,1.805,0,0,0,.39-.611,2.183,2.183,0,0,0,0-1.541,1.8,1.8,0,0,0-.39-.611,1.783,1.783,0,0,0-.611-.4,2.238,2.238,0,0,0-1.588,0,1.774,1.774,0,0,0-.611.4,1.8,1.8,0,0,0-.389.611A2.1,2.1,0,0,0,813.1,358.932Zm1.637.262v.776a1.366,1.366,0,0,1-.256.02.386.386,0,0,1-.187-.039c-.047-.026-.07-.087-.07-.184v-1.794c0-.092.072-.15.217-.175a2.1,2.1,0,0,1,.256-.039c.079-.006.164-.01.256-.01a1.262,1.262,0,0,1,.74.189.679.679,0,0,1,.125.953.779.779,0,0,1-.352.233l.563.679a.246.246,0,0,1-.1.138.307.307,0,0,1-.187.058.413.413,0,0,1-.326-.194l-.424-.612Zm.316-.4c.249,0,.375-.113.375-.339a.263.263,0,0,0-.118-.228.545.545,0,0,0-.315-.082q-.071,0-.133,0c-.042,0-.083.008-.123.015v.63Z" transform="translate(4.326 1826.874)" fill="#1f212d"/>
+ </g>
+</svg>
diff --git a/assets/img/plan9.png b/assets/img/plan9.png
new file mode 100644
index 0000000..56f9d08
--- /dev/null
+++ b/assets/img/plan9.png
Binary files differ
diff --git a/assets/img/plan9bunnysmwhite.jpg b/assets/img/plan9bunnysmwhite.jpg
new file mode 100644
index 0000000..f129817
--- /dev/null
+++ b/assets/img/plan9bunnysmwhite.jpg
Binary files differ
diff --git a/assets/img/playbilibili.png b/assets/img/playbilibili.png
new file mode 100644
index 0000000..6e26d78
--- /dev/null
+++ b/assets/img/playbilibili.png
Binary files differ
diff --git a/assets/img/power36.gif b/assets/img/power36.gif
new file mode 100644
index 0000000..548b86f
--- /dev/null
+++ b/assets/img/power36.gif
Binary files differ
diff --git a/assets/img/ps2.jpg b/assets/img/ps2.jpg
new file mode 100644
index 0000000..4383d45
--- /dev/null
+++ b/assets/img/ps2.jpg
Binary files differ
diff --git a/assets/img/rib_bar_red.png b/assets/img/rib_bar_red.png
new file mode 100644
index 0000000..16721fb
--- /dev/null
+++ b/assets/img/rib_bar_red.png
Binary files differ
diff --git a/assets/img/rib_bar_wh.png b/assets/img/rib_bar_wh.png
new file mode 100644
index 0000000..84ecc31
--- /dev/null
+++ b/assets/img/rib_bar_wh.png
Binary files differ
diff --git a/assets/img/roundcube.png b/assets/img/roundcube.png
new file mode 100644
index 0000000..4e44e3e
--- /dev/null
+++ b/assets/img/roundcube.png
Binary files differ
diff --git a/assets/img/roundcube.svg b/assets/img/roundcube.svg
new file mode 100644
index 0000000..e3ee3fe
--- /dev/null
+++ b/assets/img/roundcube.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="9.14 141.8 573.65 573.65">
+ <style>
+ .st0,.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#404f54}.st3{fill:#37beff}
+ </style>
+ <path class="st3" d="M582.79 549.77L295.96 384.1V207.27l286.83 165.68z"/>
+ <path class="st0" d="M9.14 549.77L295.96 384.1V207.27L9.14 372.95z"/>
+ <path d="M295.96 141.8c109.56 0 198.41 88.85 198.41 198.41s-88.85 198.41-198.41 198.41S97.55 449.77 97.55 340.21 186.4 141.8 295.96 141.8" fill-rule="evenodd" clip-rule="evenodd" fill="#ccc"/>
+ <path d="M295.96 141.8c109.6 0 198.48 88.85 198.48 198.41s-88.88 198.41-198.48 198.41c-62.91-42.34-88.94-127.64-88.94-198.3s26.03-156.1 88.94-198.52" fill-rule="evenodd" clip-rule="evenodd" fill="#e5e5e5"/>
+ <path class="st3" d="M582.79 372.95L295.96 538.62v176.83l286.83-165.68z"/>
+ <path class="st0" d="M9.14 372.95l286.82 165.67v176.83L9.14 549.77z"/>
+</svg>
diff --git a/assets/img/runescape.png b/assets/img/runescape.png
new file mode 100644
index 0000000..47cb15f
--- /dev/null
+++ b/assets/img/runescape.png
Binary files differ
diff --git a/assets/img/runescape2.png b/assets/img/runescape2.png
new file mode 100644
index 0000000..78904e1
--- /dev/null
+++ b/assets/img/runescape2.png
Binary files differ
diff --git a/assets/img/sillylaird-88x31.png b/assets/img/sillylaird-88x31.png
new file mode 100644
index 0000000..066f87f
--- /dev/null
+++ b/assets/img/sillylaird-88x31.png
Binary files differ
diff --git a/assets/img/sillylaird.png b/assets/img/sillylaird.png
new file mode 100644
index 0000000..eec722f
--- /dev/null
+++ b/assets/img/sillylaird.png
Binary files differ
diff --git a/assets/img/soldierfront.png b/assets/img/soldierfront.png
new file mode 100644
index 0000000..811047f
--- /dev/null
+++ b/assets/img/soldierfront.png
Binary files differ
diff --git a/assets/img/stepmania.png b/assets/img/stepmania.png
new file mode 100644
index 0000000..7a5ce9e
--- /dev/null
+++ b/assets/img/stepmania.png
Binary files differ
diff --git a/assets/img/sublime_text.png b/assets/img/sublime_text.png
new file mode 100644
index 0000000..8be1c1f
--- /dev/null
+++ b/assets/img/sublime_text.png
Binary files differ
diff --git a/assets/img/ubuntu.jpg b/assets/img/ubuntu.jpg
new file mode 100644
index 0000000..3825e1d
--- /dev/null
+++ b/assets/img/ubuntu.jpg
Binary files differ
diff --git a/assets/img/ubuntu.png b/assets/img/ubuntu.png
new file mode 100644
index 0000000..eca07c1
--- /dev/null
+++ b/assets/img/ubuntu.png
Binary files differ
diff --git a/assets/img/vidlii.png b/assets/img/vidlii.png
new file mode 100644
index 0000000..540c3a5
--- /dev/null
+++ b/assets/img/vidlii.png
Binary files differ
diff --git a/assets/img/vpn-logo.svg b/assets/img/vpn-logo.svg
new file mode 100644
index 0000000..08933a4
--- /dev/null
+++ b/assets/img/vpn-logo.svg
@@ -0,0 +1,54 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="149.338" height="47.5" viewBox="0 0 149.338 47.5">
+ <defs>
+ <clipPath id="clip-path">
+ <rect id="Rectangle_2855" data-name="Rectangle 2855" width="31.71" height="17.053" fill="#fff" stroke="#696b73" stroke-width="1"/>
+ </clipPath>
+ <clipPath id="clip-path-2">
+ <rect id="Rectangle_2851" data-name="Rectangle 2851" width="72.882" height="50.425" fill="none"/>
+ </clipPath>
+ <linearGradient id="linear-gradient" x1="0.497" y1="0.034" x2="0.501" y2="0.994" gradientUnits="objectBoundingBox">
+ <stop offset="0" stop-color="#56b14e"/>
+ <stop offset="0.539" stop-color="#4a9e41"/>
+ <stop offset="1" stop-color="#357c2a"/>
+ </linearGradient>
+ <linearGradient id="linear-gradient-2" x1="0.5" y1="0.969" x2="0.5" y2="0.011" gradientUnits="objectBoundingBox">
+ <stop offset="0" stop-color="#4a9e41"/>
+ <stop offset="1" stop-color="#56b14e"/>
+ </linearGradient>
+ <linearGradient id="linear-gradient-3" x1="0.517" y1="0.858" x2="0.496" y2="0.179" gradientUnits="objectBoundingBox">
+ <stop offset="0" stop-color="#56b14e"/>
+ <stop offset="0.3" stop-color="#4a9e41"/>
+ <stop offset="0.355" stop-color="#45973c"/>
+ <stop offset="0.523" stop-color="#3c8732"/>
+ <stop offset="0.717" stop-color="#367e2b"/>
+ <stop offset="1" stop-color="#357c2a"/>
+ </linearGradient>
+ <linearGradient id="linear-gradient-4" x1="0.517" y1="0.858" x2="0.496" y2="0.179" gradientUnits="objectBoundingBox">
+ <stop offset="0" stop-color="#56b14e" stop-opacity="0.153"/>
+ <stop offset="1" stop-color="#9bf893" stop-opacity="0.263"/>
+ </linearGradient>
+ </defs>
+ <g id="Light-bg-horizontal-web" transform="translate(-672.501 -2161)">
+ <g id="Mask_Group_11" data-name="Mask Group 11" transform="translate(673 2191.447)" clip-path="url(#clip-path)">
+ <g id="Group_37087" data-name="Group 37087" transform="translate(-19.452 -9.999)">
+ <g id="Group_37086" data-name="Group 37086" transform="translate(0)" clip-path="url(#clip-path-2)">
+ <image id="Rectangle_2850" data-name="Rectangle 2850" width="69.123" height="44.53" xlink:href="data:image/png;base64,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"/>
+ </g>
+ </g>
+ </g>
+ <path id="Path_28986" data-name="Path 28986" d="M150.331,275.306h-.489a12.711,12.711,0,0,0-12.711,12.711v3.411a1.031,1.031,0,0,0,1.031,1.031h2.743a1.031,1.031,0,0,0,1.031-1.031v-3.621a7.959,7.959,0,0,1,7.959-7.959h.384a7.959,7.959,0,0,1,7.959,7.959v3.621a1.031,1.031,0,0,0,1.031,1.031h2.742a1.031,1.031,0,0,0,1.031-1.031v-3.411A12.711,12.711,0,0,0,150.331,275.306Z" transform="translate(538.921 1885.694)" fill="url(#linear-gradient)"/>
+ <path id="Subtraction_31" data-name="Subtraction 31" d="M25.3,27.448H21.736a3.133,3.133,0,0,1-2.8-1.718H12.4a3.133,3.133,0,0,1-2.8,1.718H6.04a3.139,3.139,0,0,1-2.87-1.872A4.289,4.289,0,0,1,0,21.442V5.145A4.252,4.252,0,0,1,.588,2.977,3.016,3.016,0,0,1,3.593,0H9.362a2.971,2.971,0,0,1,2.1.854h8.82A2.964,2.964,0,0,1,22.368,0h5.775a3.01,3.01,0,0,1,3.006,3.006v.017a4.3,4.3,0,0,1,.559,2.123v16.3a4.275,4.275,0,0,1-3.582,4.227A3.15,3.15,0,0,1,25.3,27.448ZM15.849,6.227a3.644,3.644,0,0,0-.4.021,3.509,3.509,0,0,0-1.565,6.392.532.532,0,0,1,.232.525l-.864,5.85a.557.557,0,0,0,.131.443.546.546,0,0,0,.411.187H17.91a.546.546,0,0,0,.411-.187.557.557,0,0,0,.131-.443l-.864-5.85a.556.556,0,0,1,.231-.532,3.506,3.506,0,0,0-1.971-6.406Z" transform="translate(673.001 2176.169)" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1" fill="url(#linear-gradient-2)"/>
+ <path id="Path_28993" data-name="Path 28993" d="M13.381-11.211H5.8L4.272-6.455a2.8,2.8,0,0,1-.591.187,3.862,3.862,0,0,1-.746.062A2.2,2.2,0,0,1,1.66-6.548a1.162,1.162,0,0,1-.5-1.026,2.421,2.421,0,0,1,.093-.668q.093-.326.249-.731.4-1.306,1.135-3.373t1.555-4.368q.824-2.3,1.663-4.524t1.461-3.809a3.882,3.882,0,0,1,.964-.358,5.255,5.255,0,0,1,1.337-.171,4.9,4.9,0,0,1,1.617.249,1.5,1.5,0,0,1,.964.871q.684,1.772,1.492,4.026t1.617,4.648q.808,2.394,1.555,4.695T18.2-6.921a2.147,2.147,0,0,1-.731.513,2.607,2.607,0,0,1-1.073.2,1.825,1.825,0,0,1-1.213-.326,2.435,2.435,0,0,1-.622-1.1ZM9.588-22.684q-.311.839-.684,1.9t-.777,2.223q-.4,1.166-.808,2.363t-.777,2.316h6.125q-.4-1.244-.824-2.5t-.808-2.41L10.3-20.958q-.342-1.01-.56-1.726Zm25.588-.435a1.539,1.539,0,0,1-.3.933,1.8,1.8,0,0,1-.7.591,13.35,13.35,0,0,0-1.85-.995,6.446,6.446,0,0,0-2.627-.466,6.524,6.524,0,0,0-2.5.466,5.4,5.4,0,0,0-1.959,1.368,6.389,6.389,0,0,0-1.29,2.239,9.329,9.329,0,0,0-.466,3.078,7.367,7.367,0,0,0,1.741,5.317,6.1,6.1,0,0,0,4.6,1.8,7.279,7.279,0,0,0,2.643-.435,9.3,9.3,0,0,0,1.928-.995,2.4,2.4,0,0,1,.637.622,1.539,1.539,0,0,1,.3.933,1.5,1.5,0,0,1-.233.808,2.166,2.166,0,0,1-.762.684,7.315,7.315,0,0,1-1.865.777,10.942,10.942,0,0,1-2.985.342,11,11,0,0,1-3.7-.606A8.194,8.194,0,0,1,22.8-8.491,8.576,8.576,0,0,1,20.8-11.569a11.77,11.77,0,0,1-.731-4.337,11.488,11.488,0,0,1,.746-4.291,9,9,0,0,1,2.005-3.109,8.35,8.35,0,0,1,2.938-1.881,9.989,9.989,0,0,1,3.575-.637,11.381,11.381,0,0,1,2.41.233,8.094,8.094,0,0,1,1.834.606,3.576,3.576,0,0,1,1.181.871A1.548,1.548,0,0,1,35.176-23.119Zm17.535,0a1.539,1.539,0,0,1-.3.933,1.8,1.8,0,0,1-.7.591,13.35,13.35,0,0,0-1.85-.995,6.446,6.446,0,0,0-2.627-.466,6.524,6.524,0,0,0-2.5.466,5.4,5.4,0,0,0-1.959,1.368,6.389,6.389,0,0,0-1.29,2.239,9.329,9.329,0,0,0-.466,3.078,7.367,7.367,0,0,0,1.741,5.317,6.1,6.1,0,0,0,4.6,1.8,7.279,7.279,0,0,0,2.643-.435,9.3,9.3,0,0,0,1.928-.995,2.4,2.4,0,0,1,.637.622,1.539,1.539,0,0,1,.3.933,1.5,1.5,0,0,1-.233.808,2.166,2.166,0,0,1-.762.684,7.315,7.315,0,0,1-1.865.777,10.942,10.942,0,0,1-2.985.342,11,11,0,0,1-3.7-.606,8.194,8.194,0,0,1-2.985-1.834,8.576,8.576,0,0,1-2.005-3.078,11.77,11.77,0,0,1-.731-4.337,11.488,11.488,0,0,1,.746-4.291,9,9,0,0,1,2.005-3.109,8.35,8.35,0,0,1,2.938-1.881,9.989,9.989,0,0,1,3.575-.637,11.381,11.381,0,0,1,2.41.233,8.1,8.1,0,0,1,1.834.606,3.576,3.576,0,0,1,1.181.871A1.548,1.548,0,0,1,52.711-23.119ZM58.557-6.455a1.766,1.766,0,0,1-1.306-.5,1.766,1.766,0,0,1-.5-1.306V-23.586a1.714,1.714,0,0,1,.5-1.29,1.8,1.8,0,0,1,1.306-.482H68.848a1.569,1.569,0,0,1,.264.529,2.45,2.45,0,0,1,.109.746,1.555,1.555,0,0,1-.326,1.057,1.117,1.117,0,0,1-.886.373H60.049v5.161h7.866a1.568,1.568,0,0,1,.264.529,2.45,2.45,0,0,1,.109.746,1.484,1.484,0,0,1-.326,1.057,1.167,1.167,0,0,1-.886.342H60.049v5.659H69a2.549,2.549,0,0,1,.249.529,2.168,2.168,0,0,1,.124.746,1.544,1.544,0,0,1-.326,1.073,1.141,1.141,0,0,1-.886.358ZM78.672-8.786a4.339,4.339,0,0,0,2.891-.808,2.63,2.63,0,0,0,.933-2.083,2.381,2.381,0,0,0-.715-1.8,5.625,5.625,0,0,0-2.27-1.15l-2.332-.715a12.845,12.845,0,0,1-1.974-.793A6.106,6.106,0,0,1,73.7-17.2a4.064,4.064,0,0,1-.948-1.43,5.168,5.168,0,0,1-.326-1.912,4.637,4.637,0,0,1,1.819-3.871,7.825,7.825,0,0,1,4.928-1.415,12.955,12.955,0,0,1,2.363.2,8.256,8.256,0,0,1,1.881.56,3.841,3.841,0,0,1,1.244.839,1.5,1.5,0,0,1,.451,1.042,1.591,1.591,0,0,1-.264.917,2.331,2.331,0,0,1-.637.637,7.037,7.037,0,0,0-2.036-1.01,8.759,8.759,0,0,0-2.783-.42,4.408,4.408,0,0,0-2.643.684,2.161,2.161,0,0,0-.933,1.834,1.913,1.913,0,0,0,.653,1.508,5.742,5.742,0,0,0,2.145,1.01l1.8.529a10.052,10.052,0,0,1,4.057,2.13,4.8,4.8,0,0,1,1.446,3.684,5.375,5.375,0,0,1-.482,2.285A5.048,5.048,0,0,1,84.051-7.6,6.333,6.333,0,0,1,81.8-6.455a10.693,10.693,0,0,1-3.062.4A12.7,12.7,0,0,1,76.123-6.3a9.228,9.228,0,0,1-2.036-.653,4.034,4.034,0,0,1-1.321-.933A1.671,1.671,0,0,1,72.3-9a1.569,1.569,0,0,1,.373-1.057,2.411,2.411,0,0,1,.871-.653,8.435,8.435,0,0,0,2.036,1.306A7.069,7.069,0,0,0,78.672-8.786Zm16.509,0a4.339,4.339,0,0,0,2.891-.808,2.63,2.63,0,0,0,.933-2.083,2.381,2.381,0,0,0-.715-1.8,5.625,5.625,0,0,0-2.27-1.15l-2.332-.715a12.844,12.844,0,0,1-1.974-.793A6.105,6.105,0,0,1,90.207-17.2a4.064,4.064,0,0,1-.948-1.43,5.168,5.168,0,0,1-.326-1.912,4.637,4.637,0,0,1,1.819-3.871,7.825,7.825,0,0,1,4.928-1.415,12.955,12.955,0,0,1,2.363.2,8.256,8.256,0,0,1,1.881.56,3.841,3.841,0,0,1,1.244.839,1.5,1.5,0,0,1,.451,1.042,1.591,1.591,0,0,1-.264.917,2.332,2.332,0,0,1-.637.637,7.038,7.038,0,0,0-2.036-1.01,8.759,8.759,0,0,0-2.783-.42,4.408,4.408,0,0,0-2.643.684,2.161,2.161,0,0,0-.933,1.834,1.913,1.913,0,0,0,.653,1.508,5.743,5.743,0,0,0,2.145,1.01l1.8.529a10.052,10.052,0,0,1,4.057,2.13,4.8,4.8,0,0,1,1.446,3.684,5.375,5.375,0,0,1-.482,2.285A5.047,5.047,0,0,1,100.56-7.6a6.333,6.333,0,0,1-2.254,1.15,10.692,10.692,0,0,1-3.062.4A12.7,12.7,0,0,1,92.632-6.3,9.228,9.228,0,0,1,90.6-6.952a4.034,4.034,0,0,1-1.321-.933A1.671,1.671,0,0,1,88.808-9a1.569,1.569,0,0,1,.373-1.057,2.411,2.411,0,0,1,.871-.653,8.435,8.435,0,0,0,2.036,1.306A7.069,7.069,0,0,0,95.182-8.786Z" transform="translate(712.511 2209.225)" fill="#1f212d"/>
+ <path id="Path_28995" data-name="Path 28995" transform="translate(759.331 2178.4)" fill="#3ab54a"/>
+ <g id="Group_37089" data-name="Group 37089" transform="translate(713.673 2167.364)">
+ <path id="Path_28994" data-name="Path 28994" d="M3.084-6.72v3.3q-.092.031-.246.069a1.392,1.392,0,0,1-.338.038q-.676,0-.676-.568v-8.168a.529.529,0,0,1,.123-.376.847.847,0,0,1,.384-.207,5.471,5.471,0,0,1,1.082-.2q.637-.061,1.236-.061a4.134,4.134,0,0,1,2.848.821,2.859,2.859,0,0,1,.9,2.219,2.956,2.956,0,0,1-.921,2.28,4.071,4.071,0,0,1-2.84.852ZM4.558-7.764a3.12,3.12,0,0,0,1.9-.507,1.83,1.83,0,0,0,.676-1.566,1.776,1.776,0,0,0-.653-1.528,3.092,3.092,0,0,0-1.865-.484q-.415,0-.821.038t-.729.1v3.946Zm6.387,4.345a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.66,0-.66-.553v-5.2a1.044,1.044,0,0,1,.131-.56,1.419,1.419,0,0,1,.438-.407,3.953,3.953,0,0,1,1.082-.453,5.709,5.709,0,0,1,1.543-.192q1.228,0,1.228.614a.848.848,0,0,1-.046.284.716.716,0,0,1-.123.223q-.154-.031-.4-.061a3.957,3.957,0,0,0-.491-.031,4.768,4.768,0,0,0-1.244.146,3.9,3.9,0,0,0-.891.345Zm5.942,0a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.66,0-.66-.553v-6.587a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.645,0,.645.568Zm-1.413-9.3a.741.741,0,0,1,.223-.545.779.779,0,0,1,.576-.223.754.754,0,0,1,.568.223.755.755,0,0,1,.215.545.755.755,0,0,1-.215.545.754.754,0,0,1-.568.223.779.779,0,0,1-.576-.223A.741.741,0,0,1,15.474-12.723Zm9.12,2.1a.626.626,0,0,1,.422.138.5.5,0,0,1,.161.4,2.526,2.526,0,0,1-.123.591q-.123.422-.322,1T24.28-7.227q-.253.691-.537,1.374T23.174-4.54q-.284.63-.53,1.09a.719.719,0,0,1-.269.107,1.739,1.739,0,0,1-.407.046q-.568,0-.737-.322-.138-.261-.353-.722t-.461-1.044q-.246-.583-.507-1.244T19.4-7.941q-.246-.653-.461-1.259t-.353-1.082a.766.766,0,0,1,.292-.246.818.818,0,0,1,.368-.092.591.591,0,0,1,.422.138,1.252,1.252,0,0,1,.269.461l.952,2.748q.092.261.23.637t.284.768q.146.392.292.76t.238.63H22q.614-1.551,1.144-3.055t.929-2.963A.985.985,0,0,1,24.594-10.62Zm4.591,6.433a3.677,3.677,0,0,0,1.067-.131,2.49,2.49,0,0,0,.606-.253V-6.8l-1.9.2a2.39,2.39,0,0,0-1.182.361.975.975,0,0,0-.384.837,1.034,1.034,0,0,0,.445.9A2.351,2.351,0,0,0,29.185-4.187Zm-.015-6.495a3.4,3.4,0,0,1,2.119.6,2.226,2.226,0,0,1,.783,1.889v3.639a.857.857,0,0,1-.131.514,1.293,1.293,0,0,1-.392.33,4,4,0,0,1-.975.376,5.426,5.426,0,0,1-1.39.161,3.783,3.783,0,0,1-2.234-.568,1.9,1.9,0,0,1-.791-1.643,1.818,1.818,0,0,1,.66-1.528,3.577,3.577,0,0,1,1.858-.637l2.18-.215v-.43A1.323,1.323,0,0,0,30.4-9.315a1.994,1.994,0,0,0-1.244-.353,3.908,3.908,0,0,0-1.213.184,6.722,6.722,0,0,0-1.013.415,1.463,1.463,0,0,1-.223-.246.512.512,0,0,1-.1-.307.558.558,0,0,1,.107-.353,1.044,1.044,0,0,1,.338-.261,3.783,3.783,0,0,1,.952-.33A5.569,5.569,0,0,1,29.17-10.681Zm7.569,7.493A2.937,2.937,0,0,1,35-3.665a1.807,1.807,0,0,1-.668-1.581v-7.293a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.645,0,.645.553v1.766h2.518a1.167,1.167,0,0,1,.092.207.848.848,0,0,1,.046.284q0,.522-.461.522h-2.2v3.992a1.006,1.006,0,0,0,.345.867,1.625,1.625,0,0,0,.975.253,2.144,2.144,0,0,0,.583-.092A2.442,2.442,0,0,0,38-4.509a1.032,1.032,0,0,1,.169.23.653.653,0,0,1,.077.322.632.632,0,0,1-.4.553A2.355,2.355,0,0,1,36.739-3.189Zm3.7-3.025A2.378,2.378,0,0,0,43.08-4.2a3.038,3.038,0,0,0,1.167-.215,3.415,3.415,0,0,0,.829-.461.653.653,0,0,1,.368.583.6.6,0,0,1-.192.415,1.724,1.724,0,0,1-.522.353,3.562,3.562,0,0,1-.768.246,4.582,4.582,0,0,1-.944.092A3.965,3.965,0,0,1,40.2-4.156a3.678,3.678,0,0,1-1.036-2.825,4.463,4.463,0,0,1,.253-1.558A3.343,3.343,0,0,1,40.125-9.7a3.042,3.042,0,0,1,1.082-.729,3.626,3.626,0,0,1,1.366-.253,3.441,3.441,0,0,1,1.274.23,2.919,2.919,0,0,1,1.006.653,3.033,3.033,0,0,1,.66,1,3.285,3.285,0,0,1,.238,1.267.645.645,0,0,1-.154.484.72.72,0,0,1-.43.177Zm2.119-3.47a2.039,2.039,0,0,0-1.551.653,2.726,2.726,0,0,0-.645,1.865l4.222-.583a2.256,2.256,0,0,0-.591-1.39A1.878,1.878,0,0,0,42.558-9.683Z" transform="translate(-1.825 13.491)" fill="#1f212d"/>
+ <path id="Path_28992" data-name="Path 28992" d="M3.089-3.187a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.676,0-.676-.568v-8.813a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.66,0,.66.568Zm7.462-4.591a1.54,1.54,0,0,0-.484-1.259,1.975,1.975,0,0,0-1.3-.4,3.549,3.549,0,0,0-1.09.154,4.042,4.042,0,0,0-.814.353v5.742a1.919,1.919,0,0,1-.23.069A1.392,1.392,0,0,1,6.3-3.08q-.66,0-.66-.553V-8.868a.979.979,0,0,1,.131-.53A1.383,1.383,0,0,1,6.221-9.8,4.573,4.573,0,0,1,7.28-10.25a5.142,5.142,0,0,1,1.489-.2,3.492,3.492,0,0,1,2.2.63,2.357,2.357,0,0,1,.806,1.981v4.652a1.847,1.847,0,0,1-.238.069,1.42,1.42,0,0,1-.33.038q-.66,0-.66-.553Zm5.957,4.821a2.937,2.937,0,0,1-1.743-.476A1.807,1.807,0,0,1,14.1-5.014v-7.293a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.645,0,.645.553V-10.1h2.518a1.168,1.168,0,0,1,.092.207.848.848,0,0,1,.046.284q0,.522-.461.522h-2.2v3.992a1.006,1.006,0,0,0,.345.867,1.625,1.625,0,0,0,.975.253,2.144,2.144,0,0,0,.583-.092,2.442,2.442,0,0,0,.537-.215,1.032,1.032,0,0,1,.169.23.653.653,0,0,1,.077.322.632.632,0,0,1-.4.553A2.355,2.355,0,0,1,16.508-2.957Zm3.716-3.025A2.378,2.378,0,0,0,22.865-3.97a3.038,3.038,0,0,0,1.167-.215,3.416,3.416,0,0,0,.829-.461.653.653,0,0,1,.368.583.6.6,0,0,1-.192.415,1.724,1.724,0,0,1-.522.353,3.562,3.562,0,0,1-.768.246,4.582,4.582,0,0,1-.944.092,3.965,3.965,0,0,1-2.817-.967,3.678,3.678,0,0,1-1.036-2.825A4.463,4.463,0,0,1,19.2-8.308a3.343,3.343,0,0,1,.706-1.159,3.042,3.042,0,0,1,1.082-.729,3.626,3.626,0,0,1,1.366-.253,3.441,3.441,0,0,1,1.274.23,2.919,2.919,0,0,1,1.006.653,3.033,3.033,0,0,1,.66,1A3.285,3.285,0,0,1,25.536-7.3a.645.645,0,0,1-.154.484.72.72,0,0,1-.43.177Zm2.119-3.47a2.039,2.039,0,0,0-1.551.653,2.726,2.726,0,0,0-.645,1.865l4.222-.583a2.256,2.256,0,0,0-.591-1.39A1.878,1.878,0,0,0,22.342-9.451Zm6.2,6.264a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.66,0-.66-.553v-5.2a1.044,1.044,0,0,1,.131-.56,1.419,1.419,0,0,1,.438-.407,3.953,3.953,0,0,1,1.082-.453,5.709,5.709,0,0,1,1.543-.192q1.228,0,1.228.614a.848.848,0,0,1-.046.284.716.716,0,0,1-.123.223q-.154-.031-.4-.061a3.958,3.958,0,0,0-.491-.031,4.768,4.768,0,0,0-1.244.146,3.9,3.9,0,0,0-.891.345Zm9.412-4.591a1.54,1.54,0,0,0-.484-1.259,1.975,1.975,0,0,0-1.3-.4,3.549,3.549,0,0,0-1.09.154,4.042,4.042,0,0,0-.814.353v5.742a1.919,1.919,0,0,1-.23.069,1.392,1.392,0,0,1-.338.038q-.66,0-.66-.553V-8.868a.979.979,0,0,1,.131-.53,1.383,1.383,0,0,1,.453-.407,4.574,4.574,0,0,1,1.059-.445,5.142,5.142,0,0,1,1.489-.2,3.492,3.492,0,0,1,2.2.63,2.357,2.357,0,0,1,.806,1.981v4.652a1.847,1.847,0,0,1-.238.069,1.42,1.42,0,0,1-.33.038q-.66,0-.66-.553Zm4.407,1.8A2.378,2.378,0,0,0,45-3.97a3.038,3.038,0,0,0,1.167-.215A3.415,3.415,0,0,0,47-4.646a.653.653,0,0,1,.368.583.6.6,0,0,1-.192.415,1.724,1.724,0,0,1-.522.353,3.562,3.562,0,0,1-.768.246,4.582,4.582,0,0,1-.944.092,3.965,3.965,0,0,1-2.817-.967,3.678,3.678,0,0,1-1.036-2.825,4.463,4.463,0,0,1,.253-1.558,3.343,3.343,0,0,1,.706-1.159,3.042,3.042,0,0,1,1.082-.729,3.626,3.626,0,0,1,1.366-.253,3.441,3.441,0,0,1,1.274.23,2.919,2.919,0,0,1,1.006.653,3.033,3.033,0,0,1,.66,1A3.285,3.285,0,0,1,47.676-7.3a.645.645,0,0,1-.154.484.72.72,0,0,1-.43.177Zm2.119-3.47a2.039,2.039,0,0,0-1.551.653,2.726,2.726,0,0,0-.645,1.865l4.222-.583a2.256,2.256,0,0,0-.591-1.39A1.878,1.878,0,0,0,44.483-9.451Zm7.4,6.495a2.937,2.937,0,0,1-1.743-.476,1.807,1.807,0,0,1-.668-1.581v-7.293a1.847,1.847,0,0,1,.238-.069,1.489,1.489,0,0,1,.345-.038q.645,0,.645.553V-10.1h2.518a1.168,1.168,0,0,1,.092.207.848.848,0,0,1,.046.284q0,.522-.461.522H50.7v3.992a1.006,1.006,0,0,0,.345.867,1.625,1.625,0,0,0,.975.253,2.144,2.144,0,0,0,.583-.092,2.442,2.442,0,0,0,.537-.215,1.032,1.032,0,0,1,.169.23.653.653,0,0,1,.077.322.632.632,0,0,1-.4.553A2.355,2.355,0,0,1,51.883-2.957Z" transform="translate(47.793 13.259)" fill="#1e811f"/>
+ </g>
+ <path id="Path_28981" data-name="Path 28981" d="M229.379,330.53a1.442,1.442,0,1,0-1.442,1.442,1.441,1.441,0,0,0,1.442-1.442m5.675-1.441a1.442,1.442,0,1,0,1.441,1.441,1.442,1.442,0,0,0-1.441-1.441m-1.555,3.824a2.994,2.994,0,0,1-4.008,0,.374.374,0,0,0-.507.549,3.666,3.666,0,0,0,5.025,0,.374.374,0,1,0-.509-.549" transform="translate(457.359 1840.705)" fill="#56b14e"/>
+ <g id="Group_37088" data-name="Group 37088" transform="translate(683.976 2181.027)">
+ <path id="Subtraction_32" data-name="Subtraction 32" d="M6.934,16.167H2.821a1.93,1.93,0,0,1-1.9-2.2l.8-5.377A4.876,4.876,0,0,1,4.328.028,4.395,4.395,0,0,1,4.881,0,4.876,4.876,0,0,1,8.04,8.589l.8,5.377a1.922,1.922,0,0,1-1.9,2.2ZM4.885,1.37a3.864,3.864,0,0,0-.4.024A3.507,3.507,0,0,0,2.912,7.782a.521.521,0,0,1,.231.525L2.28,14.162a.55.55,0,0,0,.542.627H6.934a.557.557,0,0,0,.417-.192.545.545,0,0,0,.125-.435L6.613,8.307a.555.555,0,0,1,.237-.532,3.505,3.505,0,0,0-1.965-6.4Z" transform="translate(0 0.002)" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1" fill="url(#linear-gradient-3)"/>
+ <path id="Subtraction_30" data-name="Subtraction 30" d="M6.934,16.167H2.821a1.93,1.93,0,0,1-1.9-2.2l.8-5.377A4.876,4.876,0,0,1,4.328.028,4.395,4.395,0,0,1,4.881,0,4.876,4.876,0,0,1,8.04,8.589l.8,5.377a1.922,1.922,0,0,1-1.9,2.2ZM4.884,1.37a3.848,3.848,0,0,0-.4.024A3.507,3.507,0,0,0,2.912,7.782a.523.523,0,0,1,.231.525L2.28,14.162A.546.546,0,0,0,2.4,14.6a.556.556,0,0,0,.417.192H6.934a.55.55,0,0,0,.542-.627L6.613,8.307a.547.547,0,0,1,.231-.532,3.5,3.5,0,0,0-1.96-6.4Z" transform="translate(0 0)" stroke="rgba(0,0,0,0)" stroke-miterlimit="10" stroke-width="1" fill="url(#linear-gradient-4)"/>
+ </g>
+ <path id="Path_34" data-name="Path 34" d="M812.553,358.932a2.441,2.441,0,0,1,.178-.931,2.256,2.256,0,0,1,.5-.762,2.456,2.456,0,0,1,.779-.518,2.718,2.718,0,0,1,2.016,0,2.5,2.5,0,0,1,.789.519,2.307,2.307,0,0,1,.513.762,2.484,2.484,0,0,1,0,1.861,2.349,2.349,0,0,1-.513.766,2.4,2.4,0,0,1-.789.519,2.783,2.783,0,0,1-2.016,0,2.316,2.316,0,0,1-1.282-1.285A2.442,2.442,0,0,1,812.553,358.932Zm.542,0a2.091,2.091,0,0,0,.141.771,1.8,1.8,0,0,0,.39.611,1.769,1.769,0,0,0,.609.4,2.236,2.236,0,0,0,1.587,0,1.785,1.785,0,0,0,.611-.4,1.805,1.805,0,0,0,.39-.611,2.183,2.183,0,0,0,0-1.541,1.8,1.8,0,0,0-.39-.611,1.783,1.783,0,0,0-.611-.4,2.238,2.238,0,0,0-1.588,0,1.774,1.774,0,0,0-.611.4,1.8,1.8,0,0,0-.389.611A2.1,2.1,0,0,0,813.1,358.932Zm1.637.262v.776a1.366,1.366,0,0,1-.256.02.386.386,0,0,1-.187-.039c-.047-.026-.07-.087-.07-.184v-1.794c0-.092.072-.15.217-.175a2.1,2.1,0,0,1,.256-.039c.079-.006.164-.01.256-.01a1.262,1.262,0,0,1,.74.189.679.679,0,0,1,.125.953.779.779,0,0,1-.352.233l.563.679a.246.246,0,0,1-.1.138.307.307,0,0,1-.187.058.413.413,0,0,1-.326-.194l-.424-.612Zm.316-.4c.249,0,.375-.113.375-.339a.263.263,0,0,0-.118-.228.545.545,0,0,0-.315-.082q-.071,0-.133,0c-.042,0-.083.008-.123.015v.63Z" transform="translate(4.326 1826.874)" fill="#1f212d"/>
+ </g>
+</svg>
diff --git a/assets/img/vscode.png b/assets/img/vscode.png
new file mode 100644
index 0000000..e65bb09
--- /dev/null
+++ b/assets/img/vscode.png
Binary files differ
diff --git a/assets/img/windowserver.jpg b/assets/img/windowserver.jpg
new file mode 100644
index 0000000..c9beaf6
--- /dev/null
+++ b/assets/img/windowserver.jpg
Binary files differ
diff --git a/assets/img/windowserver.png b/assets/img/windowserver.png
new file mode 100644
index 0000000..c8ba8e5
--- /dev/null
+++ b/assets/img/windowserver.png
Binary files differ
diff --git a/assets/img/wtfpl.png b/assets/img/wtfpl.png
new file mode 100644
index 0000000..dc01d39
--- /dev/null
+++ b/assets/img/wtfpl.png
Binary files differ
diff --git a/assets/js/includes.js b/assets/js/includes.js
new file mode 100644
index 0000000..13a7933
--- /dev/null
+++ b/assets/js/includes.js
@@ -0,0 +1,33 @@
+(async function () {
+ async function loadInto(selector, url) {
+ const mount = document.querySelector(selector);
+ if (!mount) return;
+
+ try {
+ const res = await fetch(url);
+ if (!res.ok) throw new Error("HTTP " + res.status);
+ const html = await res.text();
+ mount.innerHTML = html;
+ } catch (e) {
+ // Fail open: if includes fail, keep page usable.
+ // Intentionally no console noise.
+ }
+ }
+
+ // Pages opt-in by including: <div data-include="header"></div>
+ // and/or <div data-include="footer"></div>
+ await Promise.all([
+ loadInto('[data-include="head-meta"]', "/partials/head-meta.html"),
+ loadInto('[data-include="header"]', "/partials/header.html"),
+ loadInto('[data-include="footer"]', "/partials/footer.html"),
+ ]);
+
+ // If a page uses includes, it likely removed the built-in header/footer.
+ // Re-run site.js behaviors after injection.
+ if (document.querySelector('[data-include="header"], [data-include="footer"]')) {
+ const s = document.createElement("script");
+ s.src = "/assets/js/site.js";
+ s.defer = true;
+ document.head.appendChild(s);
+ }
+})();
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();
+ }
+})();
diff --git a/assets/js/pages/404.js b/assets/js/pages/404.js
new file mode 100644
index 0000000..a97a5b1
--- /dev/null
+++ b/assets/js/pages/404.js
@@ -0,0 +1,5 @@
+(function(){
+ var el = document.getElementById('y');
+ if (!el) return;
+ el.textContent = String(new Date().getFullYear());
+ })();
diff --git a/assets/js/pages/50x.js b/assets/js/pages/50x.js
new file mode 100644
index 0000000..a97a5b1
--- /dev/null
+++ b/assets/js/pages/50x.js
@@ -0,0 +1,5 @@
+(function(){
+ var el = document.getElementById('y');
+ if (!el) return;
+ el.textContent = String(new Date().getFullYear());
+ })();
diff --git a/assets/js/pages/admin-vibe.js b/assets/js/pages/admin-vibe.js
new file mode 100644
index 0000000..0f7b79e
--- /dev/null
+++ b/assets/js/pages/admin-vibe.js
@@ -0,0 +1,71 @@
+function detectType(url) {
+ if (!url) return 'none';
+ try { new URL(url); } catch(e) { return 'none'; }
+ const path = (new URL(url)).pathname.toLowerCase();
+ const ext = path.split('.').pop();
+ const imgs = ['jpg','jpeg','png','gif','webp','bmp','svg','avif'];
+ const vids = ['mp4','webm','ogv','mov'];
+ const auds = ['mp3','flac','wav','ogg','aac','opus','m4a'];
+ if (imgs.includes(ext)) return 'image';
+ if (vids.includes(ext)) return 'video';
+ if (auds.includes(ext)) return 'audio';
+ if (/(?:youtube\.com\/watch\?.*v=|youtu\.be\/)([A-Za-z0-9_-]{11})/.test(url)) return 'youtube';
+ if (/vimeo\.com\/(\d+)/.test(url)) return 'vimeo';
+ return 'link';
+}
+
+function getYTId(url) {
+ const m = url.match(/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/);
+ return m ? m[1] : null;
+}
+function getVimeoId(url) {
+ const m = url.match(/vimeo\.com\/(\d+)/);
+ return m ? m[1] : null;
+}
+
+function updateBadge(type) {
+ const b = document.getElementById('type-badge');
+ b.textContent = type;
+}
+
+function updatePreview(url) {
+ const body = document.getElementById('preview-body');
+ if (!url) { body.innerHTML = '<p class="preview-empty">// no url set</p>'; return; }
+ const type = detectType(url);
+ const safeUrl = url.replace(/"/g, '%22');
+ let html = `<div class="preview-url"><a href="${safeUrl}" target="_blank" rel="noopener noreferrer">${url}</a></div>`;
+ if (type === 'image') {
+ html += `<img src="${safeUrl}" alt="vibe preview" loading="lazy" class="vibe-preview-media">`;
+ } else if (type === 'video') {
+ html += `<video controls preload="none" class="vibe-preview-media"><source src="${safeUrl}"></video>`;
+ } else if (type === 'audio') {
+ html += `<audio controls preload="none" class="vibe-preview-audio"><source src="${safeUrl}"></audio>`;
+ } else if (type === 'youtube') {
+ const id = getYTId(url);
+ if (id) html += `<div class="ratio-wrap"><iframe src="https://www.youtube.com/embed/${id}" allowfullscreen loading="lazy"></iframe></div>`;
+ } else if (type === 'vimeo') {
+ const id = getVimeoId(url);
+ if (id) html += `<div class="ratio-wrap"><iframe src="https://player.vimeo.com/video/${id}" allowfullscreen loading="lazy"></iframe></div>`;
+ } else {
+ html += `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer" class="vibe-preview-link">${url}</a>`;
+ }
+ body.innerHTML = html;
+ updateBadge(type);
+}
+
+const urlInput = document.getElementById('url');
+let debounce;
+urlInput.addEventListener('input', () => {
+ clearTimeout(debounce);
+ debounce = setTimeout(() => updatePreview(urlInput.value.trim()), 400);
+});
+
+document.querySelectorAll('.preset-btn').forEach(btn => {
+ btn.addEventListener('click', () => {
+ const url = btn.dataset.url;
+ const desc = btn.dataset.desc;
+ urlInput.value = url;
+ if (desc) document.getElementById('description').value = desc;
+ updatePreview(url);
+ });
+});
diff --git a/assets/js/pages/admin.js b/assets/js/pages/admin.js
new file mode 100644
index 0000000..076c5e9
--- /dev/null
+++ b/assets/js/pages/admin.js
@@ -0,0 +1,714 @@
+/* Unified admin panel client behaviour.
+ * Lives in an external file because the site's enforced CSP (nginx) uses
+ * script-src 'self' with no 'unsafe-inline' — inline <script> would be blocked.
+ */
+(function () {
+ // ---- Blog: markdown toolbar + live preview ----
+ var ed = document.getElementById('md-editor');
+ var pv = document.getElementById('md-preview');
+ if (ed) {
+ var esc = function (s) {
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+ };
+ var render = function (t) {
+ return esc(t)
+ .replace(/```([\s\S]*?)```/g, function (_, c) { return '<pre><code>' + c + '</code></pre>'; })
+ .replace(/^### (.*)$/gm, '<h3>$1</h3>')
+ .replace(/^## (.*)$/gm, '<h2>$1</h2>')
+ .replace(/^# (.*)$/gm, '<h1>$1</h1>')
+ .replace(/^&gt; (.*)$/gm, '<blockquote>$1</blockquote>')
+ .replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>')
+ .replace(/\*([^*]+)\*/g, '<i>$1</i>')
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
+ .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img alt="$1" src="$2" style="max-width:100%">')
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>')
+ .replace(/\n/g, '<br>');
+ };
+ var upd = function () {
+ if (pv) pv.innerHTML = ed.value.trim() ? render(ed.value) : '<em class="muted">Type to preview…</em>';
+ };
+ ed.addEventListener('input', upd);
+ upd();
+
+ document.querySelectorAll('.md-toolbar button[data-md]').forEach(function (btn) {
+ btn.addEventListener('click', function () {
+ var mode = btn.dataset.md, a = btn.dataset.a || '', b = btn.dataset.b || '';
+ var s = ed.selectionStart, e = ed.selectionEnd, v = ed.value;
+ if (mode === 'wrap') {
+ ed.setRangeText(a + v.substring(s, e) + b, s, e, 'end');
+ } else if (mode === 'line') {
+ var ls = s; while (ls > 0 && v[ls - 1] !== '\n') ls--;
+ ed.setRangeText(a, ls, ls, 'end');
+ } else {
+ ed.setRangeText(a, s, e, 'end');
+ }
+ ed.dispatchEvent(new Event('input'));
+ ed.focus();
+ });
+ });
+ }
+
+ // ---- MOTD: live XMPP preview (emoji / heading / body / url) ----
+ var motdPreview = document.getElementById('motd-live-preview');
+ if (motdPreview) {
+ var motdVal = function (id) {
+ var el = document.getElementById(id);
+ return el ? String(el.value || '').trim() : '';
+ };
+ var motdPaint = function () {
+ var emoji = motdVal('motd-emoji');
+ var heading = motdVal('motd-heading');
+ var body = motdVal('motd-body');
+ var url = motdVal('motd-url');
+ var head = [emoji, heading].filter(Boolean).join(' ');
+ var lines = [];
+ if (head) lines.push(head);
+ if (body) lines.push(body);
+ if (url) lines.push(url);
+ motdPreview.textContent = lines.join('\n') || '// fill in a body';
+ };
+ ['motd-emoji', 'motd-heading', 'motd-body', 'motd-url'].forEach(function (id) {
+ var el = document.getElementById(id);
+ if (el) el.addEventListener('input', motdPaint);
+ });
+ motdPaint();
+ }
+
+ // ---- Vibe: live "detected type" badge ----
+ var url = document.getElementById('v-url');
+ var badge = document.getElementById('v-type');
+ if (url && badge) {
+ var detect = function (u) {
+ if (!u) return 'none';
+ if (/\.(jpe?g|png|gif|webp|bmp|svg|avif)(\?|$)/i.test(u)) return 'image';
+ if (/\.(mp4|webm|ogv|mov)(\?|$)/i.test(u)) return 'video';
+ if (/\.(mp3|flac|wav|ogg|aac|opus|m4a)(\?|$)/i.test(u)) return 'audio';
+ if (/(youtube\.com\/watch\?.*v=|youtu\.be\/|youtube\.com\/shorts\/)[\w-]{11}/.test(u)) return 'youtube';
+ if (/vimeo\.com\/(video\/)?\d+/.test(u)) return 'vimeo';
+ if (/(dailymotion\.com\/video|dai\.ly)\/[\w]+/i.test(u)) return 'dailymotion';
+ if (/bilibili\.com\/video\/(BV[\w]+|av\d+)/i.test(u)) return 'bilibili';
+ if (/(nicovideo\.jp\/watch|nico\.ms)\/(sm|nm|so)?\d+/i.test(u)) return 'niconico';
+ return 'link';
+ };
+ url.addEventListener('input', function () { badge.textContent = detect(url.value.trim()); });
+ }
+
+ // ---- Traffic analytics charts + live numbers (vanilla canvas, no CDN) ----
+ (function initTrafficLive() {
+ var liveRoot = document.getElementById('traffic-live-root');
+ var chartRoot = document.getElementById('traffic-analytics');
+ var dataEl = document.getElementById('traffic-analytics-data');
+ // Need at least KPI root or charts to run live updates
+ if (!liveRoot && !chartRoot) return;
+
+ var data = {};
+ if (dataEl) {
+ try {
+ data = JSON.parse(dataEl.textContent || '{}');
+ } catch (e) {
+ data = {};
+ }
+ }
+
+ var light = document.body.classList.contains('light-mode');
+ var colors = {
+ bg: light ? '#f4f7fa' : '#050000',
+ grid: light ? 'rgba(0,0,0,0.08)' : 'rgba(255,255,255,0.08)',
+ axis: light ? 'rgba(0,0,0,0.45)' : 'rgba(200,200,210,0.55)',
+ hits: light ? '#1a7a12' : '#39ff14',
+ hitsFill: light ? 'rgba(26,122,18,0.15)' : 'rgba(57,255,20,0.12)',
+ uniq: light ? '#1a4f9c' : '#5b9dff',
+ uniqFill: light ? 'rgba(26,79,156,0.12)' : 'rgba(91,157,255,0.12)',
+ bar: light ? '#1a7a12' : '#39ff14',
+ barAlt: light ? '#1a4f9c' : '#5b9dff',
+ bot: light ? '#a65c00' : '#ff9f1a',
+ botFill: light ? 'rgba(166,92,0,0.12)' : 'rgba(255,159,26,0.14)',
+ botAlt: light ? '#8a3d00' : '#ff6b35',
+ text: light ? 'rgba(20,20,30,0.75)' : 'rgba(200,200,210,0.8)',
+ muted: light ? 'rgba(20,20,30,0.45)' : 'rgba(160,160,170,0.7)',
+ };
+
+ function setupCanvas(canvas) {
+ if (!canvas) return null;
+ var dpr = Math.max(1, Math.min(2, window.devicePixelRatio || 1));
+ var rect = canvas.getBoundingClientRect();
+ var cssW = Math.max(280, Math.floor(rect.width || canvas.width || 600));
+ var cssH = Math.max(160, Math.floor(canvas.height * (cssW / (canvas.width || cssW))));
+ // Prefer intrinsic aspect from attributes
+ var attrW = parseInt(canvas.getAttribute('width'), 10) || 600;
+ var attrH = parseInt(canvas.getAttribute('height'), 10) || 240;
+ cssH = Math.round(cssW * (attrH / attrW));
+ canvas.style.width = cssW + 'px';
+ canvas.style.height = cssH + 'px';
+ canvas.width = Math.floor(cssW * dpr);
+ canvas.height = Math.floor(cssH * dpr);
+ var ctx = canvas.getContext('2d');
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ return { ctx: ctx, w: cssW, h: cssH };
+ }
+
+ function maxOf(arr) {
+ var m = 0;
+ for (var i = 0; i < arr.length; i++) if (arr[i] > m) m = arr[i];
+ return m || 1;
+ }
+
+ function niceMax(n) {
+ if (n <= 1) return 1;
+ var p = Math.pow(10, Math.floor(Math.log10(n)));
+ var m = n / p;
+ var nice = m <= 1 ? 1 : m <= 2 ? 2 : m <= 5 ? 5 : 10;
+ return nice * p;
+ }
+
+ function drawEmpty(ctx, w, h, msg) {
+ ctx.fillStyle = colors.bg;
+ ctx.fillRect(0, 0, w, h);
+ ctx.fillStyle = colors.muted;
+ ctx.font = '12px ui-monospace, Menlo, Consolas, monospace';
+ ctx.textAlign = 'center';
+ ctx.fillText(msg || 'No data yet', w / 2, h / 2);
+ }
+
+ /** Multi-series line chart */
+ function drawLineChart(canvas, series, labels, opts) {
+ var s = setupCanvas(canvas);
+ if (!s) return;
+ var ctx = s.ctx, w = s.w, h = s.h;
+ var pad = { t: 16, r: 12, b: 36, l: 40 };
+ var plotW = w - pad.l - pad.r;
+ var plotH = h - pad.t - pad.b;
+ ctx.fillStyle = colors.bg;
+ ctx.fillRect(0, 0, w, h);
+
+ var all = [];
+ series.forEach(function (ser) {
+ ser.values.forEach(function (v) { all.push(v); });
+ });
+ if (!labels.length || maxOf(all) === 0) {
+ drawEmpty(ctx, w, h, 'No hits in this range yet');
+ return;
+ }
+ var yMax = niceMax(maxOf(all));
+ var n = labels.length;
+ var xAt = function (i) {
+ return pad.l + (n === 1 ? plotW / 2 : (i / (n - 1)) * plotW);
+ };
+ var yAt = function (v) {
+ return pad.t + plotH - (v / yMax) * plotH;
+ };
+
+ // grid
+ ctx.strokeStyle = colors.grid;
+ ctx.lineWidth = 1;
+ ctx.fillStyle = colors.axis;
+ ctx.font = '10px ui-monospace, Menlo, Consolas, monospace';
+ ctx.textAlign = 'right';
+ ctx.textBaseline = 'middle';
+ var ticks = 4;
+ for (var t = 0; t <= ticks; t++) {
+ var val = Math.round((yMax * t) / ticks);
+ var y = yAt(val);
+ ctx.beginPath();
+ ctx.moveTo(pad.l, y);
+ ctx.lineTo(pad.l + plotW, y);
+ ctx.stroke();
+ ctx.fillText(String(val), pad.l - 6, y);
+ }
+
+ // x labels (sparse)
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'top';
+ ctx.fillStyle = colors.axis;
+ var step = Math.max(1, Math.ceil(n / 8));
+ for (var i = 0; i < n; i += step) {
+ var lab = labels[i];
+ if (opts && opts.labelFmt) lab = opts.labelFmt(lab);
+ ctx.fillText(lab, xAt(i), pad.t + plotH + 8);
+ }
+
+ series.forEach(function (ser) {
+ // fill
+ if (ser.fill) {
+ ctx.beginPath();
+ ctx.moveTo(xAt(0), yAt(0));
+ for (var j = 0; j < ser.values.length; j++) {
+ ctx.lineTo(xAt(j), yAt(ser.values[j]));
+ }
+ ctx.lineTo(xAt(ser.values.length - 1), yAt(0));
+ ctx.closePath();
+ ctx.fillStyle = ser.fill;
+ ctx.fill();
+ }
+ // line
+ ctx.beginPath();
+ for (var k = 0; k < ser.values.length; k++) {
+ var x = xAt(k), y = yAt(ser.values[k]);
+ if (k === 0) ctx.moveTo(x, y);
+ else ctx.lineTo(x, y);
+ }
+ ctx.strokeStyle = ser.color;
+ ctx.lineWidth = 2;
+ ctx.lineJoin = 'round';
+ ctx.stroke();
+ // dots on last points / sparse
+ ctx.fillStyle = ser.color;
+ for (var d = 0; d < ser.values.length; d++) {
+ if (ser.values[d] <= 0 && d !== ser.values.length - 1) continue;
+ if (d % Math.max(1, Math.floor(n / 12)) !== 0 && d !== n - 1) continue;
+ ctx.beginPath();
+ ctx.arc(xAt(d), yAt(ser.values[d]), 2.5, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ });
+ }
+
+ /**
+ * Vertical bar chart.
+ * values: number[] (single series) OR { human: number[], bots: number[] }
+ * Bots draw in orange beside human bars.
+ */
+ function drawBarChart(canvas, values, labels, opts) {
+ var s = setupCanvas(canvas);
+ if (!s) return;
+ var ctx = s.ctx, w = s.w, h = s.h;
+ var pad = { t: 16, r: 12, b: 36, l: 40 };
+ var plotW = w - pad.l - pad.r;
+ var plotH = h - pad.t - pad.b;
+ ctx.fillStyle = colors.bg;
+ ctx.fillRect(0, 0, w, h);
+
+ var human = Array.isArray(values) ? values : (values.human || []);
+ var bots = Array.isArray(values) ? null : (values.bots || null);
+ var dual = !!(bots && bots.length === human.length);
+ var all = human.slice();
+ if (dual) bots.forEach(function (v) { all.push(v); });
+
+ if (!human.length || maxOf(all) === 0) {
+ drawEmpty(ctx, w, h, 'No hits in this range yet');
+ return;
+ }
+ var yMax = niceMax(maxOf(all));
+ var n = human.length;
+ var gap = 2;
+ var groupW = plotW / n;
+ var barW = dual
+ ? Math.max(2, (groupW - gap) / 2 - 1)
+ : Math.max(2, groupW - gap);
+
+ ctx.strokeStyle = colors.grid;
+ ctx.fillStyle = colors.axis;
+ ctx.font = '10px ui-monospace, Menlo, Consolas, monospace';
+ ctx.textAlign = 'right';
+ ctx.textBaseline = 'middle';
+ for (var t = 0; t <= 4; t++) {
+ var val = Math.round((yMax * t) / 4);
+ var y = pad.t + plotH - (val / yMax) * plotH;
+ ctx.beginPath();
+ ctx.moveTo(pad.l, y);
+ ctx.lineTo(pad.l + plotW, y);
+ ctx.stroke();
+ ctx.fillText(String(val), pad.l - 6, y);
+ }
+
+ for (var i = 0; i < n; i++) {
+ var gx = pad.l + i * groupW + gap / 2;
+ // Human (green)
+ var bh = (human[i] / yMax) * plotH;
+ var hy = pad.t + plotH - bh;
+ ctx.fillStyle = colors.bar;
+ ctx.globalAlpha = human[i] > 0 ? 0.9 : 0.15;
+ ctx.fillRect(gx, hy, barW, Math.max(bh, human[i] > 0 ? 1 : 0));
+ // Bots (orange) — separate colour, not mixed into human bars
+ if (dual) {
+ var bb = (bots[i] / yMax) * plotH;
+ var by = pad.t + plotH - bb;
+ ctx.fillStyle = colors.bot;
+ ctx.globalAlpha = bots[i] > 0 ? 0.9 : 0.15;
+ ctx.fillRect(gx + barW + 1, by, barW, Math.max(bb, bots[i] > 0 ? 1 : 0));
+ }
+ ctx.globalAlpha = 1;
+ }
+
+ ctx.fillStyle = colors.axis;
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'top';
+ var step = Math.max(1, Math.ceil(n / 8));
+ for (var j = 0; j < n; j += step) {
+ var lab = labels[j] || '';
+ if (opts && opts.labelFmt) lab = opts.labelFmt(lab);
+ var lx = pad.l + j * groupW + groupW / 2;
+ ctx.fillText(lab, lx, pad.t + plotH + 8);
+ }
+ }
+
+ /** Horizontal bar chart. opts.palette: 'bot' uses orange bot colors. */
+ function drawHBarChart(canvas, items, opts) {
+ var s = setupCanvas(canvas);
+ if (!s) return;
+ var ctx = s.ctx, w = s.w, h = s.h;
+ ctx.fillStyle = colors.bg;
+ ctx.fillRect(0, 0, w, h);
+ if (!items || !items.length) {
+ drawEmpty(ctx, w, h, 'No data yet');
+ return;
+ }
+ var pad = { t: 8, r: 48, b: 8, l: 8 };
+ var rowH = Math.min(28, (h - pad.t - pad.b) / items.length);
+ var maxLabel = 0;
+ ctx.font = '11px ui-monospace, Menlo, Consolas, monospace';
+ items.forEach(function (it) {
+ var lab = truncate(it.label, 36);
+ maxLabel = Math.max(maxLabel, ctx.measureText(lab).width);
+ });
+ var labelW = Math.min(Math.max(80, maxLabel + 8), w * 0.45);
+ var barLeft = pad.l + labelW + 8;
+ var barMaxW = w - barLeft - pad.r;
+ var maxC = maxOf(items.map(function (it) { return it.count; }));
+ maxC = niceMax(maxC);
+ var cA = (opts && opts.palette === 'bot') ? colors.bot : colors.bar;
+ var cB = (opts && opts.palette === 'bot') ? colors.botAlt : colors.barAlt;
+
+ items.forEach(function (it, i) {
+ var y = pad.t + i * rowH + rowH * 0.15;
+ var bh = rowH * 0.7;
+ var lab = truncate(it.label, 36);
+ ctx.fillStyle = colors.text;
+ ctx.textAlign = 'right';
+ ctx.textBaseline = 'middle';
+ ctx.font = '11px ui-monospace, Menlo, Consolas, monospace';
+ ctx.fillText(lab, pad.l + labelW, y + bh / 2);
+
+ var bw = maxC ? (it.count / maxC) * barMaxW : 0;
+ ctx.fillStyle = i % 2 === 0 ? cA : cB;
+ ctx.globalAlpha = 0.85;
+ ctx.fillRect(barLeft, y, Math.max(bw, it.count > 0 ? 2 : 0), bh);
+ ctx.globalAlpha = 1;
+
+ ctx.fillStyle = colors.muted;
+ ctx.textAlign = 'left';
+ ctx.fillText(String(it.count), barLeft + bw + 6, y + bh / 2);
+ });
+ }
+
+ function truncate(s, n) {
+ s = String(s || '');
+ if (s.length <= n) return s;
+ return s.slice(0, n - 1) + '…';
+ }
+
+ function esc(s) {
+ return String(s == null ? '' : s)
+ .replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;');
+ }
+
+ function pad2(n) {
+ n = String(n);
+ return n.length < 2 ? '0' + n : n;
+ }
+
+ function fmtUtc(ts, withSec) {
+ if (!ts) return '—';
+ var d = new Date(ts * 1000);
+ var s =
+ d.getUTCFullYear() +
+ '-' + pad2(d.getUTCMonth() + 1) +
+ '-' + pad2(d.getUTCDate()) +
+ ' ' + pad2(d.getUTCHours()) +
+ ':' + pad2(d.getUTCMinutes());
+ if (withSec) s += ':' + pad2(d.getUTCSeconds());
+ return s + ' UTC';
+ }
+
+ function drawAllCharts(analytics) {
+ analytics = analytics || {};
+ var daily = analytics.daily || [];
+ var dailySeries = [
+ { values: daily.map(function (d) { return d.hits; }), color: colors.hits, fill: colors.hitsFill },
+ { values: daily.map(function (d) { return d.unique; }), color: colors.uniq, fill: colors.uniqFill },
+ { values: daily.map(function (d) { return d.bots || 0; }), color: colors.bot, fill: colors.botFill },
+ ];
+ drawLineChart(
+ document.getElementById('chart-daily'),
+ dailySeries,
+ daily.map(function (d) { return d.day; }),
+ { labelFmt: function (d) { return d.slice(5); } }
+ );
+
+ var hourly = analytics.hourly || [];
+ drawBarChart(
+ document.getElementById('chart-hourly'),
+ {
+ human: hourly.map(function (d) { return d.hits; }),
+ bots: hourly.map(function (d) { return d.bots || 0; }),
+ },
+ hourly.map(function (d) { return d.hour; }),
+ { labelFmt: function (h) { return h.slice(11, 13) + 'h'; } }
+ );
+
+ drawHBarChart(document.getElementById('chart-pages'), analytics.top_pages || []);
+ drawHBarChart(document.getElementById('chart-visitors'), analytics.visitors || []);
+ drawHBarChart(document.getElementById('chart-bots'), analytics.bots || [], { palette: 'bot' });
+ drawHBarChart(document.getElementById('chart-hosts'), analytics.hosts || []);
+ drawHBarChart(document.getElementById('chart-langs'), analytics.langs || []);
+ drawHBarChart(document.getElementById('chart-refs'), analytics.refs || []);
+ }
+
+ // Animated count-up for KPI big numbers
+ var kpiRaf = {};
+ function animateKpi(el, from, to) {
+ var key = el.getAttribute('data-kpi') || el.id || 'x';
+ if (kpiRaf[key]) cancelAnimationFrame(kpiRaf[key]);
+ from = Math.round(Number(from) || 0);
+ to = Math.round(Number(to) || 0);
+ if (from === to) {
+ el.textContent = String(to);
+ el.setAttribute('data-value', String(to));
+ return;
+ }
+ var start = performance.now();
+ var dur = Math.min(700, 180 + Math.abs(to - from) * 12);
+ function frame(now) {
+ var t = Math.min(1, (now - start) / dur);
+ // ease-out cubic
+ var e = 1 - Math.pow(1 - t, 3);
+ var v = Math.round(from + (to - from) * e);
+ el.textContent = String(v);
+ if (t < 1) {
+ kpiRaf[key] = requestAnimationFrame(frame);
+ } else {
+ el.textContent = String(to);
+ el.setAttribute('data-value', String(to));
+ delete kpiRaf[key];
+ }
+ }
+ kpiRaf[key] = requestAnimationFrame(frame);
+ }
+
+ function updateKpis(summary, opts) {
+ if (!summary) return;
+ var animate = !opts || opts.animate !== false;
+ var keys = [
+ 'total_hits', 'unique_sessions', 'today', 'week',
+ 'bot_hits', 'bot_today', 'bot_week', 'total_pages',
+ ];
+ keys.forEach(function (k) {
+ if (summary[k] == null) return;
+ var el = document.querySelector('[data-kpi="' + k + '"]');
+ if (!el) return;
+ var next = Math.round(Number(summary[k]) || 0);
+ var prev = parseInt(el.getAttribute('data-value') || el.textContent || '0', 10) || 0;
+ if (prev === next && el.textContent === String(next)) return;
+ el.classList.remove('traffic-kpi-flash');
+ void el.offsetWidth;
+ el.classList.add('traffic-kpi-flash');
+ if (animate && Math.abs(next - prev) < 5000) {
+ animateKpi(el, prev, next);
+ } else {
+ el.textContent = String(next);
+ el.setAttribute('data-value', String(next));
+ }
+ });
+ }
+
+ function updateTopTable(rows) {
+ var body = document.getElementById('traffic-top-body');
+ var table = document.getElementById('traffic-top-table');
+ var empty = document.getElementById('traffic-top-empty');
+ if (!body || !table) return;
+ rows = rows || [];
+ if (!rows.length) {
+ body.innerHTML = '';
+ table.hidden = true;
+ if (empty) empty.hidden = false;
+ return;
+ }
+ if (empty) empty.hidden = true;
+ table.hidden = false;
+ var html = '';
+ for (var i = 0; i < rows.length; i++) {
+ var p = rows[i];
+ html +=
+ '<tr>' +
+ '<td><code>' + esc((p.host || '') + (p.path || '')) + '</code></td>' +
+ '<td>' + (p.hits | 0) + '</td>' +
+ '<td>' + (p.unique_sessions | 0) + '</td>' +
+ '<td>' + (p.bot_hits | 0) + '</td>' +
+ '<td>' + (p.last_local_count | 0) + '</td>' +
+ '<td>' + esc(p.last_visitor || '—') + '</td>' +
+ '<td>' + esc(p.last_hit ? fmtUtc(p.last_hit, false) : '—') + '</td>' +
+ '</tr>';
+ }
+ body.innerHTML = html;
+ }
+
+ function updateRecentTable(rows) {
+ var body = document.getElementById('traffic-recent-body');
+ var table = document.getElementById('traffic-recent-table');
+ var empty = document.getElementById('traffic-recent-empty');
+ if (!body || !table) return;
+ rows = rows || [];
+ if (!rows.length) {
+ body.innerHTML = '';
+ table.hidden = true;
+ if (empty) empty.hidden = false;
+ return;
+ }
+ if (empty) empty.hidden = true;
+ table.hidden = false;
+ var html = '';
+ for (var i = 0; i < rows.length; i++) {
+ var r = rows[i];
+ var isBot = !!(r.is_bot | 0);
+ html +=
+ '<tr class="' + (isBot ? 'traffic-row--bot' : '') + '">' +
+ '<td>' + esc(fmtUtc(r.ts, true)) + '</td>' +
+ '<td>' + esc(r.visitor || 'Unknown visitor') + '</td>' +
+ '<td>' +
+ (isBot
+ ? '<span class="type-badge traffic-badge--bot">bot</span>'
+ : '<span class="type-badge">human</span>') +
+ '</td>' +
+ '<td><code>' + esc((r.host || '') + (r.path || '')) + '</code></td>' +
+ '<td>' + (r.local_count | 0) + '</td>' +
+ '<td>' + esc(r.lang || '—') + '</td>' +
+ '<td>' + esc(r.ref_host || '—') + '</td>' +
+ '<td>' + (!isBot && (r.is_new | 0) ? 'yes' : '') + '</td>' +
+ '</tr>';
+ }
+ body.innerHTML = html;
+ }
+
+ function setLiveStatus(kind, msg) {
+ var bar = document.getElementById('traffic-live-bar');
+ var status = document.getElementById('traffic-live-status');
+ var ago = document.getElementById('traffic-live-ago');
+ if (bar) {
+ bar.classList.toggle('traffic-live-bar--err', kind === 'err');
+ bar.classList.toggle('traffic-live-bar--ok', kind === 'ok');
+ }
+ if (status) status.textContent = kind === 'err' ? 'Offline' : 'Live';
+ if (ago) ago.textContent = msg ? '· ' + msg : '';
+ }
+
+ // Initial paint from server-rendered payload
+ if (chartRoot && dataEl) {
+ drawAllCharts(data);
+ chartRoot.setAttribute('data-ready', '1');
+ }
+
+ // Redraw on resize (debounced)
+ var resizeT = null;
+ var lastAnalytics = data;
+ window.addEventListener('resize', function () {
+ clearTimeout(resizeT);
+ resizeT = setTimeout(function () {
+ if (chartRoot) drawAllCharts(lastAnalytics);
+ }, 150);
+ });
+
+ // ---- Live polling: numbers + charts + tables ----
+ var pollHost = liveRoot || chartRoot;
+ var pollUrl = (pollHost && pollHost.getAttribute('data-poll')) || '/api/traffic-stats.php';
+ var pollMs = parseInt((pollHost && pollHost.getAttribute('data-poll-ms')) || '2000', 10) || 2000;
+ pollMs = Math.max(1500, Math.min(30000, pollMs));
+ var lastRev = '';
+ var lastOkAt = 0;
+ var pollTimer = null;
+ var inFlight = false;
+ var aborted = false;
+
+ function agoLabel(ts) {
+ if (!ts) return 'waiting…';
+ var sec = Math.max(0, Math.floor((Date.now() - ts) / 1000));
+ if (sec < 2) return 'just now';
+ if (sec < 60) return sec + 's ago';
+ return Math.floor(sec / 60) + 'm ago';
+ }
+
+ function tickAgo() {
+ if (lastOkAt) setLiveStatus('ok', 'updated ' + agoLabel(lastOkAt));
+ }
+
+ function applyPayload(payload, force) {
+ if (!payload || !payload.ok) return;
+ lastOkAt = Date.now();
+ setLiveStatus('ok', 'updated ' + agoLabel(lastOkAt));
+
+ var changed = force || !payload.rev || payload.rev !== lastRev;
+ if (payload.rev) lastRev = payload.rev;
+ if (!changed) return;
+
+ // Numbers first so KPIs feel instant
+ if (payload.summary) updateKpis(payload.summary, { animate: true });
+ if (payload.analytics) {
+ lastAnalytics = payload.analytics;
+ if (dataEl) dataEl.textContent = JSON.stringify(payload.analytics);
+ if (chartRoot) drawAllCharts(payload.analytics);
+ }
+ if (payload.top) updateTopTable(payload.top);
+ if (payload.recent) updateRecentTable(payload.recent);
+ }
+
+ function pollOnce() {
+ if (aborted || inFlight || document.hidden) return;
+ inFlight = true;
+ var ctrl = typeof AbortController !== 'undefined' ? new AbortController() : null;
+ var to = setTimeout(function () {
+ if (ctrl) ctrl.abort();
+ }, 12000);
+ fetch(pollUrl + (pollUrl.indexOf('?') >= 0 ? '&' : '?') + '_=' + Date.now(), {
+ method: 'GET',
+ credentials: 'same-origin',
+ cache: 'no-store',
+ headers: { Accept: 'application/json' },
+ signal: ctrl ? ctrl.signal : undefined,
+ })
+ .then(function (res) {
+ if (res.status === 401) {
+ setLiveStatus('err', 'session expired — reload');
+ aborted = true;
+ return null;
+ }
+ if (!res.ok) throw new Error('HTTP ' + res.status);
+ return res.json();
+ })
+ .then(function (json) {
+ if (!json) return;
+ if (!json.ok) throw new Error(json.err || 'bad response');
+ applyPayload(json, false);
+ })
+ .catch(function () {
+ if (!aborted) setLiveStatus('err', 'retrying…');
+ })
+ .finally(function () {
+ clearTimeout(to);
+ inFlight = false;
+ });
+ }
+
+ function schedulePoll() {
+ clearTimeout(pollTimer);
+ if (aborted) return;
+ pollTimer = setTimeout(function () {
+ pollOnce();
+ schedulePoll();
+ }, pollMs);
+ }
+
+ // Immediate first poll so numbers move as soon as hits land
+ setTimeout(pollOnce, 400);
+ schedulePoll();
+ setInterval(tickAgo, 1000);
+
+ document.addEventListener('visibilitychange', function () {
+ if (!document.hidden && !aborted) {
+ pollOnce();
+ }
+ });
+ })();
+
+})();
diff --git a/assets/js/pages/guestbook-comments.js b/assets/js/pages/guestbook-comments.js
new file mode 100644
index 0000000..135cc24
--- /dev/null
+++ b/assets/js/pages/guestbook-comments.js
@@ -0,0 +1,39 @@
+fetch("/api/guestbook-proxy.php")
+ .then(r => r.text())
+ .then(html => {
+ const doc = new DOMParser().parseFromString(html, "text/html");
+ const entries = doc.querySelectorAll(".entry");
+ const container = document.getElementById("comments");
+ container.innerHTML = "";
+
+ entries.forEach(e => {
+ const entry = document.createElement("article");
+ entry.className = "guestbook-entry";
+
+ const nameText = e.querySelector("strong")?.textContent || "Anonymous";
+ const timeEl = e.querySelector("time");
+ const messageText = e.querySelector("p")?.textContent || "";
+
+ const strong = document.createElement("strong");
+ strong.textContent = nameText;
+ entry.appendChild(strong);
+
+ if (timeEl) {
+ const t = document.createElement("time");
+ const dt = timeEl.getAttribute("datetime");
+ if (dt) t.setAttribute("datetime", dt);
+ t.textContent = timeEl.textContent || "";
+ entry.appendChild(t);
+ }
+
+ const p = document.createElement("p");
+ p.textContent = messageText;
+ entry.appendChild(p);
+
+ container.appendChild(entry);
+ });
+ })
+ .catch(() => {
+ document.getElementById("comments").innerHTML =
+ '<p class="guestbook-error">Unable to load comments. Please try refreshing.</p>';
+ });
diff --git a/assets/js/pages/guestbook_jp.js b/assets/js/pages/guestbook_jp.js
new file mode 100644
index 0000000..5f3a055
--- /dev/null
+++ b/assets/js/pages/guestbook_jp.js
@@ -0,0 +1,91 @@
+document.addEventListener("DOMContentLoaded", function () {
+ const form = document.getElementById("guestbook-form");
+ const entriesContainer = document.getElementById("guestbook-entries");
+ const captchaCanvas = document.getElementById("captcha-canvas");
+ const refreshCaptchaButton = document.getElementById("refresh-captcha");
+ const captchaInput = document.getElementById("captcha-input");
+ const GUESTBOOK_KEY = "guestbookEntries";
+
+ let currentCaptcha = "";
+
+ function generateCaptcha() {
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let captcha = "";
+ for (let i = 0; i < 6; i++) captcha += chars.charAt(Math.floor(Math.random() * chars.length));
+ return captcha;
+ }
+
+ function drawCaptcha(captcha) {
+ const ctx = captchaCanvas.getContext("2d");
+ ctx.clearRect(0, 0, captchaCanvas.width, captchaCanvas.height);
+ ctx.font = "22px Arial";
+ ctx.fillStyle = "#000";
+ ctx.fillText(captcha, 10, 32);
+ }
+
+ function refreshCaptcha() {
+ currentCaptcha = generateCaptcha();
+ drawCaptcha(currentCaptcha);
+ }
+
+ function formatMessage(message) {
+ return message
+ .split("\n")
+ .map((line) => {
+ if (line.startsWith(">")) return `<span class="anonymous">${line}</span>`;
+ return line;
+ })
+ .join("<br>");
+ }
+
+ function loadEntries() {
+ const entries = JSON.parse(localStorage.getItem(GUESTBOOK_KEY)) || [];
+ entriesContainer.innerHTML = "";
+
+ entries
+ .slice()
+ .reverse()
+ .forEach((entry) => {
+ const entryElement = document.createElement("div");
+ entryElement.className = "entry";
+
+ const nameElement = document.createElement("h4");
+ if (entry.name === "Anonymous") nameElement.className = "anonymous";
+ nameElement.textContent = entry.name;
+ entryElement.appendChild(nameElement);
+
+ const messageElement = document.createElement("p");
+ messageElement.innerHTML = formatMessage(entry.message);
+ entryElement.appendChild(messageElement);
+
+ entriesContainer.appendChild(entryElement);
+ });
+ }
+
+ form.addEventListener("submit", function (event) {
+ event.preventDefault();
+
+ let name = document.getElementById("name").value.trim();
+ if (!name) name = "Anonymous";
+
+ const message = document.getElementById("message").value.trim();
+ const captchaValue = captchaInput.value.trim();
+
+ if (name && message && captchaValue === currentCaptcha) {
+ const entries = JSON.parse(localStorage.getItem(GUESTBOOK_KEY)) || [];
+ entries.push({ name, message });
+ localStorage.setItem(GUESTBOOK_KEY, JSON.stringify(entries));
+
+ form.reset();
+ document.getElementById("name").value = "Anonymous";
+ loadEntries();
+ refreshCaptcha();
+ } else {
+ alert("CAPTCHA is incorrect. Please try again.");
+ }
+ });
+
+ refreshCaptcha();
+ refreshCaptchaButton.addEventListener("click", refreshCaptcha);
+ loadEntries();
+ });
diff --git a/assets/js/pages/guestbook_zh.js b/assets/js/pages/guestbook_zh.js
new file mode 100644
index 0000000..5f3a055
--- /dev/null
+++ b/assets/js/pages/guestbook_zh.js
@@ -0,0 +1,91 @@
+document.addEventListener("DOMContentLoaded", function () {
+ const form = document.getElementById("guestbook-form");
+ const entriesContainer = document.getElementById("guestbook-entries");
+ const captchaCanvas = document.getElementById("captcha-canvas");
+ const refreshCaptchaButton = document.getElementById("refresh-captcha");
+ const captchaInput = document.getElementById("captcha-input");
+ const GUESTBOOK_KEY = "guestbookEntries";
+
+ let currentCaptcha = "";
+
+ function generateCaptcha() {
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ let captcha = "";
+ for (let i = 0; i < 6; i++) captcha += chars.charAt(Math.floor(Math.random() * chars.length));
+ return captcha;
+ }
+
+ function drawCaptcha(captcha) {
+ const ctx = captchaCanvas.getContext("2d");
+ ctx.clearRect(0, 0, captchaCanvas.width, captchaCanvas.height);
+ ctx.font = "22px Arial";
+ ctx.fillStyle = "#000";
+ ctx.fillText(captcha, 10, 32);
+ }
+
+ function refreshCaptcha() {
+ currentCaptcha = generateCaptcha();
+ drawCaptcha(currentCaptcha);
+ }
+
+ function formatMessage(message) {
+ return message
+ .split("\n")
+ .map((line) => {
+ if (line.startsWith(">")) return `<span class="anonymous">${line}</span>`;
+ return line;
+ })
+ .join("<br>");
+ }
+
+ function loadEntries() {
+ const entries = JSON.parse(localStorage.getItem(GUESTBOOK_KEY)) || [];
+ entriesContainer.innerHTML = "";
+
+ entries
+ .slice()
+ .reverse()
+ .forEach((entry) => {
+ const entryElement = document.createElement("div");
+ entryElement.className = "entry";
+
+ const nameElement = document.createElement("h4");
+ if (entry.name === "Anonymous") nameElement.className = "anonymous";
+ nameElement.textContent = entry.name;
+ entryElement.appendChild(nameElement);
+
+ const messageElement = document.createElement("p");
+ messageElement.innerHTML = formatMessage(entry.message);
+ entryElement.appendChild(messageElement);
+
+ entriesContainer.appendChild(entryElement);
+ });
+ }
+
+ form.addEventListener("submit", function (event) {
+ event.preventDefault();
+
+ let name = document.getElementById("name").value.trim();
+ if (!name) name = "Anonymous";
+
+ const message = document.getElementById("message").value.trim();
+ const captchaValue = captchaInput.value.trim();
+
+ if (name && message && captchaValue === currentCaptcha) {
+ const entries = JSON.parse(localStorage.getItem(GUESTBOOK_KEY)) || [];
+ entries.push({ name, message });
+ localStorage.setItem(GUESTBOOK_KEY, JSON.stringify(entries));
+
+ form.reset();
+ document.getElementById("name").value = "Anonymous";
+ loadEntries();
+ refreshCaptcha();
+ } else {
+ alert("CAPTCHA is incorrect. Please try again.");
+ }
+ });
+
+ refreshCaptcha();
+ refreshCaptchaButton.addEventListener("click", refreshCaptcha);
+ loadEntries();
+ });
diff --git a/assets/js/pages/hitcounter.js b/assets/js/pages/hitcounter.js
new file mode 100644
index 0000000..7cbced6
--- /dev/null
+++ b/assets/js/pages/hitcounter.js
@@ -0,0 +1,26 @@
+function countUniqueVisitors() {
+ // Per-page local counter (host + path), matching the site footer logic.
+ function pageKey() {
+ var path = (location.pathname || '/').replace(/\/+$/, '') || '/';
+ path = path.replace(/\/index(?:_[a-z]{2})?\.php$/i, '') || '/';
+ path = path.replace(/_(jp|zh)\.php$/i, '.php');
+ return location.hostname + path;
+ }
+ var page = pageKey();
+ var VISITOR_KEY = 'uniqueVisitorCount_v2:' + page;
+ var SESSION_KEY = 'hasVisited_v2:' + page;
+ var visitorCount = localStorage.getItem(VISITOR_KEY);
+ if (!visitorCount) visitorCount = 0;
+ else visitorCount = parseInt(visitorCount, 10) || 0;
+
+ if (!sessionStorage.getItem(SESSION_KEY)) {
+ visitorCount++;
+ localStorage.setItem(VISITOR_KEY, String(visitorCount));
+ sessionStorage.setItem(SESSION_KEY, 'true');
+ }
+
+ var el = document.getElementById('visitor-counter');
+ if (el) el.innerText = String(visitorCount);
+ }
+
+ countUniqueVisitors();
diff --git a/assets/js/pages/latest.js b/assets/js/pages/latest.js
new file mode 100644
index 0000000..85d1595
--- /dev/null
+++ b/assets/js/pages/latest.js
@@ -0,0 +1,10 @@
+(function () {
+ function sendHeight() {
+ const height = document.body.scrollHeight;
+ window.parent.postMessage({ changelogHeight: height }, "*");
+ }
+ window.addEventListener("load", sendHeight);
+ if (typeof ResizeObserver !== "undefined") {
+ new ResizeObserver(sendHeight).observe(document.body);
+ }
+})();
diff --git a/assets/js/pages/mstartpage-index.js b/assets/js/pages/mstartpage-index.js
new file mode 100644
index 0000000..db518ab
--- /dev/null
+++ b/assets/js/pages/mstartpage-index.js
@@ -0,0 +1,118 @@
+// Date and time functionality
+ function updateDateRealtime() {
+ const dateElement = document.getElementById("realtime-date");
+ const clockElement = document.getElementById("realtime-clock");
+
+ const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
+ const months = ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"];
+
+ const now = new Date();
+
+ // Date parts
+ const dayName = days[now.getDay()];
+ const month = months[now.getMonth()];
+ const dayNumber = now.getDate();
+ const year = now.getFullYear();
+
+ const ordinal = (dayNumber > 3 && dayNumber < 21) ? 'th' :
+ (dayNumber % 10 === 1) ? 'st' :
+ (dayNumber % 10 === 2) ? 'nd' :
+ (dayNumber % 10 === 3) ? 'rd' : 'th';
+
+ dateElement.textContent = `${dayName}, ${month} ${dayNumber}${ordinal}, ${year}.`;
+
+ // Clock parts (12-hour format)
+ let hours = now.getHours();
+ const minutes = now.getMinutes();
+ const seconds = now.getSeconds();
+
+ const period = hours >= 12 ? 'PM' : 'AM';
+ hours = hours % 12 || 12;
+
+ const formattedHours = hours.toString().padStart(2, "0");
+ const formattedMinutes = minutes.toString().padStart(2, "0");
+ const formattedSeconds = seconds.toString().padStart(2, "0");
+
+ clockElement.textContent = `${formattedHours}:${formattedMinutes}:${formattedSeconds} ${period}`;
+ }
+
+ updateDateRealtime();
+ setInterval(updateDateRealtime, 1000);
+
+ // Menu toggle functionality
+ document.getElementById('menu-toggle').addEventListener('click', function() {
+ const menu = document.getElementById('main-menu');
+ menu.classList.toggle('show');
+ });
+
+ // Collapsible sections
+ const collapsibles = document.getElementsByClassName("collapsible");
+
+ for (let i = 0; i < collapsibles.length; i++) {
+ collapsibles[i].addEventListener("click", function() {
+ this.classList.toggle("active");
+ const content = this.nextElementSibling;
+ if (content.style.display === "block") {
+ content.style.display = "none";
+ } else {
+ content.style.display = "block";
+ }
+ });
+ }
+
+ // XXX Verification functionality
+ const xxxToggle = document.getElementById('xxx-toggle');
+ const xxxModal = document.getElementById('xxx-modal');
+ const xxxConfirm = document.getElementById('xxx-confirm');
+ const xxxDeny = document.getElementById('xxx-deny');
+ const xxxList = document.getElementById('xxx-list');
+
+ // Check if already verified
+ let isVerified = localStorage.getItem('xxx-verified') === 'true';
+ let isContentVisible = localStorage.getItem('xxx-visible') === 'true';
+
+ // Initialize based on stored state
+ if (isVerified && isContentVisible) {
+ xxxToggle.textContent = 'Hide XXX Content';
+ xxxList.style.display = 'block';
+ } else if (isVerified && !isContentVisible) {
+ xxxToggle.textContent = 'Show XXX Content';
+ xxxList.style.display = 'none';
+ }
+
+ xxxToggle.addEventListener('click', function() {
+ if (isVerified) {
+ // Toggle visibility if already verified
+ if (xxxList.style.display === 'block') {
+ // Hide the content
+ xxxList.style.display = 'none';
+ xxxToggle.textContent = 'Show XXX Content';
+ localStorage.setItem('xxx-visible', 'false');
+ } else {
+ // Show the content
+ xxxList.style.display = 'block';
+ xxxToggle.textContent = 'Hide XXX Content';
+ localStorage.setItem('xxx-visible', 'true');
+ }
+ } else {
+ // Show verification modal if not verified
+ xxxModal.style.display = 'flex';
+ }
+ });
+
+ xxxConfirm.addEventListener('click', function() {
+ // Set verification status
+ isVerified = true;
+ localStorage.setItem('xxx-verified', 'true');
+ localStorage.setItem('xxx-visible', 'true');
+
+ // Hide modal and show content
+ xxxModal.style.display = 'none';
+ xxxList.style.display = 'block';
+ xxxToggle.textContent = 'Hide XXX Content';
+ });
+
+ xxxDeny.addEventListener('click', function() {
+ xxxModal.style.display = 'none';
+ });
diff --git a/assets/js/pages/radio-nowplaying.js b/assets/js/pages/radio-nowplaying.js
new file mode 100644
index 0000000..e263e5b
--- /dev/null
+++ b/assets/js/pages/radio-nowplaying.js
@@ -0,0 +1,41 @@
+(function () {
+ var line = document.getElementById('radio-nowplaying');
+ var el = document.getElementById('radio-nowplaying-title');
+ if (!line || !el) return;
+ var pollMs = 30000;
+ var backoffMs = pollMs;
+
+ // childNodes[0] is the "Now playing: " text node before the <span>.
+ function setLabel(text) {
+ if (line.childNodes[0] && line.childNodes[0].nodeType === 3) {
+ line.childNodes[0].textContent = text;
+ }
+ }
+ function setOffline() {
+ setLabel('Radio: ');
+ el.textContent = 'currently offline';
+ }
+ function setPlaying(title) {
+ setLabel('Now playing: ');
+ el.textContent = title;
+ }
+
+ function tick() {
+ fetch('/api/nowplaying.php', { cache: 'no-store' })
+ .then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
+ .then(function (j) {
+ var title = j && j.title ? String(j.title).trim() : '';
+ if (title) { setPlaying(title); } else { setOffline(); }
+ backoffMs = pollMs;
+ })
+ .catch(function () {
+ setOffline();
+ backoffMs = Math.min(backoffMs * 2, 5 * 60 * 1000);
+ })
+ .finally(function () { setTimeout(tick, backoffMs); });
+ }
+ if (document.visibilityState !== 'hidden') tick();
+ document.addEventListener('visibilitychange', function () {
+ if (document.visibilityState === 'visible') tick();
+ });
+})();
diff --git a/assets/js/pages/vibe-admin.js b/assets/js/pages/vibe-admin.js
new file mode 100644
index 0000000..576111c
--- /dev/null
+++ b/assets/js/pages/vibe-admin.js
@@ -0,0 +1,48 @@
+function detectType(url) {
+ if (!url) return 'none';
+ try { new URL(url); } catch(e) { return 'none'; }
+ const path = (new URL(url)).pathname.toLowerCase();
+ const ext = path.split('.').pop();
+ if (['jpg','jpeg','png','gif','webp','bmp','svg','avif'].includes(ext)) return 'image';
+ if (['mp4','webm','ogv','mov'].includes(ext)) return 'video';
+ if (['mp3','flac','wav','ogg','aac','opus','m4a'].includes(ext)) return 'audio';
+ if (/(?:youtube\.com\/watch\?.*v=|youtu\.be\/)([A-Za-z0-9_-]{11})/.test(url)) return 'youtube';
+ if (/vimeo\.com\/(\d+)/.test(url)) return 'vimeo';
+ return 'link';
+}
+
+function ytId(url) { const m = url.match(/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/); return m ? m[1] : null; }
+function vimeoId(url) { const m = url.match(/vimeo\.com\/(\d+)/); return m ? m[1] : null; }
+
+function updatePreview(url) {
+ const el = document.getElementById('preview-body');
+ const badge = document.getElementById('type-badge');
+ if (!url) { el.innerHTML = '<p class="preview-empty">// no url set</p>'; badge.textContent = 'none'; badge.className = 'badge t-none'; return; }
+ const type = detectType(url);
+ badge.textContent = type;
+ badge.className = 'badge t-' + type.replace(/:.*/, '');
+ const s = url.replace(/"/g, '%22');
+ let h = '<div class="preview-url"><a href="' + s + '" target="_blank" rel="noopener noreferrer">' + url + '</a></div>';
+ if (type === 'image') h += '<img src="' + s + '" alt="preview" loading="lazy">';
+ else if (type === 'video') h += '<video controls preload="none"><source src="' + s + '"></video>';
+ else if (type === 'audio') h += '<audio controls preload="none" class="vibe-audio-full"><source src="' + s + '"></audio>';
+ else if (type === 'youtube') { const id = ytId(url); if (id) h += '<div class="ratio-wrap"><iframe src="https://www.youtube.com/embed/' + id + '" allowfullscreen loading="lazy"></iframe></div>'; }
+ else if (type === 'vimeo') { const id = vimeoId(url); if (id) h += '<div class="ratio-wrap"><iframe src="https://player.vimeo.com/video/' + id + '" allowfullscreen loading="lazy"></iframe></div>'; }
+ else h += '<a href="' + s + '" target="_blank" rel="noopener noreferrer" class="vibe-preview-link">' + url + '</a>';
+ el.innerHTML = h;
+}
+
+const urlInput = document.getElementById('url');
+let debounce;
+urlInput.addEventListener('input', () => {
+ clearTimeout(debounce);
+ debounce = setTimeout(() => updatePreview(urlInput.value.trim()), 350);
+});
+
+document.querySelectorAll('.preset').forEach(btn => {
+ btn.addEventListener('click', () => {
+ urlInput.value = btn.dataset.url;
+ if (btn.dataset.desc) document.getElementById('description').value = btn.dataset.desc;
+ updatePreview(btn.dataset.url);
+ });
+});
diff --git a/assets/js/pages/whatsleft.js b/assets/js/pages/whatsleft.js
new file mode 100644
index 0000000..bf979ee
--- /dev/null
+++ b/assets/js/pages/whatsleft.js
@@ -0,0 +1,16 @@
+(function () {
+ var target = new Date(new Date().getFullYear() + 1, 0, 1);
+ function tick() {
+ var diff = Math.max(0, target - Date.now());
+ var d = Math.floor(diff / 86400000);
+ var h = Math.floor((diff % 86400000) / 3600000);
+ var m = Math.floor((diff % 3600000) / 60000);
+ var s = Math.floor((diff % 60000) / 1000);
+ document.getElementById('wl-days').textContent = d;
+ document.getElementById('wl-hours').textContent = h;
+ document.getElementById('wl-mins').textContent = m;
+ document.getElementById('wl-secs').textContent = s;
+ }
+ tick();
+ setInterval(tick, 1000);
+})();
diff --git a/assets/js/scroll-to-top.js b/assets/js/scroll-to-top.js
new file mode 100644
index 0000000..b2d6e67
--- /dev/null
+++ b/assets/js/scroll-to-top.js
@@ -0,0 +1,180 @@
+/* ============================================================================
+ SillyLaird scroll-to-top (network-wide).
+
+ Injected by nginx before </body> on HTML responses across *.sillylaird.ca
+ (see /etc/nginx/snippets/sillylaird-visit-counter.conf). Also loaded as a
+ fallback from site.js / nav.js. Runs once (window.__slScrollTopInit).
+
+ Shows a fixed "▲ TOP" control after the page has been scrolled, and only
+ when the document is actually taller than the viewport. Works without the
+ host page loading site.css (styles come from scroll-to-top.css).
+ ============================================================================ */
+(function () {
+ "use strict";
+
+ if (window.__slScrollTopInit) return;
+ window.__slScrollTopInit = true;
+
+ // Embedded forms / comments / changelog iframe: the parent page owns scroll.
+ try {
+ if (window.self !== window.top) return;
+ } catch (_) {
+ return;
+ }
+
+ var path = location.pathname || "/";
+ if (/\/(admin|api|login|logout|auth|captcha|relay|meta|submit)(\/|$|\.)/i.test(path)) {
+ return;
+ }
+ if (/\.(xml|json|m3u|pls|css|js|map|ico|png|jpe?g|gif|webp|svg|woff2?|mp3|flac|webm|mp4)(\?|$)/i.test(path)) {
+ return;
+ }
+
+ var CSS_HREF = "https://www.sillylaird.ca/assets/css/scroll-to-top.css?v=20260821b";
+ var THRESHOLD = 280;
+ var MIN_OVERFLOW = 240;
+
+ function labelForLang() {
+ var lang = (document.documentElement.lang || "").toLowerCase();
+ if (lang.indexOf("ja") === 0) return "先頭へ戻る";
+ if (lang.indexOf("zh") === 0) return "回到顶部";
+ return "Scroll to top";
+ }
+
+ function prefersReducedMotion() {
+ return window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+ }
+
+ function scrollY() {
+ return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
+ }
+
+ function overflowPx() {
+ var el = document.scrollingElement || document.documentElement;
+ return (el ? el.scrollHeight : 0) - (window.innerHeight || 0);
+ }
+
+ function ensureCss() {
+ var links = document.getElementsByTagName("link");
+ var i;
+ for (i = 0; i < links.length; i++) {
+ if (links[i].rel === "stylesheet" && (links[i].href || "").indexOf("scroll-to-top.css") !== -1) {
+ return;
+ }
+ }
+ var l = document.createElement("link");
+ l.rel = "stylesheet";
+ l.href = CSS_HREF;
+ document.head.appendChild(l);
+ }
+
+ function ensureTopTarget() {
+ if (document.getElementById("top")) return;
+ document.documentElement.id = "top";
+ }
+
+ function dodgeVisitCounter() {
+ var c = document.getElementById("site-visit-counter");
+ if (!c) return;
+ var pos = (c.style && c.style.position) || "";
+ var computed = window.getComputedStyle ? window.getComputedStyle(c).position : "";
+ if (pos === "fixed" || computed === "fixed") {
+ c.setAttribute("data-stt-docked", "1");
+ // Inline bottom from visit-counter.js wins over the stylesheet, so
+ // move it here or the two chips sit on top of each other.
+ c.style.bottom = "4.65rem";
+ }
+ }
+
+ function ensureButton() {
+ var existing = document.getElementById("scroll-to-top");
+ if (existing) return existing;
+
+ var a = document.createElement("a");
+ a.id = "scroll-to-top";
+ a.className = "scroll-to-top";
+ a.href = "#top";
+ var label = labelForLang();
+ a.setAttribute("aria-label", label);
+ a.title = label;
+
+ var caret = document.createElement("span");
+ caret.className = "scroll-to-top-caret";
+ caret.setAttribute("aria-hidden", "true");
+ caret.appendChild(document.createTextNode("\u25B2"));
+
+ var text = document.createElement("span");
+ text.className = "scroll-to-top-label";
+ text.setAttribute("aria-hidden", "true");
+ text.appendChild(document.createTextNode("TOP"));
+
+ a.appendChild(caret);
+ a.appendChild(text);
+ document.body.appendChild(a);
+ return a;
+ }
+
+ function goTop(e) {
+ if (e) e.preventDefault();
+ if (prefersReducedMotion() || typeof window.scrollTo !== "function") {
+ if (typeof window.scrollTo === "function") {
+ window.scrollTo(0, 0);
+ } else {
+ document.documentElement.scrollTop = 0;
+ document.body.scrollTop = 0;
+ }
+ return;
+ }
+ try {
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ } catch (_) {
+ window.scrollTo(0, 0);
+ }
+ }
+
+ function init() {
+ ensureCss();
+ ensureTopTarget();
+ var btn = ensureButton();
+ if (!btn) return;
+
+ document.body.className = (document.body.className ? document.body.className + " " : "") + "has-scroll-to-top";
+ dodgeVisitCounter();
+ window.setTimeout(dodgeVisitCounter, 50);
+ window.setTimeout(dodgeVisitCounter, 400);
+
+ var ticking = false;
+ function update() {
+ ticking = false;
+ var show = overflowPx() > MIN_OVERFLOW && scrollY() > THRESHOLD;
+ if (show) {
+ if (btn.className.indexOf("is-visible") === -1) {
+ btn.className += " is-visible";
+ }
+ } else {
+ btn.className = btn.className.replace(/\s*is-visible\b/g, "");
+ }
+ }
+
+ function onScroll() {
+ if (ticking) return;
+ ticking = true;
+ if (window.requestAnimationFrame) {
+ window.requestAnimationFrame(update);
+ } else {
+ window.setTimeout(update, 16);
+ }
+ }
+
+ window.addEventListener("scroll", onScroll, false);
+ window.addEventListener("resize", onScroll, false);
+ btn.addEventListener("click", goTop);
+ update();
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", init);
+ } else {
+ init();
+ }
+})();
diff --git a/assets/js/site.js b/assets/js/site.js
new file mode 100644
index 0000000..63ac6a8
--- /dev/null
+++ b/assets/js/site.js
@@ -0,0 +1,1341 @@
+(function () {
+ function byId(id) {
+ return document.getElementById(id);
+ }
+
+ /* ========================================================================
+ Theme Toggle (localStorage)
+ ======================================================================== */
+ function applyTheme(theme) {
+ var isLight = theme === "light";
+ document.body.classList.add("theme-transitioning");
+ window.setTimeout(function () {
+ document.body.classList.remove("theme-transitioning");
+ }, 400);
+ if (theme === "light") {
+ document.body.classList.add("light-mode");
+ } else {
+ document.body.classList.remove("light-mode");
+ }
+ document.documentElement.style.colorScheme = isLight ? "light" : "dark";
+ var themeColor = document.querySelector('meta[name="theme-color"]');
+ if (themeColor) {
+ themeColor.setAttribute("content", isLight ? "#fff8f1" : "#000000");
+ }
+ var btn = byId("theme-toggle");
+ if (btn) {
+ btn.textContent = theme === "light" ? "\u263E" : "\u2600";
+ btn.setAttribute("title",
+ theme === "light"
+ ? "Switch to dark theme"
+ : "Switch to light theme"
+ );
+ btn.setAttribute("aria-label",
+ theme === "light"
+ ? "Switch to dark theme"
+ : "Switch to light theme"
+ );
+ }
+ // Sync guestbook iframes
+ ["guestbook-form-iframe", "guestbook-comments-iframe"].forEach(function (id) {
+ var frame = byId(id);
+ if (!frame) return;
+ var src = frame.getAttribute("src") || "";
+ var base = src.split("?")[0];
+ var next = theme === "light" ? base + "?theme=light" : base;
+ if (src !== next) frame.setAttribute("src", next);
+ });
+ }
+
+ var stored = localStorage.getItem("siteTheme");
+ var prefersLight = window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches;
+ var savedTheme = stored || (prefersLight ? "light" : "dark");
+ applyTheme(savedTheme);
+
+ if (window.matchMedia) {
+ window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", function (e) {
+ if (!localStorage.getItem("siteTheme")) {
+ applyTheme(e.matches ? "light" : "dark");
+ }
+ });
+ }
+
+ function syncHeaderOffset() {
+ var header = document.querySelector("header.site-header");
+ if (!header) return;
+ document.documentElement.style.setProperty(
+ "--site-header-height",
+ Math.ceil(header.getBoundingClientRect().height) + "px"
+ );
+ }
+
+ window.addEventListener("load", syncHeaderOffset);
+ window.addEventListener("resize", syncHeaderOffset);
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", syncHeaderOffset);
+ } else {
+ syncHeaderOffset();
+ }
+
+ var themeBtn = byId("theme-toggle");
+ if (themeBtn) {
+ themeBtn.addEventListener("click", function () {
+ var next = document.body.classList.contains("light-mode") ? "dark" : "light";
+ localStorage.setItem("siteTheme", next);
+ applyTheme(next);
+ });
+ }
+
+ /* ========================================================================
+ Comfort spacing toggle (localStorage, site-wide)
+ ======================================================================== */
+ (function initComfortSpacing() {
+ if ((location.pathname || "").indexOf("/startpage") !== -1) return;
+ var KEY = "siteComfortSpacing_v1";
+ var btn = byId("comfort-toggle");
+ if (!btn) return;
+
+ function applyComfort(on) {
+ document.body.classList.toggle("comfort-spacing", on);
+ btn.setAttribute("aria-pressed", on ? "true" : "false");
+ btn.setAttribute("title", on ? "Comfort spacing: ON" : "Comfort spacing: OFF");
+ btn.setAttribute("aria-label", on ? "Comfort spacing on" : "Comfort spacing off");
+ }
+
+ applyComfort(localStorage.getItem(KEY) === "1");
+
+ btn.addEventListener("click", function () {
+ var next = !document.body.classList.contains("comfort-spacing");
+ localStorage.setItem(KEY, next ? "1" : "0");
+ applyComfort(next);
+ });
+ })();
+
+ /* ========================================================================
+ Menu & Language Dropdowns
+ ======================================================================== */
+ const menuBtn = byId("menu-toggle");
+ const nav = byId("site-nav");
+
+ const langToggle = byId("lang-toggle");
+ const langMenu = byId("lang-options");
+ const currentLang = byId("current-lang");
+
+ let openDropdown = null;
+
+ function closeMenu() {
+ if (!menuBtn || !nav) return;
+ menuBtn.classList.remove("active");
+ menuBtn.setAttribute("aria-expanded", "false");
+ nav.hidden = true;
+ if (openDropdown === "menu") openDropdown = null;
+ }
+
+ function closeLang() {
+ if (!langToggle || !langMenu) return;
+ langToggle.setAttribute("aria-expanded", "false");
+ langMenu.hidden = true;
+ if (openDropdown === "lang") openDropdown = null;
+ }
+
+ function toggleMenu(e) {
+ if (!menuBtn || !nav) return;
+ e.stopPropagation();
+
+ if (openDropdown === "lang") closeLang();
+
+ const open = menuBtn.getAttribute("aria-expanded") === "true";
+ if (open) {
+ closeMenu();
+ return;
+ }
+
+ menuBtn.classList.add("active");
+ menuBtn.setAttribute("aria-expanded", "true");
+ nav.hidden = false;
+ openDropdown = "menu";
+ }
+
+ function toggleLang(e) {
+ if (!langToggle || !langMenu) return;
+ e.stopPropagation();
+
+ if (openDropdown === "menu") closeMenu();
+
+ const open = langToggle.getAttribute("aria-expanded") === "true";
+ if (open) {
+ closeLang();
+ return;
+ }
+
+ langToggle.setAttribute("aria-expanded", "true");
+ langMenu.hidden = false;
+ openDropdown = "lang";
+ }
+
+ if (nav) nav.hidden = true;
+ if (langMenu) langMenu.hidden = true;
+
+ if (menuBtn && nav) {
+ menuBtn.addEventListener("click", toggleMenu);
+ nav.addEventListener("click", (e) => e.stopPropagation());
+ }
+
+ if (langToggle && langMenu) {
+ langToggle.addEventListener("click", toggleLang);
+ langMenu.addEventListener("click", (e) => e.stopPropagation());
+ }
+
+ if ((menuBtn && nav) || (langToggle && langMenu)) {
+ document.addEventListener("click", function (e) {
+ if (
+ menuBtn && nav &&
+ (menuBtn.contains(e.target) || nav.contains(e.target))
+ ) {
+ return;
+ }
+ if (
+ langToggle && langMenu &&
+ (langToggle.contains(e.target) || langMenu.contains(e.target))
+ ) {
+ return;
+ }
+ closeMenu();
+ closeLang();
+ });
+
+ document.addEventListener("keydown", function (e) {
+ if (e.key === "Escape") {
+ closeMenu();
+ closeLang();
+ }
+ });
+ }
+
+ if (currentLang) {
+ const docLang = (document.documentElement.lang || "en").toLowerCase();
+ currentLang.textContent = docLang === "zh" ? "ZH" : docLang === "ja" ? "JP" : "EN";
+ }
+
+ if (langMenu) {
+ langMenu.querySelectorAll("a[data-lang]").forEach((a) => {
+ a.addEventListener("click", function () {
+ localStorage.setItem("preferredLang", this.getAttribute("data-lang") || "en");
+ });
+ });
+ }
+
+ // Language menu hrefs: convert current page to *_zh.php / *_jp.php.
+ function updateLangMenuHrefs() {
+ const links = document.querySelectorAll("a[data-lang-href][data-lang]");
+ if (!links.length) return;
+
+ let path = window.location.pathname || "/";
+ if (!path.startsWith("/")) path = "/" + path;
+
+ const isDir = path.endsWith("/");
+ const file = isDir ? "index.php" : (path.split("/").pop() || "index.php");
+ const base = isDir ? path : path.slice(0, -file.length);
+
+ const baseName = file.replace(/_(zh|jp)\.php$/i, ".php");
+ const map = {
+ en: baseName,
+ zh: baseName.replace(/(_(zh|jp))?\.php$/i, "_zh.php"),
+ jp: baseName.replace(/(_(zh|jp))?\.php$/i, "_jp.php"),
+ };
+
+ links.forEach((a) => {
+ const lang = (a.getAttribute("data-lang") || "en").toLowerCase();
+ const targetFile = map[lang] || map.en;
+ a.href = base + targetFile;
+ });
+ }
+
+ function updateAriaCurrent() {
+ const path = window.location.pathname || "/";
+ const normalized = path.startsWith("/") ? path : "/" + path;
+ const isDir = normalized.endsWith("/");
+ const file = isDir ? "index.php" : (normalized.split("/").pop() || "index.php");
+ const base = isDir ? normalized : normalized.slice(0, -file.length);
+ const indexPath = base + "index.php";
+ const dirPath = base || "/";
+ const candidates = new Set([normalized, indexPath, dirPath, dirPath.replace(/\/$/, "")]);
+
+ document.querySelectorAll("#site-nav a[href]").forEach((a) => {
+ const href = a.getAttribute("href") || "";
+ const hrefUrl = new URL(href, window.location.origin);
+ if (hrefUrl.origin !== window.location.origin) {
+ a.removeAttribute("aria-current");
+ return;
+ }
+ const hrefPath = hrefUrl.pathname;
+ if (candidates.has(hrefPath)) {
+ a.setAttribute("aria-current", "page");
+ } else {
+ a.removeAttribute("aria-current");
+ }
+ });
+
+ const docLang = (document.documentElement.lang || "en").toLowerCase();
+ // "ja" is valid BCP-47 for Japanese but we label it "jp" in data-lang
+ const normalizedDocLang = docLang === "ja" ? "jp" : docLang;
+ document.querySelectorAll("#lang-options a[data-lang]").forEach((a) => {
+ const lang = (a.getAttribute("data-lang") || "en").toLowerCase();
+ if (lang === normalizedDocLang) {
+ a.setAttribute("aria-current", "page");
+ } else {
+ a.removeAttribute("aria-current");
+ }
+ });
+ }
+
+ // Run once now...
+ updateLangMenuHrefs();
+ updateAriaCurrent();
+ // ...and again after layout/injection settles.
+ setTimeout(updateLangMenuHrefs, 0);
+ setTimeout(updateAriaCurrent, 0);
+
+ /* ========================================================================
+ Skeleton Screens — replace static "Loading…" text
+ ======================================================================== */
+ function injectSkeleton(container) {
+ if (!container) return;
+ container.innerHTML =
+ '<div class="skeleton-line"></div>' +
+ '<div class="skeleton-line"></div>' +
+ '<div class="skeleton-line"></div>' +
+ '<div class="skeleton-line"></div>';
+ }
+
+ /* ========================================================================
+ Home page: Blog list (optional)
+ ======================================================================== */
+ const blogList = byId("blog-list");
+ if (blogList) {
+ injectSkeleton(blogList);
+
+ const blogHost = "https://blog.sillylaird.ca";
+ const ctrl = new AbortController();
+ const timeout = setTimeout(() => ctrl.abort(), 3500);
+
+ fetch(blogHost + "/api/posts.php", { signal: ctrl.signal })
+ .then((r) => r.json())
+ .then((posts) => {
+ blogList.textContent = "";
+
+ const top = document.createElement("div");
+
+ const strong = document.createElement("strong");
+ strong.textContent = "Posts";
+ top.appendChild(strong);
+
+ top.appendChild(document.createTextNode(" ("));
+ const allLink = document.createElement("a");
+ allLink.href = blogHost;
+ allLink.textContent = "all";
+ top.appendChild(allLink);
+ top.appendChild(document.createTextNode(")"));
+
+ blogList.appendChild(top);
+
+ const list = document.createElement("ol");
+ (posts || []).forEach((post) => {
+ const li = document.createElement("li");
+
+ const a = document.createElement("a");
+ a.href = blogHost + "/" + String(post.path || "").replace(/^\/+/, "");
+ a.textContent = String(post.title || "Untitled");
+
+ const date = document.createElement("div");
+ date.className = "muted";
+
+ const y = String(post.year || "");
+ const m = String(post.month || "").padStart(2, "0");
+ const d = String(post.day || "").padStart(2, "0");
+ const iso = y && m && d ? `${y}-${m}-${d}` : "";
+
+ const time = document.createElement("time");
+ if (iso) time.setAttribute("datetime", iso);
+ time.textContent = iso || "";
+
+ li.appendChild(a);
+ li.appendChild(document.createElement("br"));
+ date.appendChild(time);
+ li.appendChild(date);
+
+ list.appendChild(li);
+ });
+
+ blogList.appendChild(list);
+
+ if (!list.children.length) {
+ const empty = document.createElement("div");
+ empty.className = "muted";
+ empty.textContent = "No posts yet.";
+ blogList.appendChild(empty);
+ }
+ })
+ .catch((err) => {
+ blogList.textContent = err && err.name === "AbortError"
+ ? "Blog list timed out."
+ : "Cannot load posts right now.";
+ })
+ .finally(() => clearTimeout(timeout));
+ }
+
+ /* ========================================================================
+ Home page: Changelog list (optional)
+ ======================================================================== */
+ const changelogList = byId("changelog-list");
+ if (changelogList) {
+ injectSkeleton(changelogList);
+
+ const changelogPage = "https://www.sillylaird.ca/changelog/latest.php";
+ const ctrl = new AbortController();
+ const timeout = setTimeout(() => ctrl.abort(), 3500);
+
+ fetch(changelogPage, { signal: ctrl.signal })
+ .then((r) => r.text())
+ .then((html) => {
+ changelogList.textContent = "";
+ const doc = new DOMParser().parseFromString(html, "text/html");
+ const entries = Array.from(doc.querySelectorAll(".changelog-entry"));
+
+ const top = document.createElement("div");
+
+ const strong = document.createElement("strong");
+ strong.textContent = "Entries";
+ top.appendChild(strong);
+
+ top.appendChild(document.createTextNode(" ("));
+ const allLink = document.createElement("a");
+ allLink.href = "https://www.sillylaird.ca/changelog/";
+ allLink.textContent = "all";
+ top.appendChild(allLink);
+ top.appendChild(document.createTextNode(")"));
+
+ changelogList.appendChild(top);
+
+ const list = document.createElement("ol");
+ entries.forEach((entry) => {
+ const li = document.createElement("li");
+
+ const titleText = (entry.querySelector("strong, b")?.textContent || "").trim();
+ const timeEl = entry.querySelector("time");
+ const dateText = (timeEl?.textContent || "").trim();
+ const dateIso = (timeEl?.getAttribute("datetime") || "").trim();
+ const bodyEl = entry.querySelector(".text");
+ const bodyText = (bodyEl?.textContent || "").trim();
+
+ const title = document.createElement("strong");
+ if (titleText) {
+ title.textContent = titleText;
+ li.appendChild(title);
+
+ if (dateText) {
+ const date = document.createElement("div");
+ date.className = "muted";
+ const time = document.createElement("time");
+ if (dateIso) time.setAttribute("datetime", dateIso);
+ time.textContent = dateText;
+ date.appendChild(time);
+ li.appendChild(date);
+ }
+ } else if (dateText) {
+ title.textContent = dateText;
+ li.appendChild(title);
+ }
+
+ if (bodyText) {
+ const body = document.createElement("div");
+ body.className = "changelog-body";
+ body.textContent = bodyText;
+ li.appendChild(body);
+ }
+
+ list.appendChild(li);
+ });
+
+ changelogList.appendChild(list);
+
+ const filesSection = doc.querySelector(".changelog-files-section");
+ if (filesSection) {
+ changelogList.appendChild(document.importNode(filesSection, true));
+ }
+
+ if (!list.children.length) {
+ const empty = document.createElement("div");
+ empty.className = "muted";
+ empty.textContent = "No updates yet.";
+ changelogList.appendChild(empty);
+ }
+ })
+ .catch((err) => {
+ changelogList.textContent = err && err.name === "AbortError"
+ ? "Changelog list timed out."
+ : "Cannot load changelog right now.";
+ })
+ .finally(() => clearTimeout(timeout));
+ }
+
+ /* ========================================================================
+ Guestbook helpers
+ ======================================================================== */
+ const formRefreshBtn = byId("guestbook-form-refresh");
+ if (formRefreshBtn) {
+ formRefreshBtn.addEventListener("click", () => {
+ const frame = byId("guestbook-form-iframe");
+ if (!frame) return;
+ setIframeLoading(frame);
+ resetIframe(frame);
+ });
+ }
+
+ function setIframeLoading(frame) {
+ if (!frame) return;
+ const clip = frame.closest(".iframe-clip");
+ if (!clip) return;
+ clip.classList.add("is-loading");
+ const onLoad = () => {
+ clip.classList.remove("is-loading");
+ };
+ frame.addEventListener("load", onLoad, { once: true });
+ }
+
+ function resetIframe(frame) {
+ if (!frame) return;
+ const src = frame.getAttribute("src") || frame.src;
+ if (src) {
+ frame.setAttribute("src", src);
+ return;
+ }
+ if (frame.contentWindow) frame.contentWindow.location.reload();
+ }
+
+ function loadGuestbookComments() {
+ var box = byId("guestbook-comments-inline");
+ if (!box) return;
+ box.innerHTML = '<div class="retro-loading">Loading recent comments...</div>';
+ fetch("/api/guestbook-recent.php?limit=6")
+ .then(function (r) { return r.ok ? r.json() : Promise.reject(r.status); })
+ .then(function (data) {
+ var entries = data && data.entries ? data.entries : [];
+ box.textContent = "";
+ if (!entries.length) {
+ var empty = document.createElement("p");
+ empty.className = "muted";
+ empty.textContent = "No comments yet — be the first to sign!";
+ box.appendChild(empty);
+ return;
+ }
+ entries.forEach(function (row) {
+ var art = document.createElement("article");
+ art.className = "guestbook-entry";
+ var strong = document.createElement("strong");
+ strong.textContent = row.name || "Anonymous";
+ art.appendChild(strong);
+ if (row.created_at) {
+ var time = document.createElement("time");
+ time.textContent = row.created_at;
+ time.setAttribute("datetime", row.created_at);
+ art.appendChild(time);
+ }
+ var p = document.createElement("p");
+ p.textContent = row.message || "";
+ art.appendChild(p);
+ box.appendChild(art);
+ });
+ })
+ .catch(function () {
+ box.innerHTML = '<p class="muted">Could not load comments. <a href="/guestbook.php">Open guestbook</a></p>';
+ });
+ }
+
+ var gbReload = byId("guestbook-comments-reload");
+ if (gbReload) {
+ gbReload.addEventListener("click", loadGuestbookComments);
+ loadGuestbookComments();
+ }
+
+ document.querySelectorAll(".iframe-clip iframe, .vibe-embed iframe").forEach(function (frame) {
+ var clip = frame.closest(".iframe-clip") || frame.closest(".vibe-embed");
+ if (!clip) return;
+ clip.classList.add("is-loading");
+ frame.addEventListener("load", function () {
+ clip.classList.remove("is-loading");
+ }, { once: true });
+ });
+
+ /* ========================================================================
+ Home page: Last.fm live refresh (The "Pro" Way)
+ ======================================================================== */
+ const lastfmList = byId("lastfm-list");
+ const lastfmNow = byId("lastfm-now");
+ if (lastfmList || lastfmNow) {
+ let lastDataStr = "";
+
+ function renderNowPlaying(tracks) {
+ if (!lastfmNow) return;
+ lastfmNow.textContent = "";
+ var current = null;
+ if (Array.isArray(tracks)) {
+ for (var i = 0; i < tracks.length; i++) {
+ if (tracks[i].nowplaying) { current = tracks[i]; break; }
+ }
+ if (!current && tracks[0]) current = tracks[0];
+ }
+ if (!current) {
+ var fallback = document.createElement("p");
+ fallback.className = "muted";
+ fallback.innerHTML = 'Nothing scrobbling — <a href="https://www.last.fm/user/SillyLaird" target="_blank" rel="noopener noreferrer">Last.fm profile</a>';
+ lastfmNow.appendChild(fallback);
+ return;
+ }
+ var card = document.createElement("a");
+ card.className = "lastfm-track lastfm-track--featured";
+ card.href = current.url || "https://www.last.fm/user/SillyLaird";
+ card.target = "_blank";
+ card.rel = "noopener noreferrer";
+ if (current.image) {
+ var wrap = document.createElement("span");
+ wrap.className = "lastfm-image-wrap";
+ var img = document.createElement("img");
+ img.src = current.image;
+ img.alt = (current.artist || "") + " — " + (current.name || "track");
+ img.loading = "lazy";
+ img.decoding = "async";
+ wrap.appendChild(img);
+ card.appendChild(wrap);
+ }
+ var info = document.createElement("span");
+ info.className = "lastfm-info";
+ var status = document.createElement("span");
+ status.className = "lastfm-status" + (current.nowplaying ? " is-playing" : "");
+ status.textContent = current.nowplaying ? "Now playing" : "Last scrobbled";
+ var title = document.createElement("span");
+ title.className = "lastfm-title";
+ title.textContent = current.name || "Unknown track";
+ var artist = document.createElement("span");
+ artist.className = "lastfm-artist";
+ artist.textContent = current.artist || "";
+ info.appendChild(status);
+ info.appendChild(title);
+ info.appendChild(artist);
+ card.appendChild(info);
+ lastfmNow.appendChild(card);
+ }
+
+ function updateLastFM() {
+ fetch("/api/lastfm.php")
+ .then(res => {
+ if (!res.ok) throw new Error("API error: " + res.status);
+ return res.json();
+ })
+ .then(tracks => {
+ if (tracks.error) throw new Error(tracks.message || "Unknown proxy error");
+
+ // Only update DOM if the data has changed
+ const currentDataStr = JSON.stringify(tracks);
+ if (currentDataStr === lastDataStr) return;
+ lastDataStr = currentDataStr;
+
+ renderNowPlaying(tracks);
+
+ if (!lastfmList) return;
+ var ul = document.createElement("ul");
+ ul.className = "lastfm-text-list";
+ if (Array.isArray(tracks) && tracks.length > 0) {
+ tracks.forEach(function (track) {
+ var li = document.createElement("li");
+ if (track.nowplaying) {
+ var strong = document.createElement("strong");
+ strong.textContent = "\u25B6 Now playing: ";
+ li.appendChild(strong);
+ }
+ var a = document.createElement("a");
+ a.href = track.url;
+ a.target = "_blank";
+ a.rel = "noopener noreferrer";
+ a.textContent = track.name;
+ li.appendChild(a);
+ li.appendChild(document.createTextNode(" \u2014 " + track.artist));
+ ul.appendChild(li);
+ });
+ } else {
+ var empty = document.createElement("li");
+ empty.className = "muted";
+ empty.textContent = "No tracks found.";
+ ul.appendChild(empty);
+ }
+ lastfmList.textContent = "";
+ lastfmList.appendChild(ul);
+ lastfmList.dataset.loaded = "1";
+ if (lastfmNow) lastfmNow.dataset.loaded = "1";
+ })
+ .catch(err => {
+ console.error("Last.fm Widget Error:", err);
+ if (lastfmNow && !lastfmNow.dataset.loaded) {
+ lastfmNow.dataset.loaded = "1";
+ lastfmNow.innerHTML = '<p class="muted"><a href="https://www.last.fm/user/SillyLaird" target="_blank" rel="noopener noreferrer">Last.fm profile</a></p>';
+ }
+ if (lastfmList && !lastfmList.dataset.loaded) {
+ lastfmList.dataset.loaded = "1";
+ lastfmList.innerHTML = '<p class="muted"><a href="https://www.last.fm/user/SillyLaird" target="_blank" rel="noopener noreferrer">View recent tracks on Last.fm ↗</a></p>';
+ }
+ });
+ }
+
+ // Defer the first fetch until the widget scrolls into view (saves a
+ // third-party request on initial paint — matters on slow/Tor connections).
+ let refreshTimer = null;
+ function startLastFM() {
+ if (refreshTimer) return;
+ updateLastFM();
+ refreshTimer = setInterval(updateLastFM, 30000);
+ }
+ if ("IntersectionObserver" in window) {
+ const io = new IntersectionObserver(function (entries) {
+ if (entries.some(function (e) { return e.isIntersecting; })) {
+ io.disconnect();
+ startLastFM();
+ }
+ }, { rootMargin: "200px" });
+ io.observe(lastfmList || lastfmNow);
+ } else {
+ startLastFM();
+ }
+
+ // Manual refresh button
+ const refreshBtn = byId("lastfm-refresh");
+ if (refreshBtn) {
+ refreshBtn.addEventListener("click", () => {
+ refreshBtn.textContent = "Updating...";
+ refreshBtn.disabled = true;
+ updateLastFM();
+ setTimeout(() => {
+ refreshBtn.textContent = "Refresh";
+ refreshBtn.disabled = false;
+ }, 500);
+ });
+ }
+ }
+
+ /* ========================================================================
+ Footer helpers
+ ======================================================================== */
+ // Footer year: local time only.
+ (function () {
+ const el = byId("copyright-year");
+ if (!el) return;
+
+ el.textContent = String(new Date().getFullYear());
+ })();
+
+ // Footer last-updated: based on document.lastModified.
+ (function () {
+ const el = byId("last-updated");
+ if (!el) return;
+
+ const raw = document.lastModified;
+ const date = raw ? new Date(raw) : null;
+ if (!date || Number.isNaN(date.getTime())) return;
+
+ const iso = date.toISOString().slice(0, 10);
+ el.setAttribute("datetime", iso);
+ el.textContent = iso;
+ })();
+
+ /* ========================================================================
+ Force red text in same-origin iframes
+ ======================================================================== */
+ var _gbFrameIds = new Set(["guestbook-form-iframe", "guestbook-comments-iframe"]);
+
+ function forceIframeColors(frame) {
+ if (_gbFrameIds.has(frame.id)) return;
+ try {
+ var doc = frame.contentDocument || frame.contentWindow.document;
+ if (!doc) return;
+ if (doc.getElementById('forced-colors-style')) return;
+
+ var style = doc.createElement('style');
+ style.id = 'forced-colors-style';
+ style.textContent =
+ ':root { --ink: #8b0000; --bg: #000000; }' +
+ '* { color: #8b0000 !important; }' +
+ 'a { color: #8b0000 !important; text-decoration: underline; }' +
+ 'body { background-color: var(--bg, #000) !important; color: #8b0000 !important; }';
+ doc.head.appendChild(style);
+ } catch (e) {
+ // Cross-domain frames cannot be styled
+ }
+ }
+
+ function attachIframeColorForcing() {
+ document.querySelectorAll('iframe').forEach(function (frame) {
+ if (frame._colorForced) return;
+ frame._colorForced = true;
+ frame.addEventListener('load', function () { forceIframeColors(frame); });
+ // Also try immediately in case already loaded
+ forceIframeColors(frame);
+ });
+ }
+
+ window.addEventListener('load', attachIframeColorForcing);
+ document.addEventListener('DOMContentLoaded', attachIframeColorForcing);
+
+ /* ========================================================================
+ Global Header Search (unified search w/ engine picker)
+ Mirrors /startpage/ behavior; uses gh-* IDs to avoid collisions.
+ ======================================================================== */
+ var GH_SEARCH_ENGINES = [
+ { key: 'site', label: '⌂', title: 'This site', placeholder: 'Search pages on sillylaird.ca…', action: '#', param: 'q', local: true },
+ { key: 'google', label: 'G', title: 'Google Search', placeholder: 'Search Google...', action: 'https://www.google.ca/search', param: 'q' },
+ { key: 'youtube', label: 'YT', title: 'YouTube Search', placeholder: 'Search YouTube...', action: 'https://www.youtube.com/results', param: 'search_query' },
+ { key: 'sillylaird', label: 'SL', title: 'SillyLaird Search', placeholder: 'Search SillyLaird...', action: 'https://search.sillylaird.ca/search', param: 'q' },
+ { key: '4get', label: '4G', title: '4get Search', placeholder: 'Search 4get...', action: 'https://4get.sillylaird.ca/web', param: 's' },
+ { key: 'gemini', label: 'GM', title: 'Gemini Search', placeholder: 'Ask Gemini...', action: '/gemini-search.php', param: 'q' }
+ ];
+ var GH_SEARCH_KEY = 'siteHeaderSearchEngine_v1';
+
+ function initGlobalHeaderSearch() {
+ var form = byId('gh-search-form');
+ var input = byId('gh-search-input');
+ var toggle = byId('gh-search-engine-toggle');
+ var menu = byId('gh-search-engine-menu');
+ if (!form || !input || !toggle || !menu) return;
+ if (toggle._ghInit) return;
+ toggle._ghInit = true;
+
+ var saved = null;
+ try { saved = localStorage.getItem(GH_SEARCH_KEY); } catch (_) {}
+ var idx = GH_SEARCH_ENGINES.findIndex(function (e) { return e.key === saved; });
+ if (idx < 0) idx = GH_SEARCH_ENGINES.findIndex(function (e) { return e.key === 'google'; });
+ if (idx < 0) idx = 0;
+
+ GH_SEARCH_ENGINES.forEach(function (e, i) {
+ var li = document.createElement('li');
+ li.setAttribute('role', 'option');
+ li.dataset.index = String(i);
+ var icon = document.createElement('span');
+ icon.className = 'engine-icon';
+ icon.textContent = e.label;
+ var name = document.createElement('span');
+ name.className = 'engine-name';
+ name.textContent = e.title;
+ li.appendChild(icon);
+ li.appendChild(name);
+ li.addEventListener('click', function () {
+ idx = i;
+ apply(idx);
+ closeMenu();
+ input.focus();
+ });
+ menu.appendChild(li);
+ });
+
+ function apply(i) {
+ var e = GH_SEARCH_ENGINES[i];
+ form.setAttribute('action', e.local ? '#' : e.action);
+ if (e.local) {
+ form.removeAttribute('target');
+ } else {
+ form.setAttribute('target', '_blank');
+ form.setAttribute('rel', 'noopener noreferrer');
+ }
+ input.setAttribute('name', e.local ? 'site-q' : e.param);
+ input.setAttribute('placeholder', e.placeholder);
+ toggle.textContent = '';
+ toggle.appendChild(document.createTextNode(e.label + ' '));
+ var caret = document.createElement('span');
+ caret.className = 'search-engine-caret';
+ caret.textContent = '▾';
+ toggle.appendChild(caret);
+ toggle.setAttribute('title', 'Search engine: ' + e.title);
+ Array.prototype.forEach.call(menu.children, function (li, li_i) {
+ li.setAttribute('aria-selected', li_i === i ? 'true' : 'false');
+ });
+ try { localStorage.setItem(GH_SEARCH_KEY, e.key); } catch (_) {}
+ }
+
+ function openMenu() {
+ menu.hidden = false;
+ toggle.setAttribute('aria-expanded', 'true');
+ }
+ function closeMenu() {
+ menu.hidden = true;
+ toggle.setAttribute('aria-expanded', 'false');
+ }
+
+ toggle.addEventListener('click', function (ev) {
+ ev.stopPropagation();
+ if (menu.hidden) openMenu(); else closeMenu();
+ });
+
+ document.addEventListener('click', function (ev) {
+ if (menu.hidden) return;
+ if (ev.target === toggle || toggle.contains(ev.target)) return;
+ if (menu.contains(ev.target)) return;
+ closeMenu();
+ });
+
+ document.addEventListener('keydown', function (ev) {
+ if (ev.key === 'Escape' && !menu.hidden) closeMenu();
+ });
+
+ var siteHits = byId('site-search-hits');
+ var siteIndex = null;
+
+ function loadSiteIndex() {
+ if (siteIndex) return Promise.resolve(siteIndex);
+ return fetch('/assets/data/site-index.json')
+ .then(function (r) { return r.json(); })
+ .then(function (j) { siteIndex = Array.isArray(j) ? j : []; return siteIndex; })
+ .catch(function () { siteIndex = []; return siteIndex; });
+ }
+
+ function renderSiteHits(q) {
+ if (!siteHits) return;
+ var query = (q || '').trim().toLowerCase();
+ if (!query) { siteHits.hidden = true; siteHits.textContent = ''; return; }
+ loadSiteIndex().then(function (pages) {
+ var hits = pages.filter(function (p) {
+ var hay = (p.title + ' ' + p.url + ' ' + (p.tags || '')).toLowerCase();
+ return hay.indexOf(query) !== -1;
+ }).slice(0, 8);
+ siteHits.textContent = '';
+ if (!hits.length) { siteHits.hidden = true; return; }
+ hits.forEach(function (p) {
+ var li = document.createElement('li');
+ var a = document.createElement('a');
+ a.href = p.url;
+ if (/^https?:\/\//.test(p.url)) {
+ a.target = '_blank';
+ a.rel = 'noopener noreferrer';
+ }
+ a.textContent = p.title;
+ li.appendChild(a);
+ siteHits.appendChild(li);
+ });
+ siteHits.hidden = false;
+ });
+ }
+
+ input.addEventListener('input', function () {
+ if (GH_SEARCH_ENGINES[idx] && GH_SEARCH_ENGINES[idx].local) {
+ renderSiteHits(input.value);
+ } else if (siteHits) {
+ siteHits.hidden = true;
+ }
+ });
+
+ form.addEventListener('submit', function (ev) {
+ if (GH_SEARCH_ENGINES[idx] && GH_SEARCH_ENGINES[idx].local) {
+ ev.preventDefault();
+ var first = siteHits && siteHits.querySelector('a');
+ if (first) first.click();
+ else renderSiteHits(input.value);
+ }
+ });
+
+ apply(idx);
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initGlobalHeaderSearch);
+ } else {
+ initGlobalHeaderSearch();
+ }
+
+ (function initRadioWidget() {
+ var box = byId('radio-widget');
+ if (!box) return;
+ function render(title) {
+ box.textContent = '';
+ var p = document.createElement('p');
+ if (title) {
+ p.className = 'radio-now-line';
+ var strong = document.createElement('strong');
+ strong.textContent = 'Radio: ';
+ p.appendChild(strong);
+ var span = document.createElement('span');
+ span.id = 'radio-nowplaying-title';
+ span.textContent = title;
+ p.appendChild(span);
+ p.appendChild(document.createTextNode(' — '));
+ var a = document.createElement('a');
+ a.href = 'https://radio.sillylaird.ca/';
+ a.target = '_blank';
+ a.rel = 'noopener noreferrer';
+ a.textContent = 'Listen live';
+ p.appendChild(a);
+ } else {
+ p.className = 'muted';
+ p.textContent = 'Radio offline — ';
+ var link = document.createElement('a');
+ link.href = 'https://radio.sillylaird.ca/';
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ link.textContent = 'radio.sillylaird.ca';
+ p.appendChild(link);
+ }
+ box.appendChild(p);
+ }
+ function poll() {
+ fetch('/api/nowplaying.php', { cache: 'no-store' })
+ .then(function (r) { return r.ok ? r.json() : Promise.reject(); })
+ .then(function (j) {
+ var title = j && j.title ? String(j.title).trim() : '';
+ render(title);
+ })
+ .catch(function () { render(''); });
+ }
+ poll();
+ setInterval(poll, 30000);
+ })();
+
+ (function initLainEgg() {
+ var btn = byId('lain-avatar-btn');
+ if (!btn) return;
+ var secrets = [
+ btn.getAttribute('data-secret-1'),
+ btn.getAttribute('data-secret-2'),
+ btn.getAttribute('data-secret-3'),
+ ].filter(Boolean);
+ var i = 0;
+ btn.addEventListener('click', function () {
+ var msg = secrets[i % secrets.length] || "Let's all love Lain.";
+ i += 1;
+ btn.classList.add('is-lain-pulse');
+ window.setTimeout(function () { btn.classList.remove('is-lain-pulse'); }, 600);
+ var toast = document.createElement('div');
+ toast.className = 'lain-toast';
+ toast.setAttribute('role', 'status');
+ toast.textContent = msg;
+ document.body.appendChild(toast);
+ window.setTimeout(function () { toast.classList.add('is-visible'); }, 10);
+ window.setTimeout(function () {
+ toast.classList.remove('is-visible');
+ window.setTimeout(function () { toast.remove(); }, 350);
+ }, 2800);
+ });
+ })();
+
+ // Per-page visit counter: canonical implementation is visit-counter.js
+ // (nginx injects it network-wide). Fall back here if that script is absent.
+ (function initVisitCounter() {
+ if (window.__slVisitCounterInit) return;
+ if (document.querySelector('script[src*="visit-counter.js"]')) return;
+ var s = document.createElement('script');
+ // Always load from www so sibling hosts (blog/radio/…) resolve correctly
+ // even when this file itself was loaded cross-origin via nav.js.
+ s.src = 'https://www.sillylaird.ca/assets/js/visit-counter.js?v=2026080403';
+ s.defer = true;
+ document.head.appendChild(s);
+ })();
+
+ // Scroll-to-top: canonical implementation is scroll-to-top.js (nginx injects
+ // it network-wide alongside the visit counter). Fall back if absent.
+ (function initScrollToTop() {
+ if (window.__slScrollTopInit) return;
+ if (document.querySelector('script[src*="scroll-to-top.js"]')) return;
+ var s = document.createElement('script');
+ s.src = 'https://www.sillylaird.ca/assets/js/scroll-to-top.js?v=20260821b';
+ s.defer = true;
+ document.head.appendChild(s);
+ })();
+
+ (function initSectionDraw() {
+ var titles = document.querySelectorAll('.section-accent-title, .network-subtitle');
+ var sections = document.querySelectorAll('.home-content > section');
+ var targets = [];
+ titles.forEach(function (el) { targets.push(el); });
+ sections.forEach(function (el) { targets.push(el); });
+ if (!targets.length) return;
+
+ function markDrawn(el) {
+ el.classList.add('is-drawn');
+ }
+
+ if (!('IntersectionObserver' in window) ||
+ window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
+ targets.forEach(markDrawn);
+ return;
+ }
+
+ var io = new IntersectionObserver(function (entries) {
+ entries.forEach(function (entry) {
+ if (!entry.isIntersecting) return;
+ markDrawn(entry.target);
+ io.unobserve(entry.target);
+ });
+ }, { threshold: 0.35, rootMargin: '0px 0px -8% 0px' });
+
+ targets.forEach(function (el) { io.observe(el); });
+ })();
+
+ (function initNetworkAccordion() {
+ var items = document.querySelectorAll('.network-collapsible');
+ if (!items.length) return;
+ var mq = window.matchMedia('(max-width: 600px)');
+
+ function sync() {
+ var mobile = mq.matches;
+ items.forEach(function (el, i) {
+ el.open = mobile ? i === 0 : true;
+ });
+ }
+
+ sync();
+ if (typeof mq.addEventListener === 'function') {
+ mq.addEventListener('change', sync);
+ } else if (typeof mq.addListener === 'function') {
+ mq.addListener(sync);
+ }
+ })();
+
+ /* ========================================================================
+ Vibe facade: click-to-load embeds (no provider request until clicked)
+
+ The player iframe is created synchronously inside the click handler so
+ Chrome allows autoplay (gesture-driven). State is read straight from each
+ provider's postMessage protocol, so when the viewer pauses we show a
+ "paused" overlay that mirrors the load facade but with a pause glyph.
+
+ One provider entry per supported site. `state(data, iframe)` maps a raw
+ message to 'paused' | 'playing' | null. Bilibili exposes no cross-origin
+ pause event, so it has no entry and falls back to a plain autoplay iframe.
+ ======================================================================== */
+
+ function vibePost(iframe, msg, origin) {
+ try {
+ iframe.contentWindow.postMessage(typeof msg === 'string' ? msg : JSON.stringify(msg), origin || '*');
+ } catch (_) {}
+ }
+
+ function withParam(url, extra) {
+ return url + (url.indexOf('?') > -1 ? '&' : '?') + extra;
+ }
+
+ // Pause overlay — same menu as the load facade. Mode 'paused' shows a pause
+ // glyph + resume copy; mode 'ended' shows a replay glyph + replay copy.
+ function setOverlayMode(overlay, mode) {
+ if (mode === 'ended') {
+ overlay.classList.add('vibe-facade-ended');
+ overlay.setAttribute('aria-label', 'Replay the video');
+ overlay.innerHTML =
+ '<span class="vibe-facade-play" aria-hidden="true">↻</span>' +
+ '<span class="vibe-facade-text">Replay<br><small>Click to watch again</small></span>';
+ } else {
+ overlay.classList.remove('vibe-facade-ended');
+ overlay.setAttribute('aria-label', 'Resume the video');
+ overlay.innerHTML =
+ '<span class="vibe-facade-play" aria-hidden="true">⏸</span>' +
+ '<span class="vibe-facade-text">Paused<br><small>Click to resume</small></span>';
+ }
+ }
+
+ function makePauseOverlay() {
+ var overlay = document.createElement('button');
+ overlay.type = 'button';
+ overlay.className = 'vibe-facade vibe-facade-paused';
+ overlay.hidden = true;
+ setOverlayMode(overlay, 'paused');
+ return overlay;
+ }
+
+ var VIBE_PROVIDERS = {
+ youtube: {
+ match: /youtube(-nocookie)?\.com\/embed\//,
+ origin: /^https?:\/\/(www\.)?youtube(-nocookie)?\.com$/,
+ // youtube.com (not -nocookie): the postMessage API replies from
+ // youtube.com. Privacy is already moot once the user has clicked.
+ src: function (e) { return withParam(e.replace('youtube-nocookie.com', 'youtube.com'), 'autoplay=1&enablejsapi=1'); },
+ subscribe: function (f) {
+ vibePost(f, { event: 'listening', id: 1, channel: 'widget' });
+ vibePost(f, { event: 'command', func: 'addEventListener', args: ['onStateChange'] });
+ },
+ play: function (f) { vibePost(f, { event: 'command', func: 'playVideo', args: [] }); },
+ state: function (d) {
+ var s = null;
+ if (d.event === 'onStateChange') s = d.info;
+ else if (d.event === 'infoDelivery' && d.info && typeof d.info.playerState === 'number') s = d.info.playerState;
+ if (s === 0) return 'ended'; // 0 = ended → show replay button
+ if (s === 2) return 'paused'; // 2 = paused
+ if (s === 1 || s === 3) return 'playing'; // 1 = playing, 3 = buffering
+ return null;
+ }
+ },
+
+ vimeo: {
+ match: /player\.vimeo\.com\/video\//,
+ origin: /^https?:\/\/player\.vimeo\.com$/,
+ src: function (e) { return withParam(e, 'autoplay=1'); },
+ subscribe: function (f) {
+ vibePost(f, { method: 'addEventListener', value: 'play' }, 'https://player.vimeo.com');
+ vibePost(f, { method: 'addEventListener', value: 'pause' }, 'https://player.vimeo.com');
+ },
+ play: function (f) { vibePost(f, { method: 'play' }, 'https://player.vimeo.com'); },
+ state: function (d, f) {
+ if (d.event === 'ready') { this.subscribe(f); return null; } // (re)subscribe once ready
+ if (d.event === 'pause') return 'paused';
+ if (d.event === 'play' || d.event === 'playing') return 'playing';
+ return null;
+ }
+ },
+
+ dailymotion: {
+ match: /dailymotion\.com\/player/,
+ origin: /^https?:\/\/(geo\.)?dailymotion\.com$/,
+ src: function (e) { return withParam(e, 'autoplay=1'); },
+ play: function (f) {
+ vibePost(f, { command: 'play', parameters: [] });
+ vibePost(f, { method: 'play' });
+ },
+ state: function (d) {
+ var ev = d.event;
+ if (ev === 'pause' || ev === 'video_pause') return 'paused';
+ if (ev === 'play' || ev === 'playing' || ev === 'video_start') return 'playing';
+ return null;
+ }
+ },
+
+ niconico: {
+ match: /embed\.nicovideo\.jp\/watch\//,
+ origin: /^https?:\/\/embed\.nicovideo\.jp$/,
+ // jsapi=1&playerId=1 turns on Niconico's postMessage control API.
+ src: function (e) { return withParam(e, 'jsapi=1&playerId=1&autoplay=1'); },
+ play: function (f) {
+ vibePost(f, { sourceConnectorType: 1, playerId: f._vibeNicoId || '1', eventName: 'play' }, 'https://embed.nicovideo.jp');
+ },
+ state: function (d, f) {
+ if (d.playerId != null) f._vibeNicoId = d.playerId; // captured for play()
+ if (d.eventName === 'statusChange' || d.eventName === 'playerStatusChange') {
+ var ps = d.data && d.data.playerStatus;
+ if (ps === 3) return 'paused'; // 3 = pause
+ if (ps === 2) return 'playing'; // 2 = play
+ }
+ return null;
+ }
+ }
+ };
+
+ function vibeProviderFor(embed) {
+ for (var k in VIBE_PROVIDERS) {
+ if (VIBE_PROVIDERS.hasOwnProperty(k) && VIBE_PROVIDERS[k].match.test(embed)) return VIBE_PROVIDERS[k];
+ }
+ return null;
+ }
+
+ function loadProviderWithOverlay(wrap, facadeBtn, embed, prov) {
+ var f = document.createElement('iframe');
+ f.src = prov.src(embed);
+ f.title = 'Current vibe — video';
+ f.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');
+ f.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen');
+ f.setAttribute('allowfullscreen', '');
+ facadeBtn.replaceWith(f);
+
+ var overlay = makePauseOverlay();
+ wrap.appendChild(overlay);
+
+ overlay.addEventListener('click', function () {
+ overlay.hidden = true;
+ prov.play(f);
+ });
+
+ f.addEventListener('load', function () { if (prov.subscribe) prov.subscribe(f); });
+
+ window.addEventListener('message', function (ev) {
+ if (prov.origin && !prov.origin.test(ev.origin)) return;
+ var data = ev.data;
+ if (typeof data === 'string') {
+ try {
+ data = JSON.parse(data);
+ } catch (_) {
+ if (data.indexOf('=') === -1) return; // some players send querystring strings
+ var o = {};
+ data.split('&').forEach(function (kv) {
+ var p = kv.split('=');
+ o[decodeURIComponent(p[0])] = decodeURIComponent(p[1] || '');
+ });
+ data = o;
+ }
+ }
+ if (!data || typeof data !== 'object') return;
+ var st = prov.state(data, f);
+ if (st === 'ended') { setOverlayMode(overlay, 'ended'); overlay.hidden = false; }
+ else if (st === 'paused') { setOverlayMode(overlay, 'paused'); overlay.hidden = false; }
+ else if (st === 'playing') overlay.hidden = true;
+ });
+ }
+
+ // Direct linked media: native <video>/<audio> get the same pause overlay,
+ // driven by the element's own play/pause events (no postMessage). Audio has
+ // no video surface, so it gets a compact (icon + "Paused") variant.
+ function attachNativePauseOverlay(media, compact) {
+ var wrap;
+ if (compact) {
+ // Audio has no positioned container — wrap it so the overlay can sit on top.
+ wrap = media.parentNode;
+ if (!wrap || !wrap.classList.contains('vibe-audio-wrap')) {
+ wrap = document.createElement('div');
+ wrap.className = 'vibe-audio-wrap';
+ media.parentNode.insertBefore(wrap, media);
+ wrap.appendChild(media);
+ }
+ } else {
+ wrap = media.parentNode; // .vibe-embed (position:relative)
+ }
+ if (!wrap || wrap._vibePauseDone) return;
+ wrap._vibePauseDone = true;
+
+ var overlay = makePauseOverlay();
+ if (compact) overlay.classList.add('vibe-facade-compact');
+ wrap.appendChild(overlay);
+
+ overlay.addEventListener('click', function () {
+ overlay.hidden = true;
+ var p = media.play();
+ if (p && p.catch) p.catch(function () {});
+ });
+ // Only after playback has begun — don't cover the not-yet-started media.
+ media.addEventListener('pause', function () { if (!media.ended) { setOverlayMode(overlay, 'paused'); overlay.hidden = false; } });
+ media.addEventListener('play', function () { overlay.hidden = true; });
+ media.addEventListener('ended', function () { setOverlayMode(overlay, 'ended'); overlay.hidden = false; });
+ }
+
+ function scanNativeVibeMedia() {
+ var vids = document.querySelectorAll('.vibe-embed > video');
+ for (var i = 0; i < vids.length; i++) attachNativePauseOverlay(vids[i], false);
+ var auds = document.querySelectorAll('.vibe-media-audio');
+ for (var j = 0; j < auds.length; j++) attachNativePauseOverlay(auds[j], true);
+ }
+ scanNativeVibeMedia();
+ document.addEventListener('DOMContentLoaded', scanNativeVibeMedia);
+
+ document.addEventListener('click', function (ev) {
+ var b = ev.target.closest ? ev.target.closest('.vibe-facade') : null;
+ if (!b || !b.dataset.embed) return;
+ var embed = b.dataset.embed;
+ var wrap = b.parentNode; // .vibe-embed (position:relative)
+
+ // Supported provider → load with pause overlay.
+ var prov = wrap ? vibeProviderFor(embed) : null;
+ if (prov) {
+ loadProviderWithOverlay(wrap, b, embed, prov);
+ return;
+ }
+
+ // Bilibili / anything else → plain autoplay iframe.
+ var src = withParam(embed, 'autoplay=1');
+ var f = document.createElement('iframe');
+ f.src = src;
+ f.title = 'Current vibe — video';
+ f.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');
+ f.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen');
+ f.setAttribute('allowfullscreen', '');
+ b.replaceWith(f);
+ });
+
+})();
diff --git a/assets/js/visit-counter.js b/assets/js/visit-counter.js
new file mode 100644
index 0000000..1715fdb
--- /dev/null
+++ b/assets/js/visit-counter.js
@@ -0,0 +1,141 @@
+/* ============================================================================
+ SillyLaird per-page visit counter (network-wide).
+
+ Injected by nginx before </body> on HTML responses across *.sillylaird.ca
+ (see /etc/nginx/snippets/sillylaird-visit-counter.conf). Also safe to load
+ from site.js / nav.js. Runs once per page load (window.__slVisitCounterInit).
+
+ Storage is localStorage + sessionStorage, keyed by host + path so each page
+ (and each subdomain) keeps its own count. Language variants share a key.
+
+ Also beacons a privacy-friendly hit to /api/hit.php (no IP stored server-side;
+ only host/path, coarse browser·OS label, language, referrer host, local count).
+ ============================================================================ */
+(function () {
+ "use strict";
+
+ if (window.__slVisitCounterInit) return;
+ window.__slVisitCounterInit = true;
+
+ var path = location.pathname || "/";
+ // Skip admin / API / auth / binary endpoints — no counter chrome there.
+ if (/\/(admin|api|login|logout|auth|captcha|relay|meta|submit)(\/|$|\.)/i.test(path)) {
+ return;
+ }
+ if (/\.(xml|json|m3u|pls|css|js|map|ico|png|jpe?g|gif|webp|svg|woff2?|mp3|flac|webm|mp4)(\?|$)/i.test(path)) {
+ return;
+ }
+
+ var HIT_URL = "https://www.sillylaird.ca/api/hit.php";
+
+ function pageKey() {
+ var p = (location.pathname || "/").replace(/\/+$/, "") || "/";
+ // /foo/index.php and /foo/index_jp.php → /foo
+ p = p.replace(/\/index(?:_[a-z]{2})?\.php$/i, "") || "/";
+ // /links_jp.php / links_zh.php → /links.php
+ p = p.replace(/_(jp|zh)\.php$/i, ".php");
+ return location.hostname + p;
+ }
+
+ function ensureStyles() {
+ if (document.getElementById("sl-visit-counter-style")) return;
+ var s = document.createElement("style");
+ s.id = "sl-visit-counter-style";
+ s.textContent =
+ ".site-visit-counter{margin:.5rem 0 0;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;" +
+ "font-size:0.8rem;letter-spacing:0.08em;color:rgba(160,160,170,.9);}" +
+ ".site-visit-counter-digits{color:#39ff14;text-shadow:0 0 6px rgba(57,255,20,.35);}" +
+ "body.light-mode .site-visit-counter{color:rgba(40,40,50,.75);}" +
+ "body.light-mode .site-visit-counter-digits{color:#1a7a12;text-shadow:none;}";
+ (document.head || document.documentElement).appendChild(s);
+ }
+
+ function ensureMarkup() {
+ var digits = document.getElementById("site-visit-digits");
+ if (digits) return digits;
+
+ ensureStyles();
+
+ var wrap = document.createElement("p");
+ wrap.className = "site-visit-counter";
+ wrap.id = "site-visit-counter";
+ wrap.setAttribute("aria-live", "polite");
+ wrap.innerHTML =
+ '<span class="site-visit-counter-label">You are visitor</span> ' +
+ '<span class="site-visit-counter-digits" id="site-visit-digits">------</span>';
+
+ var footerInner = document.querySelector(".site-footer .footer-inner, footer .footer-inner");
+ var footer = document.querySelector("footer.site-footer, footer");
+ if (footerInner) {
+ footerInner.appendChild(wrap);
+ } else if (footer) {
+ footer.appendChild(wrap);
+ } else {
+ wrap.style.cssText =
+ "position:fixed;right:10px;bottom:8px;z-index:40;margin:0;padding:4px 8px;" +
+ "background:rgba(0,0,0,.55);border-radius:4px;backdrop-filter:blur(4px);";
+ document.body.appendChild(wrap);
+ }
+ return document.getElementById("site-visit-digits");
+ }
+
+ function reportHit(localCount, isNew) {
+ var payload = {
+ host: location.hostname,
+ path: location.pathname || "/",
+ local_count: localCount,
+ unique: !!isNew,
+ lang: (navigator.language || "").slice(0, 16),
+ ref: document.referrer || "",
+ };
+ var body = JSON.stringify(payload);
+ try {
+ if (navigator.sendBeacon) {
+ var blob = new Blob([body], { type: "application/json" });
+ if (navigator.sendBeacon(HIT_URL, blob)) return;
+ }
+ } catch (_) {}
+ try {
+ fetch(HIT_URL, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: body,
+ mode: "cors",
+ credentials: "omit",
+ keepalive: true,
+ }).catch(function () {});
+ } catch (_) {}
+ }
+
+ function count() {
+ var el = ensureMarkup();
+ if (!el) return;
+
+ var page = pageKey();
+ var KEY = "sillylaird_visit_count_v2:" + page;
+ var SESS = "sillylaird_visit_sess_v2:" + page;
+ var n = 0;
+ var isNew = false;
+ try {
+ n = parseInt(localStorage.getItem(KEY) || "0", 10) || 0;
+ } catch (_) {}
+ try {
+ if (!sessionStorage.getItem(SESS)) {
+ n += 1;
+ isNew = true;
+ localStorage.setItem(KEY, String(n));
+ sessionStorage.setItem(SESS, "1");
+ }
+ } catch (_) {}
+ el.textContent = String(n).padStart(6, "0");
+
+ // Log every page view server-side (unique flag only when new session for page).
+ reportHit(n, isNew);
+ }
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", count);
+ } else {
+ count();
+ }
+})();