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>
282 lines
8.4 KiB
Plaintext
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))
|
|
}
|