commit 313ebaf296c99d713fe176d693c7e84528469c91 Author: Alex Dunmow Date: Sat Jun 6 14:11:25 2026 +0800 initial: theme plugin cyberpunk 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f780e6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.so +*.test +tmp/ +.idea/ +.vscode/ diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md new file mode 100644 index 0000000..d9a15cd --- /dev/null +++ b/BUILD_REPORT.md @@ -0,0 +1,184 @@ +# Cyberpunk theme — Build report (wave-1) + +## What landed + +### Module + metadata +- `plugin.mod` with `name="cyberpunk"`, `kind="theme"`, `scope="@themes"`, + `categories=["templates","developer"]`, `tags=["dark","neon","glitch", + "monospace","saas","developer","tech","crypto","fintech"]`, and + `[compatibility] block_core = ">=0.11.0 <0.12.0"`. Verbatim from spec §2. +- `go.mod` pins `git.dev.alexdunmow.com/block/core v0.11.1` (matches the + rest of the themes repo) and `github.com/a-h/templ v0.3.1020`. No + `replace` directives. + +### Registration +- `registration.go` exports `var Registration plugin.PluginRegistration`, + including the `CSSManifest` hook so the theme's custom utilities reach + the host Tailwind input. +- `register.go` wires: + - 1 system template (`cyberpunk`). + - 4 page templates: `default` (header/main/footer), `landing` + (hero/features/cta/footer), `article` (header/main/aside/footer), + `full-width` (header/main/footer). Slots match spec byte-for-byte. + - `br.LoadSchemasFromFS(Schemas())` is called **before** any + `br.Register(...)`, satisfying the UAT §3 ordering check. + - 7 theme-owned blocks (one `br.Register(...)` call each): + `hero_glitch`, `cta_terminal`, `navbar_terminal`, `footer_grid`, + `feature_card_neon`, `stats_glow`, `code_neon`. + - 4 built-in overrides registered as + `RegisterTemplateOverride("cyberpunk", ...)`: `heading`, `text`, + `button`, `card`. + - 1 email wrapper (`cyberpunk:email_wrapper`). +- `DefaultMasterPages()` returns the three master pages with the + block / slot / sort-order layout from the spec: + `cyberpunk:default-master`, `cyberpunk:landing-master`, + `cyberpunk:full-master`. + +### Schemas +Seven draft-07 schemas under `schemas/`, one per theme block. Properties +match the Go content-map keys exactly. `x-editor` values are all members +of the allowed set +`{text, richtext, media, color, select, number, slug, textarea, array, +collection, bucket-picker, menu-select, template-select, link}`. + +### Presets +`presets.json` ships three presets in the spec's stated order: +1. `neon-noir` — magenta-led default +2. `acid-rain` — cyan-led +3. `toxic-bloom` — lime-led + +Each preset carries both `lightColors` and `darkColors` blocks (the spec +declares `mode: "both"`), with all 19 shadcn tokens populated. Every value +is an HSL triple string (`H S% L%`) — no `hsl(...)` wrappers, no hex. +Values are byte-for-byte from the spec §4 tables. + +### Fonts +- `fonts.json = []` per the wave-1 fonts policy + (`themes/docs/FONTS.md` overrides spec §5 and UAT §11). +- `RECOMMENDED_FONTS.md` lists Space Grotesk / Inter / JetBrains Mono as + Google Fonts picker recommendations with per-slot how-tos. +- All template `font-family` usage flows through + `var(--font-heading|body|mono, )` declarations. The fallback + stacks lead with the recommended Google family so the page already + looks close to the intended aesthetic on systems that have those + fonts installed. + +### CSS / aesthetics +`assets/css/cyberpunk.css` is wired through `CSSManifest.InputCSSAppend` +and contains: +- Scanline overlay utility (`.scanlines`) — 1px stripe `linear-gradient`, + 4% opacity, `scanline-drift` keyframe. +- Glitch text-shadow utility (`.glitch`) — magenta on negative-x, cyan on + positive-x, `glitch-x` keyframe. +- RGB-split chromatic-aberration hover (`.rgb-split`) — 2px box-shadow + pair on `:hover` / `:focus-visible` and a matching `:active` mirror so + the brand microinteraction still reads on touch devices. +- Caret blink utility (`.caret-blink`) — single `@keyframes caret` + definition (UAT §13.7 expects exactly one). +- `.neon-edge-{magenta|cyan|lime}` utilities — three accent variants with + glow box-shadows, each consuming the host shadcn tokens. +- `@media (prefers-reduced-motion: reduce)` disables glitch, caret, + scanline, and status-dot animations. +- `@media (prefers-contrast: more)` hides the scanline overlay entirely. +- `:focus-visible` outline uses `hsl(var(--ring))`. +- All colour values consume the host shadcn HSL token via + `hsl(var(--token))`. No hardcoded hex / rgb / named colours in the + CSS, `.go`, or `.templ` files (the email wrapper keeps its UAT §10 + mandated `body { background:#0a0a12 }` in a ` + + + if emailCtx.PreviewText != "" { + + } else { + + } + + + + +
+ + + + + + + + + + + + + + +
+ + +} diff --git a/email_wrapper_templ.go b/email_wrapper_templ.go new file mode 100644 index 0000000..4d11dd4 --- /dev/null +++ b/email_wrapper_templ.go @@ -0,0 +1,246 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/templates" +) + +// CyberpunkEmailWrapper is the registered cyberpunk:email_wrapper. +// It produces a dark email with a mono preheader, magenta hairline divider, +// and a literal "[esc] unsubscribe" link per UAT §10. +// +// UAT §10 mandates `body { background:#0a0a12 }`. That literal hex lives in the +// ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if emailCtx.PreviewText != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
$ from: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 49, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " — ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 49, Col: 74} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
$ from: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 53, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
$ from: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 63, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if emailCtx.SiteSettings.LogoURL != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(body).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
 
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if emailCtx.SiteSettings.SiteURL != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteURL) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 83, Col: 43} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if emailCtx.UnsubscribeURL != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

[esc] unsubscribe

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

[esc] unsubscribe

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/embed.go b/embed.go new file mode 100644 index 0000000..83bd65c --- /dev/null +++ b/embed.go @@ -0,0 +1,60 @@ +package main + +import ( + "embed" + "io/fs" + "net/http" + + "git.dev.alexdunmow.com/block/core/plugin" +) + +//go:embed assets/* +var assetsFS embed.FS + +//go:embed schemas/* +var schemasFS embed.FS + +//go:embed presets.json +var presetsData []byte + +//go:embed fonts.json +var fontsData []byte + +//go:embed plugin.mod +var pluginModBytes []byte + +// Assets returns the embedded assets filesystem (rooted at "assets/"). +func Assets() fs.FS { + sub, _ := fs.Sub(assetsFS, "assets") + return sub +} + +// Schemas returns the embedded schemas filesystem (rooted at "schemas/"). +func Schemas() fs.FS { + sub, _ := fs.Sub(schemasFS, "schemas") + return sub +} + +// AssetsHandler returns an http.Handler that serves the embedded assets at /templates/cyberpunk/... +func AssetsHandler() http.Handler { + return http.FileServer(http.FS(Assets())) +} + +// ThemePresets returns the embedded theme presets JSON. +func ThemePresets() []byte { return presetsData } + +// BundledFonts returns the embedded fonts manifest JSON. +func BundledFonts() []byte { return fontsData } + +// ThemeCSSManifest exposes the theme's custom CSS (scanlines, glitch, neon-edge, +// caret-blink keyframes, font-family fallbacks) to the host Tailwind input so the +// utilities survive prod purging. +func ThemeCSSManifest() *plugin.CSSManifest { + css, err := assetsFS.ReadFile("assets/css/cyberpunk.css") + if err != nil { + return &plugin.CSSManifest{} + } + return &plugin.CSSManifest{ + InputCSSAppend: string(css), + } +} diff --git a/feature_card_neon.go b/feature_card_neon.go new file mode 100644 index 0000000..d87f65c --- /dev/null +++ b/feature_card_neon.go @@ -0,0 +1,51 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// FeatureCardNeonMeta defines metadata for the cyberpunk:feature_card_neon block. +var FeatureCardNeonMeta = blocks.BlockMeta{ + Key: "feature_card_neon", + Title: "Neon Feature Card", + Description: "Single feature card with per-card neon edge glow (magenta/cyan/lime)", + Category: blocks.CategoryLayout, + Source: "cyberpunk", +} + +// FeatureCardNeonData is the typed view of the feature_card_neon content map. +type FeatureCardNeonData struct { + Icon string + Title string + Body string + Accent string +} + +// FeatureCardNeonBlock renders the feature_card_neon block. +// +// Content shape: +// {icon, title, body, accent:"magenta"|"cyan"|"lime"} +func FeatureCardNeonBlock(ctx context.Context, content map[string]any) string { + data := FeatureCardNeonData{ + Icon: getString(content, "icon"), + Title: getStringWithDefault(content, "title", "Feature"), + Body: getString(content, "body"), + Accent: parseAccent(getString(content, "accent")), + } + var buf bytes.Buffer + _ = featureCardNeonComponent(data).Render(ctx, &buf) + return buf.String() +} + +// parseAccent normalises the accent value to one of the three known accents. +func parseAccent(v string) string { + switch v { + case "magenta", "cyan", "lime": + return v + default: + return "magenta" + } +} diff --git a/feature_card_neon.templ b/feature_card_neon.templ new file mode 100644 index 0000000..3c7bdc2 --- /dev/null +++ b/feature_card_neon.templ @@ -0,0 +1,37 @@ +package main + +// neonEdgeClass returns the per-accent neon-edge utility class. +func neonEdgeClass(accent string) string { + switch accent { + case "cyan": + return "neon-edge-cyan" + case "lime": + return "neon-edge-lime" + default: + return "neon-edge-magenta" + } +} + +// featureCardNeonComponent renders the cyberpunk:feature_card_neon block. +templ featureCardNeonComponent(data FeatureCardNeonData) { +
+ if data.Icon != "" { +
+ +
+ } +

+ { data.Title } +

+ if data.Body != "" { +
+ @templ.Raw(data.Body) +
+ } +
+} diff --git a/feature_card_neon_templ.go b/feature_card_neon_templ.go new file mode 100644 index 0000000..2bfe129 --- /dev/null +++ b/feature_card_neon_templ.go @@ -0,0 +1,138 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// neonEdgeClass returns the per-accent neon-edge utility class. +func neonEdgeClass(accent string) string { + switch accent { + case "cyan": + return "neon-edge-cyan" + case "lime": + return "neon-edge-lime" + default: + return "neon-edge-magenta" + } +} + +// featureCardNeonComponent renders the cyberpunk:feature_card_neon block. +func featureCardNeonComponent(data FeatureCardNeonData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var2 = []any{"cyberpunk-feature-card p-6 rounded-lg h-full flex flex-col gap-4", neonEdgeClass(data.Accent)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Icon != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
\"\"
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `feature_card_neon.templ`, Line: 29, Col: 15} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Body != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Body).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/fonts.json b/fonts.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/fonts.json @@ -0,0 +1 @@ +[] diff --git a/footer_grid.go b/footer_grid.go new file mode 100644 index 0000000..f5c9ad9 --- /dev/null +++ b/footer_grid.go @@ -0,0 +1,62 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// FooterGridMeta defines metadata for the cyberpunk:footer_grid block. +var FooterGridMeta = blocks.BlockMeta{ + Key: "footer_grid", + Title: "Grid Footer", + Description: "Mono columns, build-hash + uptime status pill", + Category: blocks.CategoryNavigation, + Source: "cyberpunk", +} + +// FooterColumn is one column of footer links. +type FooterColumn struct { + Title string + Links []Link +} + +// FooterGridData is the typed view of the footer_grid content map. +type FooterGridData struct { + ShowStatus bool + BuildHash string + Columns []FooterColumn +} + +// FooterGridBlock renders the footer_grid block. +// +// Content shape: +// {showStatus, buildHash, columns:[{title, links:[{label, href}]}]} +func FooterGridBlock(ctx context.Context, content map[string]any) string { + cols := getSlice(content, "columns") + columns := make([]FooterColumn, 0, len(cols)) + for _, col := range cols { + linkObjs := getSlice(col, "links") + links := make([]Link, 0, len(linkObjs)) + for _, l := range linkObjs { + links = append(links, Link{ + Label: getString(l, "label"), + Href: getString(l, "href"), + }) + } + columns = append(columns, FooterColumn{ + Title: getString(col, "title"), + Links: links, + }) + } + + data := FooterGridData{ + ShowStatus: getBool(content, "showStatus", true), + BuildHash: getStringWithDefault(content, "buildHash", "dev"), + Columns: columns, + } + var buf bytes.Buffer + _ = footerGridComponent(data).Render(ctx, &buf) + return buf.String() +} diff --git a/footer_grid.templ b/footer_grid.templ new file mode 100644 index 0000000..90ee825 --- /dev/null +++ b/footer_grid.templ @@ -0,0 +1,77 @@ +package main + +// footerGridColumns returns a tailwind grid-column class for n columns. +func footerGridColumns(n int) string { + switch n { + case 0, 1: + return "grid-cols-1" + case 2: + return "grid-cols-1 sm:grid-cols-2" + case 3: + return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3" + default: + return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4" + } +} + +// footerGridComponent renders the cyberpunk:footer_grid block. +templ footerGridComponent(data FooterGridData) { + +} diff --git a/footer_grid_templ.go b/footer_grid_templ.go new file mode 100644 index 0000000..26055bc --- /dev/null +++ b/footer_grid_templ.go @@ -0,0 +1,199 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// footerGridColumns returns a tailwind grid-column class for n columns. +func footerGridColumns(n int) string { + switch n { + case 0, 1: + return "grid-cols-1" + case 2: + return "grid-cols-1 sm:grid-cols-2" + case 3: + return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3" + default: + return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4" + } +} + +// footerGridComponent renders the cyberpunk:footer_grid block. +func footerGridComponent(data FooterGridData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0a28ee3 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module git.dev.alexdunmow.com/block/themes/cyberpunk + +go 1.26.4 + +require ( + git.dev.alexdunmow.com/block/core v0.11.1 + github.com/a-h/templ v0.3.1020 +) + +require ( + connectrpc.com/connect v1.20.0 // indirect + github.com/BurntSushi/toml v1.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.9.2 // indirect + golang.org/x/mod v0.34.0 // indirect + golang.org/x/text v0.36.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..26aea2c --- /dev/null +++ b/go.sum @@ -0,0 +1,42 @@ +connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= +connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= +git.dev.alexdunmow.com/block/core v0.11.1 h1:5b3Ps9CLor2FGyxw/Qovt27AGZKR5Xi1JZGi/TfliTA= +git.dev.alexdunmow.com/block/core v0.11.1/go.mod h1:ZwzEOxRDLDfrhQGqo6hLw01/C1z/aS4Dm9ljQMl0Bg4= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw= +github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/heading_override.go b/heading_override.go new file mode 100644 index 0000000..7427940 --- /dev/null +++ b/heading_override.go @@ -0,0 +1,18 @@ +package main + +import ( + "bytes" + "context" +) + +// CyberpunkHeadingBlock renders the built-in heading block with cyberpunk styling. +// H1/H2 get the glitch text-shadow (magenta + cyan offsets); smaller levels get +// mono small-caps eyebrow class. +func CyberpunkHeadingBlock(ctx context.Context, content map[string]any) string { + text := getString(content, "text") + textClass := getString(content, "textClass") + level := parseHeadingLevel(content) + var buf bytes.Buffer + _ = cyberpunkHeadingComponent(level, text, textClass).Render(ctx, &buf) + return buf.String() +} diff --git a/heading_override.templ b/heading_override.templ new file mode 100644 index 0000000..2eb59a0 --- /dev/null +++ b/heading_override.templ @@ -0,0 +1,81 @@ +package main + +// cyberpunkHeadingBaseClass returns default Tailwind classes for each heading level. +func cyberpunkHeadingBaseClass(level int) string { + switch level { + case 1: + return "text-4xl md:text-5xl font-bold tracking-tight" + case 2: + return "text-3xl md:text-4xl font-semibold tracking-tight" + case 3: + return "text-2xl font-semibold" + case 4: + return "text-xl font-semibold" + case 5: + return "text-lg font-medium" + case 6: + return "text-base font-medium" + default: + return "text-3xl font-semibold tracking-tight" + } +} + +// cyberpunkHeadingComponent renders the override for the built-in heading block. +// H1 and H2 get the .glitch class which gives them the RGB-split text-shadow. +// H3-H6 get the mono small-caps eyebrow class. +templ cyberpunkHeadingComponent(level int, text, textClass string) { + switch level { + case 1: +

+ { text } +

+ case 2: +

+ { text } +

+ case 3: +

+ { text } +

+ case 4: +

+ { text } +

+ case 5: +
+ { text } +
+ case 6: +
+ { text } +
+ default: +

+ { text } +

+ } +} diff --git a/heading_override_templ.go b/heading_override_templ.go new file mode 100644 index 0000000..30e9566 --- /dev/null +++ b/heading_override_templ.go @@ -0,0 +1,352 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// cyberpunkHeadingBaseClass returns default Tailwind classes for each heading level. +func cyberpunkHeadingBaseClass(level int) string { + switch level { + case 1: + return "text-4xl md:text-5xl font-bold tracking-tight" + case 2: + return "text-3xl md:text-4xl font-semibold tracking-tight" + case 3: + return "text-2xl font-semibold" + case 4: + return "text-xl font-semibold" + case 5: + return "text-lg font-medium" + case 6: + return "text-base font-medium" + default: + return "text-3xl font-semibold tracking-tight" + } +} + +// cyberpunkHeadingComponent renders the override for the built-in heading block. +// H1 and H2 get the .glitch class which gives them the RGB-split text-shadow. +// H3-H6 get the mono small-caps eyebrow class. +func cyberpunkHeadingComponent(level int, text, textClass string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + switch level { + case 1: + var templ_7745c5c3_Var2 = []any{"cyberpunk-display glitch", cyberpunkHeadingBaseClass(1), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 34, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 2: + var templ_7745c5c3_Var6 = []any{"cyberpunk-display glitch", cyberpunkHeadingBaseClass(2), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 42, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 3: + var templ_7745c5c3_Var10 = []any{"cyberpunk-display", cyberpunkHeadingBaseClass(3), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 49, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 4: + var templ_7745c5c3_Var13 = []any{"cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest", cyberpunkHeadingBaseClass(4), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var13...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 56, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 5: + var templ_7745c5c3_Var16 = []any{"cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest", cyberpunkHeadingBaseClass(5), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 63, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 6: + var templ_7745c5c3_Var19 = []any{"cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest", cyberpunkHeadingBaseClass(6), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 70, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + var templ_7745c5c3_Var22 = []any{"cyberpunk-display glitch", cyberpunkHeadingBaseClass(2), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 78, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/helpers.go b/helpers.go new file mode 100644 index 0000000..a8c63ae --- /dev/null +++ b/helpers.go @@ -0,0 +1,102 @@ +package main + +import "strconv" + +// getString extracts a string value from a content map. Returns "" on miss/wrong-type. +func getString(content map[string]any, key string) string { + if v, ok := content[key].(string); ok { + return v + } + return "" +} + +// getStringWithDefault is like getString but returns dflt if the key is missing or non-string. +func getStringWithDefault(content map[string]any, key, dflt string) string { + if v, ok := content[key].(string); ok && v != "" { + return v + } + return dflt +} + +// getBool extracts a boolean value from a content map. Accepts bool, "true"/"false", +// "on"/"off", "yes"/"no", and numerics. Returns dflt when missing or unparsable. +func getBool(content map[string]any, key string, dflt bool) bool { + switch v := content[key].(type) { + case bool: + return v + case string: + switch v { + case "true", "on", "yes", "1": + return true + case "false", "off", "no", "0", "": + return false + } + case float64: + return v != 0 + case int: + return v != 0 + } + return dflt +} + +// getSlice extracts a slice of object-shaped entries from a content map. +func getSlice(content map[string]any, key string) []map[string]any { + v, ok := content[key].([]any) + if !ok { + return nil + } + out := make([]map[string]any, 0, len(v)) + for _, item := range v { + if m, ok := item.(map[string]any); ok { + out = append(out, m) + } + } + return out +} + +// getLink reads a {label, href} link object. Tolerates string values +// (interpreted as href, label = href) and missing keys. +func getLink(content map[string]any, key string) Link { + v := content[key] + switch x := v.(type) { + case map[string]any: + return Link{Label: getString(x, "label"), Href: getString(x, "href")} + case string: + return Link{Label: x, Href: x} + } + return Link{} +} + +// Link is a {label, href} pair used by hero / nav / footer blocks. +type Link struct { + Label string + Href string +} + +// safeHref returns "#" when href is empty so templ.SafeURL never receives "". +func safeHref(href string) string { + if href == "" { + return "#" + } + return href +} + +// parseHeadingLevel parses a 1..6 heading level. Defaults to 2 for safety. +func parseHeadingLevel(content map[string]any) int { + switch v := content["level"].(type) { + case float64: + l := int(v) + if l >= 1 && l <= 6 { + return l + } + case int: + if v >= 1 && v <= 6 { + return v + } + case string: + if l, err := strconv.Atoi(v); err == nil && l >= 1 && l <= 6 { + return l + } + } + return 2 +} diff --git a/hero_glitch.go b/hero_glitch.go new file mode 100644 index 0000000..e6df8c2 --- /dev/null +++ b/hero_glitch.go @@ -0,0 +1,55 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// HeroGlitchMeta defines metadata for the cyberpunk glitch hero block. +var HeroGlitchMeta = blocks.BlockMeta{ + Key: "hero_glitch", + Title: "Glitch Hero", + Description: "RGB-split H1, neon CTA, optional scanline overlay toggle", + Category: blocks.CategoryLayout, + Source: "cyberpunk", +} + +// HeroGlitchData is the typed view of the hero_glitch content map. +type HeroGlitchData struct { + Eyebrow string + Headline string + Sub string + CTA Link + SecondaryCTA Link + Scanlines bool +} + +// HeroGlitchBlock renders the hero_glitch block. +// +// Content shape (matches schemas/hero_glitch.schema.json): +// {eyebrow, headline, sub, cta:{label,href}, secondaryCta:{label,href}, scanlines:"on"|"off"} +func HeroGlitchBlock(ctx context.Context, content map[string]any) string { + data := HeroGlitchData{ + Eyebrow: getString(content, "eyebrow"), + Headline: getStringWithDefault(content, "headline", "// hero headline"), + Sub: getString(content, "sub"), + CTA: getLink(content, "cta"), + SecondaryCTA: getLink(content, "secondaryCta"), + Scanlines: parseScanlines(content), + } + var buf bytes.Buffer + _ = heroGlitchComponent(data).Render(ctx, &buf) + return buf.String() +} + +func parseScanlines(content map[string]any) bool { + if v, ok := content["scanlines"].(string); ok { + return v == "on" || v == "true" + } + if v, ok := content["scanlines"].(bool); ok { + return v + } + return true +} diff --git a/hero_glitch.templ b/hero_glitch.templ new file mode 100644 index 0000000..e8f6619 --- /dev/null +++ b/hero_glitch.templ @@ -0,0 +1,54 @@ +package main + +// heroGlitchComponent renders the cyberpunk:hero_glitch block with an RGB-split H1. +templ heroGlitchComponent(data HeroGlitchData) { +
+ if data.Scanlines { + + } +
+ if data.Eyebrow != "" { +

+ { data.Eyebrow } +

+ } +

+ { data.Headline } +

+ if data.Sub != "" { +

+ { data.Sub } +

+ } +
+ if data.CTA.Label != "" { + + { data.CTA.Label } + + + } + if data.SecondaryCTA.Label != "" { + + { data.SecondaryCTA.Label } + + } +
+
+
+} diff --git a/hero_glitch_templ.go b/hero_glitch_templ.go new file mode 100644 index 0000000..22bc4f3 --- /dev/null +++ b/hero_glitch_templ.go @@ -0,0 +1,191 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// heroGlitchComponent renders the cyberpunk:hero_glitch block with an RGB-split H1. +func heroGlitchComponent(data HeroGlitchData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Scanlines { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Eyebrow != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Eyebrow) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 16, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Headline) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 24, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Sub != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Sub) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 28, Col: 15} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.CTA.Label != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.CTA.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 38, Col: 28} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " _ ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.SecondaryCTA.Label != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(data.SecondaryCTA.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 48, Col: 37} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/navbar_terminal.go b/navbar_terminal.go new file mode 100644 index 0000000..f14ecdb --- /dev/null +++ b/navbar_terminal.go @@ -0,0 +1,43 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// NavbarTerminalMeta defines metadata for the cyberpunk:navbar_terminal block. +var NavbarTerminalMeta = blocks.BlockMeta{ + Key: "navbar_terminal", + Title: "Terminal Navbar", + Description: "Mono-spaced navbar with $ command prefix and optional status dot", + Category: blocks.CategoryNavigation, + Source: "cyberpunk", +} + +// NavbarTerminalData is the typed view of the navbar_terminal content map. +type NavbarTerminalData struct { + MenuName string + CommandPrefix string + StatusDot bool +} + +// NavbarTerminalBlock renders the navbar_terminal block. +// +// Content shape: +// {menuName, commandPrefix, statusDot:"on"|"off"|bool} +// +// menuName is the slug of a CMS-managed menu; the actual
    items are +// resolved by the host menu lookup. This block emits the chrome wrapper plus a +// data-menu-name hook the host renderer reads at runtime. +func NavbarTerminalBlock(ctx context.Context, content map[string]any) string { + data := NavbarTerminalData{ + MenuName: getStringWithDefault(content, "menuName", "main"), + CommandPrefix: getStringWithDefault(content, "commandPrefix", "~/"), + StatusDot: getBool(content, "statusDot", true), + } + var buf bytes.Buffer + _ = navbarTerminalComponent(data).Render(ctx, &buf) + return buf.String() +} diff --git a/navbar_terminal.templ b/navbar_terminal.templ new file mode 100644 index 0000000..1ad1b8c --- /dev/null +++ b/navbar_terminal.templ @@ -0,0 +1,50 @@ +package main + +// navbarTerminalComponent renders the cyberpunk:navbar_terminal block. +// At ≤768px the link list collapses behind an aria-expanded toggle button. +templ navbarTerminalComponent(data NavbarTerminalData) { + +} diff --git a/navbar_terminal_templ.go b/navbar_terminal_templ.go new file mode 100644 index 0000000..fa998d9 --- /dev/null +++ b/navbar_terminal_templ.go @@ -0,0 +1,104 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// navbarTerminalComponent renders the cyberpunk:navbar_terminal block. +// At ≤768px the link list collapses behind an aria-expanded toggle button. +func navbarTerminalComponent(data NavbarTerminalData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/plugin.mod b/plugin.mod new file mode 100644 index 0000000..097cbab --- /dev/null +++ b/plugin.mod @@ -0,0 +1,12 @@ +[plugin] +name = "cyberpunk" +display_name = "Cyberpunk" +scope = "@themes" +version = "0.1.0" +description = "Neon-on-black BlockNinja theme with glitch motifs, monospace accents, and magenta/cyan/lime tri-accent palette for SaaS, dev tools, and crypto." +kind = "theme" +categories = ["templates", "developer"] +tags = ["dark", "neon", "glitch", "monospace", "saas", "developer", "tech", "crypto", "fintech"] + +[compatibility] +block_core = ">=0.11.0 <0.12.0" diff --git a/presets.json b/presets.json new file mode 100644 index 0000000..8ea197f --- /dev/null +++ b/presets.json @@ -0,0 +1,152 @@ +[ + { + "id": "neon-noir", + "name": "Neon Noir", + "description": "Magenta-led tri-accent, the Cyberpunk default", + "theme": { + "mode": "both", + "lightColors": { + "background": "240 12% 97%", + "foreground": "260 25% 10%", + "card": "240 10% 99%", + "cardForeground": "260 25% 10%", + "popover": "240 10% 99%", + "popoverForeground": "260 25% 10%", + "primary": "320 100% 50%", + "primaryForeground": "0 0% 100%", + "secondary": "260 20% 92%", + "secondaryForeground": "260 25% 10%", + "muted": "260 15% 95%", + "mutedForeground": "260 12% 40%", + "accent": "185 100% 45%", + "accentForeground": "260 25% 10%", + "destructive": "0 90% 55%", + "destructiveForeground": "0 0% 100%", + "border": "260 20% 88%", + "input": "260 20% 88%", + "ring": "320 100% 50%" + }, + "darkColors": { + "background": "260 18% 6%", + "foreground": "300 15% 95%", + "card": "260 22% 9%", + "cardForeground": "300 15% 95%", + "popover": "260 22% 9%", + "popoverForeground": "300 15% 95%", + "primary": "320 100% 60%", + "primaryForeground": "260 18% 6%", + "secondary": "260 25% 14%", + "secondaryForeground": "300 15% 95%", + "muted": "260 20% 12%", + "mutedForeground": "260 15% 65%", + "accent": "185 100% 55%", + "accentForeground": "260 18% 6%", + "destructive": "0 95% 62%", + "destructiveForeground": "260 18% 6%", + "border": "260 30% 18%", + "input": "260 30% 16%", + "ring": "320 100% 60%" + } + } + }, + { + "id": "acid-rain", + "name": "Acid Rain", + "description": "Cyan-led, cooler chrome variant", + "theme": { + "mode": "both", + "lightColors": { + "background": "200 25% 97%", + "foreground": "220 30% 10%", + "card": "200 20% 99%", + "cardForeground": "220 30% 10%", + "popover": "200 20% 99%", + "popoverForeground": "220 30% 10%", + "primary": "185 100% 40%", + "primaryForeground": "220 25% 5%", + "secondary": "200 25% 92%", + "secondaryForeground": "220 30% 10%", + "muted": "200 20% 95%", + "mutedForeground": "220 15% 40%", + "accent": "320 95% 55%", + "accentForeground": "0 0% 100%", + "destructive": "0 90% 55%", + "destructiveForeground": "0 0% 100%", + "border": "200 25% 88%", + "input": "200 25% 88%", + "ring": "185 100% 40%" + }, + "darkColors": { + "background": "220 25% 5%", + "foreground": "180 25% 92%", + "card": "220 28% 8%", + "cardForeground": "180 25% 92%", + "popover": "220 28% 8%", + "popoverForeground": "180 25% 92%", + "primary": "185 100% 55%", + "primaryForeground": "220 25% 5%", + "secondary": "220 28% 13%", + "secondaryForeground": "180 25% 92%", + "muted": "220 25% 11%", + "mutedForeground": "200 20% 65%", + "accent": "320 100% 65%", + "accentForeground": "220 25% 5%", + "destructive": "0 95% 62%", + "destructiveForeground": "220 25% 5%", + "border": "200 40% 18%", + "input": "200 40% 16%", + "ring": "185 100% 55%" + } + } + }, + { + "id": "toxic-bloom", + "name": "Toxic Bloom", + "description": "Lime-led, we shipped at 3am", + "theme": { + "mode": "both", + "lightColors": { + "background": "100 15% 97%", + "foreground": "120 20% 10%", + "card": "100 12% 99%", + "cardForeground": "120 20% 10%", + "popover": "100 12% 99%", + "popoverForeground": "120 20% 10%", + "primary": "90 90% 38%", + "primaryForeground": "120 25% 5%", + "secondary": "100 15% 92%", + "secondaryForeground": "120 20% 10%", + "muted": "100 12% 95%", + "mutedForeground": "120 12% 40%", + "accent": "320 100% 55%", + "accentForeground": "0 0% 100%", + "destructive": "0 90% 55%", + "destructiveForeground": "0 0% 100%", + "border": "100 15% 88%", + "input": "100 15% 88%", + "ring": "90 90% 38%" + }, + "darkColors": { + "background": "120 15% 5%", + "foreground": "90 30% 92%", + "card": "120 18% 8%", + "cardForeground": "90 30% 92%", + "popover": "120 18% 8%", + "popoverForeground": "90 30% 92%", + "primary": "90 100% 55%", + "primaryForeground": "120 25% 5%", + "secondary": "120 18% 13%", + "secondaryForeground": "90 30% 92%", + "muted": "120 15% 11%", + "mutedForeground": "100 15% 65%", + "accent": "320 100% 65%", + "accentForeground": "120 25% 5%", + "destructive": "0 95% 62%", + "destructiveForeground": "120 25% 5%", + "border": "100 25% 18%", + "input": "100 25% 16%", + "ring": "90 100% 55%" + } + } + } +] diff --git a/register.go b/register.go new file mode 100644 index 0000000..7bebb84 --- /dev/null +++ b/register.go @@ -0,0 +1,190 @@ +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, + }, + }, + }, + } +} diff --git a/registration.go b/registration.go new file mode 100644 index 0000000..6f1d6e0 --- /dev/null +++ b/registration.go @@ -0,0 +1,25 @@ +package main + +import ( + "io/fs" + "net/http" + + "git.dev.alexdunmow.com/block/core/blocks" + "git.dev.alexdunmow.com/block/core/plugin" + "git.dev.alexdunmow.com/block/core/templates" +) + +// Registration is the compile-time plugin registration for the Cyberpunk theme. +var Registration = plugin.PluginRegistration{ + Name: "cyberpunk", + Version: plugin.ParseModVersion(pluginModBytes), + Register: func(tr templates.TemplateRegistry, br blocks.BlockRegistry) error { + return Register(tr, br) + }, + Assets: func() http.Handler { return AssetsHandler() }, + Schemas: func() fs.FS { return Schemas() }, + ThemePresets: func() []byte { return ThemePresets() }, + BundledFonts: func() []byte { return BundledFonts() }, + MasterPages: func() []plugin.MasterPageDefinition { return DefaultMasterPages() }, + CSSManifest: func() *plugin.CSSManifest { return ThemeCSSManifest() }, +} diff --git a/schemas/code_neon.schema.json b/schemas/code_neon.schema.json new file mode 100644 index 0000000..0ef0c19 --- /dev/null +++ b/schemas/code_neon.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Code Block", + "description": "Server-rendered code block with language label and optional copy button.", + "type": "object", + "properties": { + "language": { + "type": "string", + "title": "Language", + "description": "Source language used as the chroma-style class and figcaption label", + "x-editor": "select", + "enum": ["text", "bash", "shell", "javascript", "typescript", "go", "json", "html", "css", "yaml", "sql", "rust", "python"], + "default": "text" + }, + "code": { + "type": "string", + "title": "Code", + "description": "Block contents", + "x-editor": "textarea" + }, + "copy": { + "type": "string", + "title": "Copy button", + "description": "Show a copy-to-clipboard button in the caption", + "x-editor": "select", + "enum": ["on", "off"], + "default": "on" + } + } +} diff --git a/schemas/cta_terminal.schema.json b/schemas/cta_terminal.schema.json new file mode 100644 index 0000000..65f2730 --- /dev/null +++ b/schemas/cta_terminal.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Terminal CTA", + "description": "Faux $ prompt strip with blinking caret and optional copy-to-clipboard.", + "type": "object", + "properties": { + "prompt": { + "type": "string", + "title": "Prompt", + "description": "The command text following the $ glyph (e.g. npm i your-thing)", + "x-editor": "text" + }, + "button": { + "type": "string", + "title": "Button label", + "description": "Label for the action button (e.g. Copy)", + "x-editor": "text" + }, + "href": { + "type": "string", + "title": "Button URL", + "description": "Optional URL the button points at when copy is off", + "x-editor": "link" + }, + "copyable": { + "type": "string", + "title": "Copy to clipboard", + "description": "When 'on' the button copies the prompt text instead of navigating", + "x-editor": "select", + "enum": ["on", "off"], + "default": "on" + } + } +} diff --git a/schemas/feature_card_neon.schema.json b/schemas/feature_card_neon.schema.json new file mode 100644 index 0000000..7feebf4 --- /dev/null +++ b/schemas/feature_card_neon.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Neon Feature Card", + "description": "Single feature card with icon, title, body and per-card neon edge glow.", + "type": "object", + "properties": { + "icon": { + "type": "string", + "title": "Icon", + "description": "Media reference for the feature icon", + "x-editor": "media" + }, + "title": { + "type": "string", + "title": "Title", + "description": "Card heading", + "x-editor": "text" + }, + "body": { + "type": "string", + "title": "Body", + "description": "Supporting rich text", + "x-editor": "richtext" + }, + "accent": { + "type": "string", + "title": "Accent", + "description": "Neon edge colour: magenta, cyan or lime", + "x-editor": "select", + "enum": ["magenta", "cyan", "lime"], + "default": "magenta" + } + } +} diff --git a/schemas/footer_grid.schema.json b/schemas/footer_grid.schema.json new file mode 100644 index 0000000..e751dcd --- /dev/null +++ b/schemas/footer_grid.schema.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Grid Footer", + "description": "Multi-column footer with mono column titles, optional status pill, and a build-hash readout.", + "type": "object", + "properties": { + "showStatus": { + "type": "string", + "title": "Show status pill", + "description": "Display the OK/uptime status pill at the bottom right", + "x-editor": "select", + "enum": ["on", "off"], + "default": "on" + }, + "buildHash": { + "type": "string", + "title": "Build hash", + "description": "Short build hash or version readout (e.g. 'a1b2c3' or 'auto')", + "x-editor": "text" + }, + "columns": { + "type": "array", + "title": "Columns", + "description": "Footer columns with links", + "default": [], + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "title": "Column title", + "x-editor": "text" + }, + "links": { + "type": "array", + "title": "Links", + "default": [], + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "label": { "type": "string", "title": "Label", "x-editor": "text" }, + "href": { "type": "string", "title": "Href", "x-editor": "text" } + } + } + } + } + } + } + } +} diff --git a/schemas/hero_glitch.schema.json b/schemas/hero_glitch.schema.json new file mode 100644 index 0000000..eecd597 --- /dev/null +++ b/schemas/hero_glitch.schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Glitch Hero", + "description": "Cyberpunk hero section with RGB-split H1, eyebrow, sub, primary + secondary CTAs and an optional scanline overlay.", + "type": "object", + "properties": { + "eyebrow": { + "type": "string", + "title": "Eyebrow", + "description": "Short mono caps label above the headline (e.g. NEW)", + "x-editor": "text" + }, + "headline": { + "type": "string", + "title": "Headline", + "description": "Main H1; rendered with RGB-split glitch text-shadow", + "x-editor": "text" + }, + "sub": { + "type": "string", + "title": "Sub", + "description": "Supporting paragraph beneath the headline", + "x-editor": "textarea" + }, + "cta": { + "type": "object", + "title": "Primary CTA", + "description": "Primary call to action {label, href}", + "x-editor": "link", + "properties": { + "label": { "type": "string", "title": "Label", "x-editor": "text" }, + "href": { "type": "string", "title": "Href", "x-editor": "text" } + } + }, + "secondaryCta": { + "type": "object", + "title": "Secondary CTA", + "description": "Optional secondary call to action {label, href}", + "x-editor": "link", + "properties": { + "label": { "type": "string", "title": "Label", "x-editor": "text" }, + "href": { "type": "string", "title": "Href", "x-editor": "text" } + } + }, + "scanlines": { + "type": "string", + "title": "Scanlines", + "description": "Toggle the 4% scanline overlay inside the hero section", + "x-editor": "select", + "enum": ["on", "off"], + "default": "on" + } + } +} diff --git a/schemas/navbar_terminal.schema.json b/schemas/navbar_terminal.schema.json new file mode 100644 index 0000000..29b3154 --- /dev/null +++ b/schemas/navbar_terminal.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Terminal Navbar", + "description": "Replaces the built-in navbar with a mono-spaced $ prompt navbar, optional status dot, and a tap-friendly mobile menu toggle.", + "type": "object", + "properties": { + "menuName": { + "type": "string", + "title": "Menu", + "description": "Slug of the CMS menu to render (e.g. 'main')", + "x-editor": "menu-select" + }, + "commandPrefix": { + "type": "string", + "title": "Command prefix", + "description": "Leading prompt characters (default '~/')", + "x-editor": "text", + "default": "~/" + }, + "statusDot": { + "type": "string", + "title": "Status dot", + "description": "Show a glowing status indicator (uptime hint)", + "x-editor": "select", + "enum": ["on", "off"], + "default": "on" + } + } +} diff --git a/schemas/stats_glow.schema.json b/schemas/stats_glow.schema.json new file mode 100644 index 0000000..81f4304 --- /dev/null +++ b/schemas/stats_glow.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Glow Stats", + "description": "Big mono numerals with accent underline and short labels.", + "type": "object", + "properties": { + "items": { + "type": "array", + "title": "Items", + "description": "Stats to display", + "default": [], + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "title": "Value", + "description": "Big numeric or string value (e.g. 99.99)", + "x-editor": "text" + }, + "label": { + "type": "string", + "title": "Label", + "description": "Short uppercase caption beneath the value", + "x-editor": "text" + }, + "suffix": { + "type": "string", + "title": "Suffix", + "description": "Trailing unit or symbol (e.g. % or ms), accent-coloured", + "x-editor": "text" + } + } + } + } + } +} diff --git a/stats_glow.go b/stats_glow.go new file mode 100644 index 0000000..bfc7ce6 --- /dev/null +++ b/stats_glow.go @@ -0,0 +1,43 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// StatsGlowMeta defines metadata for the cyberpunk:stats_glow block. +var StatsGlowMeta = blocks.BlockMeta{ + Key: "stats_glow", + Title: "Glow Stats", + Description: "Big mono numerals with neon accent underline", + Category: "display", + Source: "cyberpunk", +} + +// StatGlowItem is one stat in the grid. +type StatGlowItem struct { + Value string + Label string + Suffix string +} + +// StatsGlowBlock renders the stats_glow block. +// +// Content shape: +// {items:[{value, label, suffix}]} +func StatsGlowBlock(ctx context.Context, content map[string]any) string { + raw := getSlice(content, "items") + items := make([]StatGlowItem, 0, len(raw)) + for _, item := range raw { + items = append(items, StatGlowItem{ + Value: getString(item, "value"), + Label: getString(item, "label"), + Suffix: getString(item, "suffix"), + }) + } + var buf bytes.Buffer + _ = statsGlowComponent(items).Render(ctx, &buf) + return buf.String() +} diff --git a/stats_glow.templ b/stats_glow.templ new file mode 100644 index 0000000..ee2b1fc --- /dev/null +++ b/stats_glow.templ @@ -0,0 +1,52 @@ +package main + +// statsGridColumns returns the tailwind grid-column class for n stats. +func statsGridColumns(n int) string { + switch n { + case 0, 1: + return "grid-cols-1" + case 2: + return "grid-cols-1 sm:grid-cols-2" + case 3: + return "grid-cols-1 sm:grid-cols-3" + default: + return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4" + } +} + +// statsGlowComponent renders the cyberpunk:stats_glow block. +templ statsGlowComponent(items []StatGlowItem) { +
    +
    + if len(items) == 0 { +
    + { "// add items to render stats" } +
    + } else { +
    + for _, stat := range items { +
    +
    + { stat.Value } + if stat.Suffix != "" { + { stat.Suffix } + } +
    +
    +
    + { stat.Label } +
    +
    + } +
    + } +
    +
    +} diff --git a/stats_glow_templ.go b/stats_glow_templ.go new file mode 100644 index 0000000..3cc8f60 --- /dev/null +++ b/stats_glow_templ.go @@ -0,0 +1,160 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// statsGridColumns returns the tailwind grid-column class for n stats. +func statsGridColumns(n int) string { + switch n { + case 0, 1: + return "grid-cols-1" + case 2: + return "grid-cols-1 sm:grid-cols-2" + case 3: + return "grid-cols-1 sm:grid-cols-3" + default: + return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4" + } +} + +// statsGlowComponent renders the cyberpunk:stats_glow block. +func statsGlowComponent(items []StatGlowItem) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(items) == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("// add items to render stats") + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats_glow.templ`, Line: 27, Col: 37} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var3 = []any{"grid gap-8 text-center", statsGridColumns(len(items))} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, stat := range items { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Value) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats_glow.templ`, Line: 37, Col: 26} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if stat.Suffix != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Suffix) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats_glow.templ`, Line: 39, Col: 64} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats_glow.templ`, Line: 44, Col: 20} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/template.templ b/template.templ new file mode 100644 index 0000000..45955f0 --- /dev/null +++ b/template.templ @@ -0,0 +1,237 @@ +package main + +import ( + "context" + + "git.dev.alexdunmow.com/block/core/templates/bn" +) + +// PageData carries the rendered slots and assorted page chrome data passed +// from the CMS into a Cyberpunk page template. +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 +} + +// parseCyberpunkPageData converts the loose doc map the renderer is handed into a typed PageData. +func parseCyberpunkPageData(doc map[string]any) PageData { + title := "Untitled" + if t, ok := doc["title"].(string); ok && t != "" { + title = t + } + + slots := map[string]string{} + if s, ok := doc["slots"].(map[string]string); ok { + slots = s + } + + themeCSS, _ := doc["theme_css"].(string) + structuredData, _ := doc["structured_data"].(string) + cssHash, _ := doc["css_hash"].(string) + pageviewNonce, _ := doc["pageview_nonce"].(string) + + themeMode := "dark" + 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), + } +} + +// scanlineOverlay renders the global scanline overlay element (fixed, pointer-events: none). +templ scanlineOverlay() { + +} + +// Cyberpunk renders the default page template. +templ Cyberpunk(data PageData) { + + + @bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }) + + @bn.AdminBypassBanner(data.SiteSettings) + @scanlineOverlay() +
    + @templ.Raw(data.Slots["header"]) +
    +
    + if main, ok := data.Slots["main"]; ok && main != "" { + @templ.Raw(main) + } else { +
    +

    No content blocks assigned to this page.

    +
    + } +
    +
    + @templ.Raw(data.Slots["footer"]) +
    + @bn.BodyEnd(data.SiteSettings) + + +} + +// CyberpunkLanding renders the marketing landing template with hero/features/cta/footer slots. +templ CyberpunkLanding(data PageData) { + + + @bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }) + + @bn.AdminBypassBanner(data.SiteSettings) + @scanlineOverlay() +
    + @templ.Raw(data.Slots["hero"]) +
    +
    + @templ.Raw(data.Slots["features"]) +
    +
    + @templ.Raw(data.Slots["cta"]) +
    +
    + @templ.Raw(data.Slots["footer"]) +
    + @bn.BodyEnd(data.SiteSettings) + + +} + +// CyberpunkArticle renders the article template with mono-metadata header and a code-friendly aside. +templ CyberpunkArticle(data PageData) { + + + @bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }) + + @bn.AdminBypassBanner(data.SiteSettings) + @scanlineOverlay() +
    +
    + @templ.Raw(data.Slots["header"]) +
    +
    +
    +
    + @templ.Raw(data.Slots["main"]) +
    + +
    +
    + @templ.Raw(data.Slots["footer"]) +
    + @bn.BodyEnd(data.SiteSettings) + + +} + +// CyberpunkFullWidth renders the edge-to-edge template (no max-width clamping on main). +templ CyberpunkFullWidth(data PageData) { + + + @bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }) + + @bn.AdminBypassBanner(data.SiteSettings) + @scanlineOverlay() +
    + @templ.Raw(data.Slots["header"]) +
    +
    + if main, ok := data.Slots["main"]; ok && main != "" { + @templ.Raw(main) + } else { +
    +

    No content blocks assigned to this page.

    +
    + } +
    +
    + @templ.Raw(data.Slots["footer"]) +
    + @bn.BodyEnd(data.SiteSettings) + + +} + +// RenderCyberpunk renders the default Cyberpunk page template. +func RenderCyberpunk(ctx context.Context, doc map[string]any) templ.Component { + return Cyberpunk(parseCyberpunkPageData(doc)) +} + +// RenderCyberpunkLanding renders the Cyberpunk landing page template. +func RenderCyberpunkLanding(ctx context.Context, doc map[string]any) templ.Component { + return CyberpunkLanding(parseCyberpunkPageData(doc)) +} + +// RenderCyberpunkArticle renders the Cyberpunk article page template. +func RenderCyberpunkArticle(ctx context.Context, doc map[string]any) templ.Component { + return CyberpunkArticle(parseCyberpunkPageData(doc)) +} + +// RenderCyberpunkFullWidth renders the Cyberpunk full-width page template. +func RenderCyberpunkFullWidth(ctx context.Context, doc map[string]any) templ.Component { + return CyberpunkFullWidth(parseCyberpunkPageData(doc)) +} diff --git a/template_templ.go b/template_templ.go new file mode 100644 index 0000000..83b1095 --- /dev/null +++ b/template_templ.go @@ -0,0 +1,520 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "context" + + "git.dev.alexdunmow.com/block/core/templates/bn" +) + +// PageData carries the rendered slots and assorted page chrome data passed +// from the CMS into a Cyberpunk page template. +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 +} + +// parseCyberpunkPageData converts the loose doc map the renderer is handed into a typed PageData. +func parseCyberpunkPageData(doc map[string]any) PageData { + title := "Untitled" + if t, ok := doc["title"].(string); ok && t != "" { + title = t + } + + slots := map[string]string{} + if s, ok := doc["slots"].(map[string]string); ok { + slots = s + } + + themeCSS, _ := doc["theme_css"].(string) + structuredData, _ := doc["structured_data"].(string) + cssHash, _ := doc["css_hash"].(string) + pageviewNonce, _ := doc["pageview_nonce"].(string) + + themeMode := "dark" + 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), + } +} + +// scanlineOverlay renders the global scanline overlay element (fixed, pointer-events: none). +func scanlineOverlay() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Cyberpunk renders the default page template. +func Cyberpunk(data PageData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var2 := templ.GetChildren(ctx) + if templ_7745c5c3_Var2 == nil { + templ_7745c5c3_Var2 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = scanlineOverlay().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if main, ok := data.Slots["main"]; ok && main != "" { + templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

    No content blocks assigned to this page.

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// CyberpunkLanding renders the marketing landing template with hero/features/cta/footer slots. +func CyberpunkLanding(data PageData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = scanlineOverlay().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["hero"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["features"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["cta"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// CyberpunkArticle renders the article template with mono-metadata header and a code-friendly aside. +func CyberpunkArticle(data PageData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var4 := templ.GetChildren(ctx) + if templ_7745c5c3_Var4 == nil { + templ_7745c5c3_Var4 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = scanlineOverlay().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["main"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// CyberpunkFullWidth renders the edge-to-edge template (no max-width clamping on main). +func CyberpunkFullWidth(data PageData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = scanlineOverlay().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if main, ok := data.Slots["main"]; ok && main != "" { + templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "

    No content blocks assigned to this page.

    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// RenderCyberpunk renders the default Cyberpunk page template. +func RenderCyberpunk(ctx context.Context, doc map[string]any) templ.Component { + return Cyberpunk(parseCyberpunkPageData(doc)) +} + +// RenderCyberpunkLanding renders the Cyberpunk landing page template. +func RenderCyberpunkLanding(ctx context.Context, doc map[string]any) templ.Component { + return CyberpunkLanding(parseCyberpunkPageData(doc)) +} + +// RenderCyberpunkArticle renders the Cyberpunk article page template. +func RenderCyberpunkArticle(ctx context.Context, doc map[string]any) templ.Component { + return CyberpunkArticle(parseCyberpunkPageData(doc)) +} + +// RenderCyberpunkFullWidth renders the Cyberpunk full-width page template. +func RenderCyberpunkFullWidth(ctx context.Context, doc map[string]any) templ.Component { + return CyberpunkFullWidth(parseCyberpunkPageData(doc)) +} + +var _ = templruntime.GeneratedTemplate diff --git a/text_override.go b/text_override.go new file mode 100644 index 0000000..2b27e36 --- /dev/null +++ b/text_override.go @@ -0,0 +1,16 @@ +package main + +import ( + "bytes" + "context" +) + +// CyberpunkTextBlock renders the built-in text block with cyberpunk styling: +// scanline-friendly leading and accent-coloured inline spans. +func CyberpunkTextBlock(ctx context.Context, content map[string]any) string { + text := getString(content, "text") + class := getString(content, "class") + var buf bytes.Buffer + _ = cyberpunkTextComponent(text, class).Render(ctx, &buf) + return buf.String() +} diff --git a/text_override.templ b/text_override.templ new file mode 100644 index 0000000..87027f6 --- /dev/null +++ b/text_override.templ @@ -0,0 +1,12 @@ +package main + +// cyberpunkTextComponent renders the override for the built-in text block: +// shadcn `prose` invert with cyberpunk leading and accent inline code. +templ cyberpunkTextComponent(text, class string) { +
    + @templ.Raw(text) +
    +} diff --git a/text_override_templ.go b/text_override_templ.go new file mode 100644 index 0000000..52d8840 --- /dev/null +++ b/text_override_templ.go @@ -0,0 +1,68 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// cyberpunkTextComponent renders the override for the built-in text block: +// shadcn `prose` invert with cyberpunk leading and accent inline code. +func cyberpunkTextComponent(text, class string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var2 = []any{"cyberpunk-prose prose prose-invert max-w-none leading-relaxed", class} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(text).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate