themes-scifi-clean/register.go
Alex Dunmow 96b87b3e81 initial: theme plugin scifi-clean
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously
an unversioned directory inside ~/src/blockninja-themes/scifi-clean.

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

198 lines
6.4 KiB
Go

package main
import (
"context"
"github.com/a-h/templ"
"git.dev.alexdunmow.com/block/core/blocks"
"git.dev.alexdunmow.com/block/core/plugin"
"git.dev.alexdunmow.com/block/core/templates"
)
// wrap adapts a templ-returning render function to templates.TemplateFunc.
// templ.Component already implements templates.HTMLComponent via Render.
func wrap(f func(ctx context.Context, doc map[string]any) templ.Component) templates.TemplateFunc {
return func(ctx context.Context, doc map[string]any) templates.HTMLComponent {
return f(ctx, doc)
}
}
// Register is the plugin entry point. It registers the system template, the
// four page templates, the six theme-specific blocks, the three overrides,
// and the email wrapper.
//
// IMPORTANT: schemas are loaded BEFORE any br.Register(...) call — this is a
// hard rule (CLAUDE.md, UAT §3) so the block registry binds content schemas
// at registration time.
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
// --- System template ---
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
Key: "scifi-clean",
Title: "Sci-Fi Clean",
Description: "Minimalist futurist theme for aerospace, robotics, and R&D — off-white surfaces, technical blue, signal-orange accents, mono numerals.",
})
// --- Page templates (spec §6) ---
if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{
Key: "default",
Title: "Default",
Description: "Standard cleanroom layout with header rail, main column, and footer instrument panel.",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderScifiClean)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{
Key: "landing",
Title: "Landing",
Description: "Hero diagram + spec strip + CTA banner.",
Slots: []string{"hero", "specs", "main", "cta", "footer"},
}, wrap(RenderScifiCleanLanding)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{
Key: "article",
Title: "Article",
Description: "Narrow column with rail metadata and figure margin.",
Slots: []string{"header", "rail", "main", "footer"},
}, wrap(RenderScifiCleanArticle)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{
Key: "full-width",
Title: "Full Width",
Description: "Edge-to-edge schematic surface for diagrams and dashboards.",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderScifiCleanFullWidth)); err != nil {
return err
}
// --- Load JSON Schemas BEFORE any block registration ---
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
return err
}
// --- Theme-specific blocks (spec §8) ---
br.Register(TechSpecBlockMeta, TechSpecBlock)
br.Register(DiagramCaptionBlockMeta, DiagramCaptionBlock)
br.Register(MissionStatBlockMeta, MissionStatBlock)
br.Register(StatusBarBlockMeta, StatusBarBlock)
br.Register(FooterBlockMeta, FooterBlock)
br.Register(SchematicHeroBlockMeta, SchematicHeroBlock)
// --- Built-in overrides (spec §9) ---
br.RegisterTemplateOverride("scifi-clean", "heading", ScifiHeadingBlock)
br.RegisterTemplateOverride("scifi-clean", "text", ScifiTextBlock)
br.RegisterTemplateOverride("scifi-clean", "button", ScifiButtonBlock)
// --- Email wrapper (spec §10) ---
tr.RegisterEmailWrapper("scifi-clean", ScifiEmailWrapper)
return nil
}
// DefaultMasterPages returns the two master pages that Sci-Fi Clean provisions
// on first plugin load (spec §7, UAT §9).
//
// - scifi-clean:default-master → attached to `default`, `article`, `full-width`
// - scifi-clean:landing-master → attached to `landing`, pre-populates `hero`
// with a mission_stat and `specs` with a tech_spec.
func DefaultMasterPages() []plugin.MasterPageDefinition {
defaultMaster := plugin.MasterPageDefinition{
Key: "scifi-clean:default-master",
Title: "Sci-Fi Clean Default Master",
PageTemplates: []string{"default", "article", "full-width"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "navbar",
Title: "Mission Header",
Content: map[string]any{"menuName": "main"},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "scifi-clean:status_bar",
Title: "System Status Rail",
Content: map[string]any{"showClock": true, "showBuild": true, "callsign": "SCF-CLN"},
Slot: "header",
SortOrder: 10,
},
{
BlockKey: "slot",
Title: "Main Content",
Content: map[string]any{"slotName": "main", "placeholder": "Page payload"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "scifi-clean:footer",
Title: "Instrument Footer",
Content: map[string]any{"showSignup": true, "callsign": "SCF-CLN"},
Slot: "footer",
SortOrder: 0,
},
},
}
landingMaster := plugin.MasterPageDefinition{
Key: "scifi-clean:landing-master",
Title: "Sci-Fi Clean Landing Master",
PageTemplates: []string{"landing"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "scifi-clean:status_bar",
Title: "System Status Rail",
Content: map[string]any{"showClock": true, "showBuild": true, "callsign": "SCF-CLN"},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "scifi-clean:mission_stat",
Title: "Hero Mission Stat",
Content: map[string]any{
"metric": "Apogee",
"value": "412",
"unit": "km",
"delta": "+12",
"trend": "up",
},
Slot: "hero",
SortOrder: 0,
},
{
BlockKey: "scifi-clean:tech_spec",
Title: "Hero Tech Spec",
Content: map[string]any{
"caption": "Launch envelope",
"rows": []any{
map[string]any{"label": "Thrust", "value": "1.42", "unit": "MN"},
map[string]any{"label": "Burn time", "value": "186", "unit": "s"},
map[string]any{"label": "Payload", "value": "320", "unit": "kg"},
},
},
Slot: "specs",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Content",
Content: map[string]any{"slotName": "main", "placeholder": "Page payload"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "scifi-clean:footer",
Title: "Instrument Footer",
Content: map[string]any{"showSignup": true, "callsign": "SCF-CLN"},
Slot: "footer",
SortOrder: 0,
},
},
}
return []plugin.MasterPageDefinition{defaultMaster, landingMaster}
}