themes-kindergarten/template.templ
Alex Dunmow ffe46a146c initial: theme plugin kindergarten
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously
an unversioned directory inside ~/src/blockninja-themes/kindergarten.

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

258 lines
7.3 KiB
Plaintext

package main
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PageData carries everything the page templates need to render.
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
}
// parseKindergartenPageData converts the unstructured doc map the engine
// passes in into the strongly-typed PageData used by the templ components.
func parseKindergartenPageData(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,
}
}
// pluginStyles is the canonical list of theme stylesheet URLs each page sends to <head>.
var pluginStyles = []string{"/templates/kindergarten/style.css"}
// Kindergarten — default template.
templ Kindergarten(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="kg-page kg-dotted-paper">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="kg-section" style="padding-block: 1.5rem;">
@templ.Raw(data.Slots["header"])
</header>
<main class="kg-container" style="padding-block: 2rem; flex: 1;">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="kg-empty">No content blocks assigned to this page.</div>
}
</main>
<footer style="margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Kindergarten — landing template with mascot hero + CTA strip.
templ KindergartenLanding(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="kg-page kg-dotted-paper">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="kg-section" style="padding-block: 1.5rem;">
@templ.Raw(data.Slots["header"])
</header>
<section class="kg-section" style="padding-top: 0;">
@templ.Raw(data.Slots["hero"])
</section>
<main class="kg-container">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
}
</main>
<section>
@templ.Raw(data.Slots["cta"])
</section>
<footer style="margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Kindergarten — article (storybook reading) template.
templ KindergartenArticle(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="kg-page kg-dotted-paper">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="kg-section" style="padding-block: 1.5rem;">
@templ.Raw(data.Slots["header"])
</header>
<div class="kg-container" style="display: grid; grid-template-columns: 1fr; gap: 2rem; padding-block: 2rem;">
<main>
if main, ok := data.Slots["main"]; ok && main != "" {
<article class="kg-text">
@templ.Raw(main)
</article>
} else {
<div class="kg-empty">No content yet — start a story.</div>
}
</main>
if aside, ok := data.Slots["aside"]; ok && aside != "" {
<aside class="kg-card">
@templ.Raw(aside)
</aside>
}
</div>
<footer style="margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Kindergarten — full-width gallery / showcase template.
templ KindergartenFullWidth(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="kg-page kg-dotted-paper">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="kg-section" style="padding-block: 1.5rem;">
@templ.Raw(data.Slots["header"])
</header>
<main style="flex: 1;">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="kg-container">
<div class="kg-empty">No content blocks assigned to this page.</div>
</div>
}
</main>
<footer style="margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Render functions invoked by the registry.
func RenderKindergarten(ctx context.Context, doc map[string]any) templ.Component {
return Kindergarten(parseKindergartenPageData(doc))
}
func RenderKindergartenLanding(ctx context.Context, doc map[string]any) templ.Component {
return KindergartenLanding(parseKindergartenPageData(doc))
}
func RenderKindergartenArticle(ctx context.Context, doc map[string]any) templ.Component {
return KindergartenArticle(parseKindergartenPageData(doc))
}
func RenderKindergartenFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return KindergartenFullWidth(parseKindergartenPageData(doc))
}