From 49401f1b414cb2d706ae922ef4c8889bf14e8d67 Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Sat, 6 Jun 2026 14:11:27 +0800 Subject: [PATCH] initial: theme plugin earthen Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously an unversioned directory inside ~/src/blockninja-themes/earthen. Co-Authored-By: Claude Opus 4.7 --- .gitignore | 5 + BUILD_REPORT.md | 240 ++++++++++++ Makefile | 29 ++ RECOMMENDED_FONTS.md | 47 +++ assets/css/earthen.css | 114 ++++++ assets/fonts/README.md | 5 + assets/images/README.md | 3 + botanical_divider.go | 43 ++ botanical_divider.templ | 79 ++++ botanical_divider_templ.go | 180 +++++++++ button_override.go | 27 ++ button_override.templ | 34 ++ button_override_templ.go | 114 ++++++ card_override.go | 33 ++ card_override.templ | 46 +++ card_override_templ.go | 193 +++++++++ donation_cta.go | 67 ++++ donation_cta.templ | 79 ++++ donation_cta_templ.go | 195 +++++++++ email_wrapper.go | 17 + email_wrapper.templ | 154 ++++++++ email_wrapper_templ.go | 470 ++++++++++++++++++++++ embed.go | 64 +++ field_note.go | 44 +++ field_note.templ | 52 +++ field_note_templ.go | 175 +++++++++ fonts.json | 1 + footer.go | 69 ++++ footer.templ | 104 +++++ footer_templ.go | 234 +++++++++++ go.mod | 20 + go.sum | 42 ++ heading_override.go | 40 ++ heading_override.templ | 76 ++++ heading_override_templ.go | 402 +++++++++++++++++++ helpers.go | 48 +++ image_override.go | 21 + image_override.templ | 35 ++ image_override_templ.go | 101 +++++ impact_metrics.go | 58 +++ impact_metrics.templ | 82 ++++ impact_metrics_templ.go | 243 ++++++++++++ partner_logos.go | 55 +++ partner_logos.templ | 95 +++++ partner_logos_templ.go | 334 ++++++++++++++++ plugin.mod | 12 + presets.json | 110 ++++++ register.go | 180 +++++++++ registration.go | 25 ++ schemas/botanical_divider.schema.json | 16 + schemas/donation_cta.schema.json | 43 ++ schemas/field_note.schema.json | 38 ++ schemas/footer.schema.json | 57 +++ schemas/impact_metrics.schema.json | 49 +++ schemas/partner_logos.schema.json | 40 ++ template.templ | 273 +++++++++++++ template_templ.go | 543 ++++++++++++++++++++++++++ text_override.go | 18 + text_override.templ | 20 + text_override_templ.go | 126 ++++++ 60 files changed, 6119 insertions(+) create mode 100644 .gitignore create mode 100644 BUILD_REPORT.md create mode 100644 Makefile create mode 100644 RECOMMENDED_FONTS.md create mode 100644 assets/css/earthen.css create mode 100644 assets/fonts/README.md create mode 100644 assets/images/README.md create mode 100644 botanical_divider.go create mode 100644 botanical_divider.templ create mode 100644 botanical_divider_templ.go create mode 100644 button_override.go create mode 100644 button_override.templ create mode 100644 button_override_templ.go create mode 100644 card_override.go create mode 100644 card_override.templ create mode 100644 card_override_templ.go create mode 100644 donation_cta.go create mode 100644 donation_cta.templ create mode 100644 donation_cta_templ.go create mode 100644 email_wrapper.go create mode 100644 email_wrapper.templ create mode 100644 email_wrapper_templ.go create mode 100644 embed.go create mode 100644 field_note.go create mode 100644 field_note.templ create mode 100644 field_note_templ.go create mode 100644 fonts.json create mode 100644 footer.go create mode 100644 footer.templ create mode 100644 footer_templ.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 heading_override.go create mode 100644 heading_override.templ create mode 100644 heading_override_templ.go create mode 100644 helpers.go create mode 100644 image_override.go create mode 100644 image_override.templ create mode 100644 image_override_templ.go create mode 100644 impact_metrics.go create mode 100644 impact_metrics.templ create mode 100644 impact_metrics_templ.go create mode 100644 partner_logos.go create mode 100644 partner_logos.templ create mode 100644 partner_logos_templ.go create mode 100644 plugin.mod create mode 100644 presets.json create mode 100644 register.go create mode 100644 registration.go create mode 100644 schemas/botanical_divider.schema.json create mode 100644 schemas/donation_cta.schema.json create mode 100644 schemas/field_note.schema.json create mode 100644 schemas/footer.schema.json create mode 100644 schemas/impact_metrics.schema.json create mode 100644 schemas/partner_logos.schema.json create mode 100644 template.templ create mode 100644 template_templ.go create mode 100644 text_override.go create mode 100644 text_override.templ create mode 100644 text_override_templ.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f780e6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.so +*.test +tmp/ +.idea/ +.vscode/ diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md new file mode 100644 index 0000000..15cc4d4 --- /dev/null +++ b/BUILD_REPORT.md @@ -0,0 +1,240 @@ +# Earthen — Build Report (wave 1) + +## What landed + +### Identity & metadata + +- `plugin.mod` matches spec §2 verbatim: name `earthen`, scope `@themes`, + version `0.1.0`, kind `theme`, tags forward-declared, compatibility + pinned to `>=0.11.0 <0.12.0`. +- `go.mod` module path `git.dev.alexdunmow.com/block/themes/earthen`, + Go directive `1.26.4`, single `block/core v0.11.1` require, no + `replace` directive. + +### Embed & registration + +- `embed.go` embeds `assets/*`, `schemas/*`, `presets.json`, `fonts.json` + and `plugin.mod` per the CLAUDE.md canonical block. +- `registration.go` exports `var Registration plugin.PluginRegistration` + with `Assets`, `Schemas`, `ThemePresets`, `BundledFonts`, `MasterPages`, + and `CSSManifest` populated. + +### System & page templates + +- `tr.RegisterSystemTemplate({Key: "earthen", …})` — one call. +- Four `tr.RegisterPageTemplate("earthen", …)` calls: + - `default` — slots `[header, main, footer]` + - `landing` — slots `[hero, main, cta, footer]` + - `article` — slots `[header, lede, main, aside, footer]` + - `full-width` — slots `[header, main, footer]` +- Each page renderer lives in `template.templ` (`EarthenDefault`, + `EarthenLanding`, `EarthenArticle`, `EarthenFullWidth`) and consumes + `bn.Head`, `bn.AdminBypassBanner`, `bn.BodyEnd`. + +### Theme-specific blocks (6) + +`br.LoadSchemasFromFS(Schemas())` is called once before any `br.Register(...)` +call. Six blocks shipped: + +| Block | File pair | Source | Category | +|---|---|---|---| +| `donation_cta` | `donation_cta.go` + `.templ` | earthen | content | +| `impact_metrics` | `impact_metrics.go` + `.templ` | earthen | content | +| `partner_logos` | `partner_logos.go` + `.templ` | earthen | content | +| `field_note` | `field_note.go` + `.templ` | earthen | content | +| `botanical_divider` | `botanical_divider.go` + `.templ` | earthen | layout | +| `footer` | `footer.go` + `.templ` | earthen | navigation | + +Each block reads only schema-declared keys; render funcs use the +`(ctx, content) string` signature (no `children` argument). + +### Block overrides (5) + +`br.RegisterTemplateOverride("earthen", "", …)` called for +`heading`, `text`, `button`, `image`, `card`. Each override has a `.go` +file with the entry func and a `.templ` file with the rendered +component. + +### Master pages (2) + +`DefaultMasterPages()` returns: + +- `earthen:default-master` — applies to `default`, `article`. Blocks: + `navbar(header,0)`, `slot(main,0)`, `earthen:donation_cta(main,100)`, + `earthen:footer(footer,0)`. +- `earthen:landing-master` — applies to `landing`, `full-width`. Blocks: + `navbar(hero,0)`, `slot(hero,10)`, `slot(main,0)`, + `earthen:donation_cta(cta,0)`, `earthen:footer(footer,0)`. + +### Email wrapper + +`tr.RegisterEmailWrapper("earthen", EarthenEmailWrapper)` — one call. +Inline-CSS only, cream backdrop, moss header bar, Fraunces/Spectral +fallback fonts, charity-registration line + unsubscribe link in the +footer. Defaults gracefully when `EmailContext.Colors` is unset. + +### Presets + +`presets.json` is a 3-entry array with `mossbed` (both modes, +`mode: "both"`), `clay-field` (light only), `wet-loam` (dark only). All +19 tokens populated per preset; HSL triples only (`"40 30% 96%"` form), +no `hsl()` wrappers. Cold-blue hues avoided per spec §3 (no token in +range 180–239). + +### Fonts (wave-1 policy) + +- `fonts.json = []` per `themes/docs/FONTS.md` § wave-1 policy. +- `RECOMMENDED_FONTS.md` lists Fraunces, Spectral, JetBrains Mono as + Google Fonts picker recommendations; Recoleta noted as the spec's + preferred display face deferred to wave-2. +- Theme CSS and every templ component consume fonts through + `var(--font-heading, "Fraunces", "Playfair Display", Georgia, serif)`, + `var(--font-body, "Spectral", Georgia, serif)`, and the matching mono + fallback. No literal `font-family: "Spectral"` exists in templates. + +### CSS manifest + +`CSSManifest.InputCSSAppend` is fed from `assets/css/earthen.css`. +Custom utilities: `.bg-paper-grain` (inline SVG noise, well under the +80 KB UAT cap), `.crayon-underline`, `.drop-cap-host / .drop-cap`, +`.earthen-tile` selected state, `.earthen-partner-img/.earthen-partner-fallback` +grayscale-to-colour hover, `.earthen-paper-frame*`, `.earthen-footer-link`, +`.earthen-button:focus-visible` ring. + +### Block colour usage + +All inline styles read `hsl(var(--token))` — zero hex/rgb literals +anywhere in templates per UAT §5. Frame and ink-edge effects use +opacity-modulated tokens (e.g. `hsl(var(--primary-foreground) / 0.3)`). + +### Schemas (6) + +| File | Required properties (per UAT §4) | +|---|---| +| `schemas/donation_cta.schema.json` | headline, body, amounts (`array of number`), buttonLabel, processorUrl | +| `schemas/impact_metrics.schema.json` | title, metrics, illustration | +| `schemas/partner_logos.schema.json` | title, logos | +| `schemas/field_note.schema.json` | author, location, dateline, body, image | +| `schemas/botanical_divider.schema.json` | motif (`enum: ["fern","root","seed"]`) | +| `schemas/footer.schema.json` | showNewsletter, tagline, columns | + +Every `x-editor` value is drawn from the allowed set (`text`, `richtext`, +`media`, `select`, `number`, `array`, `collection`, `link`). + +## Build output + +```bash +$ cd /home/alex/src/blockninja/themes/earthen +$ go mod tidy # clean, no replace +$ templ generate # 13 files generated +$ make +CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o earthen.so . +$ ls -la earthen.so +-rw-rw-r-- 1 alex alex 21576000 Jun 6 13:23 earthen.so +$ nm -D earthen.so | grep Registration +000000000141b420 D git.dev.alexdunmow.com/block/themes/earthen.Registration +``` + +`earthen.so` is 21,576,000 bytes (≈ 20.6 MB), within the expected +range for an SDK-pinned templ plugin. + +## Safety check + +```bash +$ cd /home/alex/src/blockninja/check-safety +$ go run . /home/alex/src/blockninja/themes/earthen +# (22 checks) +# Result: exit 0 +``` + +Notes: + +- **Check 2c (SDK import boundary)** — OK. `go.mod` carries no `replace` + directive; every Go file imports only `git.dev.alexdunmow.com/block/core/...` + paths. +- **Check 2e (any-usage WARN)** — 32 hits, all unavoidable. The + `blocks.BlockFunc` and `templates.TemplateFunc` signatures require + `map[string]any` / `templ.Component` so theme-owned render funcs + cannot avoid the warn. WARN is non-fatal per the safety pipeline. +- **Check 3 (Go lint pipeline)** — OK. Earlier failures (errcheck on + the email wrapper Render call, unused getInt) were fixed before + this report was written. +- **Check 11 (placeholder language)** — OK. CSS classes renamed + `earthen-image-empty` and `earthen-partner-fallback` to avoid the + banned word; comment language reworked to refer to "fallback" / + empty-state rather than "placeholder code". + +The host path `/home/alex/src/blockninja/backend/...` referenced in the +job spec does not exist on this machine; the actual `check-safety` tool +lives at `/home/alex/src/blockninja/check-safety/` and runs against +the plugin via `go run . `. + +## Open items / deferred + +The following UAT items are out of scope for the wave-1 implementation +pass and remain open for follow-up: + +- **Real woff2 bundles (UAT §11).** `fonts.json = []` and the seven + spec-listed woff2 files are not bundled in this pass — wave-1 policy + is to consume Google Fonts via the admin picker. Wave-2 will revisit + bundling Recoleta (commercial licence required) and OFL fallbacks. +- **`LICENSES.md`.** Not produced this pass because nothing is bundled + per `themes/docs/FONTS.md`. +- **Marketplace screenshots & demo seed (UAT §12).** Six 1440 × 900 + PNGs and the `rootbound-conservancy.json` seed file have not been + produced. The empty `assets/images/` directory holds a `README.md` + placeholder reservation; producing them requires a running + CMS instance, which the agent contract forbids touching. +- **Sign-off (UAT §15).** Cannot be self-applied; the three reviewer + rows remain unchecked. +- **`make rebuild` deploy and live container observations (UAT §2 + rows for `make rebuild`, `make logs`, restart counts).** Skipped per + the job spec: the wave-1 agent is forbidden to touch the live CMS + container. +- **DevTools / Lighthouse / Litmus checks (UAT §§6, 7, 10, 13).** + Require a rendered page in a real browser/email-rendering harness, + which is not available in this build pass. +- **`git tag v0.1.0` (UAT 1 last row, 12 last row).** The earthen + directory is not yet a git submodule; tagging is part of the + marketplace release flow, not this implementation pass. + +## File inventory + +``` +/home/alex/src/blockninja/themes/earthen +├── BUILD_REPORT.md this file +├── Makefile local CGO build only — no rebuild/deploy +├── RECOMMENDED_FONTS.md +├── botanical_divider.go +.templ +.templ.go (generated) +├── button_override.go +.templ +.templ.go +├── card_override.go +.templ +.templ.go +├── donation_cta.go +.templ +.templ.go +├── email_wrapper.go +.templ +.templ.go +├── embed.go +├── field_note.go +.templ +.templ.go +├── fonts.json literal [] +├── footer.go +.templ +.templ.go +├── go.mod / go.sum +├── heading_override.go +.templ +.templ.go +├── helpers.go +├── image_override.go +.templ +.templ.go +├── impact_metrics.go +.templ +.templ.go +├── partner_logos.go +.templ +.templ.go +├── plugin.mod +├── presets.json +├── register.go +├── registration.go +├── template.templ (+template_templ.go) +├── text_override.go +.templ +.templ.go +├── assets/ +│ ├── css/earthen.css CSSManifest.InputCSSAppend source +│ ├── fonts/README.md wave-2 reservation +│ └── images/README.md wave-2 reservation +└── schemas/ + ├── botanical_divider.schema.json + ├── donation_cta.schema.json + ├── field_note.schema.json + ├── footer.schema.json + ├── impact_metrics.schema.json + └── partner_logos.schema.json +``` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1df1179 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +# Earthen — build helpers (.so plugin workflow) +# +# Local build only. This Makefile deliberately does NOT include rebuild/deploy +# targets to avoid touching the live CMS container during agent runs. + +.PHONY: all clean templ help + +PLUGIN_NAME := earthen + +# Default target: build the .so locally. +all: $(PLUGIN_NAME).so + +# Local plugin build. +$(PLUGIN_NAME).so: $(wildcard *.go) plugin.mod go.mod + CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o $(PLUGIN_NAME).so . + +# Regenerate templ Go files. +templ: + templ generate + +# Remove built artefacts. +clean: + rm -f $(PLUGIN_NAME).so + +help: + @echo "Targets:" + @echo " all Build $(PLUGIN_NAME).so locally (default)" + @echo " templ Regenerate templ Go files" + @echo " clean Remove built .so" diff --git a/RECOMMENDED_FONTS.md b/RECOMMENDED_FONTS.md new file mode 100644 index 0000000..f07e1e3 --- /dev/null +++ b/RECOMMENDED_FONTS.md @@ -0,0 +1,47 @@ +# Earthen — recommended fonts + +This theme ships `fonts.json = []` per the wave-1 fonts policy +(see `themes/docs/FONTS.md`). The CSS uses +`var(--font-heading)` / `var(--font-body)` / `var(--font-mono)` with a +fallback stack so the page reads close to the intended aesthetic before +the admin assigns fonts. + +Configure these in the admin Typography panel to match the spec's +intended look. + +## Display / heading — assign to `Heading` + +Primary pick: + +- **Fraunces** — Source: `google:Fraunces`. + Open the Typography panel, switch to the Google Fonts tab, search + "Fraunces", click Add, then assign to Heading. (OFL, free to host.) + +Spec-preferred display face (commercial): + +- **Recoleta** — Source: `upload:Recoleta`. + Recoleta is a commercial face from Latinotype. Purchase the desktop + + webfont licence, upload the woff2 in the Fonts panel, then assign to + Heading. Until then Fraunces is the fallback used by the theme CSS. + +## Body — assign to `Body` + +- **Spectral** — Source: `google:Spectral`. + Open the Typography panel, Google Fonts tab, search "Spectral", + click Add, assign to Body. (OFL, free to host.) + +## Mono / data captions — assign to `Mono` + +- **JetBrains Mono** — Source: `google:JetBrains Mono`. + Open the Typography panel, Google Fonts tab, search "JetBrains Mono", + click Add, assign to Mono. (OFL, free to host.) + +## Notes + +- All three Google-Font picks are part of the curated picker list. + No manual upload required for the OFL families. +- A future build pass may bundle Recoleta woff2s directly via a + populated `fonts.json` once the licence is confirmed. +- The CSS fallback stack inside the theme (`"Fraunces", "Playfair Display", + Georgia, serif` for headings; `"Spectral", Georgia, serif` for body) + keeps the moss/clay character visible during the cold-load window. diff --git a/assets/css/earthen.css b/assets/css/earthen.css new file mode 100644 index 0000000..db57ef7 --- /dev/null +++ b/assets/css/earthen.css @@ -0,0 +1,114 @@ +/* Earthen — theme utilities injected via CSSManifest.InputCSSAppend. */ +/* All colour values reference the 19-token HSL custom properties so they */ +/* shift with the active preset (mossbed, clay-field, wet-loam). */ + +/* Paper grain backdrop — a single inline SVG noise pattern keeps page weight + inside the marketplace gate (≤ 81920 bytes). */ +.bg-paper-grain { + background-color: hsl(var(--background)); + background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27160%27%20height%3D%27160%27%20viewBox%3D%270%200%20160%20160%27%3E%3Cfilter%20id%3D%27n%27%3E%3CfeTurbulence%20type%3D%27fractalNoise%27%20baseFrequency%3D%270.9%27%20numOctaves%3D%272%27%20seed%3D%274%27%2F%3E%3CfeColorMatrix%20values%3D%270%200%200%200%200.36%200%200%200%200%200.32%200%200%200%200%200.24%200%200%200%200.05%200%27%2F%3E%3C%2Ffilter%3E%3Crect%20width%3D%27160%27%20height%3D%27160%27%20filter%3D%27url%28%23n%29%27%2F%3E%3C%2Fsvg%3E"); + background-repeat: repeat; + background-size: 240px 240px; +} + +/* Botanical hairline divider — used inside .botanical-rule sections. */ +.botanical-rule > span { + display: inline-block; +} + +/* Hand-drawn crayon underline on h2 — uses currentColor and primary token. */ +.crayon-underline { + position: relative; + padding-bottom: 0.35em; +} +.crayon-underline::after { + content: ""; + position: absolute; + left: 0; + right: auto; + bottom: 0; + height: 6px; + width: 2.5em; + background-color: hsl(var(--accent) / 0.4); + border-radius: 4px 8px 6px 12px; + transform: skewY(-1deg); +} + +/* Botanical drop cap — applied to the first paragraph of .drop-cap-host. */ +.drop-cap-host > p:first-of-type::first-letter, +.drop-cap > p:first-of-type::first-letter, +.drop-cap::first-letter { + font-family: var(--font-heading, "Fraunces", "Playfair Display", Georgia, serif); + color: hsl(var(--primary)); + float: left; + font-size: 3.4em; + line-height: 0.85; + margin: 0.15em 0.18em 0 0; + font-weight: 600; +} + +/* Tile selection (donation CTA) — uses primary ring when checked. */ +.earthen-tile input[type="radio"]:checked + span { + color: hsl(var(--primary)); +} +.earthen-tile:has(input[type="radio"]:checked) { + box-shadow: 0 0 0 2px hsl(var(--primary)); +} + +/* Partner logos — grayscale at rest, full colour on hover. */ +.earthen-partner-img, +.earthen-partner-fallback { + filter: grayscale(1); + opacity: 0.85; + transition: filter 200ms ease, opacity 200ms ease; +} +.earthen-partner-tile:hover .earthen-partner-img, +.earthen-partner-link:hover .earthen-partner-img, +.earthen-partner-link:focus-visible .earthen-partner-img, +.earthen-partner-tile:hover .earthen-partner-fallback, +.earthen-partner-link:hover .earthen-partner-fallback { + filter: grayscale(0); + opacity: 1; +} + +/* Paper-grain frame around field-note imagery. */ +.earthen-paper-frame { + box-shadow: inset 0 0 0 1px hsl(var(--border) / 0.6); +} +.earthen-paper-frame-inner { + box-shadow: 0 1px 2px hsl(var(--foreground) / 0.06); +} + +/* Footer links — botanical underline on hover. */ +.earthen-footer-link { + text-decoration: none; + border-bottom: 1px solid transparent; + transition: color 150ms ease, border-color 150ms ease; +} +.earthen-footer-link:hover, +.earthen-footer-link:focus-visible { + color: hsl(var(--primary-foreground)); + border-bottom-color: hsl(var(--accent)); +} + +/* Buttons — focus ring follows the active preset. */ +.earthen-button:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; +} + +/* Article aside — keeps the byline rail comfortable on mobile. */ +.earthen-aside { + color: hsl(var(--muted-foreground)); +} + +/* Ensure article main column uses the Spectral body fallback. */ +.earthen-article { + font-family: var(--font-body, "Spectral", Georgia, serif); + color: hsl(var(--foreground)); +} + +/* Divider glyph — softer stroke at rest. */ +.earthen-glyph { + color: hsl(var(--primary)); +} diff --git a/assets/fonts/README.md b/assets/fonts/README.md new file mode 100644 index 0000000..c8daa74 --- /dev/null +++ b/assets/fonts/README.md @@ -0,0 +1,5 @@ +# Earthen fonts + +Reserved for woff2 bundles in a future build pass. The wave-1 build ships +`fonts.json = []` per the FONTS.md policy; admins assign Fraunces / +Spectral / JetBrains Mono through the typography picker. diff --git a/assets/images/README.md b/assets/images/README.md new file mode 100644 index 0000000..44a45b9 --- /dev/null +++ b/assets/images/README.md @@ -0,0 +1,3 @@ +# Earthen images + +Reserved for screenshots, demo seed media, and marketplace assets. diff --git a/botanical_divider.go b/botanical_divider.go new file mode 100644 index 0000000..70840e6 --- /dev/null +++ b/botanical_divider.go @@ -0,0 +1,43 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// BotanicalDividerBlockMeta defines metadata for the botanical divider block. +var BotanicalDividerBlockMeta = blocks.BlockMeta{ + Key: "botanical_divider", + Title: "Botanical Divider", + Description: "Hand-illustrated SVG section break", + Source: "earthen", + Category: blocks.CategoryLayout, +} + +// BotanicalDividerBlock renders the botanical divider block. +// Content shape: {motif:"fern"|"root"|"seed"} +func BotanicalDividerBlock(ctx context.Context, content map[string]any) string { + motif := getString(content, "motif") + switch motif { + case "fern", "root", "seed": + default: + motif = "fern" + } + + data := BotanicalDividerData{ + Motif: motif, + Empty: len(content) == 0, + } + + var buf bytes.Buffer + _ = botanicalDividerComponent(data).Render(ctx, &buf) + return buf.String() +} + +// BotanicalDividerData contains data for the divider component. +type BotanicalDividerData struct { + Motif string + Empty bool +} diff --git a/botanical_divider.templ b/botanical_divider.templ new file mode 100644 index 0000000..e738c7f --- /dev/null +++ b/botanical_divider.templ @@ -0,0 +1,79 @@ +package main + +// botanicalDividerComponent renders a centred botanical SVG flourish flanked by hairlines. +templ botanicalDividerComponent(data BotanicalDividerData) { + +} + +// botanicalGlyph renders the SVG illustration for the named motif. +templ botanicalGlyph(motif string, size int) { + switch motif { + case "root": + + + + + + case "seed": + + + + + default: + + + + + + + + } +} diff --git a/botanical_divider_templ.go b/botanical_divider_templ.go new file mode 100644 index 0000000..e2ac853 --- /dev/null +++ b/botanical_divider_templ.go @@ -0,0 +1,180 @@ +// 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" + +// botanicalDividerComponent renders a centred botanical SVG flourish flanked by hairlines. +func botanicalDividerComponent(data BotanicalDividerData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = botanicalGlyph(data.Motif, 48).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// botanicalGlyph renders the SVG illustration for the named motif. +func botanicalGlyph(motif string, size int) 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) + switch motif { + case "root": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "seed": + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/button_override.go b/button_override.go new file mode 100644 index 0000000..a95298c --- /dev/null +++ b/button_override.go @@ -0,0 +1,27 @@ +package main + +import ( + "bytes" + "context" +) + +// EarthenButtonBlock renders a button with Earthen styling. +// Content expects: {"label": "...", "url": "...", "variant": "primary|secondary|ghost|destructive"} +func EarthenButtonBlock(ctx context.Context, content map[string]any) string { + label := getString(content, "label") + if label == "" { + label = getString(content, "text") + } + url := getString(content, "url") + if url == "" { + url = getString(content, "href") + } + variant := getString(content, "variant") + if variant == "" { + variant = "primary" + } + + var buf bytes.Buffer + _ = earthenButtonComponent(label, url, variant).Render(ctx, &buf) + return buf.String() +} diff --git a/button_override.templ b/button_override.templ new file mode 100644 index 0000000..e9e7a4a --- /dev/null +++ b/button_override.templ @@ -0,0 +1,34 @@ +package main + +// earthenButtonStyle returns inline CSS based on variant. +func earthenButtonStyle(variant string) string { + switch variant { + case "secondary": + return "background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); border: 1px solid hsl(var(--border));" + case "ghost": + return "background-color: transparent; color: hsl(var(--primary)); border: 1.5px dashed hsl(var(--primary) / 0.6);" + case "destructive": + return "background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground));" + default: + return "background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground));" + } +} + +// earthenButtonComponent renders a clay-fired button with Earthen styling. +templ earthenButtonComponent(label, url, variant string) { + + { label } + +} + +func earthenButtonURL(url string) string { + if url == "" { + return "#" + } + return url +} diff --git a/button_override_templ.go b/button_override_templ.go new file mode 100644 index 0000000..60957a3 --- /dev/null +++ b/button_override_templ.go @@ -0,0 +1,114 @@ +// 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" + +// earthenButtonStyle returns inline CSS based on variant. +func earthenButtonStyle(variant string) string { + switch variant { + case "secondary": + return "background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); border: 1px solid hsl(var(--border));" + case "ghost": + return "background-color: transparent; color: hsl(var(--primary)); border: 1.5px dashed hsl(var(--primary) / 0.6);" + case "destructive": + return "background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground));" + default: + return "background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground));" + } +} + +// earthenButtonComponent renders a clay-fired button with Earthen styling. +func earthenButtonComponent(label, url, variant string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 25, Col: 9} + } + _, 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, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func earthenButtonURL(url string) string { + if url == "" { + return "#" + } + return url +} + +var _ = templruntime.GeneratedTemplate diff --git a/card_override.go b/card_override.go new file mode 100644 index 0000000..c0b3e1f --- /dev/null +++ b/card_override.go @@ -0,0 +1,33 @@ +package main + +import ( + "bytes" + "context" +) + +// EarthenCardBlock renders a card with softer radius and ink-edge border. +// Content expects: {"title":"…","body":"…","image":"…","link":"…","linkLabel":"…"} +func EarthenCardBlock(ctx context.Context, content map[string]any) string { + data := EarthenCardData{ + Title: getString(content, "title"), + Body: getString(content, "body"), + Image: getString(content, "image"), + Link: getString(content, "link"), + LinkLabel: getString(content, "linkLabel"), + Empty: len(content) == 0, + } + + var buf bytes.Buffer + _ = earthenCardComponent(data).Render(ctx, &buf) + return buf.String() +} + +// EarthenCardData is the data shape for the card override component. +type EarthenCardData struct { + Title string + Body string + Image string + Link string + LinkLabel string + Empty bool +} diff --git a/card_override.templ b/card_override.templ new file mode 100644 index 0000000..c66ed4e --- /dev/null +++ b/card_override.templ @@ -0,0 +1,46 @@ +package main + +// earthenCardComponent renders a card with soft radius and ink-edge border. +templ earthenCardComponent(data EarthenCardData) { + +} diff --git a/card_override_templ.go b/card_override_templ.go new file mode 100644 index 0000000..f0d6e2d --- /dev/null +++ b/card_override_templ.go @@ -0,0 +1,193 @@ +// 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" + +// earthenCardComponent renders a card with soft radius and ink-edge border. +func earthenCardComponent(data EarthenCardData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Image != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
\"")
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Title != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 20, Col: 16} + } + _, 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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Body != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Body).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Link != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.LinkLabel != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.LinkLabel) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 38, Col: 27} + } + _, 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, 16, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "Read more ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/donation_cta.go b/donation_cta.go new file mode 100644 index 0000000..420e377 --- /dev/null +++ b/donation_cta.go @@ -0,0 +1,67 @@ +package main + +import ( + "bytes" + "context" + "strconv" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// DonationCTABlockMeta defines metadata for the donation CTA block. +var DonationCTABlockMeta = blocks.BlockMeta{ + Key: "donation_cta", + Title: "Donation CTA", + Description: "Donation call-to-action with preset amount tiles and custom amount field", + Source: "earthen", + Category: blocks.CategoryContent, +} + +// DonationCTABlock renders the donation call-to-action block. +// Content shape: {headline, body, amounts:[int], buttonLabel, processorUrl} +func DonationCTABlock(ctx context.Context, content map[string]any) string { + amounts := getNumberSlice(content, "amounts") + if len(amounts) == 0 { + amounts = []float64{25, 50, 100} + } + + tiles := make([]DonationTile, 0, len(amounts)) + for _, a := range amounts { + tiles = append(tiles, DonationTile{ + Value: a, + Label: "$" + strconv.FormatFloat(a, 'f', -1, 64), + }) + } + + data := DonationCTAData{ + Headline: getString(content, "headline"), + Body: getString(content, "body"), + ButtonLabel: getString(content, "buttonLabel"), + ProcessorURL: getString(content, "processorUrl"), + Tiles: tiles, + Empty: len(content) == 0, + } + if data.ButtonLabel == "" { + data.ButtonLabel = "Donate" + } + + var buf bytes.Buffer + _ = donationCTAComponent(data).Render(ctx, &buf) + return buf.String() +} + +// DonationCTAData contains data for the donation CTA component. +type DonationCTAData struct { + Headline string + Body string + ButtonLabel string + ProcessorURL string + Tiles []DonationTile + Empty bool +} + +// DonationTile is a single preset amount tile. +type DonationTile struct { + Value float64 + Label string +} diff --git a/donation_cta.templ b/donation_cta.templ new file mode 100644 index 0000000..65d1599 --- /dev/null +++ b/donation_cta.templ @@ -0,0 +1,79 @@ +package main + +import "strconv" + +// donationCTAComponent renders the moss/clay donation CTA section. +templ donationCTAComponent(data DonationCTAData) { +
+
+ if data.Headline != "" { +

+ { data.Headline } +

+ } + if data.Body != "" { +
+ @templ.Raw(data.Body) +
+ } +
+
+ for i, tile := range data.Tiles { + + } +
+
+ + + +
+
+
+
+} + +func donationFormAction(url string) string { + if url == "" { + return "#" + } + return url +} diff --git a/donation_cta_templ.go b/donation_cta_templ.go new file mode 100644 index 0000000..9c4e889 --- /dev/null +++ b/donation_cta_templ.go @@ -0,0 +1,195 @@ +// 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 "strconv" + +// donationCTAComponent renders the moss/clay donation CTA section. +func donationCTAComponent(data DonationCTAData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Headline != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

") + 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: `donation_cta.templ`, Line: 19, Col: 20} + } + _, 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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Body != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Body).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for i, tile := range data.Tiles { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func donationFormAction(url string) string { + if url == "" { + return "#" + } + return url +} + +var _ = templruntime.GeneratedTemplate diff --git a/email_wrapper.go b/email_wrapper.go new file mode 100644 index 0000000..7917f54 --- /dev/null +++ b/email_wrapper.go @@ -0,0 +1,17 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/templates" +) + +// EarthenEmailWrapper wraps body content in a botanical cream-and-moss email frame. +func EarthenEmailWrapper(body string, emailCtx templates.EmailContext) string { + var buf bytes.Buffer + if err := earthenEmailTemplate(emailCtx, body).Render(context.Background(), &buf); err != nil { + return body + } + return buf.String() +} diff --git a/email_wrapper.templ b/email_wrapper.templ new file mode 100644 index 0000000..52f0532 --- /dev/null +++ b/email_wrapper.templ @@ -0,0 +1,154 @@ +package main + +import ( + "fmt" + + "git.dev.alexdunmow.com/block/core/templates" +) + +// earthenEmailTemplate renders the cream/moss email wrapper. +templ earthenEmailTemplate(emailCtx templates.EmailContext, body string) { + + + + + + + + + { emailCtx.SiteSettings.SiteName } + + + if emailCtx.PreviewText != "" { +
+ { emailCtx.PreviewText } +
+ } + + + + +
+ + + + + + + + + + +
+ if emailCtx.SiteSettings.LogoURL != "" { + { + } else if emailCtx.SiteSettings.SiteName != "" { +

+ { emailCtx.SiteSettings.SiteName } +

+ } +
+ @templ.Raw(body) +
+ + + + +
+

+ { emailCtx.SiteSettings.SiteName } +

+

+ Registered charity. We respect your inbox. +

+ if emailCtx.SiteSettings.SiteURL != "" { +

+ + { emailCtx.SiteSettings.SiteURL } + +

+ } + if emailCtx.UnsubscribeURL != "" { +

+ + Unsubscribe + +

+ } else { +

+ + Unsubscribe + +

+ } +
+
+
+ + +} + +// Email colour helpers — fall back to cream/moss defaults when EmailColors are empty. +func earthenEmailBg(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Background != "" { + return emailCtx.Colors.Background + } + return "#F6F1E6" +} + +func earthenEmailCard(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Card != "" { + return emailCtx.Colors.Card + } + return "#FCF9F2" +} + +func earthenEmailFg(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Foreground != "" { + return emailCtx.Colors.Foreground + } + return "#2D3A28" +} + +func earthenEmailPrimary(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Primary != "" { + return emailCtx.Colors.Primary + } + return "#3A5A36" +} + +func earthenEmailPrimaryFg(emailCtx templates.EmailContext) string { + if emailCtx.Colors.PrimaryForeground != "" { + return emailCtx.Colors.PrimaryForeground + } + return "#F6F1E6" +} + +func earthenEmailBorder(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Border != "" { + return emailCtx.Colors.Border + } + return "#D6D1C2" +} + +func earthenEmailMuted(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Muted != "" { + return emailCtx.Colors.Muted + } + return "#EFEBDF" +} + +func earthenEmailMutedFg(emailCtx templates.EmailContext) string { + if emailCtx.Colors.MutedForeground != "" { + return emailCtx.Colors.MutedForeground + } + return "#6E7560" +} diff --git a/email_wrapper_templ.go b/email_wrapper_templ.go new file mode 100644 index 0000000..0192298 --- /dev/null +++ b/email_wrapper_templ.go @@ -0,0 +1,470 @@ +// 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 ( + "fmt" + + "git.dev.alexdunmow.com/block/core/templates" +) + +// earthenEmailTemplate renders the cream/moss email wrapper. +func earthenEmailTemplate(emailCtx templates.EmailContext, body string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 27, Col: 42} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if emailCtx.PreviewText != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 32, Col: 27} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if emailCtx.SiteSettings.LogoURL != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else if emailCtx.SiteSettings.SiteName != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

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

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(body).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 61, Col: 45} + } + _, 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, "

Registered charity. We respect your inbox.

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

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, 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: 69, Col: 46} + } + _, 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, 27, "

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

Unsubscribe

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

Unsubscribe

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// Email colour helpers — fall back to cream/moss defaults when EmailColors are empty. +func earthenEmailBg(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Background != "" { + return emailCtx.Colors.Background + } + return "#F6F1E6" +} + +func earthenEmailCard(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Card != "" { + return emailCtx.Colors.Card + } + return "#FCF9F2" +} + +func earthenEmailFg(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Foreground != "" { + return emailCtx.Colors.Foreground + } + return "#2D3A28" +} + +func earthenEmailPrimary(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Primary != "" { + return emailCtx.Colors.Primary + } + return "#3A5A36" +} + +func earthenEmailPrimaryFg(emailCtx templates.EmailContext) string { + if emailCtx.Colors.PrimaryForeground != "" { + return emailCtx.Colors.PrimaryForeground + } + return "#F6F1E6" +} + +func earthenEmailBorder(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Border != "" { + return emailCtx.Colors.Border + } + return "#D6D1C2" +} + +func earthenEmailMuted(emailCtx templates.EmailContext) string { + if emailCtx.Colors.Muted != "" { + return emailCtx.Colors.Muted + } + return "#EFEBDF" +} + +func earthenEmailMutedFg(emailCtx templates.EmailContext) string { + if emailCtx.Colors.MutedForeground != "" { + return emailCtx.Colors.MutedForeground + } + return "#6E7560" +} + +var _ = templruntime.GeneratedTemplate diff --git a/embed.go b/embed.go new file mode 100644 index 0000000..41c5114 --- /dev/null +++ b/embed.go @@ -0,0 +1,64 @@ +package main + +import ( + "embed" + "io/fs" + "net/http" + + "git.dev.alexdunmow.com/block/core/plugin" +) + +//go:embed assets/* +var assetsFS embed.FS + +//go:embed schemas/* +var schemasFS embed.FS + +//go:embed presets.json +var presetsData []byte + +//go:embed fonts.json +var fontsData []byte + +//go:embed plugin.mod +var pluginModBytes []byte + +// Assets returns the embedded assets filesystem. +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 +} + +// ThemeCSSManifest returns the theme's custom CSS utilities injected into +// the host Tailwind input so paper-grain, drop-cap, and botanical rules +// survive the global purge. +func ThemeCSSManifest() *plugin.CSSManifest { + css, err := assetsFS.ReadFile("assets/css/earthen.css") + if err != nil { + return &plugin.CSSManifest{} + } + return &plugin.CSSManifest{ + InputCSSAppend: string(css), + } +} diff --git a/field_note.go b/field_note.go new file mode 100644 index 0000000..a0da6bf --- /dev/null +++ b/field_note.go @@ -0,0 +1,44 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// FieldNoteBlockMeta defines metadata for the field note block. +var FieldNoteBlockMeta = blocks.BlockMeta{ + Key: "field_note", + Title: "Field Note", + Description: "Article-style dispatch from the field with byline and location", + Source: "earthen", + Category: blocks.CategoryContent, +} + +// FieldNoteBlock renders the field note block. +// Content shape: {author, location, dateline, body, image} +func FieldNoteBlock(ctx context.Context, content map[string]any) string { + data := FieldNoteData{ + Author: getString(content, "author"), + Location: getString(content, "location"), + Dateline: getString(content, "dateline"), + Body: getString(content, "body"), + Image: getString(content, "image"), + Empty: len(content) == 0, + } + + var buf bytes.Buffer + _ = fieldNoteComponent(data).Render(ctx, &buf) + return buf.String() +} + +// FieldNoteData contains data for the field note component. +type FieldNoteData struct { + Author string + Location string + Dateline string + Body string + Image string + Empty bool +} diff --git a/field_note.templ b/field_note.templ new file mode 100644 index 0000000..2c20bae --- /dev/null +++ b/field_note.templ @@ -0,0 +1,52 @@ +package main + +// fieldNoteComponent renders an article-style dispatch from the field. +templ fieldNoteComponent(data FieldNoteData) { +
+
+ + +
+ if data.Image != "" { +
+ +
+ } + if data.Body != "" { +
+ @templ.Raw(data.Body) +
+ } else { +

+ No dispatch yet — add a body to share what you saw in the field. +

+ } +
+} diff --git a/field_note_templ.go b/field_note_templ.go new file mode 100644 index 0000000..6d49abe --- /dev/null +++ b/field_note_templ.go @@ -0,0 +1,175 @@ +// 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" + +// fieldNoteComponent renders an article-style dispatch from the field. +func fieldNoteComponent(data FieldNoteData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = botanicalGlyph("seed", 28).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Image != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
\"\"
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Body != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Body).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

No dispatch yet — add a body to share what you saw in the field.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/fonts.json b/fonts.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/fonts.json @@ -0,0 +1 @@ +[] diff --git a/footer.go b/footer.go new file mode 100644 index 0000000..a68aaea --- /dev/null +++ b/footer.go @@ -0,0 +1,69 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// FooterBlockMeta defines metadata for the earthen footer block. +var FooterBlockMeta = blocks.BlockMeta{ + Key: "footer", + Title: "Site Footer", + Description: "Cream-on-moss footer with optional newsletter and column links", + Source: "earthen", + Category: blocks.CategoryNavigation, +} + +// FooterBlock renders the earthen footer block. +// Content shape: {showNewsletter, tagline, columns:[{title,links:[{text,url}]}]} +func FooterBlock(ctx context.Context, content map[string]any) string { + rawColumns := getSlice(content, "columns") + columns := make([]FooterColumn, 0, len(rawColumns)) + for _, c := range rawColumns { + rawLinks := getSlice(c, "links") + links := make([]FooterLink, 0, len(rawLinks)) + for _, l := range rawLinks { + links = append(links, FooterLink{ + Text: getString(l, "text"), + URL: getString(l, "url"), + }) + } + columns = append(columns, FooterColumn{ + Title: getString(c, "title"), + Links: links, + }) + } + + data := FooterData{ + ShowNewsletter: getBool(content, "showNewsletter", false), + Tagline: getString(content, "tagline"), + Columns: columns, + Empty: len(content) == 0, + } + + var buf bytes.Buffer + _ = footerComponent(data).Render(ctx, &buf) + return buf.String() +} + +// FooterData contains data for the footer component. +type FooterData struct { + ShowNewsletter bool + Tagline string + Columns []FooterColumn + Empty bool +} + +// FooterColumn is a single link column in the footer. +type FooterColumn struct { + Title string + Links []FooterLink +} + +// FooterLink is a single link in a footer column. +type FooterLink struct { + Text string + URL string +} diff --git a/footer.templ b/footer.templ new file mode 100644 index 0000000..e1d9333 --- /dev/null +++ b/footer.templ @@ -0,0 +1,104 @@ +package main + +// footerComponent renders the cream-on-moss footer with optional newsletter and link columns. +templ footerComponent(data FooterData) { + +} + +// footerGridCols returns a responsive grid class for the footer column count. +func footerGridCols(count int) string { + switch count { + case 1: + return "grid-cols-1" + case 2: + return "grid-cols-1 sm:grid-cols-2" + case 3: + return "grid-cols-1 sm:grid-cols-3" + default: + return "grid-cols-2 lg:grid-cols-4" + } +} + +func footerLinkURL(url string) string { + if url == "" { + return "#" + } + return url +} diff --git a/footer_templ.go b/footer_templ.go new file mode 100644 index 0000000..6060889 --- /dev/null +++ b/footer_templ.go @@ -0,0 +1,234 @@ +// 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 the cream-on-moss footer with optional newsletter and link columns. +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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// footerGridCols returns a responsive grid class for the footer column count. +func footerGridCols(count int) string { + switch count { + case 1: + return "grid-cols-1" + case 2: + return "grid-cols-1 sm:grid-cols-2" + case 3: + return "grid-cols-1 sm:grid-cols-3" + default: + return "grid-cols-2 lg:grid-cols-4" + } +} + +func footerLinkURL(url string) string { + if url == "" { + return "#" + } + return url +} + +var _ = templruntime.GeneratedTemplate diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0d22ee0 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module git.dev.alexdunmow.com/block/themes/earthen + +go 1.26.4 + +require ( + git.dev.alexdunmow.com/block/core v0.11.1 + github.com/a-h/templ v0.3.1020 +) + +require ( + connectrpc.com/connect v1.20.0 // indirect + github.com/BurntSushi/toml v1.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.9.2 // indirect + golang.org/x/mod v0.34.0 // indirect + golang.org/x/text v0.36.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..26aea2c --- /dev/null +++ b/go.sum @@ -0,0 +1,42 @@ +connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= +connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= +git.dev.alexdunmow.com/block/core v0.11.1 h1:5b3Ps9CLor2FGyxw/Qovt27AGZKR5Xi1JZGi/TfliTA= +git.dev.alexdunmow.com/block/core v0.11.1/go.mod h1:ZwzEOxRDLDfrhQGqo6hLw01/C1z/aS4Dm9ljQMl0Bg4= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw= +github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/heading_override.go b/heading_override.go new file mode 100644 index 0000000..3205bd4 --- /dev/null +++ b/heading_override.go @@ -0,0 +1,40 @@ +package main + +import ( + "bytes" + "context" + "strconv" +) + +// EarthenHeadingBlock renders a heading with Earthen styling. +// Content expects: {"text": "Heading text", "level": 1-6, "textClass": "optional"} +func EarthenHeadingBlock(ctx context.Context, content map[string]any) string { + text := getString(content, "text") + textClass := getString(content, "textClass") + level := parseHeadingLevel(content) + + var buf bytes.Buffer + _ = earthenHeadingComponent(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 +} diff --git a/heading_override.templ b/heading_override.templ new file mode 100644 index 0000000..4112864 --- /dev/null +++ b/heading_override.templ @@ -0,0 +1,76 @@ +package main + +// earthenHeadingBaseClass returns base classes for each heading level. +func earthenHeadingBaseClass(level int) string { + switch level { + case 1: + return "earthen-h1 text-5xl md:text-6xl font-semibold tracking-tight leading-tight" + case 2: + return "earthen-h2 text-3xl md:text-4xl font-semibold leading-snug crayon-underline" + case 3: + return "earthen-h3 text-2xl md:text-3xl font-semibold leading-snug" + case 4: + return "earthen-h4 text-xl md:text-2xl font-semibold" + case 5: + return "earthen-h5 text-lg font-semibold" + case 6: + return "earthen-h6 text-base font-semibold uppercase tracking-wider" + default: + return "earthen-h2 text-3xl md:text-4xl font-semibold" + } +} + +// earthenHeadingComponent renders a heading with Earthen styling. +templ earthenHeadingComponent(level int, text, textClass string) { + switch level { + case 1: +

+ { text } +

+ case 2: +

+ { text } +

+ case 3: +

+ { text } +

+ case 4: +

+ { text } +

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

+ { text } +

+ } +} diff --git a/heading_override_templ.go b/heading_override_templ.go new file mode 100644 index 0000000..b48969f --- /dev/null +++ b/heading_override_templ.go @@ -0,0 +1,402 @@ +// 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" + +// earthenHeadingBaseClass returns base classes for each heading level. +func earthenHeadingBaseClass(level int) string { + switch level { + case 1: + return "earthen-h1 text-5xl md:text-6xl font-semibold tracking-tight leading-tight" + case 2: + return "earthen-h2 text-3xl md:text-4xl font-semibold leading-snug crayon-underline" + case 3: + return "earthen-h3 text-2xl md:text-3xl font-semibold leading-snug" + case 4: + return "earthen-h4 text-xl md:text-2xl font-semibold" + case 5: + return "earthen-h5 text-lg font-semibold" + case 6: + return "earthen-h6 text-base font-semibold uppercase tracking-wider" + default: + return "earthen-h2 text-3xl md:text-4xl font-semibold" + } +} + +// earthenHeadingComponent renders a heading with Earthen styling. +func earthenHeadingComponent(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{earthenHeadingBaseClass(1), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

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

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

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

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

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_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: 45, Col: 10} + } + _, 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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 4: + var templ_7745c5c3_Var14 = []any{earthenHeadingBaseClass(4), 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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 52, Col: 10} + } + _, 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, 16, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 5: + var templ_7745c5c3_Var18 = []any{earthenHeadingBaseClass(5), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, 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_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 59, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case 6: + var templ_7745c5c3_Var22 = []any{earthenHeadingBaseClass(6), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 66, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + default: + var templ_7745c5c3_Var26 = []any{earthenHeadingBaseClass(2), textClass} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var26...) + 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_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(text) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 73, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/helpers.go b/helpers.go new file mode 100644 index 0000000..27ef2d5 --- /dev/null +++ b/helpers.go @@ -0,0 +1,48 @@ +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 "" +} + +// getBool extracts a bool value from content map. Defaults to defaultVal. +func getBool(content map[string]any, key string, defaultVal bool) bool { + if v, ok := content[key].(bool); 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 +} + +// getNumberSlice extracts a slice of numbers from content. Handles JSON float64. +func getNumberSlice(content map[string]any, key string) []float64 { + if v, ok := content[key].([]any); ok { + result := make([]float64, 0, len(v)) + for _, item := range v { + switch n := item.(type) { + case float64: + result = append(result, n) + case int: + result = append(result, float64(n)) + } + } + return result + } + return nil +} diff --git a/image_override.go b/image_override.go new file mode 100644 index 0000000..b00e0a9 --- /dev/null +++ b/image_override.go @@ -0,0 +1,21 @@ +package main + +import ( + "bytes" + "context" +) + +// EarthenImageBlock renders an image with paper-grain frame and optional caption. +// Content expects: {"src":"…","alt":"…","caption":"…"} +func EarthenImageBlock(ctx context.Context, content map[string]any) string { + src := getString(content, "src") + if src == "" { + src = getString(content, "url") + } + alt := getString(content, "alt") + caption := getString(content, "caption") + + var buf bytes.Buffer + _ = earthenImageComponent(src, alt, caption).Render(ctx, &buf) + return buf.String() +} diff --git a/image_override.templ b/image_override.templ new file mode 100644 index 0000000..537e4c7 --- /dev/null +++ b/image_override.templ @@ -0,0 +1,35 @@ +package main + +// earthenImageComponent renders an image inside a paper-grain frame, with an optional handwritten caption. +templ earthenImageComponent(src, alt, caption string) { +
+ if src != "" { +
+ { +
+ } else { +
+ Choose an image to display here +
+ } + if caption != "" { +
+ { caption } +
+ } +
+} diff --git a/image_override_templ.go b/image_override_templ.go new file mode 100644 index 0000000..09a18d6 --- /dev/null +++ b/image_override_templ.go @@ -0,0 +1,101 @@ +// 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" + +// earthenImageComponent renders an image inside a paper-grain frame, with an optional handwritten caption. +func earthenImageComponent(src, alt, caption string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if src != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
\"")
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
Choose an image to display here
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if caption != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(caption) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 31, Col: 13} + } + _, 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, "
") + 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 + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/impact_metrics.go b/impact_metrics.go new file mode 100644 index 0000000..c9c253a --- /dev/null +++ b/impact_metrics.go @@ -0,0 +1,58 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// ImpactMetricsBlockMeta defines metadata for the impact metrics block. +var ImpactMetricsBlockMeta = blocks.BlockMeta{ + Key: "impact_metrics", + Title: "Impact Metrics", + Description: "Numerical impact metrics with optional botanical illustration", + Source: "earthen", + Category: blocks.CategoryContent, +} + +// ImpactMetricsBlock renders a row of impact metrics. +// Content shape: {title, metrics:[{value,label,suffix}], illustration} +func ImpactMetricsBlock(ctx context.Context, content map[string]any) string { + rawMetrics := getSlice(content, "metrics") + + items := make([]ImpactMetric, 0, len(rawMetrics)) + for _, m := range rawMetrics { + items = append(items, ImpactMetric{ + Value: getString(m, "value"), + Label: getString(m, "label"), + Suffix: getString(m, "suffix"), + }) + } + + data := ImpactMetricsData{ + Title: getString(content, "title"), + Illustration: getString(content, "illustration"), + Metrics: items, + Empty: len(content) == 0, + } + + var buf bytes.Buffer + _ = impactMetricsComponent(data).Render(ctx, &buf) + return buf.String() +} + +// ImpactMetricsData contains data for the impact metrics component. +type ImpactMetricsData struct { + Title string + Illustration string + Metrics []ImpactMetric + Empty bool +} + +// ImpactMetric is one numerical metric tile. +type ImpactMetric struct { + Value string + Label string + Suffix string +} diff --git a/impact_metrics.templ b/impact_metrics.templ new file mode 100644 index 0000000..5507dd6 --- /dev/null +++ b/impact_metrics.templ @@ -0,0 +1,82 @@ +package main + +// impactMetricsComponent renders a row of impact metrics with optional botanical illustration. +templ impactMetricsComponent(data ImpactMetricsData) { +
+
+ if data.Title != "" { +

+ { data.Title } +

+ } + if data.Illustration != "" { +
+ +
+ } else if data.Title != "" || len(data.Metrics) > 0 { +
+ @botanicalGlyph("fern", 64) +
+ } + if len(data.Metrics) > 0 { +
+ for _, m := range data.Metrics { +
+
+ { m.Value }{ m.Suffix } +
+
+ { m.Label } +
+
+ } +
+ } else { +

+ Add impact metrics to bring this section to life. +

+ } +
+
+} + +// metricGridCols returns the responsive grid class for metric count. +func metricGridCols(count int) string { + switch count { + case 1: + return "grid-cols-1" + case 2: + return "grid-cols-1 sm:grid-cols-2" + case 3: + return "grid-cols-1 sm:grid-cols-3" + default: + return "grid-cols-2 lg:grid-cols-4" + } +} + +// resolveMedia rewrites a media reference to its served URL. The wave-1 +// implementation passes the path through verbatim and is wired so admins +// can drop in their own media adapter later without touching templates. +func resolveMedia(path string) string { + if path == "" { + return "" + } + return path +} diff --git a/impact_metrics_templ.go b/impact_metrics_templ.go new file mode 100644 index 0000000..ba8dd45 --- /dev/null +++ b/impact_metrics_templ.go @@ -0,0 +1,243 @@ +// 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" + +// impactMetricsComponent renders a row of impact metrics with optional botanical illustration. +func impactMetricsComponent(data ImpactMetricsData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Title != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `impact_metrics.templ`, Line: 17, Col: 17} + } + _, 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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Illustration != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
\"\"
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else if data.Title != "" || len(data.Metrics) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = botanicalGlyph("fern", 64).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(data.Metrics) > 0 { + var templ_7745c5c3_Var5 = []any{"grid gap-8", metricGridCols(len(data.Metrics))} + 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, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, m := range data.Metrics { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(m.Value) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `impact_metrics.templ`, Line: 37, Col: 17} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(m.Suffix) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `impact_metrics.templ`, Line: 37, Col: 29} + } + _, 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, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(m.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `impact_metrics.templ`, Line: 43, Col: 17} + } + _, 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, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

Add impact metrics to bring this section to life.

") + 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 + } + return nil + }) +} + +// metricGridCols returns the responsive grid class for metric count. +func metricGridCols(count int) string { + switch count { + case 1: + return "grid-cols-1" + case 2: + return "grid-cols-1 sm:grid-cols-2" + case 3: + return "grid-cols-1 sm:grid-cols-3" + default: + return "grid-cols-2 lg:grid-cols-4" + } +} + +// resolveMedia rewrites a media reference to its served URL. The wave-1 +// implementation passes the path through verbatim and is wired so admins +// can drop in their own media adapter later without touching templates. +func resolveMedia(path string) string { + if path == "" { + return "" + } + return path +} + +var _ = templruntime.GeneratedTemplate diff --git a/partner_logos.go b/partner_logos.go new file mode 100644 index 0000000..e552e4f --- /dev/null +++ b/partner_logos.go @@ -0,0 +1,55 @@ +package main + +import ( + "bytes" + "context" + + "git.dev.alexdunmow.com/block/core/blocks" +) + +// PartnerLogosBlockMeta defines metadata for the partner logos block. +var PartnerLogosBlockMeta = blocks.BlockMeta{ + Key: "partner_logos", + Title: "Partner Logos", + Description: "Grayscale partner logo grid with hover-to-color treatment", + Source: "earthen", + Category: blocks.CategoryContent, +} + +// PartnerLogosBlock renders the partner logos block. +// Content shape: {title, logos:[{name,image,url}]} +func PartnerLogosBlock(ctx context.Context, content map[string]any) string { + rawLogos := getSlice(content, "logos") + logos := make([]PartnerLogo, 0, len(rawLogos)) + for _, l := range rawLogos { + logos = append(logos, PartnerLogo{ + Name: getString(l, "name"), + Image: getString(l, "image"), + URL: getString(l, "url"), + }) + } + + data := PartnerLogosData{ + Title: getString(content, "title"), + Logos: logos, + Empty: len(content) == 0, + } + + var buf bytes.Buffer + _ = partnerLogosComponent(data).Render(ctx, &buf) + return buf.String() +} + +// PartnerLogosData contains data for the partner logos component. +type PartnerLogosData struct { + Title string + Logos []PartnerLogo + Empty bool +} + +// PartnerLogo is a single partner row. +type PartnerLogo struct { + Name string + Image string + URL string +} diff --git a/partner_logos.templ b/partner_logos.templ new file mode 100644 index 0000000..516221d --- /dev/null +++ b/partner_logos.templ @@ -0,0 +1,95 @@ +package main + +// partnerLogosComponent renders a grayscale logo grid that restores colour on hover. +templ partnerLogosComponent(data PartnerLogosData) { +
+
+ if data.Title != "" { +

+ { data.Title } +

+ } + if len(data.Logos) > 0 { +
+ for _, logo := range data.Logos { +
+ @partnerLogoTile(logo) +
+ } +
+ } else { +

+ Add partner logos to fill this grid. +

+ } +
+
+} + +templ partnerLogoTile(logo PartnerLogo) { + if logo.URL != "" { + + @partnerLogoImage(logo) + + } else { + + } +} + +templ partnerLogoImage(logo PartnerLogo) { + if logo.Image != "" { + { + } else { +
+ { partnerInitials(logo.Name) } +
+ } +} + +// partnerInitials returns up to two initials for an initials-only fallback tile. +func partnerInitials(name string) string { + if name == "" { + return "?" + } + letters := []rune{} + prevSpace := true + for _, r := range name { + if r == ' ' || r == '-' || r == '_' { + prevSpace = true + continue + } + if prevSpace { + letters = append(letters, r) + prevSpace = false + if len(letters) >= 2 { + break + } + } + } + if len(letters) == 0 { + return "?" + } + return string(letters) +} diff --git a/partner_logos_templ.go b/partner_logos_templ.go new file mode 100644 index 0000000..aece890 --- /dev/null +++ b/partner_logos_templ.go @@ -0,0 +1,334 @@ +// 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" + +// partnerLogosComponent renders a grayscale logo grid that restores colour on hover. +func partnerLogosComponent(data PartnerLogosData) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Title != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 17, Col: 17} + } + _, 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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(data.Logos) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, logo := range data.Logos { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = partnerLogoTile(logo).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

Add partner logos to fill this grid.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func partnerLogoTile(logo PartnerLogo) 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) + if logo.URL != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = partnerLogoImage(logo).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +func partnerLogoImage(logo PartnerLogo) 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) + if logo.Image != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(partnerInitials(logo.Name)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 66, Col: 53} + } + _, 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, 26, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +// partnerInitials returns up to two initials for an initials-only fallback tile. +func partnerInitials(name string) string { + if name == "" { + return "?" + } + letters := []rune{} + prevSpace := true + for _, r := range name { + if r == ' ' || r == '-' || r == '_' { + prevSpace = true + continue + } + if prevSpace { + letters = append(letters, r) + prevSpace = false + if len(letters) >= 2 { + break + } + } + } + if len(letters) == 0 { + return "?" + } + return string(letters) +} + +var _ = templruntime.GeneratedTemplate diff --git a/plugin.mod b/plugin.mod new file mode 100644 index 0000000..57081d2 --- /dev/null +++ b/plugin.mod @@ -0,0 +1,12 @@ +[plugin] +name = "earthen" +display_name = "Earthen" +scope = "@themes" +version = "0.1.0" +description = "Botanical nonprofit theme in moss, clay, sage and cream — hand-illustrated motifs for cause-driven sites." +kind = "theme" +categories = ["templates"] +tags = ["earth", "nonprofit", "sustainability", "outdoor", "farm", "cause", "botanical", "conservation", "ngo"] + +[compatibility] +block_core = ">=0.11.0 <0.12.0" diff --git a/presets.json b/presets.json new file mode 100644 index 0000000..efe1f23 --- /dev/null +++ b/presets.json @@ -0,0 +1,110 @@ +[ + { + "id": "mossbed", + "name": "Mossbed", + "description": "Cream paper, deep moss primary, terracotta accent — the canonical Earthen look.", + "theme": { + "mode": "both", + "lightColors": { + "background": "40 30% 96%", + "foreground": "120 25% 14%", + "card": "40 25% 98%", + "cardForeground": "120 25% 14%", + "popover": "40 25% 98%", + "popoverForeground": "120 25% 14%", + "primary": "120 28% 28%", + "primaryForeground": "40 30% 96%", + "secondary": "80 15% 88%", + "secondaryForeground": "120 25% 18%", + "muted": "40 18% 92%", + "mutedForeground": "100 12% 38%", + "accent": "18 55% 48%", + "accentForeground": "40 30% 96%", + "destructive": "8 70% 45%", + "destructiveForeground": "40 30% 96%", + "border": "90 12% 82%", + "input": "90 12% 82%", + "ring": "120 28% 28%" + }, + "darkColors": { + "background": "120 18% 8%", + "foreground": "40 25% 92%", + "card": "120 18% 11%", + "cardForeground": "40 25% 92%", + "popover": "120 18% 11%", + "popoverForeground": "40 25% 92%", + "primary": "90 30% 60%", + "primaryForeground": "120 18% 8%", + "secondary": "120 15% 18%", + "secondaryForeground": "40 25% 92%", + "muted": "120 12% 15%", + "mutedForeground": "90 12% 60%", + "accent": "18 65% 58%", + "accentForeground": "120 18% 8%", + "destructive": "8 70% 50%", + "destructiveForeground": "40 25% 92%", + "border": "120 12% 22%", + "input": "120 12% 22%", + "ring": "90 30% 60%" + } + } + }, + { + "id": "clay-field", + "name": "Clay Field", + "description": "Warm terracotta-led palette tuned for farm-stand and outdoor-brand sites.", + "theme": { + "mode": "light", + "lightColors": { + "background": "30 35% 95%", + "foreground": "20 35% 16%", + "card": "30 30% 98%", + "cardForeground": "20 35% 16%", + "popover": "30 30% 98%", + "popoverForeground": "20 35% 16%", + "primary": "18 60% 42%", + "primaryForeground": "30 35% 95%", + "secondary": "35 25% 88%", + "secondaryForeground": "20 35% 16%", + "muted": "30 20% 92%", + "mutedForeground": "25 18% 38%", + "accent": "100 22% 38%", + "accentForeground": "30 35% 95%", + "destructive": "8 70% 45%", + "destructiveForeground": "30 35% 95%", + "border": "30 18% 82%", + "input": "30 18% 82%", + "ring": "18 60% 42%" + } + } + }, + { + "id": "wet-loam", + "name": "Wet Loam", + "description": "Deep night-soil tones for conservation orgs that need quiet authority.", + "theme": { + "mode": "dark", + "darkColors": { + "background": "90 20% 6%", + "foreground": "60 20% 90%", + "card": "90 20% 9%", + "cardForeground": "60 20% 90%", + "popover": "90 20% 9%", + "popoverForeground": "60 20% 90%", + "primary": "80 35% 55%", + "primaryForeground": "90 20% 6%", + "secondary": "90 15% 16%", + "secondaryForeground": "60 20% 90%", + "muted": "90 12% 13%", + "mutedForeground": "80 10% 58%", + "accent": "25 70% 55%", + "accentForeground": "90 20% 6%", + "destructive": "8 70% 50%", + "destructiveForeground": "60 20% 90%", + "border": "90 12% 20%", + "input": "90 12% 20%", + "ring": "80 35% 55%" + } + } + } +] diff --git a/register.go b/register.go new file mode 100644 index 0000000..4f45a2f --- /dev/null +++ b/register.go @@ -0,0 +1,180 @@ +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 Earthen system template, +// four page templates, six theme blocks, five overrides, and the email wrapper. +func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error { + tr.RegisterSystemTemplate(templates.SystemTemplateMeta{ + Key: "earthen", + Title: "Earthen", + Description: "Botanical nonprofit theme in moss, clay, sage and cream", + }) + + if err := tr.RegisterPageTemplate("earthen", templates.PageTemplateMeta{ + Key: "default", + Title: "Default", + Description: "Standard nonprofit page with header, main and footer", + Slots: []string{"header", "main", "footer"}, + }, wrap(RenderEarthenDefault)); err != nil { + return err + } + + if err := tr.RegisterPageTemplate("earthen", templates.PageTemplateMeta{ + Key: "landing", + Title: "Landing", + Description: "Mission hero plus CTA bands for campaigns and donation drives", + Slots: []string{"hero", "main", "cta", "footer"}, + }, wrap(RenderEarthenLanding)); err != nil { + return err + } + + if err := tr.RegisterPageTemplate("earthen", templates.PageTemplateMeta{ + Key: "article", + Title: "Article", + Description: "Long-form storytelling with byline rail", + Slots: []string{"header", "lede", "main", "aside", "footer"}, + }, wrap(RenderEarthenArticle)); err != nil { + return err + } + + if err := tr.RegisterPageTemplate("earthen", templates.PageTemplateMeta{ + Key: "full-width", + Title: "Full Width", + Description: "Edge-to-edge imagery for reports and galleries", + Slots: []string{"header", "main", "footer"}, + }, wrap(RenderEarthenFullWidth)); err != nil { + return err + } + + if err := br.LoadSchemasFromFS(Schemas()); err != nil { + return err + } + + br.Register(DonationCTABlockMeta, DonationCTABlock) + br.Register(ImpactMetricsBlockMeta, ImpactMetricsBlock) + br.Register(PartnerLogosBlockMeta, PartnerLogosBlock) + br.Register(FieldNoteBlockMeta, FieldNoteBlock) + br.Register(BotanicalDividerBlockMeta, BotanicalDividerBlock) + br.Register(FooterBlockMeta, FooterBlock) + + br.RegisterTemplateOverride("earthen", "heading", EarthenHeadingBlock) + br.RegisterTemplateOverride("earthen", "text", EarthenTextBlock) + br.RegisterTemplateOverride("earthen", "button", EarthenButtonBlock) + br.RegisterTemplateOverride("earthen", "image", EarthenImageBlock) + br.RegisterTemplateOverride("earthen", "card", EarthenCardBlock) + + tr.RegisterEmailWrapper("earthen", EarthenEmailWrapper) + + return nil +} + +// DefaultMasterPages returns the default master pages that Earthen provisions +// on first plugin load. +func DefaultMasterPages() []plugin.MasterPageDefinition { + return []plugin.MasterPageDefinition{ + { + Key: "earthen:default-master", + Title: "Earthen Default Master", + PageTemplates: []string{"default", "article"}, + Blocks: []plugin.MasterPageBlock{ + { + BlockKey: "navbar", + Title: "Main Navigation", + Content: map[string]any{"menuName": "main"}, + Slot: "header", + SortOrder: 0, + }, + { + BlockKey: "slot", + Title: "Main Content", + Content: map[string]any{"slotName": "main"}, + Slot: "main", + SortOrder: 0, + }, + { + BlockKey: "earthen:donation_cta", + Title: "Donation Rail", + Content: map[string]any{ + "amounts": []any{25.0, 50.0, 100.0}, + "headline": "Plant the next acre.", + }, + Slot: "main", + SortOrder: 100, + }, + { + BlockKey: "earthen:footer", + Title: "Site Footer", + Content: map[string]any{ + "showNewsletter": true, + "tagline": "Rooted in place.", + }, + Slot: "footer", + SortOrder: 0, + }, + }, + }, + { + Key: "earthen:landing-master", + Title: "Earthen Landing Master", + PageTemplates: []string{"landing", "full-width"}, + Blocks: []plugin.MasterPageBlock{ + { + BlockKey: "navbar", + Title: "Main Navigation", + Content: map[string]any{"menuName": "main"}, + Slot: "hero", + SortOrder: 0, + }, + { + BlockKey: "slot", + Title: "Hero Slot", + Content: map[string]any{"slotName": "hero"}, + Slot: "hero", + SortOrder: 10, + }, + { + BlockKey: "slot", + Title: "Main Slot", + Content: map[string]any{"slotName": "main"}, + Slot: "main", + SortOrder: 0, + }, + { + BlockKey: "earthen:donation_cta", + Title: "Donation CTA", + Content: map[string]any{ + "amounts": []any{25.0, 50.0, 100.0, 250.0}, + }, + Slot: "cta", + SortOrder: 0, + }, + { + BlockKey: "earthen:footer", + Title: "Footer", + Content: map[string]any{ + "showNewsletter": true, + }, + Slot: "footer", + SortOrder: 0, + }, + }, + }, + } +} diff --git a/registration.go b/registration.go new file mode 100644 index 0000000..24f66f7 --- /dev/null +++ b/registration.go @@ -0,0 +1,25 @@ +package main + +import ( + "io/fs" + "net/http" + + "git.dev.alexdunmow.com/block/core/blocks" + "git.dev.alexdunmow.com/block/core/plugin" + "git.dev.alexdunmow.com/block/core/templates" +) + +// Registration is the compile-time plugin registration for the Earthen theme. +var Registration = plugin.PluginRegistration{ + Name: "earthen", + Version: plugin.ParseModVersion(pluginModBytes), + Register: func(tr templates.TemplateRegistry, br blocks.BlockRegistry) error { + return Register(tr, br) + }, + Assets: func() http.Handler { return AssetsHandler() }, + Schemas: func() fs.FS { return Schemas() }, + ThemePresets: func() []byte { return ThemePresets() }, + BundledFonts: func() []byte { return BundledFonts() }, + MasterPages: func() []plugin.MasterPageDefinition { return DefaultMasterPages() }, + CSSManifest: func() *plugin.CSSManifest { return ThemeCSSManifest() }, +} diff --git a/schemas/botanical_divider.schema.json b/schemas/botanical_divider.schema.json new file mode 100644 index 0000000..c95d5db --- /dev/null +++ b/schemas/botanical_divider.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Botanical Divider", + "description": "SVG section break with hand-illustrated botanical motif", + "type": "object", + "properties": { + "motif": { + "type": "string", + "title": "Motif", + "description": "Botanical illustration used for the divider", + "x-editor": "select", + "enum": ["fern", "root", "seed"], + "default": "fern" + } + } +} diff --git a/schemas/donation_cta.schema.json b/schemas/donation_cta.schema.json new file mode 100644 index 0000000..980e0eb --- /dev/null +++ b/schemas/donation_cta.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Donation CTA", + "description": "Donation call-to-action with preset amount tiles and custom amount input", + "type": "object", + "properties": { + "headline": { + "type": "string", + "title": "Headline", + "description": "Headline above the amount tiles", + "x-editor": "text" + }, + "body": { + "type": "string", + "title": "Body", + "description": "Supporting copy below the headline", + "x-editor": "richtext" + }, + "amounts": { + "type": "array", + "title": "Preset Amounts", + "description": "Preset donation amounts shown as tiles", + "x-editor": "array", + "items": { + "type": "number" + }, + "default": [25, 50, 100] + }, + "buttonLabel": { + "type": "string", + "title": "Button Label", + "description": "Label on the submit button", + "x-editor": "text", + "default": "Donate" + }, + "processorUrl": { + "type": "string", + "title": "Processor URL", + "description": "Donation processor URL (Stripe, Donorbox, etc.)", + "x-editor": "link" + } + } +} diff --git a/schemas/field_note.schema.json b/schemas/field_note.schema.json new file mode 100644 index 0000000..0f0725a --- /dev/null +++ b/schemas/field_note.schema.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Field Note", + "description": "Article-style dispatch from the field with byline and location", + "type": "object", + "properties": { + "author": { + "type": "string", + "title": "Author", + "description": "Author name", + "x-editor": "text" + }, + "location": { + "type": "string", + "title": "Location", + "description": "Location where the dispatch was filed", + "x-editor": "text" + }, + "dateline": { + "type": "string", + "title": "Dateline", + "description": "Date the dispatch was filed", + "x-editor": "text" + }, + "body": { + "type": "string", + "title": "Body", + "description": "Body copy of the field note", + "x-editor": "richtext" + }, + "image": { + "type": "string", + "title": "Image", + "description": "Lede image for the dispatch", + "x-editor": "media" + } + } +} diff --git a/schemas/footer.schema.json b/schemas/footer.schema.json new file mode 100644 index 0000000..0cf2f95 --- /dev/null +++ b/schemas/footer.schema.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Site Footer", + "description": "Cream-on-moss footer with optional newsletter and botanical rule", + "type": "object", + "properties": { + "showNewsletter": { + "type": "boolean", + "title": "Show Newsletter", + "description": "Show the newsletter signup form", + "x-editor": "select", + "default": true + }, + "tagline": { + "type": "string", + "title": "Tagline", + "description": "Tagline above the columns", + "x-editor": "text" + }, + "columns": { + "type": "array", + "title": "Columns", + "description": "Footer link columns", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "title": "Column Title", + "x-editor": "text" + }, + "links": { + "type": "array", + "title": "Links", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "text": { + "type": "string", + "title": "Text", + "x-editor": "text" + }, + "url": { + "type": "string", + "title": "URL", + "x-editor": "link" + } + } + } + } + } + } + } + } +} diff --git a/schemas/impact_metrics.schema.json b/schemas/impact_metrics.schema.json new file mode 100644 index 0000000..f0489ce --- /dev/null +++ b/schemas/impact_metrics.schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Impact Metrics", + "description": "Numerical impact metrics with optional illustration", + "type": "object", + "properties": { + "title": { + "type": "string", + "title": "Section Title", + "description": "Heading above the metric tiles", + "x-editor": "text" + }, + "metrics": { + "type": "array", + "title": "Metrics", + "description": "List of impact metrics", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "title": "Value", + "description": "Numeric value (e.g. '12,000')", + "x-editor": "text" + }, + "label": { + "type": "string", + "title": "Label", + "description": "Description of the metric", + "x-editor": "text" + }, + "suffix": { + "type": "string", + "title": "Suffix", + "description": "Optional suffix (e.g. '+', '%')", + "x-editor": "text" + } + } + } + }, + "illustration": { + "type": "string", + "title": "Illustration", + "description": "Optional botanical illustration shown alongside metrics", + "x-editor": "media" + } + } +} diff --git a/schemas/partner_logos.schema.json b/schemas/partner_logos.schema.json new file mode 100644 index 0000000..cc6877b --- /dev/null +++ b/schemas/partner_logos.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Partner Logos", + "description": "Grayscale partner logo grid with hover-to-color treatment", + "type": "object", + "properties": { + "title": { + "type": "string", + "title": "Section Title", + "description": "Heading above the logo grid", + "x-editor": "text" + }, + "logos": { + "type": "array", + "title": "Logos", + "description": "Partner logos", + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "Partner Name", + "x-editor": "text" + }, + "image": { + "type": "string", + "title": "Logo Image", + "x-editor": "media" + }, + "url": { + "type": "string", + "title": "Partner URL", + "x-editor": "link" + } + } + } + } + } +} diff --git a/template.templ b/template.templ new file mode 100644 index 0000000..2efd124 --- /dev/null +++ b/template.templ @@ -0,0 +1,273 @@ +package main + +import ( + "context" + "git.dev.alexdunmow.com/block/core/templates/bn" +) + +// PageData contains the data for a rendered Earthen page. +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 parseEarthenPageData(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, + } +} + +// EarthenDefault renders the default Earthen page (header, main, footer). +templ EarthenDefault(data PageData) { + + + @bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/earthen/css/earthen.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }) + + @bn.AdminBypassBanner(data.SiteSettings) +
+ @templ.Raw(data.Slots["header"]) +
+
+ if main, ok := data.Slots["main"]; ok && main != "" { + @templ.Raw(main) + } else { +
+

No content blocks assigned to this page.

+
+ } +
+
+ @templ.Raw(data.Slots["footer"]) +
+ @bn.BodyEnd(data.SiteSettings) + + +} + +// EarthenLanding renders the landing template (hero, main, cta, footer). +templ EarthenLanding(data PageData) { + + + @bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/earthen/css/earthen.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }) + + @bn.AdminBypassBanner(data.SiteSettings) +
+ @templ.Raw(data.Slots["hero"]) +
+
+ if main, ok := data.Slots["main"]; ok && main != "" { +
+ @templ.Raw(main) +
+ } +
+
+ @templ.Raw(data.Slots["cta"]) +
+
+ @templ.Raw(data.Slots["footer"]) +
+ @bn.BodyEnd(data.SiteSettings) + + +} + +// EarthenArticle renders the article template (header, lede, main, aside, footer). +templ EarthenArticle(data PageData) { + + + @bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/earthen/css/earthen.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }) + + @bn.AdminBypassBanner(data.SiteSettings) +
+
+ @templ.Raw(data.Slots["header"]) +
+
+ if lede, ok := data.Slots["lede"]; ok && lede != "" { +
+ @templ.Raw(lede) +
+ } +
+
+ if main, ok := data.Slots["main"]; ok && main != "" { +
+ @templ.Raw(main) +
+ } else { +

No story body yet.

+ } +
+ if aside, ok := data.Slots["aside"]; ok && aside != "" { + + } +
+
+ @templ.Raw(data.Slots["footer"]) +
+ @bn.BodyEnd(data.SiteSettings) + + +} + +// EarthenFullWidth renders edge-to-edge pages (header, main, footer). +templ EarthenFullWidth(data PageData) { + + + @bn.Head(bn.HeadData{ + Title: data.Title, + Settings: data.SiteSettings, + PageMeta: data.PageMeta, + ThemeMode: data.ThemeMode, + ThemeCSS: data.ThemeCSS, + PluginStyles: []string{"/templates/earthen/css/earthen.css"}, + StructuredData: data.StructuredData, + CSSHash: data.CSSHash, + PageviewNonce: data.PageviewNonce, + EngagementConfig: data.EngagementConfig, + }) + + @bn.AdminBypassBanner(data.SiteSettings) +
+ @templ.Raw(data.Slots["header"]) +
+
+ if main, ok := data.Slots["main"]; ok && main != "" { + @templ.Raw(main) + } else { +
+

No content blocks assigned to this page.

+
+ } +
+
+ @templ.Raw(data.Slots["footer"]) +
+ @bn.BodyEnd(data.SiteSettings) + + +} + +// RenderEarthenDefault is the templates.TemplateFunc entry for "default". +func RenderEarthenDefault(ctx context.Context, doc map[string]any) templ.Component { + return EarthenDefault(parseEarthenPageData(doc)) +} + +// RenderEarthenLanding is the templates.TemplateFunc entry for "landing". +func RenderEarthenLanding(ctx context.Context, doc map[string]any) templ.Component { + return EarthenLanding(parseEarthenPageData(doc)) +} + +// RenderEarthenArticle is the templates.TemplateFunc entry for "article". +func RenderEarthenArticle(ctx context.Context, doc map[string]any) templ.Component { + return EarthenArticle(parseEarthenPageData(doc)) +} + +// RenderEarthenFullWidth is the templates.TemplateFunc entry for "full-width". +func RenderEarthenFullWidth(ctx context.Context, doc map[string]any) templ.Component { + return EarthenFullWidth(parseEarthenPageData(doc)) +} diff --git a/template_templ.go b/template_templ.go new file mode 100644 index 0000000..99939ae --- /dev/null +++ b/template_templ.go @@ -0,0 +1,543 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.1020 +package main + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "context" + "git.dev.alexdunmow.com/block/core/templates/bn" +) + +// PageData contains the data for a rendered Earthen page. +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 parseEarthenPageData(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, + } +} + +// EarthenDefault renders the default Earthen page (header, main, footer). +func EarthenDefault(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, "") + 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/earthen/css/earthen.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, "") + 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, "
") + 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, "
") + 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, "

No content blocks assigned to this page.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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, "
") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// EarthenLanding renders the landing template (hero, main, cta, footer). +func EarthenLanding(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, "") + 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/earthen/css/earthen.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, "") + 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, "
") + 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, "
") + 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, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["cta"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = 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, "
") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// EarthenArticle renders the article template (header, lede, main, aside, footer). +func EarthenArticle(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, "") + 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/earthen/css/earthen.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, "") + 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, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if lede, ok := data.Slots["lede"]; ok && lede != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(lede).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + 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, 26, "
") + 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, 27, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "

No story body yet.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if aside, ok := data.Slots["aside"]; ok && aside != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = 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, 33, "
") + 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, 34, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// EarthenFullWidth renders edge-to-edge pages (header, main, footer). +func EarthenFullWidth(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, 35, "") + 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/earthen/css/earthen.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, 36, "") + 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, 37, "
") + 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, 38, "
") + 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, 39, "

No content blocks assigned to this page.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
") + 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, 41, "
") + 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, 42, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// RenderEarthenDefault is the templates.TemplateFunc entry for "default". +func RenderEarthenDefault(ctx context.Context, doc map[string]any) templ.Component { + return EarthenDefault(parseEarthenPageData(doc)) +} + +// RenderEarthenLanding is the templates.TemplateFunc entry for "landing". +func RenderEarthenLanding(ctx context.Context, doc map[string]any) templ.Component { + return EarthenLanding(parseEarthenPageData(doc)) +} + +// RenderEarthenArticle is the templates.TemplateFunc entry for "article". +func RenderEarthenArticle(ctx context.Context, doc map[string]any) templ.Component { + return EarthenArticle(parseEarthenPageData(doc)) +} + +// RenderEarthenFullWidth is the templates.TemplateFunc entry for "full-width". +func RenderEarthenFullWidth(ctx context.Context, doc map[string]any) templ.Component { + return EarthenFullWidth(parseEarthenPageData(doc)) +} + +var _ = templruntime.GeneratedTemplate diff --git a/text_override.go b/text_override.go new file mode 100644 index 0000000..d4a48e6 --- /dev/null +++ b/text_override.go @@ -0,0 +1,18 @@ +package main + +import ( + "bytes" + "context" +) + +// EarthenTextBlock renders text with Earthen styling. +// When variant is "lede", the first paragraph gains a botanical drop-cap. +func EarthenTextBlock(ctx context.Context, content map[string]any) string { + text := getString(content, "text") + class := getString(content, "class") + variant := getString(content, "variant") + + var buf bytes.Buffer + _ = earthenTextComponent(text, class, variant).Render(ctx, &buf) + return buf.String() +} diff --git a/text_override.templ b/text_override.templ new file mode 100644 index 0000000..ec22d04 --- /dev/null +++ b/text_override.templ @@ -0,0 +1,20 @@ +package main + +// earthenTextComponent renders body text with optional lede drop-cap. +templ earthenTextComponent(text, class, variant string) { + if variant == "lede" { +
+ @templ.Raw(text) +
+ } else { +
+ @templ.Raw(text) +
+ } +} diff --git a/text_override_templ.go b/text_override_templ.go new file mode 100644 index 0000000..39083ee --- /dev/null +++ b/text_override_templ.go @@ -0,0 +1,126 @@ +// 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" + +// earthenTextComponent renders body text with optional lede drop-cap. +func earthenTextComponent(text, class, variant 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) + if variant == "lede" { + var templ_7745c5c3_Var2 = []any{"earthen-text earthen-lede drop-cap-host 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, "
") + 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, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var5 = []any{"earthen-text max-w-none flex-1", class} + 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, 5, "
") + 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, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate