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>
198 lines
6.4 KiB
Go
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}
|
|
}
|