Alex Dunmow 4713787bbd initial: theme plugin corporate-modernist
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously
an unversioned directory inside ~/src/blockninja-themes/corporate-modernist.

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

275 lines
8.8 KiB
Plaintext

package main
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PageData captures parsed template data for the Corporate Modernist page renderers.
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
}
// parseCMPageData adapts the runtime doc map into PageData.
func parseCMPageData(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
}
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: bn.ParseSiteSettings(doc),
PageMeta: bn.ParsePageMeta(doc),
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: bn.ParseEngagementConfig(doc),
}
}
// pluginStyles points at the embedded plugin CSS served via /templates/<slug>/style.css.
func pluginStyles() []string {
return []string{"/templates/corporate-modernist/style.css"}
}
// Default — header + 12-col main + footer, capped at 1280px.
templ CorporateModernistDefault(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="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="cm-hairline-bottom" style="width: 100%;">
<div class="cm-content-well">
@templ.Raw(data.Slots["header"])
</div>
</header>
<main class="cm-content-well" style="flex: 1 1 auto; padding-top: 3rem; padding-bottom: 3rem;" data-content-well>
<div class="cm-swiss-12" data-grid="swiss-12">
<div style="grid-column: span 12 / span 12;">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<p class="cm-stat-label">No content blocks assigned to this page.</p>
}
</div>
</div>
</main>
<footer style="width: 100%; margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Landing — hero band + main + closing CTA + footer.
templ CorporateModernistLanding(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="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="cm-hairline-bottom" style="width: 100%;">
<div class="cm-content-well">
@templ.Raw(data.Slots["header"])
</div>
</header>
<section style="width: 100%;">
@templ.Raw(data.Slots["hero"])
</section>
<main style="flex: 1 1 auto;" data-content-well>
if main, ok := data.Slots["main"]; ok && main != "" {
<div class="cm-content-well">
<div class="cm-swiss-12" data-grid="swiss-12" style="padding-top: 3rem; padding-bottom: 3rem;">
<div style="grid-column: span 12 / span 12;">
@templ.Raw(main)
</div>
</div>
</div>
}
</main>
<section style="width: 100%;">
@templ.Raw(data.Slots["cta"])
</section>
<footer style="width: 100%; margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Article — centered measure for long-form thought leadership.
templ CorporateModernistArticle(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="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="cm-hairline-bottom" style="width: 100%;">
<div class="cm-content-well">
@templ.Raw(data.Slots["header"])
</div>
</header>
<main class="cm-content-well" style="flex: 1 1 auto; padding-top: 3rem; padding-bottom: 3rem;" data-content-well>
<div class="cm-swiss-12" data-grid="swiss-12">
<article class="cm-article-measure" style="grid-column: span 12 / span 12;">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<p class="cm-stat-label">No article content yet.</p>
}
</article>
</div>
if aside, ok := data.Slots["aside"]; ok && aside != "" {
<aside class="cm-hairline-top" style="margin-top: 3rem; padding-top: 2rem;">
@templ.Raw(aside)
</aside>
}
</main>
<footer style="width: 100%; margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Full width — edge-to-edge sections, internal grid kept.
templ CorporateModernistFullWidth(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="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="cm-hairline-bottom" style="width: 100%;">
<div class="cm-content-well">
@templ.Raw(data.Slots["header"])
</div>
</header>
<main style="flex: 1 1 auto; width: 100%;" data-content-well>
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="cm-content-well" style="padding-top: 4rem; padding-bottom: 4rem;">
<p class="cm-stat-label">No content blocks assigned to this page.</p>
</div>
}
</main>
<footer style="width: 100%; margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// RenderCM is the page-template render entry point for `default`.
func RenderCM(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistDefault(parseCMPageData(doc))
}
// RenderCMLanding is the page-template render entry point for `landing`.
func RenderCMLanding(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistLanding(parseCMPageData(doc))
}
// RenderCMArticle is the page-template render entry point for `article`.
func RenderCMArticle(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistArticle(parseCMPageData(doc))
}
// RenderCMFullWidth is the page-template render entry point for `full-width`.
func RenderCMFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistFullWidth(parseCMPageData(doc))
}