diff --git a/Makefile b/Makefile index 637e246..07b56ac 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,27 @@ -# gotham — build & publish helpers (wasm .bnp workflow) +# gotham — build & publish helpers (CODELESS .bnp workflow, WO-WZ-021) # -# The theme compiles to a wasip1 reactor-mode wasm module packed into a .bnp -# artifact (`ninja plugin build`). The legacy .so path is retired (WO-WZ-015): -# the CMS is wasm-native and can no longer load .so plugins. Distribution is -# publish-to-registry -> InstallFromRegistry (hot-load, no restart). +# This theme is a CODELESS .bnp: pure declaration the host runs — blocks +# (blocks/blocks.yaml + .ninjatpl), page/system templates, template overrides, +# an email wrapper, master pages, presets, fonts and CSS in manifest.yaml. No +# Go, no templ, no plugin.wasm, no block/core dependency. `ninja plugin build` +# classifies the repo as codeless (no Go at the root) and packs the artifact +# with manifest.pb (codeless: true) and NO plugin.wasm. # # Usage: -# make build # compile wasm + pack gotham-.bnp -# make verify # validate the built .bnp (layout/abi/name/size) -# make archive-check # prove a clean `git archive HEAD` compiles to wasm +# make build # pack gotham-.bnp (codeless — no wasm) +# make verify # validate the built .bnp (loader-identical checks) +# make archive-check # prove a clean `git archive HEAD` still builds codeless # make publish # ninja plugin publish --bnp (to the active registry) -# make templ # regenerate templ Go files # make clean # remove build artefacts -.PHONY: build verify archive-check publish templ clean help +.PHONY: build verify archive-check publish clean help PLUGIN_NAME := gotham NINJA := ninja BNP := $(PLUGIN_NAME)-$(shell grep '^version' plugin.mod | sed 's/.*"\(.*\)"/\1/').bnp -# Compile to wasm and pack the .bnp (GOOS=wasip1 GOARCH=wasm, DESCRIBE probe, -# tar.zst of plugin.wasm + plugin.mod + manifest.pb + schemas/ + assets/). +# Pack the codeless .bnp (manifest.pb synthesized from plugin.mod + manifest.yaml +# + blocks/ + templates/; no wasm compile, no DESCRIBE probe). build: $(NINJA) plugin build --dir . @@ -28,12 +29,12 @@ build: verify: $(NINJA) plugin verify $(BNP) -# Prove a clean `git archive HEAD` (what publish ships) compiles to wasm. +# Prove a clean `git archive HEAD` (what publish ships) still packs codeless. archive-check: @T=$$(mktemp -d /tmp/archive-check-$(PLUGIN_NAME).XXXXXX) && \ trap 'rm -rf "$$T"' EXIT && \ git archive HEAD | tar -x -C "$$T" && \ - cd "$$T" && GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o "$$T/check.wasm" . && \ + cd "$$T" && $(NINJA) plugin build --dir . && \ echo "archive-check OK" # Publish the prebuilt .bnp to the active registry. @@ -41,19 +42,14 @@ archive-check: publish: build $(NINJA) plugin publish --bnp $(BNP) -# Regenerate templ Go files locally. -templ: - templ generate - # Remove build artefacts. clean: - rm -f $(PLUGIN_NAME)-*.bnp $(PLUGIN_NAME).so plugin.wasm + rm -f $(PLUGIN_NAME)-*.bnp plugin.wasm help: @echo "Targets:" - @echo " build Compile wasm + pack the .bnp" + @echo " build Pack the codeless .bnp (no wasm)" @echo " verify Validate the built .bnp (loader-identical checks)" - @echo " archive-check Prove a clean git archive HEAD compiles to wasm" + @echo " archive-check Prove a clean git archive HEAD packs codeless" @echo " publish Publish the .bnp to the active registry" - @echo " templ Regenerate templ Go files" @echo " clean Remove build artefacts" diff --git a/blocks/blocks.yaml b/blocks/blocks.yaml new file mode 100644 index 0000000..d41494c --- /dev/null +++ b/blocks/blocks.yaml @@ -0,0 +1,21 @@ +# Gotham theme block definitions (codeless .bnp, WO-WZ-021). +# Keys are fully qualified (gotham:) because the codeless loader registers +# definitions verbatim — there is no PluginBlockRegistry prefixing in the +# codeless path (unlike the wasm/templ path). The qualified keys match the +# master_pages.json references. +blocks: + - key: gotham:stats + title: Stats Counter + description: Display statistics with labels and values + schema: stats.schema.json + template: stats.ninjatpl + - key: gotham:features + title: Feature Cards + description: Grid of feature cards with icons and descriptions + schema: features.schema.json + template: features.ninjatpl + - key: gotham:footer + title: Footer + description: Multi-column footer with links and copyright + schema: footer.schema.json + template: footer.ninjatpl diff --git a/blocks/features.ninjatpl b/blocks/features.ninjatpl new file mode 100644 index 0000000..3ed3b8c --- /dev/null +++ b/blocks/features.ninjatpl @@ -0,0 +1 @@ +{% if features %}
{% if section_title %}

{{ section_title }}

{% endif %}
{% for feature in features %}
{% if feature.icon %}
{% if feature.icon == "chart" %}{% elif feature.icon == "users" %}{% elif feature.icon == "clock" %}{% elif feature.icon == "star" %}{% else %}{% endif %}
{% endif %}

{{ feature.title }}

{% if feature.description %}

{{ feature.description }}

{% endif %}
{% endfor %}
{% endif %} \ No newline at end of file diff --git a/schemas/features.schema.json b/blocks/features.schema.json similarity index 100% rename from schemas/features.schema.json rename to blocks/features.schema.json diff --git a/blocks/footer.ninjatpl b/blocks/footer.ninjatpl new file mode 100644 index 0000000..5e37a72 --- /dev/null +++ b/blocks/footer.ninjatpl @@ -0,0 +1 @@ +
{% if columns %}
{% for col in columns %}
{% if col.heading %}

{{ col.heading }}

{% endif %}{% if col.links %}{% endif %}
{% endfor %}
{% endif %}{% if copyright %}

{{ copyright }}

{% endif %}
\ No newline at end of file diff --git a/schemas/footer.schema.json b/blocks/footer.schema.json similarity index 100% rename from schemas/footer.schema.json rename to blocks/footer.schema.json diff --git a/blocks/stats.ninjatpl b/blocks/stats.ninjatpl new file mode 100644 index 0000000..114bedd --- /dev/null +++ b/blocks/stats.ninjatpl @@ -0,0 +1 @@ +{% if items %}
{% for stat in items %}
{% if stat.icon %}
{% if stat.icon == "chart" %}{% elif stat.icon == "users" %}{% elif stat.icon == "clock" %}{% elif stat.icon == "star" %}{% else %}{% endif %}
{% endif %}
{{ stat.value }}
{{ stat.label }}
{% endfor %}
{% else %}

Click + to add stat items

{% endif %} \ No newline at end of file diff --git a/schemas/stats.schema.json b/blocks/stats.schema.json similarity index 100% rename from schemas/stats.schema.json rename to blocks/stats.schema.json diff --git a/email_wrapper.templ b/email_wrapper.templ deleted file mode 100644 index 6082623..0000000 --- a/email_wrapper.templ +++ /dev/null @@ -1,203 +0,0 @@ -package main - -import ( - "bytes" - "context" - "fmt" - "git.dev.alexdunmow.com/block/core/templates" -) - -// GothamEmailWrapper wraps body content in a dark, modern Gotham-branded email template. -func GothamEmailWrapper(body string, emailCtx templates.EmailContext) string { - var buf bytes.Buffer - gothamEmailTemplate(emailCtx, body).Render(context.Background(), &buf) - return buf.String() -} - -// gothamEmailTemplate is the Gotham-branded email template component. -templ gothamEmailTemplate(emailCtx templates.EmailContext, body string) { - - - - - - - - - { emailCtx.SiteSettings.SiteName } - - - - - if emailCtx.PreviewText != "" { -
- { emailCtx.PreviewText } -
-
-  ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ -
- } - - - - - - -
- - - - - - - - - - - - - - - - - - -
- - -} - -// Gotham color helper functions - return hex colors with dark theme defaults -func gothamBgColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Background != "" { - return emailCtx.Colors.Background - } - return "#0a0a0a" // Near black -} - -func gothamCardColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Card != "" { - return emailCtx.Colors.Card - } - return "#141414" // Dark card -} - -func gothamFgColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Foreground != "" { - return emailCtx.Colors.Foreground - } - return "#fafafa" // Near white -} - -func gothamPrimaryColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Primary != "" { - return emailCtx.Colors.Primary - } - return "#fafafa" // Gotham uses white as primary -} - -func gothamMutedFgColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.MutedForeground != "" { - return emailCtx.Colors.MutedForeground - } - return "#737373" // Gray -} - -func gothamMutedColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Muted != "" { - return emailCtx.Colors.Muted - } - return "#1a1a1a" // Darker muted -} - -func gothamBorderColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Border != "" { - return emailCtx.Colors.Border - } - return "#262626" // Dark border -} diff --git a/email_wrapper_templ.go b/email_wrapper_templ.go deleted file mode 100644 index 47589a9..0000000 --- a/email_wrapper_templ.go +++ /dev/null @@ -1,427 +0,0 @@ -// 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" - "fmt" - "git.dev.alexdunmow.com/block/core/templates" -) - -// GothamEmailWrapper wraps body content in a dark, modern Gotham-branded email template. -func GothamEmailWrapper(body string, emailCtx templates.EmailContext) string { - var buf bytes.Buffer - gothamEmailTemplate(emailCtx, body).Render(context.Background(), &buf) - return buf.String() -} - -// gothamEmailTemplate is the Gotham-branded email template component. -func gothamEmailTemplate(emailCtx templates.EmailContext, body 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) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, 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: 35, Col: 41} - } - _, 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, 2, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if emailCtx.PreviewText != "" { - 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: 85, Col: 26} - } - _, 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 - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - 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 - } - } else if emailCtx.SiteSettings.SiteName != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var11 string - templ_7745c5c3_Var11, 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: 106, Col: 42} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) - 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 = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") - 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, 18, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var15 string - templ_7745c5c3_Var15, 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: 126, Col: 44} - } - _, 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, 21, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if emailCtx.SiteSettings.SiteURL != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, 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: 131, Col: 45} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

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

Unsubscribe

") - 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 - } - return nil - }) -} - -// Gotham color helper functions - return hex colors with dark theme defaults -func gothamBgColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Background != "" { - return emailCtx.Colors.Background - } - return "#0a0a0a" // Near black -} - -func gothamCardColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Card != "" { - return emailCtx.Colors.Card - } - return "#141414" // Dark card -} - -func gothamFgColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Foreground != "" { - return emailCtx.Colors.Foreground - } - return "#fafafa" // Near white -} - -func gothamPrimaryColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Primary != "" { - return emailCtx.Colors.Primary - } - return "#fafafa" // Gotham uses white as primary -} - -func gothamMutedFgColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.MutedForeground != "" { - return emailCtx.Colors.MutedForeground - } - return "#737373" // Gray -} - -func gothamMutedColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Muted != "" { - return emailCtx.Colors.Muted - } - return "#1a1a1a" // Darker muted -} - -func gothamBorderColor(emailCtx templates.EmailContext) string { - if emailCtx.Colors.Border != "" { - return emailCtx.Colors.Border - } - return "#262626" // Dark border -} - -var _ = templruntime.GeneratedTemplate diff --git a/embed.go b/embed.go deleted file mode 100644 index 101c4b3..0000000 --- a/embed.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "embed" - "io/fs" - "net/http" -) - -//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. -func Assets() fs.FS { - sub, _ := fs.Sub(assetsFS, "assets") - return sub -} - -// Schemas returns the embedded schemas filesystem. -func Schemas() fs.FS { - sub, _ := fs.Sub(schemasFS, "schemas") - return sub -} - -// AssetsHandler returns an http.Handler that serves the embedded assets. -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 -} diff --git a/features.go b/features.go deleted file mode 100644 index 6d9cc79..0000000 --- a/features.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "bytes" - "context" - - "git.dev.alexdunmow.com/block/core/blocks" -) - -// FeaturesBlockMeta defines metadata for the features grid block. -var FeaturesBlockMeta = blocks.BlockMeta{ - Key: "features", - Title: "Feature Cards", - Description: "Grid of feature cards with icons and descriptions", - Source: "gotham", -} - -// FeaturesBlock renders a feature cards grid. -// Content expects: {"section_title": "...", "columns": 3, "features": [{"icon": "...", "title": "...", "description": "..."}]} -func FeaturesBlock(ctx context.Context, content map[string]any) string { - items := getSlice(content, "features") - if len(items) == 0 { - return "" - } - - var features []FeatureItem - for _, item := range items { - features = append(features, FeatureItem{ - Icon: getString(item, "icon"), - Title: getString(item, "title"), - Description: getString(item, "description"), - }) - } - - data := FeaturesData{ - SectionTitle: getString(content, "section_title"), - Columns: getInt(content, "columns", 3), - Features: features, - } - - var buf bytes.Buffer - _ = featuresComponent(data).Render(ctx, &buf) - return buf.String() -} - -// FeaturesData contains data for the features component. -type FeaturesData struct { - SectionTitle string - Columns int - Features []FeatureItem -} - -// FeatureItem represents a single feature. -type FeatureItem struct { - Icon string - Title string - Description string -} diff --git a/features.templ b/features.templ deleted file mode 100644 index 8275a7b..0000000 --- a/features.templ +++ /dev/null @@ -1,41 +0,0 @@ -package main - -// featuresComponent renders a Gotham-styled feature cards grid. -templ featuresComponent(data FeaturesData) { -
-
- if data.SectionTitle != "" { -

{ data.SectionTitle }

- } -
- for _, feature := range data.Features { -
- if feature.Icon != "" { -
- @iconSVG(feature.Icon) -
- } -

{ feature.Title }

- if feature.Description != "" { -

{ feature.Description }

- } -
- } -
-
-
-} - -// featureGridCols returns the appropriate grid column class. -func featureGridCols(cols int) string { - switch cols { - case 1: - return "grid-cols-1" - case 2: - return "grid-cols-1 md:grid-cols-2" - case 4: - return "grid-cols-1 md:grid-cols-2 lg:grid-cols-4" - default: - return "grid-cols-1 md:grid-cols-2 lg:grid-cols-3" - } -} diff --git a/features_templ.go b/features_templ.go deleted file mode 100644 index b1a34fd..0000000 --- a/features_templ.go +++ /dev/null @@ -1,160 +0,0 @@ -// 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" - -// featuresComponent renders a Gotham-styled feature cards grid. -func featuresComponent(data FeaturesData) 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.SectionTitle != "" { - 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(data.SectionTitle) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features.templ`, Line: 8, Col: 86} - } - _, 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 - } - } - var templ_7745c5c3_Var3 = []any{"grid gap-8", featureGridCols(data.Columns)} - 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 _, feature := range data.Features { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if feature.Icon != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = iconSVG(feature.Icon).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 = 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(feature.Title) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features.templ`, Line: 18, Col: 76} - } - _, 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 - } - if feature.Description != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(feature.Description) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `features.templ`, Line: 20, Col: 77} - } - _, 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, 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 - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -// featureGridCols returns the appropriate grid column class. -func featureGridCols(cols int) string { - switch cols { - case 1: - return "grid-cols-1" - case 2: - return "grid-cols-1 md:grid-cols-2" - case 4: - return "grid-cols-1 md:grid-cols-2 lg:grid-cols-4" - default: - return "grid-cols-1 md:grid-cols-2 lg:grid-cols-3" - } -} - -var _ = templruntime.GeneratedTemplate diff --git a/footer.go b/footer.go deleted file mode 100644 index 3d4f065..0000000 --- a/footer.go +++ /dev/null @@ -1,68 +0,0 @@ -package main - -import ( - "bytes" - "context" - - "git.dev.alexdunmow.com/block/core/blocks" -) - -// FooterBlockMeta defines metadata for the footer block. -var FooterBlockMeta = blocks.BlockMeta{ - Key: "footer", - Title: "Footer", - Description: "Multi-column footer with links and copyright", - Source: "gotham", -} - -// FooterBlock renders a multi-column footer. -// Content expects: {"copyright": "...", "columns": [{"heading": "...", "links": [{"text": "...", "page_id": "...", "url": "..."}]}]} -func FooterBlock(ctx context.Context, content map[string]any) string { - columns := getSlice(content, "columns") - - var footerColumns []FooterColumn - for _, col := range columns { - links := getSlice(col, "links") - var footerLinks []FooterLink - for _, link := range links { - footerLinks = append(footerLinks, FooterLink{ - Text: getString(link, "text"), - PageID: getString(link, "page_id"), - URL: getString(link, "url"), - }) - } - footerColumns = append(footerColumns, FooterColumn{ - Heading: getString(col, "heading"), - Links: footerLinks, - }) - } - - data := FooterData{ - Copyright: getString(content, "copyright"), - Columns: footerColumns, - } - - var buf bytes.Buffer - _ = footerComponent(data).Render(ctx, &buf) - return buf.String() -} - -// FooterData contains data for the footer component. -type FooterData struct { - Copyright string - Columns []FooterColumn -} - -// FooterColumn represents a column in the footer. -type FooterColumn struct { - Heading string - Links []FooterLink -} - -// FooterLink represents a link in the footer. -// If PageID is set, it's an internal page link. Otherwise, URL is used. -type FooterLink struct { - Text string - PageID string - URL string -} diff --git a/footer.templ b/footer.templ deleted file mode 100644 index e1c342b..0000000 --- a/footer.templ +++ /dev/null @@ -1,63 +0,0 @@ -package main - -// footerComponent renders a Gotham-styled multi-column footer. -// Note: The