diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-01 10:04:29 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-01 10:04:29 +0300 |
| commit | 6e0352ccf175a4a866ec03a42f3f899150ee093b (patch) | |
| tree | 0cadd1a05612e6142d5d4511f53fa3f6119e2d04 /internal | |
| parent | 000888125bbd359f8d0601a465d5347b2017dc2a (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.go | 12 | ||||
| -rw-r--r-- | internal/generator/templates/embed.go | 75 | ||||
| -rw-r--r-- | internal/generator/templates/themes/dos/FONT_LICENSE.txt | 28 | ||||
| -rw-r--r-- | internal/generator/templates/themes/dos/WebPlus_IBM_VGA_8x16.woff | bin | 0 -> 22584 bytes | |||
| -rw-r--r-- | internal/generator/templates/themes/dos/theme.css | 19 |
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 Binary files differnew file mode 100644 index 0000000..466064d --- /dev/null +++ b/internal/generator/templates/themes/dos/WebPlus_IBM_VGA_8x16.woff 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); |
