From b4899f8a322c5df78731e3c5b6d583ec0835d129 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 10 Apr 2026 10:29:33 +0300 Subject: Release v0.1.1 Per-theme Web Audio presets; pagination footer bar with reduced height; brutalist splash label tweak; doc updates. Made-with: Cursor --- internal/generator/doc.go | 2 + internal/generator/generator.go | 26 ++--- internal/generator/generator_test.go | 19 +++- internal/generator/shared.go | 74 +++++++++----- internal/generator/theme_aurora.go | 11 ++- internal/generator/theme_brutalist.go | 12 ++- internal/generator/theme_glass.go | 11 ++- internal/generator/theme_matrix.go | 10 +- internal/generator/theme_minimal.go | 11 ++- internal/generator/theme_neon.go | 11 ++- internal/generator/theme_ocean.go | 11 ++- internal/generator/theme_paper.go | 11 ++- internal/generator/theme_retro.go | 10 +- internal/generator/theme_sounds.go | 179 ++++++++++++++++++++++++++++++++++ internal/generator/theme_synthwave.go | 11 ++- internal/generator/theme_terminal.go | 10 +- internal/version.go | 2 +- 17 files changed, 347 insertions(+), 74 deletions(-) create mode 100644 internal/generator/theme_sounds.go diff --git a/internal/generator/doc.go b/internal/generator/doc.go index b22ede6..ad974ad 100644 --- a/internal/generator/doc.go +++ b/internal/generator/doc.go @@ -13,6 +13,8 @@ // {{template "navscript" .}}. // - shared.go — navDefs: shared {{define}} blocks merged at parse time with // the chosen theme so a single html/template parse sees every name. +// - theme_sounds.go — Per-theme Web Audio parameters (splash arpeggio, nav blip, +// modal open/close); embedded in pages as ThemeSoundsJSON for navscript. // - templates.go — Short pointer: where templates and registry live. // // Dependency direction: themes and shared nav templates are composed only for diff --git a/internal/generator/generator.go b/internal/generator/generator.go index e880ba3..3d1a441 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -17,11 +17,12 @@ import ( // pageData holds the template variables for a single HTML page. type pageData struct { - Posts []postView - PrevPage string // URL of the newer page, empty if none - NextPage string // URL of the older page, empty if none - PrevPageJSON template.JS - NextPageJSON template.JS + Posts []postView + PrevPage string // URL of the newer page, empty if none + NextPage string // URL of the older page, empty if none + PrevPageJSON template.JS + NextPageJSON template.JS + ThemeSoundsJSON template.JS // Web Audio preset for this theme (splash + nav) } // postView is a render-friendly representation of a post for the HTML template. @@ -121,7 +122,7 @@ const indexPageNavURL = "index.html?splash=0" // writePage renders one HTML page and writes it to cfg.OutputDir. func writePage(tmpl *template.Template, posts []*post.Post, pageIndex, totalPages int, cfg *config.Config) error { - data := buildPageData(posts, pageIndex, totalPages) + data := buildPageData(posts, pageIndex, totalPages, cfg.Theme) filename := pageFilename(pageIndex) path := filepath.Join(cfg.OutputDir, filename) @@ -140,7 +141,7 @@ func writePage(tmpl *template.Template, posts []*post.Post, pageIndex, totalPage } // buildPageData constructs the template data for a single page. -func buildPageData(posts []*post.Post, pageIndex, totalPages int) pageData { +func buildPageData(posts []*post.Post, pageIndex, totalPages int, theme string) pageData { views := make([]postView, len(posts)) for i, p := range posts { views[i] = postView{ @@ -166,11 +167,12 @@ func buildPageData(posts []*post.Post, pageIndex, totalPages int) pageData { } return pageData{ - Posts: views, - PrevPage: prevPage, - NextPage: nextPage, - PrevPageJSON: jsonStringOrNull(prevPage), - NextPageJSON: jsonStringOrNull(nextPage), + Posts: views, + PrevPage: prevPage, + NextPage: nextPage, + PrevPageJSON: jsonStringOrNull(prevPage), + NextPageJSON: jsonStringOrNull(nextPage), + ThemeSoundsJSON: themeSoundsJSON(theme), } } diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go index cfab15e..0960d87 100644 --- a/internal/generator/generator_test.go +++ b/internal/generator/generator_test.go @@ -86,6 +86,23 @@ func TestJSONStringOrNull(t *testing.T) { } } +func TestThemeSoundPresetsMatchRegistry(t *testing.T) { + t.Parallel() + for name := range themeRegistry { + if _, ok := themeSoundPresets[name]; !ok { + t.Errorf("theme %q has no sound preset in themeSoundPresets", name) + } + } +} + +func TestThemeSoundsJSONNonEmpty(t *testing.T) { + t.Parallel() + j := themeSoundsJSON("neon") + if len(j) < 50 { + t.Fatalf("themeSoundsJSON too short: %q", j) + } +} + func TestFormatPostTime(t *testing.T) { t.Parallel() @@ -161,7 +178,7 @@ func TestBuildPageData_navLinks(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - data := buildPageData([]*post.Post{p}, tt.pageIndex, tt.totalPages) + data := buildPageData([]*post.Post{p}, tt.pageIndex, tt.totalPages, "neon") if data.PrevPage != tt.wantPrev { t.Fatalf("PrevPage=%q; want %q", data.PrevPage, tt.wantPrev) } diff --git a/internal/generator/shared.go b/internal/generator/shared.go index 583851c..b10de47 100644 --- a/internal/generator/shared.go +++ b/internal/generator/shared.go @@ -6,7 +6,7 @@ package generator // 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 +// - "navscript" — keyboard navigation + Web Audio; splash/nav/modal sounds from themeSoundsJSON (per theme) // // Each theme calls {{template "splashGate"}}, {{template "navhints" .}}, {{template "navmodal" .}}, // and {{template "navscript" .}} at the appropriate points in its HTML. @@ -62,9 +62,15 @@ 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 */ +/* Pagination: newer + older in a footer bar (below scrollable posts, like the header) */ .page-nav-dual { display:flex; justify-content:center; align-items:center; flex-wrap:wrap; gap:clamp(16px,4vw,48px); } +/* Flex column layout: let #post-content shrink so overflow-y scrolls; footer stays visible */ +#post-content.content { min-height:0; } +.page-nav-footer { flex-shrink:0; width:100%; box-sizing:border-box; } +.page-nav-footer .page-nav { margin:0; } +/* ~Half-height footer bar vs default .page-nav padding */ +.page-nav-footer .page-nav a { padding-top:4px; padding-bottom:4px; } /* 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) */ @@ -104,6 +110,12 @@ html.sno-splash-skip #splash-overlay { display:none !important; visibility:hidde {{define "navscript"}}