# Y2K theme — build report Implementation pass: 2026-06-06. Reference style: templ (gotham-shaped, per spec §11 "Tech choice"). ## What landed ### Registration (`registration.go`, `register.go`) - `Registration` exported, `Name: "y2k"`, `Version: plugin.ParseModVersion(pluginModBytes)`. - `tr.RegisterSystemTemplate(Key: "y2k", ...)` — exactly one call. - `tr.RegisterPageTemplate("y2k", ...)` called four times with slots verbatim: - `default` → `["header", "main", "footer"]` - `landing` → `["hero", "marquee", "main", "cta", "footer"]` - `article` → `["header", "main", "aside", "footer"]` - `full-width` → `["header", "main", "footer"]` - `br.LoadSchemasFromFS(Schemas())` invoked once, before any `br.Register(...)`. - 10 theme blocks registered (unqualified keys, `Source: "y2k"`): `chrome_navbar`, `metaball_hero`, `waveform_player`, `marquee`, `tracklist`, `merch_card`, `webring_badge`, `glitter_divider`, `footer_chrome`, `nft_gallery`. - 4 overrides registered: `heading`, `text`, `button`, `image` via `br.RegisterTemplateOverride("y2k", ...)`. - `tr.RegisterEmailWrapper("y2k", Y2KEmailWrapper)` wired. - `DefaultMasterPages()` seeds `y2k:default-master` (applies to `default`, `article`) and `y2k:landing-master` (applies to `landing`, `full-width`) with the exact block order from spec §"Master pages". ### Plugin metadata (`plugin.mod`) - `name = "y2k"`, `display_name = "Y2K"` (3 chars, ≤40), `scope = "@themes"`. - `kind = "theme"` (per global rule, not "plugin"). - `categories = ["templates", "media"]` — both whitelisted. - `tags` — 8 entries (within the 5–9 UAT range). - `version = "0.1.0"`. - `[compatibility] block_core = ">=0.11.0 <0.12.0"` verbatim. - Description 157 chars (≤240). ### Schemas (`schemas/*.schema.json`) - 10 JSON Schema files, all draft-07. - Property names match `content["…"]` reads in the Go files one-to-one (no orphans either direction; verified by script during build). - All `x-editor` values are members of the whitelist `{text, richtext, media, color, select, number, slug, textarea, array, collection, bucket-picker, menu-select, template-select, link}`. ### Presets (`presets.json`) - Exactly 3 entries with `id` values `chrome-dream`, `cd-rom-after-hours`, `bubblegum-trapper`. - `chrome-dream` declares `mode: "both"` with both `lightColors` and `darkColors` blocks (38 tokens across both). - `cd-rom-after-hours` declares `mode: "dark"` with `darkColors` only. - `bubblegum-trapper` declares `mode: "light"` with `lightColors` only. - All 19 tokens present per colour block. - Every value matches the regex `^\d+ \d+% \d+%$` (HSL triples, no `hsl()` wrapper). Verified via Python regex check. - Values copied verbatim from spec §4 tables. ### Fonts (`fonts.json`, `RECOMMENDED_FONTS.md`) - `fonts.json` is `[]` per the wave-1 policy in `themes/docs/FONTS.md`. - `RECOMMENDED_FONTS.md` lists the spec §5 fonts as Google Fonts picker recommendations (Inter Tight via Google; VT323 via Google as the open-licensed fallback for Stretch Pro; Departure Mono and Stretch Pro as admin uploads). - All `font-family` references in templates and CSS go through `var(--font-heading|body|mono, )`. The headline heavy fallback stack is `"Stretch Pro", "VT323", "Courier New", monospace`. ### CSS (`css.go` via `CSSManifest.InputCSSAppend`) - Declares `--chrome-1..4`, `--mesh-a/b/c`, `--bevel-light/dark` custom properties on `:root` and `.dark` (UAT 13.2, 13.3, 13.4). - Defines exactly one `@keyframes marquee-x`, one `@keyframes sparkle`, one `@keyframes metaball-morph` block (UAT 13.5). - Provides utility classes: - `.y2k-chrome-bg` (layered linear gradient through `--chrome-1..4`). - `.y2k-mesh-bg` (radial-gradient mesh through `--mesh-a/b/c`). - `.y2k-bevel` (`border: 2px solid hsl(var(--border))` + inset shadows using `--bevel-light/dark`; UAT 13.4). - `.y2k-button` (3D plastic bevel with `border-style: solid`, `border-width: 2px`, `box-shadow` containing `inset` — UAT 13.9). - `.y2k-marquee` + `.y2k-marquee-track` with `overflow: hidden` and the `marquee-x` animation; `animation-play-state: paused` on hover/focus (UAT 13.10, 6.7). - `.y2k-webring-badge` at the canonical `width: 88px; height: 31px` (UAT 13.11). - `.y2k-foil::after` with `conic-gradient(` and `filter: hue-rotate(` that activates on `:hover` (UAT 13.12). - `.y2k-heading`, `.y2k-text`, `.y2k-image-frame`, `.y2k-sparkle`, `.y2k-metaball`, `.y2k-glow`. - Honors `prefers-reduced-motion: reduce` by disabling marquee, sparkle, and metaball-morph animations (UAT 6.6). - All colour references go through `hsl(var(--token))`; no hardcoded hex/rgb appears in any `.go` or `.templ` file outside the email wrapper (which uses hex fallbacks that translate the chrome-dream dark preset for Gmail/Outlook compatibility, exactly as gotham does). ### Email wrapper - `tr.RegisterEmailWrapper("y2k", Y2KEmailWrapper)` registered exactly once. - 600px centred chrome-bordered card on a near-black background (`#0c0820`-equivalent sourced from `emailCtx.Colors.Background` when set, otherwise the chrome-dream dark token value). - Gradient header bar uses `linear-gradient(90deg, primary, secondary)`. - Inter Tight body with system fallback stack. - Marquee footer pill renders the same items as the in-page marquee. - Plain-text fallback is deferred to the CMS-default stripping (the wrapper receives `body` already rendered; the marquee items are duplicated into a visible string so a plain-text view still surfaces them). ## Build output ``` $ cd /home/alex/src/blockninja/themes/y2k && go mod tidy (exit 0) $ cd /home/alex/src/blockninja/themes/y2k && /home/alex/go/bin/templ generate (✓) Complete [ updates=16 duration=10ms ] $ cd /home/alex/src/blockninja/themes/y2k && make CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o y2k.so . (exit 0, no warnings) $ ls -la y2k.so 21,543,872 bytes (≈21 MB) ELF 64-bit LSB shared object, stripped ``` `make` exits 0 with zero `warning` lines in stdout/stderr (UAT 2.1). ## Safety check ``` $ /tmp/check-safety . --plugin-dir /home/alex/src/blockninja/themes/y2k (... 22 checks, all OK or SKIP for "no frontend sources" ...) exit=0 ``` NOTE: the task brief references `cd ~/src/blockninja/backend && go run ./cmd/check-safety …`. The actual command path on this host is `~/src/blockninja/check-safety/` (no `/backend/cmd/` subpath). The invocation above is the host-local equivalent and exits 0. Specifically verified: - Check 21 (presets.json validation): OK — single preset file validated. - Check 22 (no hand-rolled HTML sanitization): OK. - No `git.dev.alexdunmow.com/block/cms/...` import boundary violations. - No `^replace ` directives in `go.mod`. - `block/core v0.11.1` pinned to match `cms/backend/go.mod`. ## Open items / deferred The following spec/UAT requirements are intentionally deferred to a later wave and would block sign-off on the running container but do not block this build pass: - **Bundled woff2 files**. Per `themes/docs/FONTS.md` wave-1 policy this pass ships `fonts.json = []`. UAT §11 file-existence checks pass trivially. Wave-2 will commission/licence Stretch Pro and Departure Mono and bundle them. - **`LICENSES.md`** — explicitly skipped per FONTS.md wave-1 policy ("No `LICENSES.md` needed in this pass"). - **Marketplace screenshots** (`marketplace/screenshots/*.png`, 1440×900). Not produced in this pass — gated by a running container with deployed CSS. - **Demo content seed** ("Static Lagoon" fictional artist with 6 tracks, 4 merch items, 2 zine articles, 5 webring entries). Not produced — the data plane is out of scope for the .so build pass. - **Container-level UAT items**: §2.7–2.8 (`make rebuild`, log scrape), §5.7 (no console errors at the rendered URL), §6.1–6.7 (Lighthouse / computed-style assertions on a running site), §7.1–7.6 (responsive viewport checks), §8.* (rendering each block in three states in the browser), §9.7 (admin-replace round-trip), §10.2–10.4 (Litmus / Gmail / Apple Mail / Outlook 365 previews), §11.4–11.5 (network-tab font 200 checks, FOUT trace), §12.* (marketplace assets, demo seed), §13.1, 13.6 (computed-style assertions on rendered pages), §14.* (three-theme install regression), §15.* (three named reviewer sign-offs). - **Waveform peak-data source**. The block ships with a placeholder `` element that the CMS' client runtime can populate via WebAudio decode (deferred to v0.2 per spec open question). - **Cursor-trail sparkle script**. Only the CSS classes are shipped; the document-level cursor-trail script is deferred and gated by `data-editor-mode` on `` per the spec's open question. - **Mobile menu drawer JS**. The chrome navbar exposes the `[aria-controls]` toggle button required by UAT §7.4 but the drawer-open behaviour is owned by the host menu script. ## File inventory ``` y2k/ ├── BUILD_REPORT.md (this file) ├── Makefile ├── RECOMMENDED_FONTS.md ├── assets/ │ └── placeholder.txt ├── button_override.{go,templ,_templ.go} ├── chrome_navbar.{go,templ,_templ.go} ├── css.go ├── email_wrapper.{go,templ,_templ.go} ├── embed.go ├── fonts.json ├── footer_chrome.{go,templ,_templ.go} ├── glitter_divider.{go,templ,_templ.go} ├── go.mod ├── go.sum ├── heading_override.{go,templ,_templ.go} ├── helpers.go ├── image_override.{go,templ,_templ.go} ├── marquee.{go,templ,_templ.go} ├── merch_card.{go,templ,_templ.go} ├── metaball_hero.{go,templ,_templ.go} ├── nft_gallery.{go,templ,_templ.go} ├── plugin.mod ├── presets.json ├── register.go ├── registration.go ├── schemas/ │ ├── chrome_navbar.schema.json │ ├── footer_chrome.schema.json │ ├── glitter_divider.schema.json │ ├── marquee.schema.json │ ├── merch_card.schema.json │ ├── metaball_hero.schema.json │ ├── nft_gallery.schema.json │ ├── tracklist.schema.json │ ├── waveform_player.schema.json │ └── webring_badge.schema.json ├── template.{templ,_templ.go} ├── text_override.{go,templ,_templ.go} ├── tracklist.{go,templ,_templ.go} ├── waveform_player.{go,templ,_templ.go} ├── webring_badge.{go,templ,_templ.go} └── y2k.so (21 MB) ```