summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-01 10:04:29 +0300
committerPaul Buetow <paul@buetow.org>2026-05-01 10:04:29 +0300
commit6e0352ccf175a4a866ec03a42f3f899150ee093b (patch)
tree0cadd1a05612e6142d5d4511f53fa3f6119e2d04 /internal
parent000888125bbd359f8d0601a465d5347b2017dc2a (diff)
webfonts: bundle dos theme font + document per-theme webfont conventions (task ra)
- dos theme: bundle 'WebPlus IBM VGA 8x16' (.woff) by VileR from the Ultimate Oldschool PC Font Pack v2.2 (CC BY-SA 4.0); replace the never-loaded VT323 reference with @font-face + a self-hosted bitmap face matching the BBS/DOS aesthetic. - templates/embed.go: add ThemeExtraFiles + extend the //go:embed glob with themes/*/*.woff and themes/*/FONT_LICENSE.txt so any theme can ship its own font assets without further code changes; add a NOTE comment block documenting the per-theme directory convention and the //go:embed pattern-matching constraint. - generator/writeThemeAsset: copy any extra files (fonts, license notices) verbatim from the embedded FS into dist/themes/<name>/. - README.md: rewrite the 'Third-party assets' section into a bullet list with self-hosting policy, attribution for the bundled dos font, and a step-by-step recipe for adding new bundled fonts. - dos/FONT_LICENSE.txt: full attribution + CC BY-SA 4.0 notice. Self-hosted, no third-party CDN requests at page load. Amp-Thread-ID: https://ampcode.com/threads/T-019de000-c062-73c5-9afc-c9af422473cf Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/generator/generator.go12
-rw-r--r--internal/generator/templates/embed.go75
-rw-r--r--internal/generator/templates/themes/dos/FONT_LICENSE.txt28
-rw-r--r--internal/generator/templates/themes/dos/WebPlus_IBM_VGA_8x16.woffbin0 -> 22584 bytes
-rw-r--r--internal/generator/templates/themes/dos/theme.css19
5 files changed, 129 insertions, 5 deletions
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index 533a771..aae4c97 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -352,6 +352,18 @@ func writeThemeAsset(dir, name string) error {
return fmt.Errorf("write %s/sounds.json: %w", name, err)
}
+ // Copy any additional per-theme files verbatim (e.g. bundled web fonts
+ // like .woff and their accompanying FONT_LICENSE.txt notices).
+ extras, err := templates.ThemeExtraFiles(name)
+ if err != nil {
+ return fmt.Errorf("list %s extras: %w", name, err)
+ }
+ for _, f := range extras {
+ if err := os.WriteFile(filepath.Join(dir, f.Name), f.Data, 0o644); err != nil {
+ return fmt.Errorf("write %s/%s: %w", name, f.Name, err)
+ }
+ }
+
return nil
}
diff --git a/internal/generator/templates/embed.go b/internal/generator/templates/embed.go
index c5a4b22..a402a71 100644
--- a/internal/generator/templates/embed.go
+++ b/internal/generator/templates/embed.go
@@ -14,9 +14,82 @@ import (
"sort"
)
-//go:embed shell.tmpl shared/*.tmpl shared/shared.css shared/shared.js themes/*/theme.css themes/*/theme.js themes/*/meta.json themes/*/sounds.json
+// Per-theme directory convention (under templates/themes/<name>/):
+//
+// theme.css — required, always present
+// theme.js — required, always present
+// meta.json — required, always present
+// sounds.json — required, always present
+// <Family-Weight>.woff2 — optional, one or more self-hosted web fonts
+// <Family-Weight>.woff — optional, fallback / non-woff2 web fonts
+// FONT_LICENSE.txt — required iff any font file is present;
+// contains attribution + license + source URL
+//
+// All fonts are served from the same origin as the site (no third-party
+// CDNs). The four required files above have dedicated accessors; every
+// other file in a theme directory is shipped verbatim by ThemeExtraFiles
+// and writeThemeAsset.
+//
+// NOTE on the embed glob below: //go:embed requires every pattern to
+// match at least one file, so each font extension is only added once
+// the first theme actually ships such a file. When you introduce the
+// first .woff2 file (or any new extension), append the matching glob
+// here, e.g.: themes/*/*.woff2
+//
+//go:embed shell.tmpl shared/*.tmpl shared/shared.css shared/shared.js themes/*/theme.css themes/*/theme.js themes/*/meta.json themes/*/sounds.json themes/*/*.woff themes/*/FONT_LICENSE.txt
var FS embed.FS
+// themeStandardFiles lists the per-theme files that have dedicated
+// accessor functions and that writeThemeAsset writes by name.
+// Any other file inside a theme directory (fonts, license notices, …)
+// is treated as an "extra" asset and copied verbatim by ThemeExtraFiles.
+var themeStandardFiles = map[string]struct{}{
+ "theme.css": {},
+ "theme.js": {},
+ "meta.json": {},
+ "sounds.json": {},
+}
+
+// ThemeExtraFile represents a per-theme asset (e.g. a .woff font or a
+// FONT_LICENSE.txt) that is not one of the four standard files but should
+// still be copied verbatim into dist/themes/<name>/.
+type ThemeExtraFile struct {
+ // Name is the basename of the file inside the theme directory.
+ Name string
+ // Data is the raw file content.
+ Data []byte
+}
+
+// ThemeExtraFiles returns every file under templates/themes/<name>/ that
+// is NOT one of the four standard files (theme.css, theme.js, meta.json,
+// sounds.json). The returned slice is sorted by name for deterministic
+// output.
+func ThemeExtraFiles(name string) ([]ThemeExtraFile, error) {
+ dir := path.Join("themes", name)
+ entries, err := fs.ReadDir(FS, dir)
+ if err != nil {
+ return nil, fmt.Errorf("list theme dir %q: %w", name, err)
+ }
+
+ out := make([]ThemeExtraFile, 0, len(entries))
+ for _, e := range entries {
+ if e.IsDir() {
+ continue
+ }
+ if _, ok := themeStandardFiles[e.Name()]; ok {
+ continue
+ }
+ b, err := FS.ReadFile(path.Join(dir, e.Name()))
+ if err != nil {
+ return nil, fmt.Errorf("read theme extra %q/%q: %w", name, e.Name(), err)
+ }
+ out = append(out, ThemeExtraFile{Name: e.Name(), Data: b})
+ }
+
+ sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
+ return out, nil
+}
+
// Shell returns the body of shell.tmpl — the single page template used for
// every generated HTML page. Theme-specific markup is injected at gen time
// from the default theme's meta.json (and at runtime by shared.js for any
diff --git a/internal/generator/templates/themes/dos/FONT_LICENSE.txt b/internal/generator/templates/themes/dos/FONT_LICENSE.txt
new file mode 100644
index 0000000..e8c2557
--- /dev/null
+++ b/internal/generator/templates/themes/dos/FONT_LICENSE.txt
@@ -0,0 +1,28 @@
+WebPlus IBM VGA 8x16
+====================
+
+This directory bundles the web font:
+
+ WebPlus_IBM_VGA_8x16.woff
+
+It is taken from "The Ultimate Oldschool PC Font Pack" v2.2 by VileR
+(https://int10h.org/oldschool-pc-fonts/) and is used here unmodified.
+
+The font pack — including this file — is licensed under the Creative
+Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0).
+
+ - License summary: https://creativecommons.org/licenses/by-sa/4.0/
+ - Full license text: https://creativecommons.org/licenses/by-sa/4.0/legalcode
+ - Project page: https://int10h.org/oldschool-pc-fonts/
+ - Readme & terms: https://int10h.org/oldschool-pc-fonts/readme/
+
+Required attribution
+--------------------
+Font: "WebPlus IBM VGA 8x16" from the Ultimate Oldschool PC Font Pack
+ by VileR — https://int10h.org/oldschool-pc-fonts/
+ Licensed under CC BY-SA 4.0.
+
+Because this work is distributed under CC BY-SA 4.0, any modified version
+of the font itself (not the surrounding snonux code) must be released
+under the same license. Embedding/serving the font as part of a web
+page is not considered a derivative work of the font.
diff --git a/internal/generator/templates/themes/dos/WebPlus_IBM_VGA_8x16.woff b/internal/generator/templates/themes/dos/WebPlus_IBM_VGA_8x16.woff
new file mode 100644
index 0000000..466064d
--- /dev/null
+++ b/internal/generator/templates/themes/dos/WebPlus_IBM_VGA_8x16.woff
Binary files differ
diff --git a/internal/generator/templates/themes/dos/theme.css b/internal/generator/templates/themes/dos/theme.css
index 4d33a88..5a90699 100644
--- a/internal/generator/templates/themes/dos/theme.css
+++ b/internal/generator/templates/themes/dos/theme.css
@@ -1,8 +1,19 @@
+ /* Bundled bitmap font: "WebPlus IBM VGA 8x16" by VileR
+ (https://int10h.org/oldschool-pc-fonts/), used unmodified under
+ CC BY-SA 4.0. See FONT_LICENSE.txt in this directory. */
+ @font-face {
+ font-family: 'WebPlus IBM VGA 8x16';
+ src: url('WebPlus_IBM_VGA_8x16.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+ }
:root { --dos-blue:#0000aa; --dos-lblue:#5555ff; --dos-white:#aaaaaa;
--dos-bwhite:#ffffff; --dos-yellow:#ffff55; --dos-cyan:#55ffff;
- --dos-red:#ff5555; --dos-bg:#000088; --dos-black:#000000; }
+ --dos-red:#ff5555; --dos-bg:#000088; --dos-black:#000000;
+ --dos-font:'WebPlus IBM VGA 8x16','VT323','Courier New',monospace; }
* { margin:0; padding:0; box-sizing:border-box; }
- body { font-family:'VT323','Courier New',monospace; background:var(--dos-blue);
+ body { font-family:var(--dos-font); background:var(--dos-blue);
color:var(--dos-bwhite); overflow:hidden; height:100vh; height:100dvh; font-size:18px; }
#three-canvas { position:fixed; top:0; left:0; width:100%; height:100%; z-index:1; }
.overlay { position:relative; z-index:10; height:100vh; height:100dvh; display:flex; flex-direction:column; }
@@ -58,12 +69,12 @@
border:2px solid var(--dos-yellow); padding:24px;
box-shadow:0 0 20px rgba(85,85,255,0.4); }
.modal-close { float:right; background:var(--dos-white); border:2px outset var(--dos-bwhite);
- color:var(--dos-blue); font-family:'VT323','Courier New',monospace;
+ color:var(--dos-blue); font-family:var(--dos-font);
font-size:1rem; cursor:pointer; padding:2px 8px; }
.modal-close:hover { background:var(--dos-blue); color:var(--dos-bwhite);
border-style:inset; }
@media(max-width:640px) { .nav-hints{display:none;} header{padding:6px 12px;} .content{padding:8px 12px;} .modal-inner{padding:20px 14px} .transmit-btn{padding-top:11px;padding-bottom:11px;min-height:44px} .modal-close{padding:10px 14px;min-width:44px;min-height:44px;text-align:center} }
- [data-sno-theme="dos"] .splash-overlay { background:var(--dos-blue); font-family:'VT323','Courier New',monospace; }
+ [data-sno-theme="dos"] .splash-overlay { background:var(--dos-blue); font-family:var(--dos-font); }
[data-sno-theme="dos"] .splash-inner { position:relative; z-index:1; }
[data-sno-theme="dos"] .splash-title {
font-size:clamp(1.4rem,4.5vw,2rem); color:var(--dos-bwhite);