themes-scifi-clean/template.templ
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

282 lines
8.4 KiB
Plaintext

package main
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PageData holds the values lifted out of the render `doc` map for the
// four Sci-Fi Clean page templates.
type PageData struct {
Title string
Slots map[string]string
ThemeMode string
ThemeCSS string
SiteSettings bn.SiteSettingsData
PageMeta bn.PageMeta
StructuredData string
CSSHash string
PageviewNonce string
EngagementConfig bn.EngagementConfig
}
// parseScifiPageData lifts the well-known keys out of the inbound doc map.
// Unknown keys (e.g. block content) are ignored by the page renderers.
func parseScifiPageData(doc map[string]any) PageData {
title := "Untitled"
if t, ok := doc["title"].(string); ok {
title = t
}
slots := make(map[string]string)
if s, ok := doc["slots"].(map[string]string); ok {
slots = s
}
themeCSS := ""
if tc, ok := doc["theme_css"].(string); ok {
themeCSS = tc
}
structuredData := ""
if sd, ok := doc["structured_data"].(string); ok {
structuredData = sd
}
cssHash := ""
if ch, ok := doc["css_hash"].(string); ok {
cssHash = ch
}
pageviewNonce := ""
if pn, ok := doc["pageview_nonce"].(string); ok {
pageviewNonce = pn
}
themeMode := "light"
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
themeMode = tm
}
siteSettings := bn.ParseSiteSettings(doc)
pageMeta := bn.ParsePageMeta(doc)
engagementConfig := bn.ParseEngagementConfig(doc)
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: siteSettings,
PageMeta: pageMeta,
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: engagementConfig,
}
}
// htmlClassForMode returns the root <html> class for the current theme mode.
func htmlClassForMode(mode string) string {
if mode == "dark" {
return "dark"
}
return ""
}
// ===== Default template (header / main / footer) =====
templ ScifiClean(data PageData) {
<!DOCTYPE html>
<html lang="en" class={ htmlClassForMode(data.ThemeMode) }>
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col" style="font-family: var(--font-body);">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full">
@templ.Raw(data.Slots["header"])
</header>
<main class="flex-grow max-w-5xl mx-auto w-full px-4 py-8">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="py-20 text-center scifi-mono uppercase tracking-widest text-xs text-muted-foreground">
No content blocks assigned to this page.
</div>
}
</main>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// ===== Landing template (hero / specs / main / cta / footer) =====
templ ScifiCleanLanding(data PageData) {
<!DOCTYPE html>
<html lang="en" class={ htmlClassForMode(data.ThemeMode) }>
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col" style="font-family: var(--font-body);">
@bn.AdminBypassBanner(data.SiteSettings)
<section class="w-full">
@templ.Raw(data.Slots["hero"])
</section>
if specs, ok := data.Slots["specs"]; ok && specs != "" {
<section class="w-full hairline-b py-12">
<div class="max-w-6xl mx-auto px-4">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
@templ.Raw(specs)
</div>
</div>
</section>
}
<main class="flex-grow w-full">
if main, ok := data.Slots["main"]; ok && main != "" {
<div class="max-w-6xl mx-auto px-4 py-16">
@templ.Raw(main)
</div>
}
</main>
if cta, ok := data.Slots["cta"]; ok && cta != "" {
<section class="w-full hairline-t py-16">
<div class="max-w-6xl mx-auto px-4">
@templ.Raw(cta)
</div>
</section>
}
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// ===== Article template (header / rail / main / footer) =====
templ ScifiCleanArticle(data PageData) {
<!DOCTYPE html>
<html lang="en" class={ htmlClassForMode(data.ThemeMode) }>
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col" style="font-family: var(--font-body);">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full hairline-b">
@templ.Raw(data.Slots["header"])
</header>
<main class="flex-grow w-full">
<div class="max-w-5xl mx-auto px-4 py-12 grid grid-cols-12 gap-8">
if rail, ok := data.Slots["rail"]; ok && rail != "" {
<aside class="col-span-12 md:col-span-3 scifi-mono uppercase text-xs tracking-widest text-muted-foreground space-y-3">
@templ.Raw(rail)
</aside>
}
<article class="col-span-12 md:col-span-9 scifi-body max-w-2xl">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<p class="text-muted-foreground">No article body.</p>
}
</article>
</div>
</main>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// ===== Full-width template (header / main / footer) =====
templ ScifiCleanFullWidth(data PageData) {
<!DOCTYPE html>
<html lang="en" class={ htmlClassForMode(data.ThemeMode) }>
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col" style="font-family: var(--font-body);">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full">
@templ.Raw(data.Slots["header"])
</header>
<main class="flex-grow w-full bg-grid">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="max-w-5xl mx-auto py-20 px-4 text-center scifi-mono uppercase tracking-widest text-xs text-muted-foreground">
No content blocks assigned to this page.
</div>
}
</main>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// ===== Render entry points (adapted by wrap() in register.go) =====
func RenderScifiClean(ctx context.Context, doc map[string]any) templ.Component {
return ScifiClean(parseScifiPageData(doc))
}
func RenderScifiCleanLanding(ctx context.Context, doc map[string]any) templ.Component {
return ScifiCleanLanding(parseScifiPageData(doc))
}
func RenderScifiCleanArticle(ctx context.Context, doc map[string]any) templ.Component {
return ScifiCleanArticle(parseScifiPageData(doc))
}
func RenderScifiCleanFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return ScifiCleanFullWidth(parseScifiPageData(doc))
}