feat(gotham): initial extraction from blockninja monorepo
This commit is contained in:
commit
1e7ee2cc1e
191
Makefile
Normal file
191
Makefile
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
# Gotham — build & deploy helpers (.so plugin workflow)
|
||||||
|
#
|
||||||
|
# The plugin compiles to a .so shared object loaded by the CMS at runtime.
|
||||||
|
# `make rebuild` copies source to the container, builds the .so, and restarts.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# make rebuild # Full rebuild: frontend + .so + CSS + migrations, restart
|
||||||
|
# make backend # Build .so + migrations, restart
|
||||||
|
# make build-css # Rebuild Tailwind CSS
|
||||||
|
# make logs # Tail instance logs
|
||||||
|
# make status # Show instance container status
|
||||||
|
|
||||||
|
.PHONY: rebuild backend build-frontend build-base-binary build-so copy-plugin-source sync-migrations build-css deploy-css logs status help spinup templ bump-patch bump-minor bump-major sync-version
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
BLOCKNINJA_DIR := $(HOME)/src/blockninja
|
||||||
|
PLUGIN_SRC := $(CURDIR)
|
||||||
|
PLUGIN_NAME := gotham
|
||||||
|
MIGRATIONS_SRC := $(BLOCKNINJA_DIR)/backend/sql/migrations
|
||||||
|
GO_BUILDER := localhost/blockninja-go-builder:latest
|
||||||
|
CONTAINER := instance-gotham
|
||||||
|
ACCOUNT_SLUG := blockninja
|
||||||
|
INSTANCE_SLUG := gotham
|
||||||
|
STYLES_DIR := /var/lib/blockninja/$(ACCOUNT_SLUG)/$(INSTANCE_SLUG)/styles
|
||||||
|
PLUGIN_DEST := /app/data/plugins/src/$(PLUGIN_NAME)
|
||||||
|
|
||||||
|
# Default target: build the .so locally for development.
|
||||||
|
all: $(PLUGIN_NAME).so
|
||||||
|
|
||||||
|
# Local plugin build (no container). Useful for CI / quick checks.
|
||||||
|
$(PLUGIN_NAME).so: $(wildcard *.go) plugin.mod go.mod
|
||||||
|
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o $(PLUGIN_NAME).so .
|
||||||
|
|
||||||
|
# Ensure blockninja core services and the instance container are running.
|
||||||
|
spinup:
|
||||||
|
$(MAKE) -C $(BLOCKNINJA_DIR) spinup
|
||||||
|
|
||||||
|
# Full rebuild: frontend + .so plugin + CSS + migrations, restart.
|
||||||
|
rebuild: spinup
|
||||||
|
$(MAKE) build-frontend
|
||||||
|
$(MAKE) build-base-binary
|
||||||
|
$(MAKE) copy-plugin-source
|
||||||
|
$(MAKE) build-so
|
||||||
|
$(MAKE) build-css
|
||||||
|
$(MAKE) sync-migrations
|
||||||
|
podman restart $(CONTAINER)
|
||||||
|
@sleep 2
|
||||||
|
$(MAKE) deploy-css
|
||||||
|
@echo ""
|
||||||
|
@echo "Done. https://$(INSTANCE_SLUG).localdev.blockninjacms.com/"
|
||||||
|
|
||||||
|
# Backend-only rebuild: .so plugin + migrations, restart.
|
||||||
|
backend: spinup
|
||||||
|
$(MAKE) build-base-binary
|
||||||
|
$(MAKE) copy-plugin-source
|
||||||
|
$(MAKE) build-so
|
||||||
|
$(MAKE) sync-migrations
|
||||||
|
podman restart $(CONTAINER)
|
||||||
|
@echo "Backend updated."
|
||||||
|
|
||||||
|
# Build host admin UI and deploy to container.
|
||||||
|
build-frontend:
|
||||||
|
@echo "==> Building @block-ninja/ui ..."
|
||||||
|
cd $(BLOCKNINJA_DIR)/packages/ui && pnpm run build
|
||||||
|
@echo "==> Building host admin UI ..."
|
||||||
|
cd $(BLOCKNINJA_DIR)/web && pnpm run build
|
||||||
|
@echo "==> Deploying frontend to container ..."
|
||||||
|
podman exec $(CONTAINER) rm -rf /app/web/dist
|
||||||
|
podman cp $(BLOCKNINJA_DIR)/web/dist $(CONTAINER):/app/web/dist
|
||||||
|
@echo "Frontend deployed."
|
||||||
|
|
||||||
|
# Build the base CMS binary (without external plugins) and copy to container.
|
||||||
|
build-base-binary:
|
||||||
|
@echo "==> Building base CMS binary ..."
|
||||||
|
podman run --rm \
|
||||||
|
-v $(BLOCKNINJA_DIR)/backend:/src/backend:ro \
|
||||||
|
-v blockninja_go_cache:/go/pkg/mod \
|
||||||
|
-v /tmp:/out \
|
||||||
|
-w /src/backend \
|
||||||
|
$(GO_BUILDER) \
|
||||||
|
go build -o /out/blockninja-server ./cmd/server
|
||||||
|
podman cp /tmp/blockninja-server $(CONTAINER):/app/server
|
||||||
|
rm -f /tmp/blockninja-server
|
||||||
|
|
||||||
|
# Copy plugin source into the container's plugin source directory.
|
||||||
|
copy-plugin-source:
|
||||||
|
@echo "==> Copying $(PLUGIN_NAME) source to container ..."
|
||||||
|
podman exec $(CONTAINER) rm -rf $(PLUGIN_DEST)
|
||||||
|
podman exec $(CONTAINER) mkdir -p $(PLUGIN_DEST)
|
||||||
|
podman cp $(PLUGIN_SRC)/. $(CONTAINER):$(PLUGIN_DEST)/
|
||||||
|
podman exec $(CONTAINER) rm -rf $(PLUGIN_DEST)/.git $(PLUGIN_DEST)/Makefile
|
||||||
|
@echo "Plugin source copied."
|
||||||
|
|
||||||
|
# Build the .so using the go-builder container (same toolchain as CMS binary).
|
||||||
|
# Both builds resolve block/core from the shared module cache — no local replace.
|
||||||
|
build-so:
|
||||||
|
@echo "==> Building $(PLUGIN_NAME).so ..."
|
||||||
|
podman run --rm \
|
||||||
|
-v $(PLUGIN_SRC):/src/plugin:ro \
|
||||||
|
-v blockninja_go_cache:/go/pkg/mod \
|
||||||
|
-v /tmp:/out \
|
||||||
|
-w /src/plugin \
|
||||||
|
-e CGO_ENABLED=1 \
|
||||||
|
$(GO_BUILDER) \
|
||||||
|
go build -buildmode=plugin -ldflags="-s -w" -o /out/$(PLUGIN_NAME).so .
|
||||||
|
podman exec $(CONTAINER) mkdir -p /app/data/plugins/so
|
||||||
|
podman cp /tmp/$(PLUGIN_NAME).so $(CONTAINER):/app/data/plugins/so/$(PLUGIN_NAME).so
|
||||||
|
rm -f /tmp/$(PLUGIN_NAME).so
|
||||||
|
@echo "$(PLUGIN_NAME).so built."
|
||||||
|
|
||||||
|
# Sync base blockninja migration files from host to container.
|
||||||
|
sync-migrations:
|
||||||
|
@echo "==> Syncing migrations ..."
|
||||||
|
@podman unshare bash -c ' \
|
||||||
|
M=$$(podman mount $(CONTAINER)) && \
|
||||||
|
rm -rf "$$M/app/migrations" && \
|
||||||
|
mkdir -p "$$M/app/migrations" && \
|
||||||
|
podman umount $(CONTAINER)'
|
||||||
|
@podman cp $(MIGRATIONS_SRC)/. $(CONTAINER):/app/migrations/
|
||||||
|
@echo "Migrations synced."
|
||||||
|
|
||||||
|
# Rebuild Tailwind CSS.
|
||||||
|
build-css:
|
||||||
|
@echo "==> Building CSS ..."
|
||||||
|
cd $(BLOCKNINJA_DIR) && make css
|
||||||
|
|
||||||
|
# Copy built CSS to instance styles dir and container.
|
||||||
|
deploy-css:
|
||||||
|
@mkdir -p $(STYLES_DIR)
|
||||||
|
cp $(BLOCKNINJA_DIR)/data/styles/styles.css $(STYLES_DIR)/styles.css
|
||||||
|
podman cp $(BLOCKNINJA_DIR)/data/styles/styles.css $(CONTAINER):/app/data/styles/styles.css
|
||||||
|
podman cp $(BLOCKNINJA_DIR)/styles/input.base.css $(CONTAINER):/app/styles/input.base.css
|
||||||
|
@echo "CSS deployed."
|
||||||
|
|
||||||
|
# Regenerate templ Go files locally (for development).
|
||||||
|
templ:
|
||||||
|
cd $(PLUGIN_SRC) && templ generate
|
||||||
|
|
||||||
|
# Tail instance logs.
|
||||||
|
logs:
|
||||||
|
podman logs -f $(CONTAINER)
|
||||||
|
|
||||||
|
# Show instance container status.
|
||||||
|
status:
|
||||||
|
@podman inspect $(CONTAINER) --format \
|
||||||
|
'Name: {{.Name}}\nImage: {{.Config.Image}}\nStatus: {{.State.Status}}\nHealth: {{.State.Health.Status}}\nStarted: {{.State.StartedAt}}' \
|
||||||
|
2>/dev/null || echo "Container $(CONTAINER) not found."
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Targets:"
|
||||||
|
@echo " all Build $(PLUGIN_NAME).so locally (default)"
|
||||||
|
@echo " spinup Start blockninja core services + instance container if stopped"
|
||||||
|
@echo " rebuild Full rebuild: frontend + .so + CSS + migrations, restart"
|
||||||
|
@echo " backend Build .so + migrations, restart"
|
||||||
|
@echo " build-frontend Build host admin UI, deploy to container"
|
||||||
|
@echo " build-base-binary Build base CMS binary, copy to container"
|
||||||
|
@echo " copy-plugin-source Copy plugin source into container"
|
||||||
|
@echo " build-so Build .so inside container"
|
||||||
|
@echo " sync-migrations Copy migration files from host to container"
|
||||||
|
@echo " build-css Rebuild Tailwind CSS"
|
||||||
|
@echo " deploy-css Copy CSS to instance styles dir"
|
||||||
|
@echo " templ Regenerate templ Go files locally"
|
||||||
|
@echo " logs Tail instance container logs"
|
||||||
|
@echo " status Show instance container status"
|
||||||
|
|
||||||
|
# --- Version bump targets ---
|
||||||
|
CURRENT_VERSION := $(shell grep '^version' plugin.mod | sed 's/.*"\(.*\)"/\1/')
|
||||||
|
|
||||||
|
bump-patch:
|
||||||
|
@NEW=$$(echo $(CURRENT_VERSION) | awk -F. '{printf "%d.%d.%d", $$1, $$2, $$3+1}'); \
|
||||||
|
sed -i 's/version = "$(CURRENT_VERSION)"/version = "'$$NEW'"/' plugin.mod; \
|
||||||
|
git add plugin.mod && git commit -m "chore: bump version to $$NEW" && git tag "v$$NEW"; \
|
||||||
|
echo "Bumped to $$NEW and tagged v$$NEW"
|
||||||
|
|
||||||
|
bump-minor:
|
||||||
|
@NEW=$$(echo $(CURRENT_VERSION) | awk -F. '{printf "%d.%d.0", $$1, $$2+1}'); \
|
||||||
|
sed -i 's/version = "$(CURRENT_VERSION)"/version = "'$$NEW'"/' plugin.mod; \
|
||||||
|
git add plugin.mod && git commit -m "chore: bump version to $$NEW" && git tag "v$$NEW"; \
|
||||||
|
echo "Bumped to $$NEW and tagged v$$NEW"
|
||||||
|
|
||||||
|
bump-major:
|
||||||
|
@NEW=$$(echo $(CURRENT_VERSION) | awk -F. '{printf "%d.0.0", $$1+1}'); \
|
||||||
|
sed -i 's/version = "$(CURRENT_VERSION)"/version = "'$$NEW'"/' plugin.mod; \
|
||||||
|
git add plugin.mod && git commit -m "chore: bump version to $$NEW" && git tag "v$$NEW"; \
|
||||||
|
echo "Bumped to $$NEW and tagged v$$NEW"
|
||||||
|
|
||||||
|
sync-version:
|
||||||
|
@TAG=$$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//'); \
|
||||||
|
if [ -z "$$TAG" ]; then echo "No tags found"; exit 1; fi; \
|
||||||
|
sed -i 's/version = "$(CURRENT_VERSION)"/version = "'$$TAG'"/' plugin.mod; \
|
||||||
|
echo "Synced plugin.mod to $$TAG"
|
||||||
36
README.md
Normal file
36
README.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Gotham
|
||||||
|
|
||||||
|
Dark, modern BlockNinja theme plugin. Bold typography, high-contrast accents,
|
||||||
|
and a small set of theme-specific blocks (stats, features, footer) plus
|
||||||
|
overrides for the built-in `heading` and `text` blocks when the Gotham
|
||||||
|
template is active.
|
||||||
|
|
||||||
|
## Page templates
|
||||||
|
|
||||||
|
- `default` — header / main / footer
|
||||||
|
- `landing` — hero / main / cta / footer
|
||||||
|
- `full-width` — edge-to-edge header / main / footer
|
||||||
|
- `centered` — narrow centered content for articles and docs
|
||||||
|
|
||||||
|
## Blocks
|
||||||
|
|
||||||
|
- `gotham:stats` — stat row with configurable items
|
||||||
|
- `gotham:features` — feature grid
|
||||||
|
- `gotham:footer` — branded footer with optional signup form
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```
|
||||||
|
make # builds gotham.so locally
|
||||||
|
make rebuild # rebuilds plugin inside an instance container
|
||||||
|
```
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
```
|
||||||
|
make bump-patch # 0.1.0 -> 0.1.1
|
||||||
|
make bump-minor # 0.1.0 -> 0.2.0
|
||||||
|
make bump-major # 0.1.0 -> 1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `git push --tags` and `ninja plugin publish` to release.
|
||||||
0
assets/fonts/.gitkeep
Normal file
0
assets/fonts/.gitkeep
Normal file
1
assets/fonts/placeholder.txt
Normal file
1
assets/fonts/placeholder.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
/* placeholder for fonts */
|
||||||
33
assets/style.css
Normal file
33
assets/style.css
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/* Gotham template styles - uses theme CSS variables */
|
||||||
|
|
||||||
|
/* Accent color classes - mapped to theme primary */
|
||||||
|
.gotham-accent {
|
||||||
|
color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.gotham-accent-bg {
|
||||||
|
background-color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
.gotham-accent-bg:hover {
|
||||||
|
background-color: hsl(var(--primary) / 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Border accent */
|
||||||
|
.gotham-border-accent {
|
||||||
|
border-color: hsl(var(--primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Additional Gotham-specific utilities */
|
||||||
|
.gotham-gradient {
|
||||||
|
background: linear-gradient(180deg, hsl(var(--background)) 0%, hsl(var(--card)) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gotham-card {
|
||||||
|
background-color: hsl(var(--card));
|
||||||
|
border: 1px solid hsl(var(--border));
|
||||||
|
}
|
||||||
|
|
||||||
|
.gotham-glow {
|
||||||
|
box-shadow: 0 0 20px hsl(var(--primary) / 0.3);
|
||||||
|
}
|
||||||
203
email_wrapper.templ
Normal file
203
email_wrapper.templ
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
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) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<meta name="x-apple-disable-message-reformatting"/>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||||
|
<!--[if mso]>
|
||||||
|
<noscript>
|
||||||
|
<xml>
|
||||||
|
<o:OfficeDocumentSettings>
|
||||||
|
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||||
|
</o:OfficeDocumentSettings>
|
||||||
|
</xml>
|
||||||
|
</noscript>
|
||||||
|
<![endif]-->
|
||||||
|
<title>{ emailCtx.SiteSettings.SiteName }</title>
|
||||||
|
<style type="text/css">
|
||||||
|
/* CSS Reset for Email */
|
||||||
|
body, table, td, p, a, li, blockquote {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
}
|
||||||
|
table, td {
|
||||||
|
mso-table-lspace: 0pt;
|
||||||
|
mso-table-rspace: 0pt;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
-ms-interpolation-mode: bicubic;
|
||||||
|
border: 0;
|
||||||
|
height: auto;
|
||||||
|
line-height: 100%;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
a[x-apple-data-detectors] {
|
||||||
|
color: inherit !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
/* Gotham-specific styles */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
/* Responsive styles */
|
||||||
|
@media only screen and (max-width: 620px) {
|
||||||
|
.email-container {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
.content-padding {
|
||||||
|
padding-left: 24px !important;
|
||||||
|
padding-right: 24px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;", gothamBgColor(emailCtx)) }>
|
||||||
|
<!-- Preview text (hidden) -->
|
||||||
|
if emailCtx.PreviewText != "" {
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">
|
||||||
|
{ emailCtx.PreviewText }
|
||||||
|
</div>
|
||||||
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">
|
||||||
|
‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Main Container -->
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style={ fmt.Sprintf("padding: 48px 10px; background-color: %s;", gothamBgColor(emailCtx)) }>
|
||||||
|
<!-- Email Container -->
|
||||||
|
<table role="presentation" class="email-container" width="600" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s;", gothamCardColor(emailCtx), gothamBorderColor(emailCtx)) }>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<tr>
|
||||||
|
<td align="center" style={ fmt.Sprintf("padding: 32px 40px; background-color: %s; border-bottom: 1px solid %s;", gothamCardColor(emailCtx), gothamBorderColor(emailCtx)) }>
|
||||||
|
if emailCtx.SiteSettings.LogoURL != "" {
|
||||||
|
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="max-height: 48px; width: auto; display: block;"/>
|
||||||
|
} else if emailCtx.SiteSettings.SiteName != "" {
|
||||||
|
<h1 style={ fmt.Sprintf("margin: 0; font-size: 24px; font-weight: 800; color: %s; letter-spacing: -0.5px; text-transform: uppercase;", gothamFgColor(emailCtx)) }>
|
||||||
|
{ emailCtx.SiteSettings.SiteName }
|
||||||
|
</h1>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Body Content -->
|
||||||
|
<tr>
|
||||||
|
<td class="content-padding" style={ fmt.Sprintf("padding: 40px 48px; color: %s; font-size: 16px; line-height: 1.75; letter-spacing: 0.01em;", gothamFgColor(emailCtx)) }>
|
||||||
|
@templ.Raw(body)
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<tr>
|
||||||
|
<td style={ fmt.Sprintf("padding: 32px 48px; background-color: %s; border-top: 1px solid %s;", gothamMutedColor(emailCtx), gothamBorderColor(emailCtx)) }>
|
||||||
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<p style={ fmt.Sprintf("margin: 0 0 8px; font-size: 13px; font-weight: 700; color: %s; text-transform: uppercase; letter-spacing: 0.1em;", gothamFgColor(emailCtx)) }>
|
||||||
|
{ emailCtx.SiteSettings.SiteName }
|
||||||
|
</p>
|
||||||
|
if emailCtx.SiteSettings.SiteURL != "" {
|
||||||
|
<p style={ fmt.Sprintf("margin: 0 0 16px; font-size: 13px; color: %s;", gothamMutedFgColor(emailCtx)) }>
|
||||||
|
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style={ fmt.Sprintf("color: %s; text-decoration: none; border-bottom: 1px solid %s;", gothamPrimaryColor(emailCtx), gothamPrimaryColor(emailCtx)) }>
|
||||||
|
{ emailCtx.SiteSettings.SiteURL }
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
if emailCtx.UnsubscribeURL != "" {
|
||||||
|
<p style={ fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", gothamMutedFgColor(emailCtx)) }>
|
||||||
|
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: none;", gothamMutedFgColor(emailCtx)) }>
|
||||||
|
Unsubscribe
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
427
email_wrapper_templ.go
Normal file
427
email_wrapper_templ.go
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
// 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, "<!doctype html><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"x-apple-disable-message-reformatting\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><!--[if mso]>\n\t\t<noscript>\n\t\t\t<xml>\n\t\t\t\t<o:OfficeDocumentSettings>\n\t\t\t\t\t<o:PixelsPerInch>96</o:PixelsPerInch>\n\t\t\t\t</o:OfficeDocumentSettings>\n\t\t\t</xml>\n\t\t</noscript>\n\t\t<![endif]--><title>")
|
||||||
|
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, "</title><style type=\"text/css\">\n\t\t\t/* CSS Reset for Email */\n\t\t\tbody, table, td, p, a, li, blockquote {\n\t\t\t\t-webkit-text-size-adjust: 100%;\n\t\t\t\t-ms-text-size-adjust: 100%;\n\t\t\t}\n\t\t\ttable, td {\n\t\t\t\tmso-table-lspace: 0pt;\n\t\t\t\tmso-table-rspace: 0pt;\n\t\t\t}\n\t\t\timg {\n\t\t\t\t-ms-interpolation-mode: bicubic;\n\t\t\t\tborder: 0;\n\t\t\t\theight: auto;\n\t\t\t\tline-height: 100%;\n\t\t\t\toutline: none;\n\t\t\t\ttext-decoration: none;\n\t\t\t}\n\t\t\tbody {\n\t\t\t\tmargin: 0 !important;\n\t\t\t\tpadding: 0 !important;\n\t\t\t\twidth: 100% !important;\n\t\t\t}\n\t\t\ta[x-apple-data-detectors] {\n\t\t\t\tcolor: inherit !important;\n\t\t\t\ttext-decoration: none !important;\n\t\t\t}\n\t\t\t/* Gotham-specific styles */\n\t\t\th1, h2, h3, h4, h5, h6 {\n\t\t\t\tfont-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n\t\t\t\tfont-weight: 700;\n\t\t\t}\n\t\t\t/* Responsive styles */\n\t\t\t@media only screen and (max-width: 620px) {\n\t\t\t\t.email-container {\n\t\t\t\t\twidth: 100% !important;\n\t\t\t\t\tmax-width: 100% !important;\n\t\t\t\t}\n\t\t\t\t.content-padding {\n\t\t\t\t\tpadding-left: 24px !important;\n\t\t\t\t\tpadding-right: 24px !important;\n\t\t\t\t}\n\t\t\t}\n\t\t</style></head><body style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;", gothamBgColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 81, Col: 188}
|
||||||
|
}
|
||||||
|
_, 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, 3, "\"><!-- Preview text (hidden) -->")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if emailCtx.PreviewText != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div style=\"display: none; max-height: 0; overflow: hidden; mso-hide: all;\">")
|
||||||
|
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, "</div><div style=\"display: none; max-height: 0; overflow: hidden; mso-hide: all;\"> ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<!-- Main Container --><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\" style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 string
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 48px 10px; background-color: %s;", gothamBgColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 95, Col: 112}
|
||||||
|
}
|
||||||
|
_, 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, "\"><!-- Email Container --><table role=\"presentation\" class=\"email-container\" width=\"600\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s;", gothamCardColor(emailCtx), gothamBorderColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 97, Col: 251}
|
||||||
|
}
|
||||||
|
_, 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, 8, "\"><!-- Header --><tr><td align=\"center\" style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 32px 40px; background-color: %s; border-bottom: 1px solid %s;", gothamCardColor(emailCtx), gothamBorderColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 101, Col: 175}
|
||||||
|
}
|
||||||
|
_, 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, 9, "\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if emailCtx.SiteSettings.LogoURL != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<img src=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 string
|
||||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.LogoURL)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 103, Col: 49}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" alt=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var9 string
|
||||||
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.SiteName)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 103, Col: 88}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" style=\"max-height: 48px; width: auto; display: block;\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
} else if emailCtx.SiteSettings.SiteName != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h1 style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var10 string
|
||||||
|
templ_7745c5c3_Var10, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0; font-size: 24px; font-weight: 800; color: %s; letter-spacing: -0.5px; text-transform: uppercase;", gothamFgColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 105, Col: 168}
|
||||||
|
}
|
||||||
|
_, 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, 14, "\">")
|
||||||
|
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, "</h1>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</td></tr><!-- Body Content --><tr><td class=\"content-padding\" style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var12 string
|
||||||
|
templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 40px 48px; color: %s; font-size: 16px; line-height: 1.75; letter-spacing: 0.01em;", gothamFgColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 114, Col: 173}
|
||||||
|
}
|
||||||
|
_, 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, 17, "\">")
|
||||||
|
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, "</td></tr><!-- Footer --><tr><td style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var13 string
|
||||||
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 32px 48px; background-color: %s; border-top: 1px solid %s;", gothamMutedColor(emailCtx), gothamBorderColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 121, Col: 158}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\"><p style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var14 string
|
||||||
|
templ_7745c5c3_Var14, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0 0 8px; font-size: 13px; font-weight: 700; color: %s; text-transform: uppercase; letter-spacing: 0.1em;", gothamFgColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 125, Col: 174}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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, "</p>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if emailCtx.SiteSettings.SiteURL != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<p style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var16 string
|
||||||
|
templ_7745c5c3_Var16, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0 0 16px; font-size: 13px; color: %s;", gothamMutedFgColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 129, Col: 113}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\"><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var17 templ.SafeURL
|
||||||
|
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.SiteSettings.SiteURL))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 130, Col: 67}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var18 string
|
||||||
|
templ_7745c5c3_Var18, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: none; border-bottom: 1px solid %s;", gothamPrimaryColor(emailCtx), gothamPrimaryColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 130, Col: 215}
|
||||||
|
}
|
||||||
|
_, 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, 25, "\">")
|
||||||
|
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, "</a></p>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if emailCtx.UnsubscribeURL != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<p style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var20 string
|
||||||
|
templ_7745c5c3_Var20, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", gothamMutedFgColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 136, Col: 106}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\"><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var21 templ.SafeURL
|
||||||
|
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.UnsubscribeURL))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 137, Col: 61}
|
||||||
|
}
|
||||||
|
_, 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, 29, "\" style=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var22 string
|
||||||
|
templ_7745c5c3_Var22, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: none;", gothamMutedFgColor(emailCtx)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 137, Col: 150}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\">Unsubscribe</a></p>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</td></tr></table></td></tr></table></td></tr></table></body></html>")
|
||||||
|
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
|
||||||
49
embed.go
Normal file
49
embed.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
58
features.go
Normal file
58
features.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
41
features.templ
Normal file
41
features.templ
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// featuresComponent renders a Gotham-styled feature cards grid.
|
||||||
|
templ featuresComponent(data FeaturesData) {
|
||||||
|
<section class="py-20 flex-1">
|
||||||
|
<div class="max-w-6xl mx-auto px-4">
|
||||||
|
if data.SectionTitle != "" {
|
||||||
|
<h2 class="text-3xl font-bold text-center mb-12 gotham-accent">{ data.SectionTitle }</h2>
|
||||||
|
}
|
||||||
|
<div class={ "grid gap-8", featureGridCols(data.Columns) }>
|
||||||
|
for _, feature := range data.Features {
|
||||||
|
<div class="p-6 gotham-surface rounded-lg gotham-border border hover:border-primary/30 transition-colors">
|
||||||
|
if feature.Icon != "" {
|
||||||
|
<div class="mb-4 gotham-accent">
|
||||||
|
@iconSVG(feature.Icon)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<h3 class="text-xl font-semibold text-foreground mb-3">{ feature.Title }</h3>
|
||||||
|
if feature.Description != "" {
|
||||||
|
<p class="text-muted-foreground leading-relaxed">{ feature.Description }</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
160
features_templ.go
Normal file
160
features_templ.go
Normal file
@ -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"
|
||||||
|
|
||||||
|
// 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, "<section class=\"py-20 flex-1\"><div class=\"max-w-6xl mx-auto px-4\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if data.SectionTitle != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h2 class=\"text-3xl font-bold text-center mb-12 gotham-accent\">")
|
||||||
|
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, "</h2>")
|
||||||
|
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, "<div class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var3).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `features.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(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
|
||||||
|
}
|
||||||
|
for _, feature := range data.Features {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"p-6 gotham-surface rounded-lg gotham-border border hover:border-primary/30 transition-colors\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if feature.Icon != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"mb-4 gotham-accent\">")
|
||||||
|
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, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<h3 class=\"text-xl font-semibold text-foreground mb-3\">")
|
||||||
|
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, "</h3>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if feature.Description != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p class=\"text-muted-foreground leading-relaxed\">")
|
||||||
|
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, "</p>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></div></section>")
|
||||||
|
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
|
||||||
88
fonts.json
Normal file
88
fonts.json
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Gotham",
|
||||||
|
"family": "Gotham",
|
||||||
|
"variants": [
|
||||||
|
{
|
||||||
|
"weight": "100",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/web/gothamthin-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "100",
|
||||||
|
"style": "italic",
|
||||||
|
"file": "fonts/web/gothamthinitalic-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "200",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/web/gothamxlight-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "200",
|
||||||
|
"style": "italic",
|
||||||
|
"file": "fonts/web/gothamxlightitalic-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "300",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/web/gothamlight-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "300",
|
||||||
|
"style": "italic",
|
||||||
|
"file": "fonts/web/gothamlightitalic-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "400",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/web/gothambook-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "400",
|
||||||
|
"style": "italic",
|
||||||
|
"file": "fonts/web/gothambookitalic-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "500",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/web/gothammedium-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "500",
|
||||||
|
"style": "italic",
|
||||||
|
"file": "fonts/web/gothammediumitalic-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "700",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/web/gothambold-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "700",
|
||||||
|
"style": "italic",
|
||||||
|
"file": "fonts/web/gothambolditalic-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "800",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/web/gothamblack-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "800",
|
||||||
|
"style": "italic",
|
||||||
|
"file": "fonts/web/gothamblackitalic-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "900",
|
||||||
|
"style": "normal",
|
||||||
|
"file": "fonts/web/gothamultra-webfont.woff2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"weight": "900",
|
||||||
|
"style": "italic",
|
||||||
|
"file": "fonts/web/gothamultraitalic-webfont.woff2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
68
footer.go
Normal file
68
footer.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
63
footer.templ
Normal file
63
footer.templ
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// footerComponent renders a Gotham-styled multi-column footer.
|
||||||
|
// Note: The <footer> wrapper element is handled by the block wrapper (render/blocks.go)
|
||||||
|
templ footerComponent(data FooterData) {
|
||||||
|
<div class="max-w-6xl mx-auto px-4">
|
||||||
|
if len(data.Columns) > 0 {
|
||||||
|
<div class={ "grid gap-8 mb-12", footerGridCols(len(data.Columns)) }>
|
||||||
|
for _, col := range data.Columns {
|
||||||
|
<div>
|
||||||
|
if col.Heading != "" {
|
||||||
|
<h4 class="text-lg font-semibold gotham-accent mb-4">{ col.Heading }</h4>
|
||||||
|
}
|
||||||
|
if len(col.Links) > 0 {
|
||||||
|
<ul class="space-y-2">
|
||||||
|
for _, link := range col.Links {
|
||||||
|
<li>
|
||||||
|
<a href={ templ.SafeURL(getLinkURL(link)) } class="text-muted-foreground hover:text-foreground transition-colors">
|
||||||
|
{ link.Text }
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
if data.Copyright != "" {
|
||||||
|
<div class="pt-8 gotham-border border-t text-center">
|
||||||
|
<p class="text-muted-foreground text-sm">{ data.Copyright }</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// footerGridCols returns grid column class for footer.
|
||||||
|
func footerGridCols(count int) string {
|
||||||
|
switch count {
|
||||||
|
case 1:
|
||||||
|
return "grid-cols-1"
|
||||||
|
case 2:
|
||||||
|
return "grid-cols-1 md:grid-cols-2"
|
||||||
|
case 3:
|
||||||
|
return "grid-cols-1 md:grid-cols-3"
|
||||||
|
default:
|
||||||
|
return "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLinkURL returns the appropriate URL for a footer link.
|
||||||
|
// If PageID is set, constructs an internal link. Otherwise returns the URL.
|
||||||
|
func getLinkURL(link FooterLink) string {
|
||||||
|
if link.PageID != "" {
|
||||||
|
// Internal page links use the page ID as slug placeholder
|
||||||
|
// In production, this would be resolved to the actual page slug
|
||||||
|
return "/pages/" + link.PageID
|
||||||
|
}
|
||||||
|
if link.URL != "" {
|
||||||
|
return link.URL
|
||||||
|
}
|
||||||
|
return "#"
|
||||||
|
}
|
||||||
192
footer_templ.go
Normal file
192
footer_templ.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
// footerComponent renders a Gotham-styled multi-column footer.
|
||||||
|
// Note: The <footer> wrapper element is handled by the block wrapper (render/blocks.go)
|
||||||
|
func footerComponent(data FooterData) 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, "<div class=\"max-w-6xl mx-auto px-4\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if len(data.Columns) > 0 {
|
||||||
|
var templ_7745c5c3_Var2 = []any{"grid gap-8 mb-12", footerGridCols(len(data.Columns))}
|
||||||
|
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, 2, "<div class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
for _, col := range data.Columns {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if col.Heading != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<h4 class=\"text-lg font-semibold gotham-accent mb-4\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Heading)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 12, Col: 73}
|
||||||
|
}
|
||||||
|
_, 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, 6, "</h4>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(col.Links) > 0 {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<ul class=\"space-y-2\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
for _, link := range col.Links {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<li><a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 templ.SafeURL
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(getLinkURL(link)))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 18, Col: 51}
|
||||||
|
}
|
||||||
|
_, 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, 9, "\" class=\"text-muted-foreground hover:text-foreground transition-colors\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(link.Text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 19, Col: 22}
|
||||||
|
}
|
||||||
|
_, 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, 10, "</a></li>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</ul>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if data.Copyright != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"pt-8 gotham-border border-t text-center\"><p class=\"text-muted-foreground text-sm\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.Copyright)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 31, Col: 61}
|
||||||
|
}
|
||||||
|
_, 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, 15, "</p></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// footerGridCols returns grid column class for footer.
|
||||||
|
func footerGridCols(count int) string {
|
||||||
|
switch count {
|
||||||
|
case 1:
|
||||||
|
return "grid-cols-1"
|
||||||
|
case 2:
|
||||||
|
return "grid-cols-1 md:grid-cols-2"
|
||||||
|
case 3:
|
||||||
|
return "grid-cols-1 md:grid-cols-3"
|
||||||
|
default:
|
||||||
|
return "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLinkURL returns the appropriate URL for a footer link.
|
||||||
|
// If PageID is set, constructs an internal link. Otherwise returns the URL.
|
||||||
|
func getLinkURL(link FooterLink) string {
|
||||||
|
if link.PageID != "" {
|
||||||
|
// Internal page links use the page ID as slug placeholder
|
||||||
|
// In production, this would be resolved to the actual page slug
|
||||||
|
return "/pages/" + link.PageID
|
||||||
|
}
|
||||||
|
if link.URL != "" {
|
||||||
|
return link.URL
|
||||||
|
}
|
||||||
|
return "#"
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
20
go.mod
Normal file
20
go.mod
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module git.dev.alexdunmow.com/block/themes/gotham
|
||||||
|
|
||||||
|
go 1.26.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.dev.alexdunmow.com/block/core v0.10.1-0.20260603033123-041a7c2e3fae
|
||||||
|
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
|
||||||
|
)
|
||||||
42
go.sum
Normal file
42
go.sum
Normal file
@ -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.10.1-0.20260603033123-041a7c2e3fae h1:DKTKe7iMTxPpgnHFbGVDjbbORTsbyobjSiOdkcMpqB8=
|
||||||
|
git.dev.alexdunmow.com/block/core v0.10.1-0.20260603033123-041a7c2e3fae/go.mod h1:OPUWn+Ry75Lpi+rnYf9S8ZLrNeRsO+MTF8GyCii6Nx8=
|
||||||
|
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=
|
||||||
42
heading_override.go
Normal file
42
heading_override.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GothamHeadingBlock renders a heading with Gotham styling.
|
||||||
|
// Uses the same schema as the base heading block.
|
||||||
|
// Content expects: {"text": "Heading text", "level": 1-6, "textClass": "optional-class-for-h1"}
|
||||||
|
// Note: "class" is reserved for wrapper styling, use "textClass" for the heading element itself
|
||||||
|
func GothamHeadingBlock(ctx context.Context, content map[string]any) string {
|
||||||
|
text := getString(content, "text")
|
||||||
|
textClass := getString(content, "textClass")
|
||||||
|
level := parseHeadingLevel(content)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_ = gothamHeadingComponent(level, text, textClass).Render(ctx, &buf)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseHeadingLevel parses the level from content, defaulting to 2.
|
||||||
|
func parseHeadingLevel(content map[string]any) int {
|
||||||
|
if level, ok := content["level"].(float64); ok {
|
||||||
|
l := int(level)
|
||||||
|
if l >= 1 && l <= 6 {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if level, ok := content["level"].(int); ok {
|
||||||
|
if level >= 1 && level <= 6 {
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if level, ok := content["level"].(string); ok {
|
||||||
|
if l, err := strconv.Atoi(level); err == nil && l >= 1 && l <= 6 {
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
41
heading_override.templ
Normal file
41
heading_override.templ
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// gothamHeadingBaseClass returns default Tailwind classes for each heading level.
|
||||||
|
func gothamHeadingBaseClass(level int) string {
|
||||||
|
switch level {
|
||||||
|
case 1:
|
||||||
|
return "text-4xl font-bold tracking-tight"
|
||||||
|
case 2:
|
||||||
|
return "text-3xl 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gothamHeadingComponent renders a heading with Gotham accent styling.
|
||||||
|
templ gothamHeadingComponent(level int, text, textClass string) {
|
||||||
|
switch level {
|
||||||
|
case 1:
|
||||||
|
<h1 class={ gothamHeadingBaseClass(1), "text-accent", textClass }>{ text }</h1>
|
||||||
|
case 2:
|
||||||
|
<h2 class={ gothamHeadingBaseClass(2), "text-accent", textClass }>{ text }</h2>
|
||||||
|
case 3:
|
||||||
|
<h3 class={ gothamHeadingBaseClass(3), "text-accent", textClass }>{ text }</h3>
|
||||||
|
case 4:
|
||||||
|
<h4 class={ gothamHeadingBaseClass(4), "text-accent", textClass }>{ text }</h4>
|
||||||
|
case 5:
|
||||||
|
<h5 class={ gothamHeadingBaseClass(5), "text-accent", textClass }>{ text }</h5>
|
||||||
|
case 6:
|
||||||
|
<h6 class={ gothamHeadingBaseClass(6), "text-accent", textClass }>{ text }</h6>
|
||||||
|
default:
|
||||||
|
<h2 class={ gothamHeadingBaseClass(2), "text-accent", textClass }>{ text }</h2>
|
||||||
|
}
|
||||||
|
}
|
||||||
311
heading_override_templ.go
Normal file
311
heading_override_templ.go
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
// gothamHeadingBaseClass returns default Tailwind classes for each heading level.
|
||||||
|
func gothamHeadingBaseClass(level int) string {
|
||||||
|
switch level {
|
||||||
|
case 1:
|
||||||
|
return "text-4xl font-bold tracking-tight"
|
||||||
|
case 2:
|
||||||
|
return "text-3xl 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gothamHeadingComponent renders a heading with Gotham accent styling.
|
||||||
|
func gothamHeadingComponent(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{gothamHeadingBaseClass(1), "text-accent", 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, "<h1 class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 27, Col: 75}
|
||||||
|
}
|
||||||
|
_, 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, 3, "</h1>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
var templ_7745c5c3_Var5 = []any{gothamHeadingBaseClass(2), "text-accent", textClass}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h2 class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var5).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(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_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 29, Col: 75}
|
||||||
|
}
|
||||||
|
_, 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, 6, "</h2>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
var templ_7745c5c3_Var8 = []any{gothamHeadingBaseClass(3), "text-accent", textClass}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h3 class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var9 string
|
||||||
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var8).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(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
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var10 string
|
||||||
|
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 31, Col: 75}
|
||||||
|
}
|
||||||
|
_, 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, 9, "</h3>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
var templ_7745c5c3_Var11 = []any{gothamHeadingBaseClass(4), "text-accent", textClass}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<h4 class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var12 string
|
||||||
|
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var11).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(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
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var13 string
|
||||||
|
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 33, Col: 75}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</h4>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
case 5:
|
||||||
|
var templ_7745c5c3_Var14 = []any{gothamHeadingBaseClass(5), "text-accent", textClass}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h5 class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var15 string
|
||||||
|
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var14).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(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
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var16 string
|
||||||
|
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 35, Col: 75}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h5>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
var templ_7745c5c3_Var17 = []any{gothamHeadingBaseClass(6), "text-accent", textClass}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h6 class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var18 string
|
||||||
|
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(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
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var19 string
|
||||||
|
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 37, Col: 75}
|
||||||
|
}
|
||||||
|
_, 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, 18, "</h6>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
var templ_7745c5c3_Var20 = []any{gothamHeadingBaseClass(2), "text-accent", textClass}
|
||||||
|
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h2 class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var21 string
|
||||||
|
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var20).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(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
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var22 string
|
||||||
|
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 39, Col: 75}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</h2>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
34
helpers.go
Normal file
34
helpers.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// getString extracts a string value from content map.
|
||||||
|
func getString(content map[string]any, key string) string {
|
||||||
|
if v, ok := content[key].(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInt extracts an int value from content map (handles float64 from JSON).
|
||||||
|
func getInt(content map[string]any, key string, defaultVal int) int {
|
||||||
|
if v, ok := content[key].(float64); ok {
|
||||||
|
return int(v)
|
||||||
|
}
|
||||||
|
if v, ok := content[key].(int); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSlice extracts a slice of maps from content.
|
||||||
|
func getSlice(content map[string]any, key string) []map[string]any {
|
||||||
|
if v, ok := content[key].([]any); ok {
|
||||||
|
result := make([]map[string]any, 0, len(v))
|
||||||
|
for _, item := range v {
|
||||||
|
if m, ok := item.(map[string]any); ok {
|
||||||
|
result = append(result, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
41
hero.go
Normal file
41
hero.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.dev.alexdunmow.com/block/core/blocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeroBlockMeta defines metadata for the hero section block.
|
||||||
|
var HeroBlockMeta = blocks.BlockMeta{
|
||||||
|
Key: "hero",
|
||||||
|
Title: "Hero Section",
|
||||||
|
Description: "Large hero section with headline and optional CTA",
|
||||||
|
Source: "gotham",
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeroBlock renders a hero section.
|
||||||
|
// Content expects: {"headline": "...", "subheadline": "...", "background_url": "...", "cta_text": "...", "cta_url": "..."}
|
||||||
|
func HeroBlock(ctx context.Context, content map[string]any, _ []string) string {
|
||||||
|
data := HeroData{
|
||||||
|
Headline: getString(content, "headline"),
|
||||||
|
Subheadline: getString(content, "subheadline"),
|
||||||
|
BackgroundURL: getString(content, "background_url"),
|
||||||
|
CTAText: getString(content, "cta_text"),
|
||||||
|
CTAURL: getString(content, "cta_url"),
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_ = heroComponent(data).Render(ctx, &buf)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeroData contains data for the hero component.
|
||||||
|
type HeroData struct {
|
||||||
|
Headline string
|
||||||
|
Subheadline string
|
||||||
|
BackgroundURL string
|
||||||
|
CTAText string
|
||||||
|
CTAURL string
|
||||||
|
}
|
||||||
29
hero.templ
Normal file
29
hero.templ
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// heroComponent renders a Gotham-styled hero section.
|
||||||
|
templ heroComponent(data HeroData) {
|
||||||
|
<section class="relative py-32 overflow-hidden">
|
||||||
|
if data.BackgroundURL != "" {
|
||||||
|
<div class="absolute inset-0 z-0">
|
||||||
|
<img src={ data.BackgroundURL } alt="" class="w-full h-full object-cover opacity-20"/>
|
||||||
|
<div class="absolute inset-0 bg-gradient-to-b from-background/80 via-background/60 to-background"></div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="relative z-10 max-w-4xl mx-auto px-4 text-center">
|
||||||
|
<h1 class="text-5xl md:text-6xl font-bold mb-6">
|
||||||
|
<span class="gotham-accent">{ data.Headline }</span>
|
||||||
|
</h1>
|
||||||
|
if data.Subheadline != "" {
|
||||||
|
<p class="text-xl md:text-2xl text-muted-foreground mb-10 max-w-2xl mx-auto">{ data.Subheadline }</p>
|
||||||
|
}
|
||||||
|
if data.CTAText != "" && data.CTAURL != "" {
|
||||||
|
<a
|
||||||
|
href={ templ.SafeURL(data.CTAURL) }
|
||||||
|
class="inline-block px-8 py-4 gotham-accent-bg text-primary-foreground font-semibold rounded-lg transition-colors text-lg"
|
||||||
|
>
|
||||||
|
{ data.CTAText }
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
132
hero_templ.go
Normal file
132
hero_templ.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
// heroComponent renders a Gotham-styled hero section.
|
||||||
|
func heroComponent(data HeroData) 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, "<section class=\"relative py-32 overflow-hidden\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if data.BackgroundURL != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"absolute inset-0 z-0\"><img src=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 string
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.BackgroundURL)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero.templ`, Line: 8, Col: 33}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" alt=\"\" class=\"w-full h-full object-cover opacity-20\"><div class=\"absolute inset-0 bg-gradient-to-b from-background/80 via-background/60 to-background\"></div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"relative z-10 max-w-4xl mx-auto px-4 text-center\"><h1 class=\"text-5xl md:text-6xl font-bold mb-6\"><span class=\"gotham-accent\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Headline)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero.templ`, Line: 14, Col: 47}
|
||||||
|
}
|
||||||
|
_, 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, 5, "</span></h1>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if data.Subheadline != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<p class=\"text-xl md:text-2xl text-muted-foreground mb-10 max-w-2xl mx-auto\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var4 string
|
||||||
|
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Subheadline)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero.templ`, Line: 17, Col: 99}
|
||||||
|
}
|
||||||
|
_, 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, 7, "</p>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if data.CTAText != "" && data.CTAURL != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<a href=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var5 templ.SafeURL
|
||||||
|
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.CTAURL))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero.templ`, Line: 21, Col: 38}
|
||||||
|
}
|
||||||
|
_, 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, 9, "\" class=\"inline-block px-8 py-4 gotham-accent-bg text-primary-foreground font-semibold rounded-lg transition-colors text-lg\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 string
|
||||||
|
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.CTAText)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero.templ`, Line: 24, Col: 19}
|
||||||
|
}
|
||||||
|
_, 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, 10, "</a>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></section>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
8
plugin.mod
Normal file
8
plugin.mod
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[plugin]
|
||||||
|
name = "gotham"
|
||||||
|
display_name = "Gotham"
|
||||||
|
scope = "@themes"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Dark, modern BlockNinja theme with bold typography and high-contrast accents."
|
||||||
|
kind = "plugin"
|
||||||
|
categories = ["templates"]
|
||||||
502
presets.json
Normal file
502
presets.json
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "dark-knight",
|
||||||
|
"name": "The Dark Knight",
|
||||||
|
"description": "Classic Gotham - dark with amber accents",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "0 0% 3.9%",
|
||||||
|
"card": "0 0% 100%",
|
||||||
|
"cardForeground": "0 0% 3.9%",
|
||||||
|
"popover": "0 0% 100%",
|
||||||
|
"popoverForeground": "0 0% 3.9%",
|
||||||
|
"primary": "45 93% 47%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "0 0% 96%",
|
||||||
|
"secondaryForeground": "0 0% 9%",
|
||||||
|
"muted": "0 0% 96%",
|
||||||
|
"mutedForeground": "0 0% 45%",
|
||||||
|
"accent": "45 93% 47%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "0 0% 90%",
|
||||||
|
"input": "0 0% 90%",
|
||||||
|
"ring": "45 93% 47%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 3.9%",
|
||||||
|
"foreground": "0 0% 98%",
|
||||||
|
"card": "0 0% 6%",
|
||||||
|
"cardForeground": "0 0% 98%",
|
||||||
|
"popover": "0 0% 6%",
|
||||||
|
"popoverForeground": "0 0% 98%",
|
||||||
|
"primary": "45 93% 47%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "0 0% 10%",
|
||||||
|
"secondaryForeground": "0 0% 98%",
|
||||||
|
"muted": "0 0% 10%",
|
||||||
|
"mutedForeground": "0 0% 60%",
|
||||||
|
"accent": "45 93% 47%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 62% 30%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "0 0% 14%",
|
||||||
|
"input": "0 0% 14%",
|
||||||
|
"ring": "45 93% 47%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "joker",
|
||||||
|
"name": "The Joker",
|
||||||
|
"description": "Chaotic purple and toxic green",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "270 50% 15%",
|
||||||
|
"card": "0 0% 98%",
|
||||||
|
"cardForeground": "270 50% 15%",
|
||||||
|
"popover": "0 0% 98%",
|
||||||
|
"popoverForeground": "270 50% 15%",
|
||||||
|
"primary": "120 80% 35%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "270 40% 90%",
|
||||||
|
"secondaryForeground": "270 50% 15%",
|
||||||
|
"muted": "270 20% 95%",
|
||||||
|
"mutedForeground": "270 30% 40%",
|
||||||
|
"accent": "300 100% 70%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "270 20% 85%",
|
||||||
|
"input": "270 20% 85%",
|
||||||
|
"ring": "120 80% 35%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "270 30% 8%",
|
||||||
|
"foreground": "120 100% 80%",
|
||||||
|
"card": "270 30% 12%",
|
||||||
|
"cardForeground": "120 100% 80%",
|
||||||
|
"popover": "270 30% 12%",
|
||||||
|
"popoverForeground": "120 100% 80%",
|
||||||
|
"primary": "120 100% 40%",
|
||||||
|
"primaryForeground": "270 30% 8%",
|
||||||
|
"secondary": "270 50% 20%",
|
||||||
|
"secondaryForeground": "120 100% 80%",
|
||||||
|
"muted": "270 30% 15%",
|
||||||
|
"mutedForeground": "270 20% 60%",
|
||||||
|
"accent": "300 100% 50%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "270 30% 20%",
|
||||||
|
"input": "270 30% 20%",
|
||||||
|
"ring": "120 100% 40%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "riddler",
|
||||||
|
"name": "The Riddler",
|
||||||
|
"description": "Bright neon green with purple mystery",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "0 0% 5%",
|
||||||
|
"card": "0 0% 98%",
|
||||||
|
"cardForeground": "0 0% 5%",
|
||||||
|
"popover": "0 0% 98%",
|
||||||
|
"popoverForeground": "0 0% 5%",
|
||||||
|
"primary": "120 70% 35%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "280 50% 85%",
|
||||||
|
"secondaryForeground": "280 80% 20%",
|
||||||
|
"muted": "0 0% 96%",
|
||||||
|
"mutedForeground": "120 30% 35%",
|
||||||
|
"accent": "280 80% 55%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "120 30% 85%",
|
||||||
|
"input": "0 0% 90%",
|
||||||
|
"ring": "120 70% 35%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 4%",
|
||||||
|
"foreground": "120 100% 50%",
|
||||||
|
"card": "0 0% 8%",
|
||||||
|
"cardForeground": "120 100% 50%",
|
||||||
|
"popover": "0 0% 8%",
|
||||||
|
"popoverForeground": "120 100% 50%",
|
||||||
|
"primary": "120 100% 45%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "280 80% 30%",
|
||||||
|
"secondaryForeground": "120 100% 50%",
|
||||||
|
"muted": "0 0% 12%",
|
||||||
|
"mutedForeground": "120 50% 40%",
|
||||||
|
"accent": "280 100% 60%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "120 100% 20%",
|
||||||
|
"input": "0 0% 15%",
|
||||||
|
"ring": "120 100% 45%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "harley-quinn",
|
||||||
|
"name": "Harley Quinn",
|
||||||
|
"description": "Bold red, black, and playful pink",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "0 0% 5%",
|
||||||
|
"card": "350 50% 98%",
|
||||||
|
"cardForeground": "0 0% 5%",
|
||||||
|
"popover": "350 50% 98%",
|
||||||
|
"popoverForeground": "0 0% 5%",
|
||||||
|
"primary": "350 85% 50%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "210 80% 90%",
|
||||||
|
"secondaryForeground": "210 100% 20%",
|
||||||
|
"muted": "0 0% 96%",
|
||||||
|
"mutedForeground": "350 20% 40%",
|
||||||
|
"accent": "330 100% 75%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "350 30% 88%",
|
||||||
|
"input": "0 0% 90%",
|
||||||
|
"ring": "350 85% 50%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 5%",
|
||||||
|
"foreground": "0 0% 95%",
|
||||||
|
"card": "350 80% 10%",
|
||||||
|
"cardForeground": "0 0% 95%",
|
||||||
|
"popover": "350 80% 10%",
|
||||||
|
"popoverForeground": "0 0% 95%",
|
||||||
|
"primary": "350 100% 55%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "210 100% 50%",
|
||||||
|
"secondaryForeground": "0 0% 100%",
|
||||||
|
"muted": "0 0% 12%",
|
||||||
|
"mutedForeground": "350 30% 60%",
|
||||||
|
"accent": "330 100% 65%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "350 50% 20%",
|
||||||
|
"input": "0 0% 15%",
|
||||||
|
"ring": "350 100% 55%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mr-freeze",
|
||||||
|
"name": "Mr. Freeze",
|
||||||
|
"description": "Cold ice blue and frozen white",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "200 60% 10%",
|
||||||
|
"card": "200 40% 98%",
|
||||||
|
"cardForeground": "200 60% 10%",
|
||||||
|
"popover": "200 40% 98%",
|
||||||
|
"popoverForeground": "200 60% 10%",
|
||||||
|
"primary": "195 90% 45%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "200 40% 92%",
|
||||||
|
"secondaryForeground": "200 60% 10%",
|
||||||
|
"muted": "200 30% 95%",
|
||||||
|
"mutedForeground": "200 30% 40%",
|
||||||
|
"accent": "180 80% 60%",
|
||||||
|
"accentForeground": "200 60% 10%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "200 30% 88%",
|
||||||
|
"input": "200 30% 85%",
|
||||||
|
"ring": "195 90% 45%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "200 50% 8%",
|
||||||
|
"foreground": "200 100% 95%",
|
||||||
|
"card": "200 50% 12%",
|
||||||
|
"cardForeground": "200 100% 95%",
|
||||||
|
"popover": "200 50% 12%",
|
||||||
|
"popoverForeground": "200 100% 95%",
|
||||||
|
"primary": "195 100% 50%",
|
||||||
|
"primaryForeground": "200 50% 8%",
|
||||||
|
"secondary": "200 60% 20%",
|
||||||
|
"secondaryForeground": "200 100% 95%",
|
||||||
|
"muted": "200 40% 15%",
|
||||||
|
"mutedForeground": "200 40% 60%",
|
||||||
|
"accent": "180 100% 70%",
|
||||||
|
"accentForeground": "200 50% 8%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "200 40% 25%",
|
||||||
|
"input": "200 40% 20%",
|
||||||
|
"ring": "195 100% 50%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "poison-ivy",
|
||||||
|
"name": "Poison Ivy",
|
||||||
|
"description": "Deep forest green with seductive red",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "140 50% 12%",
|
||||||
|
"card": "140 30% 98%",
|
||||||
|
"cardForeground": "140 50% 12%",
|
||||||
|
"popover": "140 30% 98%",
|
||||||
|
"popoverForeground": "140 50% 12%",
|
||||||
|
"primary": "140 60% 35%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "0 50% 92%",
|
||||||
|
"secondaryForeground": "0 70% 25%",
|
||||||
|
"muted": "140 20% 95%",
|
||||||
|
"mutedForeground": "140 25% 40%",
|
||||||
|
"accent": "0 70% 55%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "140 25% 88%",
|
||||||
|
"input": "140 20% 85%",
|
||||||
|
"ring": "140 60% 35%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "140 40% 6%",
|
||||||
|
"foreground": "140 60% 90%",
|
||||||
|
"card": "140 40% 10%",
|
||||||
|
"cardForeground": "140 60% 90%",
|
||||||
|
"popover": "140 40% 10%",
|
||||||
|
"popoverForeground": "140 60% 90%",
|
||||||
|
"primary": "140 70% 40%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "0 70% 40%",
|
||||||
|
"secondaryForeground": "0 0% 100%",
|
||||||
|
"muted": "140 30% 15%",
|
||||||
|
"mutedForeground": "140 30% 55%",
|
||||||
|
"accent": "0 80% 50%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "140 30% 20%",
|
||||||
|
"input": "140 30% 15%",
|
||||||
|
"ring": "140 70% 40%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "two-face",
|
||||||
|
"name": "Two-Face",
|
||||||
|
"description": "Split personality - half dark, half light",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "0 0% 5%",
|
||||||
|
"card": "0 0% 98%",
|
||||||
|
"cardForeground": "0 0% 5%",
|
||||||
|
"popover": "0 0% 98%",
|
||||||
|
"popoverForeground": "0 0% 5%",
|
||||||
|
"primary": "0 0% 10%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "0 0% 92%",
|
||||||
|
"secondaryForeground": "0 0% 10%",
|
||||||
|
"muted": "0 0% 96%",
|
||||||
|
"mutedForeground": "0 0% 45%",
|
||||||
|
"accent": "45 100% 50%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "0 0% 88%",
|
||||||
|
"input": "0 0% 85%",
|
||||||
|
"ring": "0 0% 10%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "0 0% 5%",
|
||||||
|
"foreground": "0 0% 95%",
|
||||||
|
"card": "0 0% 10%",
|
||||||
|
"cardForeground": "0 0% 95%",
|
||||||
|
"popover": "0 0% 10%",
|
||||||
|
"popoverForeground": "0 0% 95%",
|
||||||
|
"primary": "0 0% 95%",
|
||||||
|
"primaryForeground": "0 0% 5%",
|
||||||
|
"secondary": "0 0% 20%",
|
||||||
|
"secondaryForeground": "0 0% 95%",
|
||||||
|
"muted": "0 0% 15%",
|
||||||
|
"mutedForeground": "0 0% 55%",
|
||||||
|
"accent": "45 100% 50%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "0 0% 25%",
|
||||||
|
"input": "0 0% 20%",
|
||||||
|
"ring": "0 0% 95%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "catwoman",
|
||||||
|
"name": "Catwoman",
|
||||||
|
"description": "Sleek purple and black elegance",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "280 30% 15%",
|
||||||
|
"card": "280 20% 98%",
|
||||||
|
"cardForeground": "280 30% 15%",
|
||||||
|
"popover": "280 20% 98%",
|
||||||
|
"popoverForeground": "280 30% 15%",
|
||||||
|
"primary": "280 50% 45%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "280 25% 92%",
|
||||||
|
"secondaryForeground": "280 30% 15%",
|
||||||
|
"muted": "280 15% 96%",
|
||||||
|
"mutedForeground": "280 15% 40%",
|
||||||
|
"accent": "320 70% 60%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "280 20% 88%",
|
||||||
|
"input": "280 15% 85%",
|
||||||
|
"ring": "280 50% 45%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "280 20% 6%",
|
||||||
|
"foreground": "280 10% 90%",
|
||||||
|
"card": "280 20% 10%",
|
||||||
|
"cardForeground": "280 10% 90%",
|
||||||
|
"popover": "280 20% 10%",
|
||||||
|
"popoverForeground": "280 10% 90%",
|
||||||
|
"primary": "280 60% 55%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "280 30% 20%",
|
||||||
|
"secondaryForeground": "280 10% 90%",
|
||||||
|
"muted": "280 15% 15%",
|
||||||
|
"mutedForeground": "280 10% 55%",
|
||||||
|
"accent": "320 80% 60%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "280 20% 22%",
|
||||||
|
"input": "280 20% 18%",
|
||||||
|
"ring": "280 60% 55%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "scarecrow",
|
||||||
|
"name": "Scarecrow",
|
||||||
|
"description": "Fear-inducing orange and decayed brown",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "30 40% 15%",
|
||||||
|
"card": "35 30% 98%",
|
||||||
|
"cardForeground": "30 40% 15%",
|
||||||
|
"popover": "35 30% 98%",
|
||||||
|
"popoverForeground": "30 40% 15%",
|
||||||
|
"primary": "25 85% 45%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "30 30% 92%",
|
||||||
|
"secondaryForeground": "30 40% 15%",
|
||||||
|
"muted": "30 20% 95%",
|
||||||
|
"mutedForeground": "30 25% 40%",
|
||||||
|
"accent": "15 90% 50%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "30 25% 88%",
|
||||||
|
"input": "30 20% 85%",
|
||||||
|
"ring": "25 85% 45%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "30 30% 6%",
|
||||||
|
"foreground": "35 60% 85%",
|
||||||
|
"card": "30 30% 10%",
|
||||||
|
"cardForeground": "35 60% 85%",
|
||||||
|
"popover": "30 30% 10%",
|
||||||
|
"popoverForeground": "35 60% 85%",
|
||||||
|
"primary": "25 90% 50%",
|
||||||
|
"primaryForeground": "0 0% 0%",
|
||||||
|
"secondary": "30 40% 20%",
|
||||||
|
"secondaryForeground": "35 60% 85%",
|
||||||
|
"muted": "30 25% 15%",
|
||||||
|
"mutedForeground": "30 20% 50%",
|
||||||
|
"accent": "15 100% 45%",
|
||||||
|
"accentForeground": "0 0% 100%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "30 30% 20%",
|
||||||
|
"input": "30 30% 15%",
|
||||||
|
"ring": "25 90% 50%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bane",
|
||||||
|
"name": "Bane",
|
||||||
|
"description": "Military tactical green and black",
|
||||||
|
"theme": {
|
||||||
|
"lightColors": {
|
||||||
|
"background": "0 0% 100%",
|
||||||
|
"foreground": "100 30% 15%",
|
||||||
|
"card": "100 20% 98%",
|
||||||
|
"cardForeground": "100 30% 15%",
|
||||||
|
"popover": "100 20% 98%",
|
||||||
|
"popoverForeground": "100 30% 15%",
|
||||||
|
"primary": "100 45% 30%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "100 20% 92%",
|
||||||
|
"secondaryForeground": "100 30% 15%",
|
||||||
|
"muted": "100 15% 95%",
|
||||||
|
"mutedForeground": "100 20% 40%",
|
||||||
|
"accent": "45 75% 50%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "100 20% 88%",
|
||||||
|
"input": "100 15% 85%",
|
||||||
|
"ring": "100 45% 30%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "100 20% 5%",
|
||||||
|
"foreground": "100 10% 85%",
|
||||||
|
"card": "100 20% 9%",
|
||||||
|
"cardForeground": "100 10% 85%",
|
||||||
|
"popover": "100 20% 9%",
|
||||||
|
"popoverForeground": "100 10% 85%",
|
||||||
|
"primary": "100 50% 35%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "100 25% 18%",
|
||||||
|
"secondaryForeground": "100 10% 85%",
|
||||||
|
"muted": "100 15% 14%",
|
||||||
|
"mutedForeground": "100 10% 50%",
|
||||||
|
"accent": "45 80% 45%",
|
||||||
|
"accentForeground": "0 0% 0%",
|
||||||
|
"destructive": "0 84% 60%",
|
||||||
|
"destructiveForeground": "0 0% 98%",
|
||||||
|
"border": "100 20% 18%",
|
||||||
|
"input": "100 20% 14%",
|
||||||
|
"ring": "100 50% 35%"
|
||||||
|
},
|
||||||
|
"mode": "dark"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
116
register.go
Normal file
116
register.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
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 Gotham template and blocks.
|
||||||
|
// Returns an error if template registration fails (e.g., validation failure).
|
||||||
|
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
|
||||||
|
// Register the Gotham system template with metadata
|
||||||
|
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
|
||||||
|
Key: "gotham",
|
||||||
|
Title: "Gotham",
|
||||||
|
Description: "Dark, modern theme with bold typography",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Register page templates for the Gotham system template
|
||||||
|
if err := tr.RegisterPageTemplate("gotham", templates.PageTemplateMeta{
|
||||||
|
Key: "default",
|
||||||
|
Title: "Default",
|
||||||
|
Description: "Standard dark page layout with header, main content, and footer",
|
||||||
|
Slots: []string{"header", "main", "footer"},
|
||||||
|
}, wrap(RenderGotham)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tr.RegisterPageTemplate("gotham", templates.PageTemplateMeta{
|
||||||
|
Key: "landing",
|
||||||
|
Title: "Landing Page",
|
||||||
|
Description: "Full-width hero sections, ideal for marketing pages",
|
||||||
|
Slots: []string{"hero", "main", "cta", "footer"},
|
||||||
|
}, wrap(RenderGothamLanding)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tr.RegisterPageTemplate("gotham", templates.PageTemplateMeta{
|
||||||
|
Key: "full-width",
|
||||||
|
Title: "Full Width",
|
||||||
|
Description: "Edge-to-edge content without max-width constraints",
|
||||||
|
Slots: []string{"header", "main", "footer"},
|
||||||
|
}, wrap(RenderGothamFullWidth)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tr.RegisterPageTemplate("gotham", templates.PageTemplateMeta{
|
||||||
|
Key: "centered",
|
||||||
|
Title: "Centered Content",
|
||||||
|
Description: "Narrow, centered content for articles and documentation",
|
||||||
|
Slots: []string{"header", "main", "footer"},
|
||||||
|
}, wrap(RenderGothamCentered)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register Gotham-specific blocks
|
||||||
|
br.Register(StatsBlockMeta, StatsBlock)
|
||||||
|
br.Register(FeaturesBlockMeta, FeaturesBlock)
|
||||||
|
br.Register(FooterBlockMeta, FooterBlock)
|
||||||
|
|
||||||
|
// Register overrides for built-in blocks when using Gotham template
|
||||||
|
br.RegisterTemplateOverride("gotham", "heading", GothamHeadingBlock)
|
||||||
|
br.RegisterTemplateOverride("gotham", "text", GothamTextBlock)
|
||||||
|
|
||||||
|
// Register email wrapper for branded communications
|
||||||
|
tr.RegisterEmailWrapper("gotham", GothamEmailWrapper)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultMasterPages returns the default master pages that Gotham provides.
|
||||||
|
// These are created automatically when the plugin is first loaded.
|
||||||
|
func DefaultMasterPages() []plugin.MasterPageDefinition {
|
||||||
|
return []plugin.MasterPageDefinition{
|
||||||
|
{
|
||||||
|
Key: "gotham:default-master",
|
||||||
|
Title: "Gotham Default Master",
|
||||||
|
PageTemplates: []string{"default", "centered"},
|
||||||
|
Blocks: []plugin.MasterPageBlock{
|
||||||
|
{
|
||||||
|
BlockKey: "navbar",
|
||||||
|
Title: "Main Navigation",
|
||||||
|
Content: map[string]any{"menuName": "main"},
|
||||||
|
Slot: "header",
|
||||||
|
SortOrder: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BlockKey: "slot",
|
||||||
|
Title: "Main Content Slot",
|
||||||
|
Content: map[string]any{"slotName": "main", "placeholder": "Page content will appear here"},
|
||||||
|
Slot: "main",
|
||||||
|
SortOrder: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BlockKey: "gotham:footer",
|
||||||
|
Title: "Site Footer",
|
||||||
|
Content: map[string]any{"showSignup": true},
|
||||||
|
Slot: "footer",
|
||||||
|
SortOrder: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
24
registration.go
Normal file
24
registration.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.dev.alexdunmow.com/block/core/blocks"
|
||||||
|
"git.dev.alexdunmow.com/block/core/templates"
|
||||||
|
"git.dev.alexdunmow.com/block/core/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Registration is the compile-time plugin registration for the Gotham template.
|
||||||
|
var Registration = plugin.PluginRegistration{
|
||||||
|
Name: "gotham",
|
||||||
|
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() },
|
||||||
|
}
|
||||||
55
schemas/features.schema.json
Normal file
55
schemas/features.schema.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Features Block",
|
||||||
|
"description": "Grid of feature cards with icons and descriptions",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"section_title": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Section Title",
|
||||||
|
"description": "Optional title above the feature cards",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Columns",
|
||||||
|
"description": "Number of columns (1-4)",
|
||||||
|
"x-editor": "number",
|
||||||
|
"minimum": 1,
|
||||||
|
"maximum": 4,
|
||||||
|
"default": 3
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Features",
|
||||||
|
"description": "List of feature cards",
|
||||||
|
"default": [],
|
||||||
|
"x-editor": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Icon",
|
||||||
|
"description": "Icon name (chart, users, clock, star)",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["", "chart", "users", "clock", "star"]
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Title",
|
||||||
|
"description": "Feature title",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Description",
|
||||||
|
"description": "Feature description",
|
||||||
|
"x-editor": "textarea"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
schemas/footer.schema.json
Normal file
63
schemas/footer.schema.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Footer Block",
|
||||||
|
"description": "Multi-column footer with links and copyright",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"copyright": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Copyright Text",
|
||||||
|
"description": "Copyright notice displayed at the bottom",
|
||||||
|
"default": "© 2025 Your Company. All rights reserved.",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Footer Columns",
|
||||||
|
"description": "Columns with headings and links",
|
||||||
|
"x-editor": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"heading": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Column Heading",
|
||||||
|
"description": "Title for this column",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Links",
|
||||||
|
"description": "Links in this column",
|
||||||
|
"x-editor": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Link Text",
|
||||||
|
"description": "Display text for the link",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"page_id": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Page",
|
||||||
|
"description": "Select an internal page (overrides URL)",
|
||||||
|
"x-editor": "page-select"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "External URL",
|
||||||
|
"description": "External URL (used if no page selected)",
|
||||||
|
"x-editor": "url"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["text"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["heading"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
schemas/hero.schema.json
Normal file
39
schemas/hero.schema.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Hero Block",
|
||||||
|
"description": "Large hero section with headline and optional CTA",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"headline": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Headline",
|
||||||
|
"description": "Main headline text",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"subheadline": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Subheadline",
|
||||||
|
"description": "Supporting text below the headline",
|
||||||
|
"x-editor": "textarea"
|
||||||
|
},
|
||||||
|
"background_url": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Background Image URL",
|
||||||
|
"description": "URL for the background image",
|
||||||
|
"x-editor": "url"
|
||||||
|
},
|
||||||
|
"cta_text": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "CTA Button Text",
|
||||||
|
"description": "Text for the call-to-action button",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"cta_url": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "CTA Button URL",
|
||||||
|
"description": "Link for the call-to-action button",
|
||||||
|
"x-editor": "url"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["headline"]
|
||||||
|
}
|
||||||
29
schemas/stat_item.schema.json
Normal file
29
schemas/stat_item.schema.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Stat Item",
|
||||||
|
"description": "A single statistic with value, label, and optional icon",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"value": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Value",
|
||||||
|
"description": "The statistic value (e.g., '100+', '99%')",
|
||||||
|
"default": "0",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Label",
|
||||||
|
"description": "Description of the statistic",
|
||||||
|
"default": "Stat Label",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Icon",
|
||||||
|
"description": "Icon name (chart, users, clock, star)",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["", "chart", "users", "clock", "star"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
schemas/stats.schema.json
Normal file
40
schemas/stats.schema.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Stats Block",
|
||||||
|
"description": "Display statistics with labels and values",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Statistics",
|
||||||
|
"description": "List of statistic items to display",
|
||||||
|
"x-editor": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"value": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Value",
|
||||||
|
"description": "The statistic value (e.g., '100+', '99%')",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Label",
|
||||||
|
"description": "Description of the statistic",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Icon",
|
||||||
|
"description": "Icon name (chart, users, clock, star)",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["", "chart", "users", "clock", "star"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["value", "label"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": []
|
||||||
|
}
|
||||||
35
stat_item.go
Normal file
35
stat_item.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.dev.alexdunmow.com/block/core/blocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatItemBlockMeta defines metadata for a single stat item.
|
||||||
|
var StatItemBlockMeta = blocks.BlockMeta{
|
||||||
|
Key: "stat_item",
|
||||||
|
Title: "Stat Item",
|
||||||
|
Description: "A single statistic with value, label, and optional icon",
|
||||||
|
Source: "gotham",
|
||||||
|
Hidden: true, // Only available as child of stats block
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatItemBlock renders a single stat item.
|
||||||
|
// Content expects: {"value": "100+", "label": "Projects", "icon": "chart"}
|
||||||
|
func StatItemBlock(ctx context.Context, content map[string]any) string {
|
||||||
|
item := StatItem{
|
||||||
|
Value: getString(content, "value"),
|
||||||
|
Label: getString(content, "label"),
|
||||||
|
Icon: getString(content, "icon"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Value == "" && item.Label == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_ = statItemComponent(item).Render(ctx, &buf)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
14
stat_item.templ
Normal file
14
stat_item.templ
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// statItemComponent renders a single Gotham-styled stat item.
|
||||||
|
templ statItemComponent(stat StatItem) {
|
||||||
|
<div class="text-center p-6 rounded-lg gotham-border border">
|
||||||
|
if stat.Icon != "" {
|
||||||
|
<div class="mb-3 gotham-accent">
|
||||||
|
@iconSVG(stat.Icon)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="text-4xl font-bold gotham-accent mb-2">{ stat.Value }</div>
|
||||||
|
<div class="text-muted-foreground uppercase text-sm tracking-wider">{ stat.Label }</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
85
stat_item_templ.go
Normal file
85
stat_item_templ.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
// statItemComponent renders a single Gotham-styled stat item.
|
||||||
|
func statItemComponent(stat StatItem) 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, "<div class=\"text-center p-6 rounded-lg gotham-border border\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if stat.Icon != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"mb-3 gotham-accent\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = iconSVG(stat.Icon).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"text-4xl font-bold gotham-accent mb-2\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 string
|
||||||
|
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Value)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stat_item.templ`, Line: 11, Col: 65}
|
||||||
|
}
|
||||||
|
_, 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, "</div><div class=\"text-muted-foreground uppercase text-sm tracking-wider\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Label)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stat_item.templ`, Line: 12, Col: 82}
|
||||||
|
}
|
||||||
|
_, 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, 6, "</div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
48
stats.go
Normal file
48
stats.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.dev.alexdunmow.com/block/core/blocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatsBlockMeta defines metadata for the stats counter block.
|
||||||
|
// Stats uses a schema-defined items array, not child blocks.
|
||||||
|
var StatsBlockMeta = blocks.BlockMeta{
|
||||||
|
Key: "stats",
|
||||||
|
Title: "Stats Counter",
|
||||||
|
Description: "Display statistics with labels and values",
|
||||||
|
Source: "gotham",
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatsBlock renders a stats counter grid from content.items array.
|
||||||
|
func StatsBlock(ctx context.Context, content map[string]any) string {
|
||||||
|
items := getSlice(content, "items")
|
||||||
|
if len(items) == 0 {
|
||||||
|
// Empty state - show placeholder
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_ = statsEmptyComponent().Render(ctx, &buf)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
var stats []StatItem
|
||||||
|
for _, item := range items {
|
||||||
|
stats = append(stats, StatItem{
|
||||||
|
Value: getString(item, "value"),
|
||||||
|
Label: getString(item, "label"),
|
||||||
|
Icon: getString(item, "icon"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_ = statsComponent(stats).Render(ctx, &buf)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatItem represents a single statistic.
|
||||||
|
type StatItem struct {
|
||||||
|
Value string
|
||||||
|
Label string
|
||||||
|
Icon string
|
||||||
|
}
|
||||||
84
stats.templ
Normal file
84
stats.templ
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// statsContainerComponent renders the stats grid container with pre-rendered children.
|
||||||
|
templ statsContainerComponent(childCount int, childrenHTML string) {
|
||||||
|
<section class="py-16 gotham-surface flex-1">
|
||||||
|
<div class="max-w-6xl mx-auto px-4">
|
||||||
|
<div class={ "grid gap-8", gridCols(childCount) }>
|
||||||
|
@templ.Raw(childrenHTML)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
// statsEmptyComponent renders an empty placeholder for stats without items.
|
||||||
|
templ statsEmptyComponent() {
|
||||||
|
<section class="py-16 gotham-surface flex-1">
|
||||||
|
<div class="max-w-6xl mx-auto px-4">
|
||||||
|
<div class="text-center p-8 border border-dashed border-border rounded-lg">
|
||||||
|
<p class="text-muted-foreground">Click + to add stat items</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
// statsComponent renders a Gotham-styled stats counter grid (legacy content-based).
|
||||||
|
templ statsComponent(items []StatItem) {
|
||||||
|
<section class="py-16 gotham-surface flex-1">
|
||||||
|
<div class="max-w-6xl mx-auto px-4">
|
||||||
|
<div class={ "grid gap-8", gridCols(len(items)) }>
|
||||||
|
for _, stat := range items {
|
||||||
|
<div class="text-center p-6 rounded-lg gotham-border border">
|
||||||
|
if stat.Icon != "" {
|
||||||
|
<div class="mb-3 gotham-accent">
|
||||||
|
@iconSVG(stat.Icon)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="text-4xl font-bold gotham-accent mb-2">{ stat.Value }</div>
|
||||||
|
<div class="text-muted-foreground uppercase text-sm tracking-wider">{ stat.Label }</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
// gridCols returns the appropriate grid column class based on item count.
|
||||||
|
func gridCols(count int) string {
|
||||||
|
switch count {
|
||||||
|
case 1:
|
||||||
|
return "grid-cols-1"
|
||||||
|
case 2:
|
||||||
|
return "grid-cols-1 md:grid-cols-2"
|
||||||
|
case 3:
|
||||||
|
return "grid-cols-1 md:grid-cols-3"
|
||||||
|
default:
|
||||||
|
return "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iconSVG renders a simple icon based on name.
|
||||||
|
templ iconSVG(name string) {
|
||||||
|
switch name {
|
||||||
|
case "chart":
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
||||||
|
</svg>
|
||||||
|
case "users":
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
|
||||||
|
</svg>
|
||||||
|
case "clock":
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
</svg>
|
||||||
|
case "star":
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"/>
|
||||||
|
</svg>
|
||||||
|
default:
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-10 h-10 mx-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
}
|
||||||
274
stats_templ.go
Normal file
274
stats_templ.go
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
// statsContainerComponent renders the stats grid container with pre-rendered children.
|
||||||
|
func statsContainerComponent(childCount int, childrenHTML 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, "<section class=\"py-16 gotham-surface flex-1\"><div class=\"max-w-6xl mx-auto px-4\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var2 = []any{"grid gap-8", gridCols(childCount)}
|
||||||
|
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, 2, "<div class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||||
|
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 = templ.Raw(childrenHTML).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div></div></section>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// statsEmptyComponent renders an empty placeholder for stats without items.
|
||||||
|
func statsEmptyComponent() 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, 5, "<section class=\"py-16 gotham-surface flex-1\"><div class=\"max-w-6xl mx-auto px-4\"><div class=\"text-center p-8 border border-dashed border-border rounded-lg\"><p class=\"text-muted-foreground\">Click + to add stat items</p></div></div></section>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// statsComponent renders a Gotham-styled stats counter grid (legacy content-based).
|
||||||
|
func statsComponent(items []StatItem) 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, 6, "<section class=\"py-16 gotham-surface flex-1\"><div class=\"max-w-6xl mx-auto px-4\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var6 = []any{"grid gap-8", gridCols(len(items))}
|
||||||
|
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, 7, "<div class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var7 string
|
||||||
|
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var6).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
for _, stat := range items {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"text-center p-6 rounded-lg gotham-border border\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if stat.Icon != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"mb-3 gotham-accent\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = iconSVG(stat.Icon).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"text-4xl font-bold gotham-accent mb-2\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var8 string
|
||||||
|
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Value)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats.templ`, Line: 37, Col: 69}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div><div class=\"text-muted-foreground uppercase text-sm tracking-wider\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var9 string
|
||||||
|
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Label)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats.templ`, Line: 38, Col: 86}
|
||||||
|
}
|
||||||
|
_, 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, 14, "</div></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></div></section>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// gridCols returns the appropriate grid column class based on item count.
|
||||||
|
func gridCols(count int) string {
|
||||||
|
switch count {
|
||||||
|
case 1:
|
||||||
|
return "grid-cols-1"
|
||||||
|
case 2:
|
||||||
|
return "grid-cols-1 md:grid-cols-2"
|
||||||
|
case 3:
|
||||||
|
return "grid-cols-1 md:grid-cols-3"
|
||||||
|
default:
|
||||||
|
return "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// iconSVG renders a simple icon based on name.
|
||||||
|
func iconSVG(name 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_Var10 := templ.GetChildren(ctx)
|
||||||
|
if templ_7745c5c3_Var10 == nil {
|
||||||
|
templ_7745c5c3_Var10 = templ.NopComponent
|
||||||
|
}
|
||||||
|
ctx = templ.ClearChildren(ctx)
|
||||||
|
switch name {
|
||||||
|
case "chart":
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"w-10 h-10 mx-auto\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z\"></path></svg>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
case "users":
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"w-10 h-10 mx-auto\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z\"></path></svg>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
case "clock":
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"w-10 h-10 mx-auto\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\"></path></svg>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
case "star":
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"w-10 h-10 mx-auto\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z\"></path></svg>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"w-10 h-10 mx-auto\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M13 10V3L4 14h7v7l9-11h-7z\"></path></svg>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
239
template.templ
Normal file
239
template.templ
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.dev.alexdunmow.com/block/core/templates/bn"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGothamPageData(doc map[string]any) PageData {
|
||||||
|
title := "Untitled"
|
||||||
|
if t, ok := doc["title"].(string); ok {
|
||||||
|
title = t
|
||||||
|
}
|
||||||
|
|
||||||
|
slots := make(map[string]string)
|
||||||
|
if s, ok := doc["slots"].(map[string]string); ok {
|
||||||
|
slots = s
|
||||||
|
}
|
||||||
|
|
||||||
|
themeCSS := ""
|
||||||
|
if tc, ok := doc["theme_css"].(string); ok {
|
||||||
|
themeCSS = tc
|
||||||
|
}
|
||||||
|
|
||||||
|
structuredData := ""
|
||||||
|
if sd, ok := doc["structured_data"].(string); ok {
|
||||||
|
structuredData = sd
|
||||||
|
}
|
||||||
|
|
||||||
|
cssHash := ""
|
||||||
|
if ch, ok := doc["css_hash"].(string); ok {
|
||||||
|
cssHash = ch
|
||||||
|
}
|
||||||
|
|
||||||
|
pageviewNonce := ""
|
||||||
|
if pn, ok := doc["pageview_nonce"].(string); ok {
|
||||||
|
pageviewNonce = pn
|
||||||
|
}
|
||||||
|
|
||||||
|
themeMode := "light"
|
||||||
|
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
|
||||||
|
themeMode = tm
|
||||||
|
}
|
||||||
|
|
||||||
|
siteSettings := bn.ParseSiteSettings(doc)
|
||||||
|
pageMeta := bn.ParsePageMeta(doc)
|
||||||
|
engagementConfig := bn.ParseEngagementConfig(doc)
|
||||||
|
|
||||||
|
return PageData{Title: title, Slots: slots, ThemeMode: themeMode, ThemeCSS: themeCSS, SiteSettings: siteSettings, PageMeta: pageMeta, StructuredData: structuredData, CSSHash: cssHash, PageviewNonce: pageviewNonce, EngagementConfig: engagementConfig}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default Gotham page template
|
||||||
|
templ Gotham(data PageData) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="dark">
|
||||||
|
@bn.Head(bn.HeadData{
|
||||||
|
Title: data.Title,
|
||||||
|
Settings: data.SiteSettings,
|
||||||
|
PageMeta: data.PageMeta,
|
||||||
|
ThemeMode: data.ThemeMode,
|
||||||
|
ThemeCSS: data.ThemeCSS,
|
||||||
|
PluginStyles: []string{"/templates/gotham/style.css"},
|
||||||
|
StructuredData: data.StructuredData,
|
||||||
|
CSSHash: data.CSSHash,
|
||||||
|
PageviewNonce: data.PageviewNonce,
|
||||||
|
EngagementConfig: data.EngagementConfig,
|
||||||
|
})
|
||||||
|
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
|
@bn.AdminBypassBanner(data.SiteSettings)
|
||||||
|
<header class="w-full">
|
||||||
|
@templ.Raw(data.Slots["header"])
|
||||||
|
</header>
|
||||||
|
<main class="flex-grow max-w-4xl mx-auto w-full px-4 py-8">
|
||||||
|
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||||
|
@templ.Raw(main)
|
||||||
|
} else {
|
||||||
|
<div class="py-20 text-center">
|
||||||
|
<p class="text-muted-foreground">No content blocks assigned to this page.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</main>
|
||||||
|
<footer class="w-full mt-auto">
|
||||||
|
@templ.Raw(data.Slots["footer"])
|
||||||
|
</footer>
|
||||||
|
@bn.BodyEnd(data.SiteSettings)
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Landing page template - full-width hero sections for marketing pages
|
||||||
|
templ GothamLanding(data PageData) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="dark">
|
||||||
|
@bn.Head(bn.HeadData{
|
||||||
|
Title: data.Title,
|
||||||
|
Settings: data.SiteSettings,
|
||||||
|
PageMeta: data.PageMeta,
|
||||||
|
ThemeMode: data.ThemeMode,
|
||||||
|
ThemeCSS: data.ThemeCSS,
|
||||||
|
PluginStyles: []string{"/templates/gotham/style.css"},
|
||||||
|
StructuredData: data.StructuredData,
|
||||||
|
CSSHash: data.CSSHash,
|
||||||
|
PageviewNonce: data.PageviewNonce,
|
||||||
|
EngagementConfig: data.EngagementConfig,
|
||||||
|
})
|
||||||
|
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
|
@bn.AdminBypassBanner(data.SiteSettings)
|
||||||
|
// Hero section - full width
|
||||||
|
<section class="w-full">
|
||||||
|
@templ.Raw(data.Slots["hero"])
|
||||||
|
</section>
|
||||||
|
// Main content
|
||||||
|
<main class="flex-grow">
|
||||||
|
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||||
|
<div class="max-w-6xl mx-auto px-4 py-16">
|
||||||
|
@templ.Raw(main)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</main>
|
||||||
|
// Call to action section - full width
|
||||||
|
<section class="w-full">
|
||||||
|
@templ.Raw(data.Slots["cta"])
|
||||||
|
</section>
|
||||||
|
// Footer
|
||||||
|
<footer class="w-full mt-auto">
|
||||||
|
@templ.Raw(data.Slots["footer"])
|
||||||
|
</footer>
|
||||||
|
@bn.BodyEnd(data.SiteSettings)
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full-width page template - edge-to-edge content
|
||||||
|
templ GothamFullWidth(data PageData) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="dark">
|
||||||
|
@bn.Head(bn.HeadData{
|
||||||
|
Title: data.Title,
|
||||||
|
Settings: data.SiteSettings,
|
||||||
|
PageMeta: data.PageMeta,
|
||||||
|
ThemeMode: data.ThemeMode,
|
||||||
|
ThemeCSS: data.ThemeCSS,
|
||||||
|
PluginStyles: []string{"/templates/gotham/style.css"},
|
||||||
|
StructuredData: data.StructuredData,
|
||||||
|
CSSHash: data.CSSHash,
|
||||||
|
PageviewNonce: data.PageviewNonce,
|
||||||
|
EngagementConfig: data.EngagementConfig,
|
||||||
|
})
|
||||||
|
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
|
@bn.AdminBypassBanner(data.SiteSettings)
|
||||||
|
<header class="w-full">
|
||||||
|
@templ.Raw(data.Slots["header"])
|
||||||
|
</header>
|
||||||
|
<main class="flex-grow w-full">
|
||||||
|
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||||
|
@templ.Raw(main)
|
||||||
|
} else {
|
||||||
|
<div class="max-w-4xl mx-auto py-20 px-4 text-center">
|
||||||
|
<p class="text-muted-foreground">No content blocks assigned to this page.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</main>
|
||||||
|
<footer class="w-full mt-auto">
|
||||||
|
@templ.Raw(data.Slots["footer"])
|
||||||
|
</footer>
|
||||||
|
@bn.BodyEnd(data.SiteSettings)
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Centered content page template - narrow, centered layout for articles
|
||||||
|
templ GothamCentered(data PageData) {
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="dark">
|
||||||
|
@bn.Head(bn.HeadData{
|
||||||
|
Title: data.Title,
|
||||||
|
Settings: data.SiteSettings,
|
||||||
|
PageMeta: data.PageMeta,
|
||||||
|
ThemeMode: data.ThemeMode,
|
||||||
|
ThemeCSS: data.ThemeCSS,
|
||||||
|
PluginStyles: []string{"/templates/gotham/style.css"},
|
||||||
|
StructuredData: data.StructuredData,
|
||||||
|
CSSHash: data.CSSHash,
|
||||||
|
PageviewNonce: data.PageviewNonce,
|
||||||
|
EngagementConfig: data.EngagementConfig,
|
||||||
|
})
|
||||||
|
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
|
||||||
|
@bn.AdminBypassBanner(data.SiteSettings)
|
||||||
|
<header class="w-full border-b border-border">
|
||||||
|
<div class="max-w-2xl mx-auto px-4">
|
||||||
|
@templ.Raw(data.Slots["header"])
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="flex-grow max-w-2xl mx-auto w-full px-4 py-12">
|
||||||
|
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||||
|
<article class="prose prose-invert prose-lg max-w-none">
|
||||||
|
@templ.Raw(main)
|
||||||
|
</article>
|
||||||
|
} else {
|
||||||
|
<div class="py-20 text-center">
|
||||||
|
<p class="text-muted-foreground">No content blocks assigned to this page.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</main>
|
||||||
|
<footer class="w-full mt-auto">
|
||||||
|
@templ.Raw(data.Slots["footer"])
|
||||||
|
</footer>
|
||||||
|
@bn.BodyEnd(data.SiteSettings)
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderGotham(ctx context.Context, doc map[string]any) templ.Component {
|
||||||
|
return Gotham(parseGothamPageData(doc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderGothamLanding(ctx context.Context, doc map[string]any) templ.Component {
|
||||||
|
return GothamLanding(parseGothamPageData(doc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderGothamFullWidth(ctx context.Context, doc map[string]any) templ.Component {
|
||||||
|
return GothamFullWidth(parseGothamPageData(doc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderGothamCentered(ctx context.Context, doc map[string]any) templ.Component {
|
||||||
|
return GothamCentered(parseGothamPageData(doc))
|
||||||
|
}
|
||||||
491
template_templ.go
Normal file
491
template_templ.go
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
// 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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGothamPageData(doc map[string]any) PageData {
|
||||||
|
title := "Untitled"
|
||||||
|
if t, ok := doc["title"].(string); ok {
|
||||||
|
title = t
|
||||||
|
}
|
||||||
|
|
||||||
|
slots := make(map[string]string)
|
||||||
|
if s, ok := doc["slots"].(map[string]string); ok {
|
||||||
|
slots = s
|
||||||
|
}
|
||||||
|
|
||||||
|
themeCSS := ""
|
||||||
|
if tc, ok := doc["theme_css"].(string); ok {
|
||||||
|
themeCSS = tc
|
||||||
|
}
|
||||||
|
|
||||||
|
structuredData := ""
|
||||||
|
if sd, ok := doc["structured_data"].(string); ok {
|
||||||
|
structuredData = sd
|
||||||
|
}
|
||||||
|
|
||||||
|
cssHash := ""
|
||||||
|
if ch, ok := doc["css_hash"].(string); ok {
|
||||||
|
cssHash = ch
|
||||||
|
}
|
||||||
|
|
||||||
|
pageviewNonce := ""
|
||||||
|
if pn, ok := doc["pageview_nonce"].(string); ok {
|
||||||
|
pageviewNonce = pn
|
||||||
|
}
|
||||||
|
|
||||||
|
themeMode := "light"
|
||||||
|
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
|
||||||
|
themeMode = tm
|
||||||
|
}
|
||||||
|
|
||||||
|
siteSettings := bn.ParseSiteSettings(doc)
|
||||||
|
pageMeta := bn.ParsePageMeta(doc)
|
||||||
|
engagementConfig := bn.ParseEngagementConfig(doc)
|
||||||
|
|
||||||
|
return PageData{Title: title, Slots: slots, ThemeMode: themeMode, ThemeCSS: themeCSS, SiteSettings: siteSettings, PageMeta: pageMeta, StructuredData: structuredData, CSSHash: cssHash, PageviewNonce: pageviewNonce, EngagementConfig: engagementConfig}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default Gotham page template
|
||||||
|
func Gotham(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_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, "<!doctype html><html lang=\"en\" class=\"dark\">")
|
||||||
|
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/gotham/style.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, 2, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col\">")
|
||||||
|
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 = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<header class=\"w-full\">")
|
||||||
|
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, 4, "</header><main class=\"flex-grow max-w-4xl mx-auto w-full px-4 py-8\">")
|
||||||
|
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, 5, "<div class=\"py-20 text-center\"><p class=\"text-muted-foreground\">No content blocks assigned to this page.</p></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</main><footer class=\"w-full mt-auto\">")
|
||||||
|
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, 7, "</footer>")
|
||||||
|
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, 8, "</body></html>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Landing page template - full-width hero sections for marketing pages
|
||||||
|
func GothamLanding(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, 9, "<!doctype html><html lang=\"en\" class=\"dark\">")
|
||||||
|
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/gotham/style.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, 10, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col\">")
|
||||||
|
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 = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<section class=\"w-full\">")
|
||||||
|
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, 12, "</section><main class=\"flex-grow\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"max-w-6xl mx-auto px-4 py-16\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</main><section class=\"w-full\">")
|
||||||
|
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, 16, "</section><footer class=\"w-full mt-auto\">")
|
||||||
|
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, 17, "</footer>")
|
||||||
|
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, 18, "</body></html>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full-width page template - edge-to-edge content
|
||||||
|
func GothamFullWidth(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, 19, "<!doctype html><html lang=\"en\" class=\"dark\">")
|
||||||
|
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/gotham/style.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, 20, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col\">")
|
||||||
|
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 = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<header class=\"w-full\">")
|
||||||
|
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, 22, "</header><main class=\"flex-grow w-full\">")
|
||||||
|
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, 23, "<div class=\"max-w-4xl mx-auto py-20 px-4 text-center\"><p class=\"text-muted-foreground\">No content blocks assigned to this page.</p></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</main><footer class=\"w-full mt-auto\">")
|
||||||
|
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, 25, "</footer>")
|
||||||
|
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, 26, "</body></html>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Centered content page template - narrow, centered layout for articles
|
||||||
|
func GothamCentered(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, 27, "<!doctype html><html lang=\"en\" class=\"dark\">")
|
||||||
|
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/gotham/style.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, 28, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col\">")
|
||||||
|
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 = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<header class=\"w-full border-b border-border\"><div class=\"max-w-2xl mx-auto px-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, 30, "</div></header><main class=\"flex-grow max-w-2xl mx-auto w-full px-4 py-12\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<article class=\"prose prose-invert prose-lg max-w-none\">")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</article>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div class=\"py-20 text-center\"><p class=\"text-muted-foreground\">No content blocks assigned to this page.</p></div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</main><footer class=\"w-full mt-auto\">")
|
||||||
|
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, 35, "</footer>")
|
||||||
|
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, 36, "</body></html>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderGotham(ctx context.Context, doc map[string]any) templ.Component {
|
||||||
|
return Gotham(parseGothamPageData(doc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderGothamLanding(ctx context.Context, doc map[string]any) templ.Component {
|
||||||
|
return GothamLanding(parseGothamPageData(doc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderGothamFullWidth(ctx context.Context, doc map[string]any) templ.Component {
|
||||||
|
return GothamFullWidth(parseGothamPageData(doc))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderGothamCentered(ctx context.Context, doc map[string]any) templ.Component {
|
||||||
|
return GothamCentered(parseGothamPageData(doc))
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
17
text_override.go
Normal file
17
text_override.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GothamTextBlock renders text with Gotham styling.
|
||||||
|
// Uses the same schema as the base text block.
|
||||||
|
func GothamTextBlock(ctx context.Context, content map[string]any) string {
|
||||||
|
text := getString(content, "text")
|
||||||
|
class := getString(content, "class")
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_ = gothamTextComponent(text, class).Render(ctx, &buf)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
8
text_override.templ
Normal file
8
text_override.templ
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// gothamTextComponent renders text with Gotham's dark theme styling.
|
||||||
|
templ gothamTextComponent(text, class string) {
|
||||||
|
<div class={ "prose prose-invert prose-amber max-w-none flex-1", class }>
|
||||||
|
@templ.Raw(text)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
67
text_override_templ.go
Normal file
67
text_override_templ.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
// gothamTextComponent renders text with Gotham's dark theme styling.
|
||||||
|
func gothamTextComponent(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{"prose prose-invert prose-amber max-w-none flex-1", 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, "<div class=\"")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
var templ_7745c5c3_Var3 string
|
||||||
|
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ.Error{Err: templ_7745c5c3_Err, FileName: `text_override.templ`, Line: 1, Col: 0}
|
||||||
|
}
|
||||||
|
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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, "</div>")
|
||||||
|
if templ_7745c5c3_Err != nil {
|
||||||
|
return templ_7745c5c3_Err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = templruntime.GeneratedTemplate
|
||||||
Loading…
x
Reference in New Issue
Block a user