summaryrefslogtreecommitdiff
path: root/internal/generator/shared.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-10 10:23:20 +0300
committerPaul Buetow <paul@buetow.org>2026-04-10 10:23:20 +0300
commitf40fee44e8f256328ca1419863b5441123a1014e (patch)
treeb3a5eabc0b8ac0801240544392edaadf5a6d8ac4 /internal/generator/shared.go
parentbc45b7af3bc93ccd3e4359d29e93417d0af407e1 (diff)
Release v0.1.0v0.1.0
Splash: skip via ?splash=0 on pagination to index; frosted panel and vignette for readable copy; brighter hint/tag colors. Pagination links only at bottom of each page. Tests updated for prev href to index. Made-with: Cursor
Diffstat (limited to 'internal/generator/shared.go')
-rw-r--r--internal/generator/shared.go139
1 files changed, 136 insertions, 3 deletions
diff --git a/internal/generator/shared.go b/internal/generator/shared.go
index a34c5a5..583851c 100644
--- a/internal/generator/shared.go
+++ b/internal/generator/shared.go
@@ -1,15 +1,48 @@
package generator
// navDefs is appended to every theme template when parsing.
-// It defines three named sub-templates shared across all themes:
+// It defines named sub-templates shared across all themes:
+// - "splashGate" — synchronous script: first child of <body>; sets html.sno-splash-skip when
+// splash should not run (?splash=0, not index.html, or Referer from same-site index/pageN).
// - "navhints" — keyboard shortcut hint bar HTML
// - "navmodal" — full-screen expanded-post modal HTML + image-sizing CSS
// - "navscript" — keyboard navigation JavaScript with distinct sounds per action
//
-// Each theme calls {{template "navhints" .}}, {{template "navmodal" .}}, and
-// {{template "navscript" .}} at the appropriate points in its HTML.
+// Each theme calls {{template "splashGate"}}, {{template "navhints" .}}, {{template "navmodal" .}},
+// and {{template "navscript" .}} at the appropriate points in its HTML.
// All theme-specific CSS lives in each theme file so themes stay self-contained.
const navDefs = `
+{{define "splashGate"}}
+<script>
+(function(){
+ try {
+ var sp = new URLSearchParams(location.search);
+ if (sp.get('splash') === '0') {
+ document.documentElement.classList.add('sno-splash-skip');
+ return;
+ }
+ } catch (_) {}
+ var parts = location.pathname.split('/').filter(function(s) { return s.length; });
+ var seg = (parts.length ? parts[parts.length - 1] : '').toLowerCase();
+ var onIndex = (!seg || seg === 'index.html');
+ var ref = document.referrer;
+ function refIsSameSiteBlogPage(url) {
+ if (!url) return false;
+ try {
+ var ru = new URL(url), cu = new URL(location.href);
+ if (ru.origin !== cu.origin) return false;
+ var rp = ru.pathname.split('/').filter(function(s) { return s.length; });
+ var rs = (rp.length ? rp[rp.length - 1] : '').toLowerCase();
+ if (rs === 'index.html' || rs === '') return true;
+ if (/^page\d+\.html$/.test(rs)) return true;
+ return false;
+ } catch (_) { return false; }
+ }
+ if (!onIndex || refIsSameSiteBlogPage(ref)) document.documentElement.classList.add('sno-splash-skip');
+})();
+</script>
+{{end}}
+
{{define "navhints"}}
<div class="nav-hints" aria-label="keyboard shortcuts">
<span><kbd>j</kbd><kbd>k</kbd> or <kbd>↑</kbd><kbd>↓</kbd> select post</span>
@@ -29,6 +62,37 @@ const navDefs = `
.post-modal { background:rgba(0,0,0,0.55) !important; backdrop-filter:blur(6px) !important; }
/* Content area max-width across all themes */
.overlay { max-width:1200px; margin-left:auto; margin-right:auto; }
+/* Pagination: newer + older side by side at the bottom of the feed */
+.page-nav-dual { display:flex; justify-content:center; align-items:center; flex-wrap:wrap;
+ gap:clamp(16px,4vw,48px); }
+/* Host note under the site subtitle (all themes) */
+.logo-host { font-size:0.65rem; opacity:0.55; margin-top:4px; letter-spacing:0.3px; line-height:1.3; }
+/* Atom feed link in header (paired with transmit in .nav) */
+.nav { display:flex; align-items:center; gap:clamp(10px,2.2vw,20px); flex-wrap:wrap; justify-content:flex-end; }
+a.header-feed-link { font-size:0.8rem; text-decoration:none; opacity:0.82; letter-spacing:0.04em; white-space:nowrap; }
+a.header-feed-link:hover { opacity:1; text-decoration:underline; }
+/* Full-viewport splash (theme-specific colours/animation on each .splash-THEMENAME) */
+#splash-overlay { position:fixed; inset:0; z-index:2000; display:flex; flex-direction:column; align-items:center;
+ justify-content:center; text-align:center; padding:max(16px,4vw); box-sizing:border-box; cursor:pointer;
+ transition:opacity .55s ease, visibility .55s ease, transform .55s ease; }
+#splash-overlay.splash--dismissed { opacity:0 !important; visibility:hidden !important;
+ pointer-events:none !important; transform:scale(1.02); }
+#splash-overlay:focus { outline:2px solid rgba(255,255,255,0.35); outline-offset:4px; }
+/* Vignette over WebGL so 3D motion does not overpower the edges */
+#splash-overlay::before { content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
+ background: radial-gradient(ellipse 92% 82% at 50% 42%, rgba(0,0,0,0) 32%, rgba(0,0,0,0.26) 68%, rgba(0,0,0,0.48) 100%); }
+.splash-title { font-weight:700; letter-spacing:0.06em; line-height:1.15; }
+.splash-tag { margin-top:0.35rem; font-size:0.76rem; letter-spacing:0.2em; text-transform:uppercase; }
+.splash-hint { margin-top:1.25rem; font-size:0.72rem; letter-spacing:0.12em; }
+#splash-overlay .splash-gl-canvas { position:absolute; inset:0; width:100%; height:100%; display:block; z-index:0; pointer-events:none; }
+/* Frosted panel so title/tag/hint stay readable over busy shaders */
+#splash-overlay .splash-inner { position:relative; z-index:2; max-width:min(520px,92vw);
+ padding: clamp(1.15rem, 3.2vw, 1.75rem) clamp(1.3rem, 3.8vw, 1.95rem); border-radius:14px;
+ background: rgba(0, 0, 0, 0.58); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
+ box-shadow: 0 14px 44px rgba(0, 0, 0, 0.58), inset 0 1px 0 rgba(255, 255, 255, 0.07); }
+#splash-overlay.splash-brutalist .splash-inner.splash-frame {
+ padding: clamp(1.4rem, 4.5vw, 2.25rem) clamp(1.1rem, 3.5vw, 1.9rem); background: rgba(0, 0, 0, 0.78); }
+html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidden !important; pointer-events:none !important; }
</style>
<div class="post-modal" id="post-modal">
<div class="modal-inner">
@@ -40,6 +104,67 @@ const navDefs = `
{{define "navscript"}}
<script>
+ (function splashSetup() {
+ var el = document.getElementById('splash-overlay');
+ if (!el) return;
+ if (document.documentElement.classList.contains('sno-splash-skip')) {
+ if (typeof window._snonuxSplashWebGLCleanup === 'function') {
+ try { window._snonuxSplashWebGLCleanup(); } catch (_) {}
+ window._snonuxSplashWebGLCleanup = null;
+ }
+ el.remove();
+ return;
+ }
+ var splashAudioCtx = null;
+ var splashChimePlayed = false;
+ // Soft major arpeggio (G4 → C5 → E5 → G5); works once autopolicy allows audio.
+ function playSplashChime() {
+ if (splashChimePlayed) return;
+ try {
+ if (!splashAudioCtx) {
+ splashAudioCtx = new (window.AudioContext || window.webkitAudioContext)();
+ }
+ var ctx = splashAudioCtx;
+ function ring() {
+ splashChimePlayed = true;
+ var now = ctx.currentTime;
+ var freqs = [392, 523.25, 659.25, 783.99];
+ var i, osc, g, t0;
+ for (i = 0; i < freqs.length; i++) {
+ osc = ctx.createOscillator();
+ g = ctx.createGain();
+ osc.connect(g);
+ g.connect(ctx.destination);
+ osc.type = 'sine';
+ osc.frequency.value = freqs[i];
+ t0 = now + i * 0.075;
+ g.gain.setValueAtTime(0, t0);
+ g.gain.linearRampToValueAtTime(0.1, t0 + 0.028);
+ g.gain.exponentialRampToValueAtTime(0.001, t0 + 0.52);
+ osc.start(t0);
+ osc.stop(t0 + 0.55);
+ }
+ }
+ ctx.resume().then(ring).catch(function() {});
+ } catch (_) {}
+ }
+ playSplashChime();
+ el.addEventListener('pointerdown', function() { playSplashChime(); }, { passive: true });
+ function dismiss() {
+ if (el.classList.contains('splash--dismissed')) return;
+ playSplashChime();
+ if (typeof window._snonuxSplashWebGLCleanup === 'function') {
+ try { window._snonuxSplashWebGLCleanup(); } catch (_) {}
+ window._snonuxSplashWebGLCleanup = null;
+ }
+ el.classList.add('splash--dismissed');
+ setTimeout(function() { if (el.parentNode) el.parentNode.removeChild(el); }, 600);
+ }
+ el.addEventListener('click', function(e) { e.preventDefault(); dismiss(); });
+ window._snonuxDismissSplash = dismiss;
+ el.focus({ preventScroll: true });
+ })();
+
// === KEYBOARD NAVIGATION ===
// j / ArrowDown → next post k / ArrowUp → previous post
// h / ArrowLeft → previous page l / ArrowRight → next page
@@ -121,6 +246,14 @@ const navDefs = `
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
+ var splash = document.getElementById('splash-overlay');
+ if (splash && !splash.classList.contains('splash--dismissed')) {
+ if (e.key === 'Enter' || e.key === ' ' || e.key === 'Escape') {
+ e.preventDefault();
+ if (window._snonuxDismissSplash) window._snonuxDismissSplash();
+ }
+ return;
+ }
if (document.getElementById('post-modal').classList.contains('active')) {
if (e.key === 'Escape') { closeModal(); e.preventDefault(); }
return;