themes-y2k/BUILD_REPORT.md
Alex Dunmow 49f9c90589 initial: theme plugin y2k
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously
an unversioned directory inside ~/src/blockninja-themes/y2k.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 14:11:46 +08:00

10 KiB
Raw Blame History

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 59 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.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, <fallback-stack>). 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.72.8 (make rebuild, log scrape), §5.7 (no console errors at the rendered URL), §6.16.7 (Lighthouse / computed-style assertions on a running site), §7.17.6 (responsive viewport checks), §8.* (rendering each block in three states in the browser), §9.7 (admin-replace round-trip), §10.210.4 (Litmus / Gmail / Apple Mail / Outlook 365 previews), §11.411.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 <canvas data-y2k-waveform> 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 <html> 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)