Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously an unversioned directory inside ~/src/blockninja-themes/cyberpunk. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
191 lines
6.2 KiB
Go
191 lines
6.2 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 that registers the Cyberpunk system template,
|
|
// page templates, theme-owned blocks, built-in overrides, and the email wrapper.
|
|
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
|
|
// 1. System template metadata.
|
|
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
|
|
Key: "cyberpunk",
|
|
Title: "Cyberpunk",
|
|
Description: "Neon-on-black BlockNinja theme with glitch motifs, monospace accents, and magenta/cyan/lime tri-accent palette for SaaS, dev tools, and crypto.",
|
|
})
|
|
|
|
// 2. Page templates — slot lists match docs/works/cyberpunk.md byte-for-byte.
|
|
if err := tr.RegisterPageTemplate("cyberpunk", templates.PageTemplateMeta{
|
|
Key: "default",
|
|
Title: "Default",
|
|
Description: "Standard dark page with scanline overlay, sticky terminal-style header",
|
|
Slots: []string{"header", "main", "footer"},
|
|
}, wrap(RenderCyberpunk)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tr.RegisterPageTemplate("cyberpunk", templates.PageTemplateMeta{
|
|
Key: "landing",
|
|
Title: "Landing",
|
|
Description: "Hero with glitch H1, neon CTA, feature grid, social proof, footer",
|
|
Slots: []string{"hero", "features", "cta", "footer"},
|
|
}, wrap(RenderCyberpunkLanding)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tr.RegisterPageTemplate("cyberpunk", templates.PageTemplateMeta{
|
|
Key: "article",
|
|
Title: "Article",
|
|
Description: "Mono-metadata header, prose column, code-friendly aside",
|
|
Slots: []string{"header", "main", "aside", "footer"},
|
|
}, wrap(RenderCyberpunkArticle)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := tr.RegisterPageTemplate("cyberpunk", templates.PageTemplateMeta{
|
|
Key: "full-width",
|
|
Title: "Full Width",
|
|
Description: "Edge-to-edge dashboard / showcase layout, no max-width",
|
|
Slots: []string{"header", "main", "footer"},
|
|
}, wrap(RenderCyberpunkFullWidth)); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 3. Load block schemas BEFORE registering any blocks.
|
|
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 4. Theme-owned blocks (seven, per spec section "Blocks to build").
|
|
br.Register(HeroGlitchMeta, HeroGlitchBlock)
|
|
br.Register(CTATerminalMeta, CTATerminalBlock)
|
|
br.Register(NavbarTerminalMeta, NavbarTerminalBlock)
|
|
br.Register(FooterGridMeta, FooterGridBlock)
|
|
br.Register(FeatureCardNeonMeta, FeatureCardNeonBlock)
|
|
br.Register(StatsGlowMeta, StatsGlowBlock)
|
|
br.Register(CodeNeonMeta, CodeNeonBlock)
|
|
|
|
// 5. Built-in block overrides — applied only when this theme is active.
|
|
br.RegisterTemplateOverride("cyberpunk", "heading", CyberpunkHeadingBlock)
|
|
br.RegisterTemplateOverride("cyberpunk", "text", CyberpunkTextBlock)
|
|
br.RegisterTemplateOverride("cyberpunk", "button", CyberpunkButtonBlock)
|
|
br.RegisterTemplateOverride("cyberpunk", "card", CyberpunkCardBlock)
|
|
|
|
// 6. Email wrapper.
|
|
tr.RegisterEmailWrapper("cyberpunk", CyberpunkEmailWrapper)
|
|
|
|
return nil
|
|
}
|
|
|
|
// DefaultMasterPages returns the three master pages Cyberpunk seeds on first load.
|
|
// Spec table — section "Master pages" — drives keys, blocks, slots and sort orders.
|
|
func DefaultMasterPages() []plugin.MasterPageDefinition {
|
|
return []plugin.MasterPageDefinition{
|
|
{
|
|
Key: "cyberpunk:default-master",
|
|
Title: "Cyberpunk Default Master",
|
|
PageTemplates: []string{"default", "article"},
|
|
Blocks: []plugin.MasterPageBlock{
|
|
{
|
|
BlockKey: "cyberpunk:navbar_terminal",
|
|
Title: "Top Nav",
|
|
Content: map[string]any{"menuName": "main", "commandPrefix": "~/"},
|
|
Slot: "header",
|
|
SortOrder: 0,
|
|
},
|
|
{
|
|
BlockKey: "slot",
|
|
Title: "Main Slot",
|
|
Content: map[string]any{"slotName": "main", "placeholder": "// content"},
|
|
Slot: "main",
|
|
SortOrder: 0,
|
|
},
|
|
{
|
|
BlockKey: "cyberpunk:footer_grid",
|
|
Title: "Site Footer",
|
|
Content: map[string]any{"showStatus": true, "buildHash": "auto"},
|
|
Slot: "footer",
|
|
SortOrder: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Key: "cyberpunk:landing-master",
|
|
Title: "Cyberpunk Landing Master",
|
|
PageTemplates: []string{"landing"},
|
|
Blocks: []plugin.MasterPageBlock{
|
|
{
|
|
BlockKey: "cyberpunk:hero_glitch",
|
|
Title: "Glitch Hero",
|
|
Content: map[string]any{"eyebrow": "NEW", "headline": "Ship like it's 2049", "cta": map[string]any{"label": "Get the SDK", "href": "#"}},
|
|
Slot: "hero",
|
|
SortOrder: 0,
|
|
},
|
|
{
|
|
BlockKey: "slot",
|
|
Title: "Features Slot",
|
|
Content: map[string]any{"slotName": "features"},
|
|
Slot: "features",
|
|
SortOrder: 0,
|
|
},
|
|
{
|
|
BlockKey: "cyberpunk:cta_terminal",
|
|
Title: "CTA Strip",
|
|
Content: map[string]any{"prompt": "$ npm i your-thing", "button": "Copy"},
|
|
Slot: "cta",
|
|
SortOrder: 0,
|
|
},
|
|
{
|
|
BlockKey: "cyberpunk:footer_grid",
|
|
Title: "Site Footer",
|
|
Content: map[string]any{"showStatus": true},
|
|
Slot: "footer",
|
|
SortOrder: 0,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Key: "cyberpunk:full-master",
|
|
Title: "Cyberpunk Full Master",
|
|
PageTemplates: []string{"full-width"},
|
|
Blocks: []plugin.MasterPageBlock{
|
|
{
|
|
BlockKey: "cyberpunk:navbar_terminal",
|
|
Title: "Top Nav",
|
|
Content: map[string]any{"menuName": "main"},
|
|
Slot: "header",
|
|
SortOrder: 0,
|
|
},
|
|
{
|
|
BlockKey: "slot",
|
|
Title: "Main Slot",
|
|
Content: map[string]any{"slotName": "main"},
|
|
Slot: "main",
|
|
SortOrder: 0,
|
|
},
|
|
{
|
|
BlockKey: "cyberpunk:footer_grid",
|
|
Title: "Site Footer",
|
|
Content: map[string]any{"showStatus": false},
|
|
Slot: "footer",
|
|
SortOrder: 0,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|