Compare commits
No commits in common. "main" and "v0.1.1" have entirely different histories.
1
.gitignore
vendored
@ -3,4 +3,3 @@
|
||||
tmp/
|
||||
.idea/
|
||||
.vscode/
|
||||
*.bnp
|
||||
|
||||
60
Makefile
@ -1,55 +1,29 @@
|
||||
# earthen — build & publish helpers (CODELESS .bnp workflow, WO-WZ-021)
|
||||
# Earthen — build helpers (.so plugin workflow)
|
||||
#
|
||||
# This theme is a CODELESS .bnp: pure declaration the host runs — blocks
|
||||
# (blocks/blocks.yaml + .ninjatpl), page/system templates, template overrides,
|
||||
# an email wrapper, master pages, presets, fonts and CSS in manifest.yaml. No
|
||||
# Go, no templ, no plugin.wasm, no block/core dependency. `ninja plugin build`
|
||||
# classifies the repo as codeless (no Go at the root) and packs the artifact
|
||||
# with manifest.pb (codeless: true) and NO plugin.wasm.
|
||||
#
|
||||
# Usage:
|
||||
# make build # pack earthen-<version>.bnp (codeless — no wasm)
|
||||
# make verify # validate the built .bnp (loader-identical checks)
|
||||
# make archive-check # prove a clean `git archive HEAD` still builds codeless
|
||||
# make publish # ninja plugin publish --bnp (to the active registry)
|
||||
# make clean # remove build artefacts
|
||||
# Local build only. This Makefile deliberately does NOT include rebuild/deploy
|
||||
# targets to avoid touching the live CMS container during agent runs.
|
||||
|
||||
.PHONY: build verify archive-check publish clean help
|
||||
.PHONY: all clean templ help
|
||||
|
||||
PLUGIN_NAME := earthen
|
||||
NINJA := ninja
|
||||
BNP := $(PLUGIN_NAME)-$(shell grep '^version' plugin.mod | sed 's/.*"\(.*\)"/\1/').bnp
|
||||
|
||||
# Pack the codeless .bnp (manifest.pb synthesized from plugin.mod + manifest.yaml
|
||||
# + blocks/ + templates/; no wasm compile, no DESCRIBE probe).
|
||||
build:
|
||||
$(NINJA) plugin build --dir .
|
||||
# Default target: build the .so locally.
|
||||
all: $(PLUGIN_NAME).so
|
||||
|
||||
# Validate the built .bnp exactly as the registry/loader will.
|
||||
verify:
|
||||
$(NINJA) plugin verify $(BNP)
|
||||
# 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 .
|
||||
|
||||
# Prove a clean `git archive HEAD` (what publish ships) still packs codeless.
|
||||
archive-check:
|
||||
@T=$$(mktemp -d /tmp/archive-check-$(PLUGIN_NAME).XXXXXX) && \
|
||||
trap 'rm -rf "$$T"' EXIT && \
|
||||
git archive HEAD | tar -x -C "$$T" && \
|
||||
cd "$$T" && $(NINJA) plugin build --dir . && \
|
||||
echo "archive-check OK"
|
||||
# Regenerate templ Go files.
|
||||
templ:
|
||||
templ generate
|
||||
|
||||
# Publish the prebuilt .bnp to the active registry.
|
||||
# Pass --host https://my.blockninja.dev for the dev orchestrator.
|
||||
publish: build
|
||||
$(NINJA) plugin publish --bnp $(BNP)
|
||||
|
||||
# Remove build artefacts.
|
||||
# Remove built artefacts.
|
||||
clean:
|
||||
rm -f $(PLUGIN_NAME)-*.bnp plugin.wasm
|
||||
rm -f $(PLUGIN_NAME).so
|
||||
|
||||
help:
|
||||
@echo "Targets:"
|
||||
@echo " build Pack the codeless .bnp (no wasm)"
|
||||
@echo " verify Validate the built .bnp (loader-identical checks)"
|
||||
@echo " archive-check Prove a clean git archive HEAD packs codeless"
|
||||
@echo " publish Publish the .bnp to the active registry"
|
||||
@echo " clean Remove build artefacts"
|
||||
@echo " all Build $(PLUGIN_NAME).so locally (default)"
|
||||
@echo " templ Regenerate templ Go files"
|
||||
@echo " clean Remove built .so"
|
||||
|
||||
24
README.md
@ -1,24 +0,0 @@
|
||||
# Earthen
|
||||
|
||||
A warm, hand-crafted theme in moss, clay, and sage over sun-bleached cream. Botanical line-art and soft organic shapes sit on a paper-grain ground, so a page feels grounded and hopeful rather than corporate. It was made for causes people care about. The palette carries a quiet, outdoor calm.
|
||||
|
||||
## Good for
|
||||
|
||||
We built Earthen with cause-driven work in mind. Treat these as starting points, not limits. If the mood fits your brand, use it:
|
||||
|
||||
- Conservation groups and land trusts
|
||||
- Nonprofits and NGOs running donation drives
|
||||
- Community farms and food co-ops
|
||||
- Sustainability and outdoor education projects
|
||||
|
||||
## What you get
|
||||
|
||||
- Five color presets, each with a tuned light mode and a tuned dark mode: Mossbed, Clay Field, Wet Loam, Sage Morning, and Ember Loam.
|
||||
- Fraunces for headings and Work Sans for body text, both bundled and ready. Fonts stay admin-controlled, so you can swap them from the typography settings.
|
||||
- Three persona blocks: a donation appeal, a field-dispatch note, and an impact report.
|
||||
- A demo site that installs when you activate the theme: a home page, an about page, a short blog, and a contact page whose form emails your admins. It comes seeded as a conservation org called Rootline Trust.
|
||||
- Every core block restyled to match, including botanical dividers, plus a themed login screen and a themed 404 page.
|
||||
|
||||
## Install
|
||||
|
||||
Install from the BlockNinja registry, then pick a preset and assign your fonts in the admin. Activate the demo content if you want a fully dressed starting point.
|
||||
@ -1,39 +1,47 @@
|
||||
# Earthen — fonts
|
||||
# Earthen — recommended fonts
|
||||
|
||||
Earthen **bundles** its display and body faces as `source=template` rows via
|
||||
`fonts.json`, so the theme reads correctly on activation with no admin setup.
|
||||
All CSS references fonts through `var(--font-heading)` / `var(--font-body)` /
|
||||
`var(--font-mono)` with a fallback stack, so the admin can still override any
|
||||
role from the Typography panel.
|
||||
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.
|
||||
|
||||
## Bundled faces (ship with the theme)
|
||||
Configure these in the admin Typography panel to match the spec's
|
||||
intended look.
|
||||
|
||||
| Role | Family | Weights | File | License |
|
||||
|------|--------|---------|------|---------|
|
||||
| Heading | **Fraunces** | 400, 600 | `assets/fonts/fraunces-latin-400.woff2`, `fraunces-latin-600.woff2` | SIL Open Font License 1.1 (`assets/fonts/OFL.txt`) |
|
||||
| Body | **Work Sans** | 400, 600 | `assets/fonts/worksans-latin-400.woff2`, `worksans-latin-600.woff2` | SIL Open Font License 1.1 (`assets/fonts/OFL.txt`) |
|
||||
## Display / heading — assign to `Heading`
|
||||
|
||||
Fraunces gives the organic, slightly wobbly display voice the persona wants;
|
||||
Work Sans keeps body copy warm but legible for donation and report pages. Both
|
||||
are OFL, subset to the Latin range to stay inside the marketplace size gate.
|
||||
Primary pick:
|
||||
|
||||
## Fallback stacks (used before/without admin fonts)
|
||||
- **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.)
|
||||
|
||||
- Heading: `var(--font-heading, "Fraunces", "Playfair Display", Georgia, serif)`
|
||||
- Body: `var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif)`
|
||||
- Mono: `var(--font-mono, "JetBrains Mono", ui-monospace, monospace)`
|
||||
Spec-preferred display face (commercial):
|
||||
|
||||
## Optional admin choices
|
||||
- **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.
|
||||
|
||||
- **Mono** is not bundled. If your content uses monospace (data captions, code),
|
||||
add **JetBrains Mono** (`google:JetBrains Mono`, OFL) from the Typography panel
|
||||
Google Fonts tab and assign it to Mono. Until then the system mono stack applies.
|
||||
- **Alegreya** (`google:Alegreya`, OFL) is a good serif alternative for Body if you
|
||||
want a more literary, long-read feel for field journals. Assign it to Body to swap.
|
||||
## 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
|
||||
|
||||
- Do not hardcode a `font-family` anywhere. Every family flows through the CSS
|
||||
variables so the admin font picker keeps working.
|
||||
- Bundled woff2 files are subset to Latin. Extend the subset only if you serve
|
||||
non-Latin content, and keep the total artifact within the registry size gate.
|
||||
- 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.
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
# Earthen — icon packs
|
||||
|
||||
Earthen renders icons through the sprite system only
|
||||
(`<svg><use href="/icons/<pack>.svg#<name>"/></svg>`, or the `::pack:name:size::`
|
||||
shorthand in templates). It does **not** inline icon SVGs. The theme's botanical
|
||||
line-art (fern, seed, root motifs) is decorative illustration, not icons, and is
|
||||
drawn inline on purpose.
|
||||
|
||||
## Required pack
|
||||
|
||||
- **Lucide** — declared in `plugin.mod` `required_icon_packs = ["lucide"]`.
|
||||
Lucide ships bundled with the CMS, so no action is normally needed. If it is
|
||||
missing on your instance, open `/admin/icons`, install **Lucide**, and enable it.
|
||||
|
||||
Block schemas that expose an icon picker default to Lucide names, so the theme
|
||||
renders correctly out of the box. When the loader begins honoring
|
||||
`required_icon_packs`, Lucide will be installed automatically on activation.
|
||||
|
||||
## Optional
|
||||
|
||||
No other packs are required. If you add icons from another pack in block content,
|
||||
install that pack via `/admin/icons` first, then reference it as `"<pack>:<name>"`
|
||||
in the block's icon field.
|
||||
114
assets/css/earthen.css
Normal file
@ -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));
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
Bundled fonts — SIL Open Font License 1.1
|
||||
=========================================
|
||||
|
||||
This directory bundles latin-subset woff2 files for three families, all
|
||||
licensed under the SIL Open Font License, Version 1.1 (OFL-1.1).
|
||||
|
||||
Copyright notices
|
||||
-----------------
|
||||
Fraunces: Copyright 2018 The Fraunces Project Authors (https://github.com/undercasetype/Fraunces)
|
||||
Work Sans: Copyright 2019 The Work Sans Project Authors (https://github.com/weiweihuanghuang/Work-Sans)
|
||||
Caveat: Copyright 2019 The Caveat Project Authors (https://github.com/googlefonts/caveat)
|
||||
|
||||
Files
|
||||
-----
|
||||
fraunces-latin-400.woff2, fraunces-latin-600.woff2 — Fraunces (heading)
|
||||
worksans-latin-400.woff2, worksans-latin-600.woff2 — Work Sans (body)
|
||||
caveat-latin-400.woff2, caveat-latin-700.woff2 — Caveat (hand-drawn accents)
|
||||
|
||||
Full license text (SIL OFL 1.1)
|
||||
-------------------------------
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@ -1,9 +1,5 @@
|
||||
# Earthen fonts
|
||||
|
||||
Bundled woff2 faces, served as `source=template` rows via `../../fonts.json`:
|
||||
|
||||
- **Fraunces** 400/600 (`fraunces-latin-400.woff2`, `fraunces-latin-600.woff2`) — heading.
|
||||
- **Work Sans** 400/600 (`worksans-latin-400.woff2`, `worksans-latin-600.woff2`) — body.
|
||||
|
||||
Both are SIL Open Font License 1.1 (see `OFL.txt`), subset to Latin. See
|
||||
`../../RECOMMENDED_FONTS.md` for admin assignment and the mono option.
|
||||
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.
|
||||
|
||||
268
assets/style.css
@ -1,268 +0,0 @@
|
||||
/* 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 Work Sans body fallback. */
|
||||
.earthen-article {
|
||||
font-family: var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif);
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* Divider glyph — softer stroke at rest. */
|
||||
.earthen-glyph {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* Organic blob — soft asymmetric corners for cards, media frames and hero art.
|
||||
The signature earthen shape: hand-crafted, never a hard rectangle. */
|
||||
.earthen-blob {
|
||||
border-radius: 42% 58% 62% 38% / 42% 44% 56% 58%;
|
||||
}
|
||||
.earthen-blob-soft {
|
||||
border-radius: 1.75rem 2.25rem 1.75rem 2.25rem;
|
||||
}
|
||||
|
||||
/* Soft card / panel frame — cream surface with a hairline botanical border. */
|
||||
.earthen-panel {
|
||||
background-color: hsl(var(--card));
|
||||
color: hsl(var(--card-foreground));
|
||||
border: 1px solid hsl(var(--border));
|
||||
box-shadow: inset 0 0 0 1px hsl(var(--border) / 0.35);
|
||||
}
|
||||
|
||||
/* Terracotta seedling button — the primary call to action across the theme. */
|
||||
.earthen-cta {
|
||||
background-color: hsl(var(--accent));
|
||||
color: hsl(var(--accent-foreground));
|
||||
border-radius: 0.75rem;
|
||||
transition: box-shadow 200ms ease, transform 120ms ease;
|
||||
}
|
||||
.earthen-cta:hover,
|
||||
.earthen-cta:focus-visible {
|
||||
box-shadow: 0 6px 0 -2px hsl(var(--accent) / 0.35);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.earthen-cta:focus-visible {
|
||||
outline: 2px solid hsl(var(--ring));
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Sage link — dotted botanical underline that fills on hover. */
|
||||
.earthen-link {
|
||||
color: hsl(var(--primary));
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted hsl(var(--primary) / 0.6);
|
||||
transition: color 150ms ease, border-color 150ms ease;
|
||||
}
|
||||
.earthen-link:hover,
|
||||
.earthen-link:focus-visible {
|
||||
color: hsl(var(--accent));
|
||||
border-bottom-color: hsl(var(--accent));
|
||||
}
|
||||
|
||||
/* Leaf line-draw — a hairline that draws in on scroll where motion is allowed. */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.earthen-draw {
|
||||
stroke-dasharray: 240;
|
||||
stroke-dashoffset: 240;
|
||||
animation: earthen-draw-in 1.4s ease forwards;
|
||||
}
|
||||
@keyframes earthen-draw-in {
|
||||
to { stroke-dashoffset: 0; }
|
||||
}
|
||||
.earthen-blob-morph {
|
||||
animation: earthen-blob-morph 18s ease-in-out infinite;
|
||||
}
|
||||
@keyframes earthen-blob-morph {
|
||||
0%, 100% { border-radius: 42% 58% 62% 38% / 42% 44% 56% 58%; }
|
||||
50% { border-radius: 58% 42% 38% 62% / 56% 58% 42% 44%; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Responsive layout primitives ──────────────────────────────────────────
|
||||
These are shipped as compiled CSS (not Tailwind utilities) on purpose: the
|
||||
host Tailwind JIT does not scan a theme's page/layout, override or
|
||||
master-page templates, so responsive grid utilities that live only in those
|
||||
files (e.g. sm:grid-cols-3, md:grid-cols-[2fr_1fr]) are silently dropped and
|
||||
never collapse or expand. Compiling them here guarantees the layout works on
|
||||
both mobile and desktop. Layout-only — no colour tokens. */
|
||||
|
||||
/* Donation CTA — preset amount tiles: 1-up on mobile, 3-up from 640px. */
|
||||
.earthen-amount-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.earthen-amount-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
/* Donation CTA — custom amount + submit: stack on mobile, inline from 640px. */
|
||||
.earthen-amount-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
align-items: stretch;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.earthen-amount-row { flex-direction: row; align-items: center; }
|
||||
}
|
||||
|
||||
/* Field Journal — post cards: 1-up mobile, 2-up >=640px, 3-up >=1024px. */
|
||||
.earthen-post-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
}
|
||||
@media (min-width: 640px) {
|
||||
.earthen-post-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.earthen-post-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
}
|
||||
|
||||
/* Contact — form + aside: stacked on mobile, 2fr/1fr from 768px. */
|
||||
.earthen-contact-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 3rem;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.earthen-contact-grid { grid-template-columns: 2fr 1fr; }
|
||||
}
|
||||
|
||||
/* Article — body + byline rail: stacked on mobile, main + 260px rail >=1024px. */
|
||||
.earthen-article-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 3rem;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.earthen-article-grid { grid-template-columns: minmax(0, 1fr) 260px; }
|
||||
}
|
||||
|
||||
/* Hero — comfortable padded band. A text-only (image-less) hero uses padding
|
||||
only, so it never balloons into a tall empty band. When a background image
|
||||
IS present the taller band is applied via the --media modifier. */
|
||||
.earthen-hero {
|
||||
padding-top: 4rem;
|
||||
padding-bottom: 4rem;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.earthen-hero { padding-top: 6rem; padding-bottom: 6rem; }
|
||||
}
|
||||
.earthen-hero.earthen-hero--media {
|
||||
min-height: 60vh;
|
||||
padding-top: 3rem;
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.earthen-hero.earthen-hero--media { min-height: 72vh; }
|
||||
}
|
||||
|
||||
/* Split hero — the media column needs a floor so its absolutely-positioned
|
||||
image has height to fill. Only rendered when an image is present. */
|
||||
.earthen-hero-split-media {
|
||||
position: relative;
|
||||
min-height: 320px;
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
# Earthen theme block definitions (codeless .bnp, WO-WZ-021 / WO-TF-016).
|
||||
# Only TRUE persona furniture remains as theme blocks. Everything that
|
||||
# duplicated a builtin (footer, stats/impact-metrics, logo-strip/partner-logos,
|
||||
# divider/botanical-divider) is now a template OVERRIDE of the builtin key
|
||||
# (templates/overrides/earthen/). Keys are fully qualified (earthen:<key>) — the
|
||||
# codeless loader registers definitions verbatim; the qualified keys match the
|
||||
# data-block attributes, master_pages.json and seed references.
|
||||
blocks:
|
||||
- key: earthen:donation_cta
|
||||
title: Donation CTA
|
||||
description: Donation appeal with preset amount tiles and a custom amount field.
|
||||
category: content
|
||||
schema: donation_cta.schema.json
|
||||
template: donation_cta.ninjatpl
|
||||
- key: earthen:field_note
|
||||
title: Field Note
|
||||
description: Dispatch from the field with byline, location and a lede image.
|
||||
category: content
|
||||
schema: field_note.schema.json
|
||||
template: field_note.ninjatpl
|
||||
- key: earthen:impact_report
|
||||
title: Impact Report
|
||||
description: Narrative impact report with a summary lede and outcome rows.
|
||||
category: content
|
||||
schema: impact_report.schema.json
|
||||
template: impact_report.ninjatpl
|
||||
@ -1,16 +0,0 @@
|
||||
<section data-block="earthen:donation_cta"{% if not headline and not body and not amounts and not buttonLabel and not processorUrl %} data-empty{% endif %} class="earthen-donation-cta py-16 px-4" style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground));">
|
||||
<div class="max-w-3xl mx-auto text-center">
|
||||
{% if headline %}<h2 class="earthen-display text-3xl md:text-4xl mb-4" style="font-family: var(--font-heading, \000022Fraunces\000022, \000022Playfair Display\000022, Georgia, serif); color: hsl(var(--primary));">{{ headline }}</h2>{% endif %}
|
||||
{% if body %}<div class="earthen-body text-lg mb-8 mx-auto max-w-xl" style="font-family: var(--font-body, \000022Work Sans\000022, ui-sans-serif, system-ui, sans-serif); color: hsl(var(--foreground));">{{ body|safe }}</div>{% endif %}
|
||||
<form action="{{ processorUrl|default:"#" }}" method="get" class="space-y-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3" role="radiogroup" aria-label="Preset donation amounts">
|
||||
{% if amounts %}{% for a in amounts %}<label data-amount-tile class="earthen-tile flex items-center justify-center cursor-pointer rounded-lg py-4 px-3 text-lg font-semibold transition-colors" style="border: 1px solid hsl(var(--border)); background-color: hsl(var(--background)); color: hsl(var(--foreground));"><input type="radio" name="amount" value="{{ a }}" class="sr-only"{% if forloop.First %} checked{% endif %}><span>${{ a }}</span></label>{% endfor %}{% else %}<label data-amount-tile class="earthen-tile flex items-center justify-center cursor-pointer rounded-lg py-4 px-3 text-lg font-semibold transition-colors" style="border: 1px solid hsl(var(--border)); background-color: hsl(var(--background)); color: hsl(var(--foreground));"><input type="radio" name="amount" value="25" class="sr-only" checked><span>$25</span></label><label data-amount-tile class="earthen-tile flex items-center justify-center cursor-pointer rounded-lg py-4 px-3 text-lg font-semibold transition-colors" style="border: 1px solid hsl(var(--border)); background-color: hsl(var(--background)); color: hsl(var(--foreground));"><input type="radio" name="amount" value="50" class="sr-only"><span>$50</span></label><label data-amount-tile class="earthen-tile flex items-center justify-center cursor-pointer rounded-lg py-4 px-3 text-lg font-semibold transition-colors" style="border: 1px solid hsl(var(--border)); background-color: hsl(var(--background)); color: hsl(var(--foreground));"><input type="radio" name="amount" value="100" class="sr-only"><span>$100</span></label>{% endif %}
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-3 items-stretch sm:items-center">
|
||||
<label class="sr-only" for="earthen-custom-amount">Custom amount</label>
|
||||
<input id="earthen-custom-amount" type="number" name="customAmount" min="1" step="1" placeholder="Other amount" class="flex-1 rounded-lg py-3 px-4 text-base" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); border: 1px solid hsl(var(--input));">
|
||||
<button type="submit" class="earthen-button inline-block rounded-lg py-3 px-6 font-semibold transition-colors" style="background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));">{{ buttonLabel|default:"Donate" }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
@ -1,8 +0,0 @@
|
||||
<article data-block="earthen:field_note"{% if not author and not location and not dateline and not body and not image %} data-empty{% endif %} class="earthen-field-note earthen-paper-frame mx-auto my-12 max-w-3xl p-8 rounded-lg" style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));">
|
||||
<header class="mb-6">
|
||||
<div class="earthen-byline flex flex-wrap gap-x-3 gap-y-1 uppercase tracking-wider text-xs mb-2" style="color: hsl(var(--muted-foreground));">{% if author %}<span class="earthen-byline-author">By {{ author }}</span>{% endif %}{% if location %}<span class="earthen-byline-location">{{ location }}</span>{% endif %}{% if dateline %}<span class="earthen-byline-date">{{ dateline }}</span>{% endif %}</div>
|
||||
<div class="botanical-rule" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="28" height="28" data-motif="seed" class="earthen-glyph" style="color: hsl(var(--accent));" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="24" cy="24" rx="6" ry="10"></ellipse><path d="M24 14V6M18 18l-6-6M30 18l6-6M24 34v8M18 30l-6 6M30 30l6 6"></path></svg></div>
|
||||
</header>
|
||||
{% if image %}<figure class="my-6 earthen-paper-frame-inner overflow-hidden earthen-blob-soft">{% img image alt="" class="w-full h-auto" %}</figure>{% endif %}
|
||||
{% if body %}<div class="earthen-body drop-cap prose-base" style="font-family: var(--font-body, \000022Work Sans\000022, ui-sans-serif, system-ui, sans-serif); color: hsl(var(--foreground));">{{ body|safe }}</div>{% else %}<p class="earthen-empty" style="color: hsl(var(--muted-foreground));">No dispatch yet — add a body to share what you saw in the field.</p>{% endif %}
|
||||
</article>
|
||||
@ -1,14 +0,0 @@
|
||||
<section data-block="earthen:impact_report"{% if not title and not summary and not outcomes and not period %} data-empty{% endif %} class="earthen-impact-report bg-paper-grain py-16 px-4" style="color: hsl(var(--foreground));">
|
||||
<div class="earthen-panel earthen-blob-soft mx-auto max-w-4xl p-8 md:p-12">
|
||||
<div class="mb-8 flex items-start gap-4">
|
||||
<span class="mt-1 shrink-0" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="40" height="40" data-motif="fern" class="earthen-glyph" style="color: hsl(var(--primary));" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M24 44V8"></path><path d="M24 12c-4-2-7-2-9 0M24 12c4-2 7-2 9 0"></path><path d="M24 18c-6-2-10-1-12 1M24 18c6-2 10-1 12 1"></path><path d="M24 25c-7-1-12 0-14 3M24 25c7-1 12 0 14 3"></path><path d="M24 33c-7 0-11 2-12 5M24 33c7 0 11 2 12 5"></path></svg></span>
|
||||
<div>
|
||||
{% if period %}<p class="mb-1 text-xs uppercase tracking-widest" style="color: hsl(var(--accent)); font-family: var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif);">{{ period }}</p>{% endif %}
|
||||
{% if title %}<h2 class="crayon-underline text-3xl md:text-4xl" style="font-family: var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif); color: hsl(var(--primary));">{{ title }}</h2>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if summary %}<div class="mb-10 max-w-2xl text-lg leading-relaxed" style="font-family: var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif); color: hsl(var(--foreground));">{{ summary|safe }}</div>{% endif %}
|
||||
{% if outcomes %}<dl class="divide-y" style="border-color: hsl(var(--border));">{% for o in outcomes %}<div class="flex flex-col gap-1 py-5 sm:flex-row sm:items-baseline sm:gap-6" style="border-color: hsl(var(--border));"><dt class="shrink-0 text-4xl font-semibold sm:w-48" style="font-family: var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif); color: hsl(var(--accent));">{{ o.value }}</dt><dd class="flex-1"><p class="text-base font-semibold" style="color: hsl(var(--foreground));">{{ o.label }}</p>{% if o.detail %}<p class="mt-1 text-sm" style="color: hsl(var(--muted-foreground));">{{ o.detail }}</p>{% endif %}</dd></div>{% endfor %}</dl>{% else %}<p class="text-center" style="color: hsl(var(--muted-foreground));">Add outcomes to show what this season grew.</p>{% endif %}
|
||||
{% if sourceNote %}<p class="mt-8 text-xs italic" style="color: hsl(var(--muted-foreground));">{{ sourceNote }}</p>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
@ -1,62 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Impact Report",
|
||||
"description": "Narrative impact report with a summary lede and a list of outcome rows",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"period": {
|
||||
"type": "string",
|
||||
"title": "Period",
|
||||
"description": "Reporting period label (e.g. 'Season 2025')",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"title": "Title",
|
||||
"description": "Report heading",
|
||||
"x-editor": "text",
|
||||
"default": "What we grew together"
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"title": "Summary",
|
||||
"description": "Opening paragraph of the report",
|
||||
"x-editor": "richtext"
|
||||
},
|
||||
"outcomes": {
|
||||
"type": "array",
|
||||
"title": "Outcomes",
|
||||
"description": "Reported outcomes shown as rows",
|
||||
"x-editor": "collection",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "string",
|
||||
"title": "Value",
|
||||
"description": "Headline figure or result (e.g. '840 acres')",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"title": "Label",
|
||||
"description": "What the figure counts",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"detail": {
|
||||
"type": "string",
|
||||
"title": "Detail",
|
||||
"description": "One line of context",
|
||||
"x-editor": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourceNote": {
|
||||
"type": "string",
|
||||
"title": "Source Note",
|
||||
"description": "Footnote crediting the source of the figures",
|
||||
"x-editor": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
43
botanical_divider.go
Normal file
@ -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
|
||||
}
|
||||
79
botanical_divider.templ
Normal file
@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
// botanicalDividerComponent renders a centred botanical SVG flourish flanked by hairlines.
|
||||
templ botanicalDividerComponent(data BotanicalDividerData) {
|
||||
<div
|
||||
data-block="earthen:botanical_divider"
|
||||
data-empty?={ data.Empty }
|
||||
class="earthen-divider flex items-center justify-center gap-4 my-12 px-4"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span class="earthen-divider-rule flex-1 h-px" style="background-color: hsl(var(--border));"></span>
|
||||
@botanicalGlyph(data.Motif, 48)
|
||||
<span class="earthen-divider-rule flex-1 h-px" style="background-color: hsl(var(--border));"></span>
|
||||
</div>
|
||||
}
|
||||
|
||||
// botanicalGlyph renders the SVG illustration for the named motif.
|
||||
templ botanicalGlyph(motif string, size int) {
|
||||
switch motif {
|
||||
case "root":
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 48 48"
|
||||
width={ size }
|
||||
height={ size }
|
||||
data-motif="root"
|
||||
class="earthen-glyph"
|
||||
style="color: hsl(var(--primary));"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M24 4v18"/>
|
||||
<path d="M24 22c-4 4-9 5-13 4M24 22c4 4 9 5 13 4M24 22c-2 7-3 12-5 16M24 22c2 7 3 12 5 16M24 22c0 9-1 15-1 20"/>
|
||||
<circle cx="24" cy="22" r="2"/>
|
||||
</svg>
|
||||
case "seed":
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 48 48"
|
||||
width={ size }
|
||||
height={ size }
|
||||
data-motif="seed"
|
||||
class="earthen-glyph"
|
||||
style="color: hsl(var(--accent));"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<ellipse cx="24" cy="24" rx="6" ry="10"/>
|
||||
<path d="M24 14V6M18 18l-6-6M30 18l6-6M24 34v8M18 30l-6 6M30 30l6 6"/>
|
||||
</svg>
|
||||
default:
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 48 48"
|
||||
width={ size }
|
||||
height={ size }
|
||||
data-motif="fern"
|
||||
class="earthen-glyph"
|
||||
style="color: hsl(var(--primary));"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M24 44V8"/>
|
||||
<path d="M24 12c-4-2-7-2-9 0M24 12c4-2 7-2 9 0"/>
|
||||
<path d="M24 18c-6-2-10-1-12 1M24 18c6-2 10-1 12 1"/>
|
||||
<path d="M24 25c-7-1-12 0-14 3M24 25c7-1 12 0 14 3"/>
|
||||
<path d="M24 33c-7 0-11 2-12 5M24 33c7 0 11 2 12 5"/>
|
||||
</svg>
|
||||
}
|
||||
}
|
||||
180
botanical_divider_templ.go
Normal file
@ -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, "<div data-block=\"earthen:botanical_divider\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Empty {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " data-empty")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " class=\"earthen-divider flex items-center justify-center gap-4 my-12 px-4\" aria-hidden=\"true\"><span class=\"earthen-divider-rule flex-1 h-px\" style=\"background-color: hsl(var(--border));\"></span>")
|
||||
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, "<span class=\"earthen-divider-rule flex-1 h-px\" style=\"background-color: hsl(var(--border));\"></span></div>")
|
||||
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, "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" width=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(size)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `botanical_divider.templ`, Line: 24, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" height=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(size)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `botanical_divider.templ`, Line: 25, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" data-motif=\"root\" class=\"earthen-glyph\" style=\"color: hsl(var(--primary));\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M24 4v18\"></path> <path d=\"M24 22c-4 4-9 5-13 4M24 22c4 4 9 5 13 4M24 22c-2 7-3 12-5 16M24 22c2 7 3 12 5 16M24 22c0 9-1 15-1 20\"></path> <circle cx=\"24\" cy=\"22\" r=\"2\"></circle></svg>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
case "seed":
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" width=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(size)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `botanical_divider.templ`, Line: 43, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" height=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(size)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `botanical_divider.templ`, Line: 44, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" data-motif=\"seed\" class=\"earthen-glyph\" style=\"color: hsl(var(--accent));\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><ellipse cx=\"24\" cy=\"24\" rx=\"6\" ry=\"10\"></ellipse> <path d=\"M24 14V6M18 18l-6-6M30 18l6-6M24 34v8M18 30l-6 6M30 30l6 6\"></path></svg>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
default:
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 48 48\" width=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(size)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `botanical_divider.templ`, Line: 61, Col: 16}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" height=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(size)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `botanical_divider.templ`, Line: 62, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" data-motif=\"fern\" class=\"earthen-glyph\" style=\"color: hsl(var(--primary));\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M24 44V8\"></path> <path d=\"M24 12c-4-2-7-2-9 0M24 12c4-2 7-2 9 0\"></path> <path d=\"M24 18c-6-2-10-1-12 1M24 18c6-2 10-1 12 1\"></path> <path d=\"M24 25c-7-1-12 0-14 3M24 25c7-1 12 0 14 3\"></path> <path d=\"M24 33c-7 0-11 2-12 5M24 33c7 0 11 2 12 5\"></path></svg>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
27
button_override.go
Normal file
@ -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()
|
||||
}
|
||||
34
button_override.templ
Normal file
@ -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) {
|
||||
<a
|
||||
href={ templ.SafeURL(earthenButtonURL(url)) }
|
||||
data-variant={ variant }
|
||||
class="earthen-button inline-flex items-center justify-center px-6 py-3 rounded-lg font-semibold transition-colors focus:outline-none"
|
||||
style={ earthenButtonStyle(variant) }
|
||||
>
|
||||
{ label }
|
||||
</a>
|
||||
}
|
||||
|
||||
func earthenButtonURL(url string) string {
|
||||
if url == "" {
|
||||
return "#"
|
||||
}
|
||||
return url
|
||||
}
|
||||
114
button_override_templ.go
Normal file
@ -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, "<a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 templ.SafeURL
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(earthenButtonURL(url)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 20, Col: 45}
|
||||
}
|
||||
_, 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, "\" data-variant=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(variant)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 21, Col: 24}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" class=\"earthen-button inline-flex items-center justify-center px-6 py-3 rounded-lg font-semibold transition-colors focus:outline-none\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(earthenButtonStyle(variant))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 23, Col: 37}
|
||||
}
|
||||
_, 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, 4, "\">")
|
||||
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, "</a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func earthenButtonURL(url string) string {
|
||||
if url == "" {
|
||||
return "#"
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
33
card_override.go
Normal file
@ -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
|
||||
}
|
||||
46
card_override.templ
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
// earthenCardComponent renders a card with soft radius and ink-edge border.
|
||||
templ earthenCardComponent(data EarthenCardData) {
|
||||
<article
|
||||
class="earthen-card rounded-xl overflow-hidden p-6"
|
||||
data-empty?={ data.Empty }
|
||||
style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border)); box-shadow: inset 0 0 0 1px hsl(var(--border) / 0.4);"
|
||||
>
|
||||
if data.Image != "" {
|
||||
<div class="earthen-card-image -mx-6 -mt-6 mb-4 overflow-hidden">
|
||||
<img src={ resolveMedia(data.Image) } alt={ data.Title } class="w-full h-auto"/>
|
||||
</div>
|
||||
}
|
||||
if data.Title != "" {
|
||||
<h3
|
||||
class="earthen-card-title text-2xl mb-3"
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));" }
|
||||
>
|
||||
{ data.Title }
|
||||
</h3>
|
||||
}
|
||||
if data.Body != "" {
|
||||
<div
|
||||
class="earthen-card-body mb-4"
|
||||
style={ "font-family: var(--font-body, \"Spectral\", Georgia, serif); color: hsl(var(--foreground));" }
|
||||
>
|
||||
@templ.Raw(data.Body)
|
||||
</div>
|
||||
}
|
||||
if data.Link != "" {
|
||||
<a
|
||||
href={ templ.SafeURL(data.Link) }
|
||||
class="earthen-card-link inline-flex items-center gap-2 font-semibold"
|
||||
style="color: hsl(var(--accent));"
|
||||
>
|
||||
if data.LinkLabel != "" {
|
||||
<span>{ data.LinkLabel }</span>
|
||||
} else {
|
||||
<span>Read more</span>
|
||||
}
|
||||
<span aria-hidden="true">→</span>
|
||||
</a>
|
||||
}
|
||||
</article>
|
||||
}
|
||||
193
card_override_templ.go
Normal file
@ -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, "<article class=\"earthen-card rounded-xl overflow-hidden p-6\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Empty {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " data-empty")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " style=\"background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border)); box-shadow: inset 0 0 0 1px hsl(var(--border) / 0.4);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Image != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"earthen-card-image -mx-6 -mt-6 mb-4 overflow-hidden\"><img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(resolveMedia(data.Image))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 12, Col: 39}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" alt=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 12, Col: 58}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" class=\"w-full h-auto\"></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.Title != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h3 class=\"earthen-card-title text-2xl mb-3\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 18, Col: 127}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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, "</h3>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.Body != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"earthen-card-body mb-4\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-body, \"Spectral\", Georgia, serif); color: hsl(var(--foreground));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 26, Col: 105}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">")
|
||||
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, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.Link != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 templ.SafeURL
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.Link))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 33, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" class=\"earthen-card-link inline-flex items-center gap-2 font-semibold\" style=\"color: hsl(var(--accent));\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.LinkLabel != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span>")
|
||||
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, "</span> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span>Read more</span> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<span aria-hidden=\"true\">→</span></a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</article>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
67
donation_cta.go
Normal file
@ -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
|
||||
}
|
||||
79
donation_cta.templ
Normal file
@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
import "strconv"
|
||||
|
||||
// donationCTAComponent renders the moss/clay donation CTA section.
|
||||
templ donationCTAComponent(data DonationCTAData) {
|
||||
<section
|
||||
data-block="earthen:donation_cta"
|
||||
data-empty?={ data.Empty }
|
||||
class="earthen-donation-cta py-16 px-4"
|
||||
style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground));"
|
||||
>
|
||||
<div class="max-w-3xl mx-auto text-center">
|
||||
if data.Headline != "" {
|
||||
<h2
|
||||
class="earthen-display text-3xl md:text-4xl mb-4"
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));" }
|
||||
>
|
||||
{ data.Headline }
|
||||
</h2>
|
||||
}
|
||||
if data.Body != "" {
|
||||
<div
|
||||
class="earthen-body text-lg mb-8 mx-auto max-w-xl"
|
||||
style={ "font-family: var(--font-body, \"Spectral\", Georgia, serif); color: hsl(var(--foreground));" }
|
||||
>
|
||||
@templ.Raw(data.Body)
|
||||
</div>
|
||||
}
|
||||
<form action={ templ.SafeURL(donationFormAction(data.ProcessorURL)) } method="get" class="space-y-6">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3" role="radiogroup" aria-label="Preset donation amounts">
|
||||
for i, tile := range data.Tiles {
|
||||
<label
|
||||
data-amount-tile
|
||||
class="earthen-tile flex items-center justify-center cursor-pointer rounded-lg py-4 px-3 text-lg font-semibold transition-colors"
|
||||
style="border: 1px solid hsl(var(--border)); background-color: hsl(var(--background)); color: hsl(var(--foreground));"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="amount"
|
||||
value={ strconv.FormatFloat(tile.Value, 'f', -1, 64) }
|
||||
class="sr-only"
|
||||
checked?={ i == 0 }
|
||||
/>
|
||||
<span>{ tile.Label }</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row gap-3 items-stretch sm:items-center">
|
||||
<label class="sr-only" for="earthen-custom-amount">Custom amount</label>
|
||||
<input
|
||||
id="earthen-custom-amount"
|
||||
type="number"
|
||||
name="customAmount"
|
||||
min="1"
|
||||
step="1"
|
||||
placeholder="Other amount"
|
||||
class="flex-1 rounded-lg py-3 px-4 text-base"
|
||||
style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); border: 1px solid hsl(var(--input));"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="earthen-button inline-block rounded-lg py-3 px-6 font-semibold transition-colors"
|
||||
style="background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));"
|
||||
>
|
||||
{ data.ButtonLabel }
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
func donationFormAction(url string) string {
|
||||
if url == "" {
|
||||
return "#"
|
||||
}
|
||||
return url
|
||||
}
|
||||
195
donation_cta_templ.go
Normal file
@ -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, "<section data-block=\"earthen:donation_cta\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Empty {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " data-empty")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " class=\"earthen-donation-cta py-16 px-4\" style=\"background-color: hsl(var(--card)); color: hsl(var(--card-foreground));\"><div class=\"max-w-3xl mx-auto text-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Headline != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h2 class=\"earthen-display text-3xl md:text-4xl mb-4\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `donation_cta.templ`, Line: 17, Col: 128}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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, "</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.Body != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"earthen-body text-lg mb-8 mx-auto max-w-xl\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-body, \"Spectral\", Georgia, serif); color: hsl(var(--foreground));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `donation_cta.templ`, Line: 25, Col: 106}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<form action=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 templ.SafeURL
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(donationFormAction(data.ProcessorURL)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `donation_cta.templ`, Line: 30, Col: 70}
|
||||
}
|
||||
_, 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, 11, "\" method=\"get\" class=\"space-y-6\"><div class=\"grid grid-cols-1 sm:grid-cols-3 gap-3\" role=\"radiogroup\" aria-label=\"Preset donation amounts\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for i, tile := range data.Tiles {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<label data-amount-tile class=\"earthen-tile flex items-center justify-center cursor-pointer rounded-lg py-4 px-3 text-lg font-semibold transition-colors\" style=\"border: 1px solid hsl(var(--border)); background-color: hsl(var(--background)); color: hsl(var(--foreground));\"><input type=\"radio\" name=\"amount\" value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(strconv.FormatFloat(tile.Value, 'f', -1, 64))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `donation_cta.templ`, Line: 41, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" class=\"sr-only\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if i == 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " checked")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "> <span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(tile.Label)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `donation_cta.templ`, Line: 45, Col: 25}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</span></label>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div><div class=\"flex flex-col sm:flex-row gap-3 items-stretch sm:items-center\"><label class=\"sr-only\" for=\"earthen-custom-amount\">Custom amount</label> <input id=\"earthen-custom-amount\" type=\"number\" name=\"customAmount\" min=\"1\" step=\"1\" placeholder=\"Other amount\" class=\"flex-1 rounded-lg py-3 px-4 text-base\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground)); border: 1px solid hsl(var(--input));\"> <button type=\"submit\" class=\"earthen-button inline-block rounded-lg py-3 px-6 font-semibold transition-colors\" style=\"background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.ButtonLabel)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `donation_cta.templ`, Line: 66, Col: 24}
|
||||
}
|
||||
_, 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, 18, "</button></div></form></div></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func donationFormAction(url string) string {
|
||||
if url == "" {
|
||||
return "#"
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
17
email_wrapper.go
Normal file
@ -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()
|
||||
}
|
||||
154
email_wrapper.templ
Normal file
@ -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) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="x-apple-disable-message-reformatting"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||
<!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]-->
|
||||
<title>{ emailCtx.SiteSettings.SiteName }</title>
|
||||
</head>
|
||||
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Spectral', Georgia, serif; color: %s;", earthenEmailBg(emailCtx), earthenEmailFg(emailCtx)) }>
|
||||
if emailCtx.PreviewText != "" {
|
||||
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">
|
||||
{ emailCtx.PreviewText }
|
||||
</div>
|
||||
}
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("background-color: %s;", earthenEmailBg(emailCtx)) }>
|
||||
<tr>
|
||||
<td align="center" style="padding: 32px 12px;">
|
||||
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s; border-radius: 12px; overflow: hidden;", earthenEmailCard(emailCtx), earthenEmailBorder(emailCtx)) }>
|
||||
<tr>
|
||||
<td align="center" style={ fmt.Sprintf("padding: 28px 32px; background-color: %s; color: %s;", earthenEmailPrimary(emailCtx), earthenEmailPrimaryFg(emailCtx)) }>
|
||||
if emailCtx.SiteSettings.LogoURL != "" {
|
||||
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="max-height: 44px; width: auto; display: block;"/>
|
||||
} else if emailCtx.SiteSettings.SiteName != "" {
|
||||
<h1 style={ fmt.Sprintf("margin: 0; font-family: 'Fraunces', 'Playfair Display', Georgia, serif; font-size: 26px; font-weight: 600; letter-spacing: -0.01em; color: %s;", earthenEmailPrimaryFg(emailCtx)) }>
|
||||
{ emailCtx.SiteSettings.SiteName }
|
||||
</h1>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={ fmt.Sprintf("padding: 36px 40px; font-size: 16px; line-height: 1.7; color: %s; font-family: 'Spectral', Georgia, serif;", earthenEmailFg(emailCtx)) }>
|
||||
@templ.Raw(body)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style={ fmt.Sprintf("padding: 24px 40px; background-color: %s; border-top: 1px solid %s;", earthenEmailMuted(emailCtx), earthenEmailBorder(emailCtx)) }>
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<p style={ fmt.Sprintf("margin: 0 0 8px; font-family: 'Fraunces', 'Playfair Display', Georgia, serif; font-size: 14px; font-weight: 600; color: %s;", earthenEmailFg(emailCtx)) }>
|
||||
{ emailCtx.SiteSettings.SiteName }
|
||||
</p>
|
||||
<p style={ fmt.Sprintf("margin: 0 0 6px; font-size: 11px; color: %s;", earthenEmailMutedFg(emailCtx)) }>
|
||||
Registered charity. We respect your inbox.
|
||||
</p>
|
||||
if emailCtx.SiteSettings.SiteURL != "" {
|
||||
<p style={ fmt.Sprintf("margin: 0 0 12px; font-size: 12px; color: %s;", earthenEmailMutedFg(emailCtx)) }>
|
||||
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style={ fmt.Sprintf("color: %s; text-decoration: none; border-bottom: 1px dotted %s;", earthenEmailPrimary(emailCtx), earthenEmailPrimary(emailCtx)) }>
|
||||
{ emailCtx.SiteSettings.SiteURL }
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
if emailCtx.UnsubscribeURL != "" {
|
||||
<p style={ fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", earthenEmailMutedFg(emailCtx)) }>
|
||||
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", earthenEmailMutedFg(emailCtx)) }>
|
||||
Unsubscribe
|
||||
</a>
|
||||
</p>
|
||||
} else {
|
||||
<p style={ fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", earthenEmailMutedFg(emailCtx)) }>
|
||||
<a href="#" style={ fmt.Sprintf("color: %s; text-decoration: underline;", earthenEmailMutedFg(emailCtx)) }>
|
||||
Unsubscribe
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
470
email_wrapper_templ.go
Normal file
@ -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, "<!doctype html><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"x-apple-disable-message-reformatting\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><!--[if mso]>\n\t\t\t<noscript>\n\t\t\t\t<xml>\n\t\t\t\t\t<o:OfficeDocumentSettings>\n\t\t\t\t\t\t<o:PixelsPerInch>96</o:PixelsPerInch>\n\t\t\t\t\t</o:OfficeDocumentSettings>\n\t\t\t\t</xml>\n\t\t\t</noscript>\n\t\t\t<![endif]--><title>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 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, "</title></head><body style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Spectral', Georgia, serif; color: %s;", earthenEmailBg(emailCtx), earthenEmailFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 29, Col: 179}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if emailCtx.PreviewText != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div style=\"display: none; max-height: 0; overflow: hidden; mso-hide: all;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 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, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("background-color: %s;", earthenEmailBg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 35, Col: 156}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><tr><td align=\"center\" style=\"padding: 32px 12px;\"><table role=\"presentation\" width=\"600\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s; border-radius: 12px; overflow: hidden;", earthenEmailCard(emailCtx), earthenEmailBorder(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 38, Col: 269}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><tr><td align=\"center\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 28px 32px; background-color: %s; color: %s;", earthenEmailPrimary(emailCtx), earthenEmailPrimaryFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 40, Col: 166}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if emailCtx.SiteSettings.LogoURL != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.LogoURL)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 42, Col: 50}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" alt=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.SiteName)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 42, Col: 89}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" style=\"max-height: 44px; width: auto; display: block;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else if emailCtx.SiteSettings.SiteName != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h1 style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0; font-family: 'Fraunces', 'Playfair Display', Georgia, serif; font-size: 26px; font-weight: 600; letter-spacing: -0.01em; color: %s;", earthenEmailPrimaryFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 44, Col: 212}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 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, "</h1>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</td></tr><tr><td style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 36px 40px; font-size: 16px; line-height: 1.7; color: %s; font-family: 'Spectral', Georgia, serif;", earthenEmailFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 51, Col: 167}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(body).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</td></tr><tr><td style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 24px 40px; background-color: %s; border-top: 1px solid %s;", earthenEmailMuted(emailCtx), earthenEmailBorder(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 56, Col: 161}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\"><p style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0 0 8px; font-family: 'Fraunces', 'Playfair Display', Georgia, serif; font-size: 14px; font-weight: 600; color: %s;", earthenEmailFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 60, Col: 187}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 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, "</p><p style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0 0 6px; font-size: 11px; color: %s;", earthenEmailMutedFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 63, Col: 113}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">Registered charity. We respect your inbox.</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if emailCtx.SiteSettings.SiteURL != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<p style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 string
|
||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0 0 12px; font-size: 12px; color: %s;", earthenEmailMutedFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 67, Col: 115}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\"><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var18 templ.SafeURL
|
||||
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.SiteSettings.SiteURL))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 68, Col: 68}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var19 string
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: none; border-bottom: 1px dotted %s;", earthenEmailPrimary(emailCtx), earthenEmailPrimary(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 68, Col: 219}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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, "</a></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if emailCtx.UnsubscribeURL != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<p style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var21 string
|
||||
templ_7745c5c3_Var21, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", earthenEmailMutedFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 74, Col: 108}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "\"><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var22 templ.SafeURL
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.UnsubscribeURL))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 75, Col: 62}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var23 string
|
||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: underline;", earthenEmailMutedFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 75, Col: 157}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "\">Unsubscribe</a></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<p style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var24 string
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", earthenEmailMutedFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 80, Col: 108}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\"><a href=\"#\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var25 string
|
||||
templ_7745c5c3_Var25, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: underline;", earthenEmailMutedFg(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 81, Col: 118}
|
||||
}
|
||||
_, 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, 34, "\">Unsubscribe</a></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</td></tr></table></td></tr></table></td></tr></table></body></html>")
|
||||
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
|
||||
64
embed.go
Normal file
@ -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),
|
||||
}
|
||||
}
|
||||
44
field_note.go
Normal file
@ -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
|
||||
}
|
||||
52
field_note.templ
Normal file
@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
// fieldNoteComponent renders an article-style dispatch from the field.
|
||||
templ fieldNoteComponent(data FieldNoteData) {
|
||||
<article
|
||||
data-block="earthen:field_note"
|
||||
data-empty?={ data.Empty }
|
||||
class="earthen-field-note earthen-paper-frame mx-auto my-12 max-w-3xl p-8 rounded-lg"
|
||||
style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));"
|
||||
>
|
||||
<header class="mb-6">
|
||||
<div
|
||||
class="earthen-byline flex flex-wrap gap-x-3 gap-y-1 uppercase tracking-wider text-xs mb-2"
|
||||
style="color: hsl(var(--muted-foreground));"
|
||||
>
|
||||
if data.Author != "" {
|
||||
<span class="earthen-byline-author">By { data.Author }</span>
|
||||
}
|
||||
if data.Location != "" {
|
||||
<span class="earthen-byline-location">{ data.Location }</span>
|
||||
}
|
||||
if data.Dateline != "" {
|
||||
<span class="earthen-byline-date">{ data.Dateline }</span>
|
||||
}
|
||||
</div>
|
||||
<div class="botanical-rule" aria-hidden="true">
|
||||
@botanicalGlyph("seed", 28)
|
||||
</div>
|
||||
</header>
|
||||
if data.Image != "" {
|
||||
<figure class="my-6 earthen-paper-frame-inner">
|
||||
<img
|
||||
src={ resolveMedia(data.Image) }
|
||||
alt=""
|
||||
class="w-full h-auto rounded"
|
||||
/>
|
||||
</figure>
|
||||
}
|
||||
if data.Body != "" {
|
||||
<div
|
||||
class="earthen-body drop-cap prose-base"
|
||||
style={ "font-family: var(--font-body, \"Spectral\", Georgia, serif); color: hsl(var(--foreground));" }
|
||||
>
|
||||
@templ.Raw(data.Body)
|
||||
</div>
|
||||
} else {
|
||||
<p class="earthen-empty" style="color: hsl(var(--muted-foreground));">
|
||||
No dispatch yet — add a body to share what you saw in the field.
|
||||
</p>
|
||||
}
|
||||
</article>
|
||||
}
|
||||
175
field_note_templ.go
Normal file
@ -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, "<article data-block=\"earthen:field_note\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Empty {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " data-empty")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " class=\"earthen-field-note earthen-paper-frame mx-auto my-12 max-w-3xl p-8 rounded-lg\" style=\"background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));\"><header class=\"mb-6\"><div class=\"earthen-byline flex flex-wrap gap-x-3 gap-y-1 uppercase tracking-wider text-xs mb-2\" style=\"color: hsl(var(--muted-foreground));\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Author != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"earthen-byline-author\">By ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Author)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `field_note.templ`, Line: 17, Col: 57}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.Location != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"earthen-byline-location\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Location)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `field_note.templ`, Line: 20, Col: 58}
|
||||
}
|
||||
_, 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, 7, "</span> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.Dateline != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<span class=\"earthen-byline-date\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Dateline)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `field_note.templ`, Line: 23, Col: 54}
|
||||
}
|
||||
_, 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, 9, "</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div><div class=\"botanical-rule\" aria-hidden=\"true\">")
|
||||
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, "</div></header>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Image != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<figure class=\"my-6 earthen-paper-frame-inner\"><img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(resolveMedia(data.Image))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `field_note.templ`, Line: 33, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" alt=\"\" class=\"w-full h-auto rounded\"></figure>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.Body != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"earthen-body drop-cap prose-base\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-body, \"Spectral\", Georgia, serif); color: hsl(var(--foreground));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `field_note.templ`, Line: 42, Col: 105}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\">")
|
||||
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, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<p class=\"earthen-empty\" style=\"color: hsl(var(--muted-foreground));\">No dispatch yet — add a body to share what you saw in the field.</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</article>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
19
fonts.json
@ -1,18 +1 @@
|
||||
[
|
||||
{
|
||||
"name": "Fraunces",
|
||||
"family": "Fraunces",
|
||||
"variants": [
|
||||
{ "weight": "400", "style": "normal", "file": "fonts/fraunces-latin-400.woff2" },
|
||||
{ "weight": "600", "style": "normal", "file": "fonts/fraunces-latin-600.woff2" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Work Sans",
|
||||
"family": "Work Sans",
|
||||
"variants": [
|
||||
{ "weight": "400", "style": "normal", "file": "fonts/worksans-latin-400.woff2" },
|
||||
{ "weight": "600", "style": "normal", "file": "fonts/worksans-latin-600.woff2" }
|
||||
]
|
||||
}
|
||||
]
|
||||
[]
|
||||
|
||||
69
footer.go
Normal file
@ -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
|
||||
}
|
||||
104
footer.templ
Normal file
@ -0,0 +1,104 @@
|
||||
package main
|
||||
|
||||
// footerComponent renders the cream-on-moss footer with optional newsletter and link columns.
|
||||
templ footerComponent(data FooterData) {
|
||||
<div
|
||||
data-block="earthen:footer"
|
||||
data-empty?={ data.Empty }
|
||||
class="earthen-footer w-full"
|
||||
style="background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));"
|
||||
>
|
||||
<div class="max-w-6xl mx-auto px-6 py-16">
|
||||
<div class="botanical-rule flex items-center gap-4 mb-10" aria-hidden="true">
|
||||
<span class="flex-1 h-px" style="background-color: hsl(var(--primary-foreground) / 0.3);"></span>
|
||||
@botanicalGlyph("fern", 32)
|
||||
<span class="flex-1 h-px" style="background-color: hsl(var(--primary-foreground) / 0.3);"></span>
|
||||
</div>
|
||||
if data.Tagline != "" {
|
||||
<p
|
||||
class="earthen-display text-2xl md:text-3xl text-center mb-10"
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif);" }
|
||||
>
|
||||
{ data.Tagline }
|
||||
</p>
|
||||
}
|
||||
if data.ShowNewsletter {
|
||||
<form class="max-w-xl mx-auto mb-12 flex flex-col sm:flex-row gap-3">
|
||||
<label class="sr-only" for="earthen-footer-email">Email address</label>
|
||||
<input
|
||||
id="earthen-footer-email"
|
||||
type="email"
|
||||
placeholder="you@example.org"
|
||||
class="flex-1 rounded-lg px-4 py-3 text-base"
|
||||
style="background-color: hsl(var(--primary-foreground) / 0.1); color: hsl(var(--primary-foreground)); border: 1px solid hsl(var(--primary-foreground) / 0.3);"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-lg px-6 py-3 font-semibold"
|
||||
style="background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground));"
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
if len(data.Columns) > 0 {
|
||||
<div class={ "grid gap-8 mb-10", footerGridCols(len(data.Columns)) }>
|
||||
for _, col := range data.Columns {
|
||||
<div>
|
||||
if col.Title != "" {
|
||||
<h4
|
||||
class="earthen-display text-lg mb-4"
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif);" }
|
||||
>
|
||||
{ col.Title }
|
||||
</h4>
|
||||
}
|
||||
if len(col.Links) > 0 {
|
||||
<ul class="space-y-2">
|
||||
for _, link := range col.Links {
|
||||
<li>
|
||||
<a
|
||||
href={ templ.SafeURL(footerLinkURL(link.URL)) }
|
||||
class="earthen-footer-link transition-colors"
|
||||
style="color: hsl(var(--primary-foreground) / 0.85);"
|
||||
>
|
||||
{ link.Text }
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
class="text-center text-sm pt-6"
|
||||
style="border-top: 1px solid hsl(var(--primary-foreground) / 0.2); color: hsl(var(--primary-foreground) / 0.7);"
|
||||
>
|
||||
<span>Rooted in place. Made with care.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
234
footer_templ.go
Normal file
@ -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, "<div data-block=\"earthen:footer\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Empty {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " data-empty")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " class=\"earthen-footer w-full\" style=\"background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));\"><div class=\"max-w-6xl mx-auto px-6 py-16\"><div class=\"botanical-rule flex items-center gap-4 mb-10\" aria-hidden=\"true\"><span class=\"flex-1 h-px\" style=\"background-color: hsl(var(--primary-foreground) / 0.3);\"></span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = botanicalGlyph("fern", 32).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"flex-1 h-px\" style=\"background-color: hsl(var(--primary-foreground) / 0.3);\"></span></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Tagline != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<p class=\"earthen-display text-2xl md:text-3xl text-center mb-10\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif);")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 20, Col: 100}
|
||||
}
|
||||
_, 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, 6, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Tagline)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 22, Col: 19}
|
||||
}
|
||||
_, 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, 7, "</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.ShowNewsletter {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<form class=\"max-w-xl mx-auto mb-12 flex flex-col sm:flex-row gap-3\"><label class=\"sr-only\" for=\"earthen-footer-email\">Email address</label> <input id=\"earthen-footer-email\" type=\"email\" placeholder=\"you@example.org\" class=\"flex-1 rounded-lg px-4 py-3 text-base\" style=\"background-color: hsl(var(--primary-foreground) / 0.1); color: hsl(var(--primary-foreground)); border: 1px solid hsl(var(--primary-foreground) / 0.3);\"> <button type=\"submit\" class=\"rounded-lg px-6 py-3 font-semibold\" style=\"background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground));\">Subscribe</button></form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if len(data.Columns) > 0 {
|
||||
var templ_7745c5c3_Var4 = []any{"grid gap-8 mb-10", footerGridCols(len(data.Columns))}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var4).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, col := range data.Columns {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if col.Title != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<h4 class=\"earthen-display text-lg mb-4\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif);")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 51, Col: 104}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(col.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 53, Col: 20}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</h4>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if len(col.Links) > 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<ul class=\"space-y-2\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, link := range col.Links {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<li><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 templ.SafeURL
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(footerLinkURL(link.URL)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 61, Col: 57}
|
||||
}
|
||||
_, 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, 17, "\" class=\"earthen-footer-link transition-colors\" style=\"color: hsl(var(--primary-foreground) / 0.85);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(link.Text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 65, Col: 23}
|
||||
}
|
||||
_, 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, 18, "</a></li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</ul>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div class=\"text-center text-sm pt-6\" style=\"border-top: 1px solid hsl(var(--primary-foreground) / 0.2); color: hsl(var(--primary-foreground) / 0.7);\"><span>Rooted in place. Made with care.</span></div></div></div>")
|
||||
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
|
||||
20
go.mod
Normal file
@ -0,0 +1,20 @@
|
||||
module git.dev.alexdunmow.com/block/themes/earthen
|
||||
|
||||
go 1.26.4
|
||||
|
||||
require (
|
||||
git.dev.alexdunmow.com/block/core v0.14.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
|
||||
)
|
||||
44
go.sum
Normal file
@ -0,0 +1,44 @@
|
||||
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.14.0 h1:cO6QQQPndhxwh1zbDvDOzebwrFS8Ka3WsxD8JBJLw/Y=
|
||||
git.dev.alexdunmow.com/block/core v0.14.0/go.mod h1:S0ZfpGZ9BQhhmuTUd78ailH7m2vo7QRCTIionvCVm9s=
|
||||
git.dev.alexdunmow.com/block/core v0.14.1 h1:63b25yzWoqrhBd3wdDFhsJzyGNM99wYmdh4WkN35hDk=
|
||||
git.dev.alexdunmow.com/block/core v0.14.1/go.mod h1:S0ZfpGZ9BQhhmuTUd78ailH7m2vo7QRCTIionvCVm9s=
|
||||
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=
|
||||
40
heading_override.go
Normal file
@ -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
|
||||
}
|
||||
76
heading_override.templ
Normal file
@ -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:
|
||||
<h1
|
||||
class={ earthenHeadingBaseClass(1), textClass }
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));" }
|
||||
>
|
||||
{ text }
|
||||
</h1>
|
||||
case 2:
|
||||
<h2
|
||||
class={ earthenHeadingBaseClass(2), textClass }
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));" }
|
||||
>
|
||||
{ text }
|
||||
</h2>
|
||||
case 3:
|
||||
<h3
|
||||
class={ earthenHeadingBaseClass(3), textClass }
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--foreground));" }
|
||||
>
|
||||
{ text }
|
||||
</h3>
|
||||
case 4:
|
||||
<h4
|
||||
class={ earthenHeadingBaseClass(4), textClass }
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--foreground));" }
|
||||
>
|
||||
{ text }
|
||||
</h4>
|
||||
case 5:
|
||||
<h5
|
||||
class={ earthenHeadingBaseClass(5), textClass }
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--foreground));" }
|
||||
>
|
||||
{ text }
|
||||
</h5>
|
||||
case 6:
|
||||
<h6
|
||||
class={ earthenHeadingBaseClass(6), textClass }
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--muted-foreground));" }
|
||||
>
|
||||
{ text }
|
||||
</h6>
|
||||
default:
|
||||
<h2
|
||||
class={ earthenHeadingBaseClass(2), textClass }
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));" }
|
||||
>
|
||||
{ text }
|
||||
</h2>
|
||||
}
|
||||
}
|
||||
402
heading_override_templ.go
Normal file
@ -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, "<h1 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 29, Col: 127}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
|
||||
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, "</h1>")
|
||||
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, "<h2 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var6).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 36, Col: 127}
|
||||
}
|
||||
_, 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, 7, "\">")
|
||||
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, "</h2>")
|
||||
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, "<h3 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var10).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--foreground));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 43, Col: 130}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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, "</h3>")
|
||||
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, "<h4 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var14).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--foreground));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 50, Col: 130}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\">")
|
||||
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, "</h4>")
|
||||
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, "<h5 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var19 string
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var18).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var19)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var20 string
|
||||
templ_7745c5c3_Var20, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--foreground));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 57, Col: 130}
|
||||
}
|
||||
_, 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, 19, "\">")
|
||||
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, "</h5>")
|
||||
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, "<h6 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var23 string
|
||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var22).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var24 string
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--muted-foreground));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 64, Col: 136}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\">")
|
||||
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, "</h6>")
|
||||
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, "<h2 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var27 string
|
||||
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var26).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var27)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var28 string
|
||||
templ_7745c5c3_Var28, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 71, Col: 127}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
|
||||
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
|
||||
}
|
||||
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, "</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
48
helpers.go
Normal file
@ -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
|
||||
}
|
||||
21
image_override.go
Normal file
@ -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()
|
||||
}
|
||||
35
image_override.templ
Normal file
@ -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) {
|
||||
<figure class="earthen-image my-8 mx-auto" style="max-width: 100%;">
|
||||
if src != "" {
|
||||
<div
|
||||
class="earthen-paper-frame-inner rounded-lg overflow-hidden"
|
||||
style="border: 1px solid hsl(var(--border)); padding: 6px; background-color: hsl(var(--card));"
|
||||
>
|
||||
<img
|
||||
src={ resolveMedia(src) }
|
||||
alt={ alt }
|
||||
class="w-full h-auto rounded-md"
|
||||
/>
|
||||
</div>
|
||||
} else {
|
||||
<div
|
||||
data-empty="true"
|
||||
class="earthen-image-empty w-full h-48 rounded-lg flex items-center justify-center"
|
||||
style="background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); border: 1.5px dashed hsl(var(--border));"
|
||||
>
|
||||
<span class="text-sm">Choose an image to display here</span>
|
||||
</div>
|
||||
}
|
||||
if caption != "" {
|
||||
<figcaption
|
||||
class="earthen-caption text-center text-sm mt-3"
|
||||
style="color: hsl(var(--muted-foreground)); font-style: italic;"
|
||||
>
|
||||
{ caption }
|
||||
</figcaption>
|
||||
}
|
||||
</figure>
|
||||
}
|
||||
101
image_override_templ.go
Normal file
@ -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, "<figure class=\"earthen-image my-8 mx-auto\" style=\"max-width: 100%;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if src != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"earthen-paper-frame-inner rounded-lg overflow-hidden\" style=\"border: 1px solid hsl(var(--border)); padding: 6px; background-color: hsl(var(--card));\"><img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(resolveMedia(src))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 12, Col: 28}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" alt=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(alt)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 13, Col: 14}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" class=\"w-full h-auto rounded-md\"></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div data-empty=\"true\" class=\"earthen-image-empty w-full h-48 rounded-lg flex items-center justify-center\" style=\"background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); border: 1.5px dashed hsl(var(--border));\"><span class=\"text-sm\">Choose an image to display here</span></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if caption != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<figcaption class=\"earthen-caption text-center text-sm mt-3\" style=\"color: hsl(var(--muted-foreground)); font-style: italic;\">")
|
||||
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, "</figcaption>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</figure>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
58
impact_metrics.go
Normal file
@ -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
|
||||
}
|
||||
82
impact_metrics.templ
Normal file
@ -0,0 +1,82 @@
|
||||
package main
|
||||
|
||||
// impactMetricsComponent renders a row of impact metrics with optional botanical illustration.
|
||||
templ impactMetricsComponent(data ImpactMetricsData) {
|
||||
<section
|
||||
data-block="earthen:impact_metrics"
|
||||
data-empty?={ data.Empty }
|
||||
class="earthen-impact-metrics py-16 px-4"
|
||||
style="background-color: hsl(var(--background)); color: hsl(var(--foreground));"
|
||||
>
|
||||
<div class="max-w-5xl mx-auto">
|
||||
if data.Title != "" {
|
||||
<h2
|
||||
class="earthen-display text-3xl md:text-4xl text-center mb-12"
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));" }
|
||||
>
|
||||
{ data.Title }
|
||||
</h2>
|
||||
}
|
||||
if data.Illustration != "" {
|
||||
<div class="flex justify-center mb-8">
|
||||
<img src={ resolveMedia(data.Illustration) } alt="" class="max-h-40 object-contain"/>
|
||||
</div>
|
||||
} else if data.Title != "" || len(data.Metrics) > 0 {
|
||||
<div class="flex justify-center mb-8">
|
||||
@botanicalGlyph("fern", 64)
|
||||
</div>
|
||||
}
|
||||
if len(data.Metrics) > 0 {
|
||||
<div class={ "grid gap-8", metricGridCols(len(data.Metrics)) }>
|
||||
for _, m := range data.Metrics {
|
||||
<div class="text-center px-4">
|
||||
<div
|
||||
class="earthen-display text-5xl font-semibold mb-2"
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--accent));" }
|
||||
>
|
||||
{ m.Value }{ m.Suffix }
|
||||
</div>
|
||||
<div
|
||||
class="earthen-body uppercase tracking-wider text-sm"
|
||||
style={ "font-family: var(--font-body, \"Spectral\", Georgia, serif); color: hsl(var(--muted-foreground));" }
|
||||
>
|
||||
{ m.Label }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
<p
|
||||
class="text-center"
|
||||
style="color: hsl(var(--muted-foreground));"
|
||||
>
|
||||
Add impact metrics to bring this section to life.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
243
impact_metrics_templ.go
Normal file
@ -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, "<section data-block=\"earthen:impact_metrics\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Empty {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " data-empty")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " class=\"earthen-impact-metrics py-16 px-4\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\"><div class=\"max-w-5xl mx-auto\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Title != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h2 class=\"earthen-display text-3xl md:text-4xl text-center mb-12\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `impact_metrics.templ`, Line: 15, Col: 128}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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, "</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.Illustration != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"flex justify-center mb-8\"><img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(resolveMedia(data.Illustration))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `impact_metrics.templ`, Line: 22, Col: 47}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" alt=\"\" class=\"max-h-40 object-contain\"></div>")
|
||||
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, "<div class=\"flex justify-center mb-8\">")
|
||||
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, "</div>")
|
||||
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, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var5).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `impact_metrics.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, m := range data.Metrics {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"text-center px-4\"><div class=\"earthen-display text-5xl font-semibold mb-2\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--accent));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `impact_metrics.templ`, Line: 35, Col: 130}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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, "</div><div class=\"earthen-body uppercase tracking-wider text-sm\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-body, \"Spectral\", Georgia, serif); color: hsl(var(--muted-foreground));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `impact_metrics.templ`, Line: 41, Col: 115}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">")
|
||||
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, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<p class=\"text-center\" style=\"color: hsl(var(--muted-foreground));\">Add impact metrics to bring this section to life.</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div></section>")
|
||||
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
|
||||
237
manifest.yaml
@ -1,237 +0,0 @@
|
||||
# Earthen theme — codeless .bnp manifest (WO-WZ-021 Phase B1).
|
||||
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
|
||||
theme_presets: presets.json
|
||||
bundled_fonts: fonts.json
|
||||
master_pages: master_pages.json
|
||||
required_icon_packs: [lucide]
|
||||
|
||||
system_templates:
|
||||
- key: earthen
|
||||
title: Earthen
|
||||
description: "Botanical nonprofit theme in moss, clay, sage and cream"
|
||||
|
||||
page_templates:
|
||||
- system: earthen
|
||||
key: default
|
||||
title: Default
|
||||
description: "Standard nonprofit page with header, main and footer"
|
||||
slots: [header, main, footer]
|
||||
- system: earthen
|
||||
key: landing
|
||||
title: Landing
|
||||
description: "Mission hero plus CTA bands for campaigns and donation drives"
|
||||
slots: [hero, main, cta, footer]
|
||||
- system: earthen
|
||||
key: article
|
||||
title: Article
|
||||
description: "Long-form storytelling with byline rail"
|
||||
slots: [header, lede, main, aside, footer]
|
||||
- system: earthen
|
||||
key: full-width
|
||||
title: Full Width
|
||||
description: "Edge-to-edge imagery for reports and galleries"
|
||||
slots: [header, main, footer]
|
||||
- system: earthen
|
||||
key: blog-index
|
||||
title: Field Journal
|
||||
description: "Blog index with a botanical masthead, featured and post-grid slots"
|
||||
slots: [header, featured, main, footer]
|
||||
- system: earthen
|
||||
key: contact
|
||||
title: Get in Touch
|
||||
description: "Contact layout with an optional aside for hours and location"
|
||||
slots: [header, main, aside, footer]
|
||||
- system: earthen
|
||||
key: auth
|
||||
title: Members
|
||||
description: "Centred auth shell with wordmark and a fern crest"
|
||||
slots: [main, footer]
|
||||
|
||||
template_overrides:
|
||||
# Chrome (4)
|
||||
- { template: earthen, block: navbar }
|
||||
- { template: earthen, block: footer }
|
||||
- { template: earthen, block: utility-bar }
|
||||
- { template: earthen, block: announcement-bar }
|
||||
# Marketing (12)
|
||||
- { template: earthen, block: hero }
|
||||
- { template: earthen, block: feature-grid }
|
||||
- { template: earthen, block: rich-section }
|
||||
- { template: earthen, block: stats }
|
||||
- { template: earthen, block: testimonial }
|
||||
- { template: earthen, block: logo-strip }
|
||||
- { template: earthen, block: pricing }
|
||||
- { template: earthen, block: team }
|
||||
- { template: earthen, block: faq }
|
||||
- { template: earthen, block: timeline }
|
||||
- { template: earthen, block: cta }
|
||||
- { template: earthen, block: countdown }
|
||||
# Primitives (11)
|
||||
- { template: earthen, block: heading }
|
||||
- { template: earthen, block: text }
|
||||
- { template: earthen, block: rich-text }
|
||||
- { template: earthen, block: image }
|
||||
- { template: earthen, block: quote }
|
||||
- { template: earthen, block: divider }
|
||||
- { template: earthen, block: button }
|
||||
- { template: earthen, block: card }
|
||||
- { template: earthen, block: gallery }
|
||||
- { template: earthen, block: video-embed }
|
||||
- { template: earthen, block: audio-embed }
|
||||
# Commerce / local (6)
|
||||
- { template: earthen, block: menu-list }
|
||||
- { template: earthen, block: event-list }
|
||||
- { template: earthen, block: contact-form }
|
||||
- { template: earthen, block: contact-card }
|
||||
- { template: earthen, block: hours-location }
|
||||
- { template: earthen, block: social-links }
|
||||
# Blog family (12)
|
||||
- { template: earthen, block: blog-hero }
|
||||
- { template: earthen, block: featured-posts }
|
||||
- { template: earthen, block: related-posts }
|
||||
- { template: earthen, block: popular-posts }
|
||||
- { template: earthen, block: category-list }
|
||||
- { template: earthen, block: post-hero }
|
||||
- { template: earthen, block: post-metadata }
|
||||
- { template: earthen, block: author-bio }
|
||||
- { template: earthen, block: author-bio-hero }
|
||||
- { template: earthen, block: newsletter-signup }
|
||||
- { template: earthen, block: breadcrumbs }
|
||||
- { template: earthen, block: sidebar-nav }
|
||||
# Auth surface (3)
|
||||
- { template: earthen, block: auth-form }
|
||||
- { template: earthen, block: password-reset-form }
|
||||
- { template: earthen, block: auth-status }
|
||||
# System (1)
|
||||
- { template: earthen, block: page-suggestions }
|
||||
|
||||
email_wrappers:
|
||||
- earthen
|
||||
|
||||
css:
|
||||
input_css_append: |
|
||||
/* 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). */
|
||||
|
||||
/* Bundled faces. The host does not yet emit @font-face for fonts.json
|
||||
* (LoadedPlugin.BundledFonts is inert), so the theme declares them itself
|
||||
* against the served /templates/earthen/fonts/... paths. Presets set
|
||||
* typography.fontHeading/body to template:earthen:<Family> so
|
||||
* --font-heading/--font-body resolve to these families. Families/weights
|
||||
* match fonts.json exactly. */
|
||||
@font-face { font-family: 'Fraunces'; src: url('/templates/earthen/fonts/fraunces-latin-400.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'Fraunces'; src: url('/templates/earthen/fonts/fraunces-latin-600.woff2') format('woff2'); font-weight: 600; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'Work Sans'; src: url('/templates/earthen/fonts/worksans-latin-400.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'Work Sans'; src: url('/templates/earthen/fonts/worksans-latin-600.woff2') format('woff2'); font-weight: 600; font-style: normal; font-display: swap; }
|
||||
|
||||
/* 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));
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
[
|
||||
{
|
||||
"key": "earthen:default-master",
|
||||
"title": "Earthen Default Master",
|
||||
"page_templates": ["default", "article", "contact", "full-width", "blog-index"],
|
||||
"blocks": [
|
||||
{ "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 },
|
||||
{ "block_key": "slot", "title": "Main Content", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
|
||||
{ "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showNewsletter": true, "newsletterTitle": "Field notes in your inbox", "companyName": "Rootline Trust", "tagline": "Rooted in place. We restore land with the people who live on it.", "menuName": "main" }, "slot": "footer", "sort_order": 0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "earthen:landing-master",
|
||||
"title": "Earthen Landing Master",
|
||||
"page_templates": ["landing"],
|
||||
"blocks": [
|
||||
{ "block_key": "navbar", "title": "Main Navigation", "content": { "menuName": "main" }, "slot": "hero", "sort_order": 0 },
|
||||
{ "block_key": "slot", "title": "Hero Slot", "content": { "slotName": "hero" }, "slot": "hero", "sort_order": 10 },
|
||||
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
|
||||
{ "block_key": "earthen:donation_cta", "title": "Donation Appeal", "content": { "headline": "Plant the next acre.", "amounts": [25, 50, 100, 250] }, "slot": "cta", "sort_order": 0 },
|
||||
{ "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showNewsletter": true, "newsletterTitle": "Field notes in your inbox", "companyName": "Rootline Trust", "tagline": "Rooted in place. We restore land with the people who live on it.", "menuName": "main" }, "slot": "footer", "sort_order": 0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "earthen:auth-master",
|
||||
"title": "Earthen Auth Master",
|
||||
"page_templates": ["auth"],
|
||||
"blocks": [
|
||||
{ "block_key": "slot", "title": "Auth Form Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }
|
||||
]
|
||||
}
|
||||
]
|
||||
55
partner_logos.go
Normal file
@ -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
|
||||
}
|
||||
95
partner_logos.templ
Normal file
@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
// partnerLogosComponent renders a grayscale logo grid that restores colour on hover.
|
||||
templ partnerLogosComponent(data PartnerLogosData) {
|
||||
<section
|
||||
data-block="earthen:partner_logos"
|
||||
data-empty?={ data.Empty }
|
||||
class="earthen-partner-logos py-16 px-4"
|
||||
style="background-color: hsl(var(--background)); color: hsl(var(--foreground));"
|
||||
>
|
||||
<div class="max-w-5xl mx-auto">
|
||||
if data.Title != "" {
|
||||
<h2
|
||||
class="earthen-display text-2xl md:text-3xl text-center mb-10"
|
||||
style={ "font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));" }
|
||||
>
|
||||
{ data.Title }
|
||||
</h2>
|
||||
}
|
||||
if len(data.Logos) > 0 {
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-8 items-center">
|
||||
for _, logo := range data.Logos {
|
||||
<div class="earthen-partner-tile flex justify-center items-center">
|
||||
@partnerLogoTile(logo)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
<p class="text-center" style="color: hsl(var(--muted-foreground));">
|
||||
Add partner logos to fill this grid.
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
templ partnerLogoTile(logo PartnerLogo) {
|
||||
if logo.URL != "" {
|
||||
<a
|
||||
href={ templ.SafeURL(logo.URL) }
|
||||
class="earthen-partner-link block transition-colors"
|
||||
title={ logo.Name }
|
||||
aria-label={ logo.Name }
|
||||
>
|
||||
@partnerLogoImage(logo)
|
||||
</a>
|
||||
} else {
|
||||
<div class="earthen-partner-link block" title={ logo.Name } aria-label={ logo.Name }>
|
||||
@partnerLogoImage(logo)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ partnerLogoImage(logo PartnerLogo) {
|
||||
if logo.Image != "" {
|
||||
<img
|
||||
src={ resolveMedia(logo.Image) }
|
||||
alt={ logo.Name }
|
||||
class="earthen-partner-img max-h-12 object-contain w-full"
|
||||
/>
|
||||
} else {
|
||||
<div
|
||||
class="earthen-partner-fallback w-full h-12 flex items-center justify-center rounded"
|
||||
style="background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground));"
|
||||
>
|
||||
<span class="text-sm">{ partnerInitials(logo.Name) }</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
334
partner_logos_templ.go
Normal file
@ -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, "<section data-block=\"earthen:partner_logos\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Empty {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " data-empty")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " class=\"earthen-partner-logos py-16 px-4\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\"><div class=\"max-w-5xl mx-auto\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Title != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h2 class=\"earthen-display text-2xl md:text-3xl text-center mb-10\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-heading, \"Fraunces\", \"Playfair Display\", Georgia, serif); color: hsl(var(--primary));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 15, Col: 128}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
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, "</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if len(data.Logos) > 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-8 items-center\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, logo := range data.Logos {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"earthen-partner-tile flex justify-center items-center\">")
|
||||
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, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p class=\"text-center\" style=\"color: hsl(var(--muted-foreground));\">Add partner logos to fill this grid.</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></section>")
|
||||
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, "<a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 templ.SafeURL
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(logo.URL))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 40, Col: 33}
|
||||
}
|
||||
_, 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, 14, "\" class=\"earthen-partner-link block transition-colors\" title=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(logo.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 42, Col: 20}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\" aria-label=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(logo.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 43, Col: 25}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\">")
|
||||
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, "</a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<div class=\"earthen-partner-link block\" title=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(logo.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 48, Col: 59}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" aria-label=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(logo.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 48, Col: 84}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
|
||||
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, 21, "</div>")
|
||||
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, "<img src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(resolveMedia(logo.Image))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 57, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" alt=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(logo.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `partner_logos.templ`, Line: 58, Col: 18}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" class=\"earthen-partner-img max-h-12 object-contain w-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"earthen-partner-fallback w-full h-12 flex items-center justify-center rounded\" style=\"background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground));\"><span class=\"text-sm\">")
|
||||
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, "</span></div>")
|
||||
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
|
||||
@ -2,8 +2,11 @@
|
||||
name = "earthen"
|
||||
display_name = "Earthen"
|
||||
scope = "@themes"
|
||||
version = "0.5.0"
|
||||
version = "0.1.1"
|
||||
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"
|
||||
|
||||
120
presets.json
@ -5,10 +5,6 @@
|
||||
"description": "Cream paper, deep moss primary, terracotta accent — the canonical Earthen look.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:earthen:Fraunces",
|
||||
"fontBody": "template:earthen:Work Sans"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "40 30% 96%",
|
||||
"foreground": "120 25% 14%",
|
||||
@ -59,10 +55,6 @@
|
||||
"description": "Warm terracotta-led palette tuned for farm-stand and outdoor-brand sites.",
|
||||
"theme": {
|
||||
"mode": "light",
|
||||
"typography": {
|
||||
"fontHeading": "template:earthen:Fraunces",
|
||||
"fontBody": "template:earthen:Work Sans"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "30 35% 95%",
|
||||
"foreground": "20 35% 16%",
|
||||
@ -92,10 +84,6 @@
|
||||
"description": "Deep night-soil tones for conservation orgs that need quiet authority.",
|
||||
"theme": {
|
||||
"mode": "dark",
|
||||
"typography": {
|
||||
"fontHeading": "template:earthen:Fraunces",
|
||||
"fontBody": "template:earthen:Work Sans"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "90 20% 6%",
|
||||
"foreground": "60 20% 90%",
|
||||
@ -118,113 +106,5 @@
|
||||
"ring": "80 35% 55%"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sage-morning",
|
||||
"name": "Sage Morning",
|
||||
"description": "Pale sage paper and honey light, misty forest dark — a softer, hopeful reading of the palette.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:earthen:Fraunces",
|
||||
"fontBody": "template:earthen:Work Sans"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "70 25% 95%",
|
||||
"foreground": "130 20% 16%",
|
||||
"card": "70 22% 98%",
|
||||
"cardForeground": "130 20% 16%",
|
||||
"popover": "70 22% 98%",
|
||||
"popoverForeground": "130 20% 16%",
|
||||
"primary": "140 25% 34%",
|
||||
"primaryForeground": "70 25% 96%",
|
||||
"secondary": "75 18% 87%",
|
||||
"secondaryForeground": "130 22% 20%",
|
||||
"muted": "70 16% 90%",
|
||||
"mutedForeground": "110 10% 40%",
|
||||
"accent": "38 62% 48%",
|
||||
"accentForeground": "40 30% 97%",
|
||||
"destructive": "8 70% 45%",
|
||||
"destructiveForeground": "40 30% 96%",
|
||||
"border": "90 12% 80%",
|
||||
"input": "90 12% 80%",
|
||||
"ring": "140 25% 34%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "140 15% 9%",
|
||||
"foreground": "70 18% 90%",
|
||||
"card": "140 15% 12%",
|
||||
"cardForeground": "70 18% 90%",
|
||||
"popover": "140 15% 12%",
|
||||
"popoverForeground": "70 18% 90%",
|
||||
"primary": "95 28% 58%",
|
||||
"primaryForeground": "140 15% 9%",
|
||||
"secondary": "140 12% 18%",
|
||||
"secondaryForeground": "70 18% 90%",
|
||||
"muted": "140 10% 15%",
|
||||
"mutedForeground": "90 10% 58%",
|
||||
"accent": "40 65% 56%",
|
||||
"accentForeground": "140 15% 9%",
|
||||
"destructive": "8 70% 50%",
|
||||
"destructiveForeground": "70 18% 90%",
|
||||
"border": "140 10% 22%",
|
||||
"input": "140 10% 22%",
|
||||
"ring": "95 28% 58%"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ember-loam",
|
||||
"name": "Ember Loam",
|
||||
"description": "Terracotta-led warm clay in light, firelight ember over deep loam in dark — moss becomes the accent.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:earthen:Fraunces",
|
||||
"fontBody": "template:earthen:Work Sans"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "28 38% 95%",
|
||||
"foreground": "18 40% 15%",
|
||||
"card": "28 32% 98%",
|
||||
"cardForeground": "18 40% 15%",
|
||||
"popover": "28 32% 98%",
|
||||
"popoverForeground": "18 40% 15%",
|
||||
"primary": "14 62% 44%",
|
||||
"primaryForeground": "28 38% 96%",
|
||||
"secondary": "32 26% 88%",
|
||||
"secondaryForeground": "18 40% 16%",
|
||||
"muted": "28 22% 91%",
|
||||
"mutedForeground": "22 20% 38%",
|
||||
"accent": "95 24% 34%",
|
||||
"accentForeground": "28 38% 96%",
|
||||
"destructive": "6 72% 44%",
|
||||
"destructiveForeground": "28 38% 96%",
|
||||
"border": "28 18% 81%",
|
||||
"input": "28 18% 81%",
|
||||
"ring": "14 62% 44%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "20 22% 7%",
|
||||
"foreground": "35 25% 90%",
|
||||
"card": "20 22% 10%",
|
||||
"cardForeground": "35 25% 90%",
|
||||
"popover": "20 22% 10%",
|
||||
"popoverForeground": "35 25% 90%",
|
||||
"primary": "22 72% 56%",
|
||||
"primaryForeground": "20 22% 7%",
|
||||
"secondary": "20 16% 17%",
|
||||
"secondaryForeground": "35 25% 90%",
|
||||
"muted": "20 14% 14%",
|
||||
"mutedForeground": "30 14% 58%",
|
||||
"accent": "90 30% 52%",
|
||||
"accentForeground": "20 22% 7%",
|
||||
"destructive": "6 72% 52%",
|
||||
"destructiveForeground": "35 25% 90%",
|
||||
"border": "20 14% 21%",
|
||||
"input": "20 14% 21%",
|
||||
"ring": "22 72% 56%"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
BIN
preview.png
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 351 KiB |
180
register.go
Normal file
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
25
registration.go
Normal file
@ -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() },
|
||||
}
|
||||
16
schemas/botanical_divider.schema.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
57
schemas/footer.schema.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
schemas/impact_metrics.schema.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
40
schemas/partner_logos.schema.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 3.0 MiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 415 KiB |
|
Before Width: | Height: | Size: 317 KiB |
|
Before Width: | Height: | Size: 738 KiB |
|
Before Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 244 KiB |
583
seed/seed.json
@ -1,583 +0,0 @@
|
||||
{
|
||||
"demo": true,
|
||||
"settings": {
|
||||
"merge": {
|
||||
"companyName": "Rootline Trust",
|
||||
"tagline": "We restore land with the people who live on it."
|
||||
}
|
||||
},
|
||||
"media": [
|
||||
{
|
||||
"id": "41830fd8-442a-4255-bc96-9fb12e428ef1",
|
||||
"file": "media/hands-soil.jpg",
|
||||
"alt": "Hands lowering a young seedling into dark soil",
|
||||
"folder": "earthen"
|
||||
},
|
||||
{
|
||||
"id": "152f8386-ecf1-4f0c-899a-2c947766644d",
|
||||
"file": "media/seedling.jpg",
|
||||
"alt": "Cupped hands holding a seedling above turned earth",
|
||||
"folder": "earthen"
|
||||
},
|
||||
{
|
||||
"id": "c71264ea-0217-4826-a796-baffab1cf041",
|
||||
"file": "media/forest.jpg",
|
||||
"alt": "Sunlight through moss-covered trees in regrown forest",
|
||||
"folder": "earthen"
|
||||
},
|
||||
{
|
||||
"id": "603fdac3-2fa5-4511-8357-aa65550d9a31",
|
||||
"file": "media/leaf-veins.jpg",
|
||||
"alt": "Rain-washed leaf showing its veins in deep green",
|
||||
"folder": "earthen"
|
||||
},
|
||||
{
|
||||
"id": "02a30527-5464-45e2-a1f9-529fd9f46787",
|
||||
"file": "media/terracotta.jpg",
|
||||
"alt": "Terracotta pots of native tube stock ready for planting",
|
||||
"folder": "earthen"
|
||||
},
|
||||
{
|
||||
"id": "a8c164d1-5f0c-496d-bb77-d1e8ba769527",
|
||||
"file": "media/botanical.jpg",
|
||||
"alt": "Fern fronds unfurling on the forest floor",
|
||||
"folder": "earthen"
|
||||
}
|
||||
],
|
||||
"data_tables": [
|
||||
{
|
||||
"key": "contact_submissions",
|
||||
"name": "Contact Enquiries",
|
||||
"description": "Contact-form submissions for Rootline Trust. A submission becomes a row here and emails the site admins.",
|
||||
"columns": [
|
||||
{
|
||||
"key": "name",
|
||||
"label": "Name",
|
||||
"type": "text",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "email",
|
||||
"label": "Email",
|
||||
"type": "email",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "region",
|
||||
"label": "Region",
|
||||
"type": "text",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "message",
|
||||
"label": "Message",
|
||||
"type": "text",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"slug": "/",
|
||||
"title": "Rootline Trust",
|
||||
"template_key": "landing",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "hero",
|
||||
"title": "Hero",
|
||||
"slot": "hero",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"template": "split",
|
||||
"eyebrow": "Land restoration",
|
||||
"headline": "Bring the land back to life.",
|
||||
"subheadline": "We restore land with the people who live on it.",
|
||||
"description": "Rootline Trust plants native cover, mends waterways and hands the care of the land back to local hands. Small work, done every season, in the places that need it most.",
|
||||
"backgroundImage": "media:41830fd8-442a-4255-bc96-9fb12e428ef1",
|
||||
"mediaPosition": "right",
|
||||
"ctaPrimary": {
|
||||
"text": "Donate",
|
||||
"url": "/contact"
|
||||
},
|
||||
"ctaSecondary": {
|
||||
"text": "See our work",
|
||||
"url": "/about"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "stats",
|
||||
"title": "Season in numbers",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"heading": "The last season, in the ground",
|
||||
"intro": "Every figure here is an acre planted, a bank mended, or a day given by someone who lives beside the work.",
|
||||
"columns": 4,
|
||||
"countUp": false,
|
||||
"items": [
|
||||
{
|
||||
"value": "840",
|
||||
"suffix": " acres",
|
||||
"label": "Native cover planted"
|
||||
},
|
||||
{
|
||||
"value": "19",
|
||||
"suffix": " km",
|
||||
"label": "Creek bank mended"
|
||||
},
|
||||
{
|
||||
"value": "1,200",
|
||||
"label": "Volunteer days given"
|
||||
},
|
||||
{
|
||||
"value": "12",
|
||||
"label": "Restoration sites"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "feature-grid",
|
||||
"title": "How we work",
|
||||
"slot": "main",
|
||||
"sort_order": 2,
|
||||
"content": {
|
||||
"eyebrow": "How we work",
|
||||
"heading": "Three steps, every site",
|
||||
"intro": "Restoration is not one grand act. It is the same patient sequence, repeated season after season, with the people who live on the land.",
|
||||
"columns": 3,
|
||||
"layout": "card",
|
||||
"items": [
|
||||
{
|
||||
"icon": "lucide:sprout",
|
||||
"title": "Plant native cover",
|
||||
"text": "We raise local tube stock and plant the species that belong on each site — the ones that hold soil, feed pollinators and come back on their own."
|
||||
},
|
||||
{
|
||||
"icon": "lucide:waves",
|
||||
"title": "Mend the waterways",
|
||||
"text": "We fence stock out of the creek lines, stabilise the banks and let the water run clean again. A mended creek carries a whole valley with it."
|
||||
},
|
||||
{
|
||||
"icon": "lucide:heart-handshake",
|
||||
"title": "Hand the land back",
|
||||
"text": "From the first day we work beside local carers. By the third season we step back, and the people who live there keep it going."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "divider",
|
||||
"title": "Botanical divider",
|
||||
"slot": "main",
|
||||
"sort_order": 3,
|
||||
"content": {
|
||||
"variant": "ornament",
|
||||
"width": "wide",
|
||||
"icon": "lucide:leaf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "earthen:field_note",
|
||||
"title": "Field Note",
|
||||
"slot": "main",
|
||||
"sort_order": 4,
|
||||
"content": {
|
||||
"author": "Mara Ellison",
|
||||
"location": "Wirra Creek",
|
||||
"dateline": "March 2025",
|
||||
"image": "media:152f8386-ecf1-4f0c-899a-2c947766644d",
|
||||
"body": "<p>The first rain after planting is the one that tells you. We walked the creek line at dawn and the tube stock had held — thousands of small green points, holding the bank against the next flood.</p><p>You plant, you fence, you wait. Restoration keeps its own slow clock. Then one ordinary morning the land answers back, and you understand you were only ever helping it remember what it already knew how to do.</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "newsletter-signup",
|
||||
"title": "Newsletter",
|
||||
"slot": "main",
|
||||
"sort_order": 5,
|
||||
"content": {
|
||||
"title": "Field notes in your inbox",
|
||||
"description": "A short dispatch from the sites each season — what we planted, what held, and where we are headed next. No noise.",
|
||||
"listSlug": "main",
|
||||
"source": "home",
|
||||
"submitText": "Subscribe",
|
||||
"placeholder": "you@example.com",
|
||||
"layout": "card"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/about",
|
||||
"title": "Our work",
|
||||
"template_key": "default",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "heading",
|
||||
"title": "Heading",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"text": "Our work",
|
||||
"level": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "image",
|
||||
"title": "Forest regrowth",
|
||||
"slot": "main",
|
||||
"sort_order": 3,
|
||||
"content": {
|
||||
"src": "media:a8c164d1-5f0c-496d-bb77-d1e8ba769527",
|
||||
"alt": "Fern fronds unfurling on the forest floor",
|
||||
"caption": "Ferns returning to the understorey at Wirra Creek, three seasons after fencing.",
|
||||
"size": "lg",
|
||||
"aspectRatio": "wide"
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "rich-text",
|
||||
"title": "Story",
|
||||
"slot": "main",
|
||||
"sort_order": 2,
|
||||
"content": {
|
||||
"html": "<p>Rootline Trust began with one degraded paddock and a handful of neighbours who wanted it back. We plant native cover, mend waterways and fence stock out of the creek lines. Then we hand the care of the land to the people who live beside it.</p><p>We work slowly and in public. Every site is chosen with local carers, planted from local stock, and reported honestly to the people who fund it. Nothing here happens in a season — it happens over three, or five, or ten, and it lasts because the people who live there stay long after we have gone.</p><p>We are a small trust on purpose. It keeps us close to the ground, close to the water, and close to the people whose land we are helping to mend.</p>"
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "earthen:impact_report",
|
||||
"title": "Impact Report",
|
||||
"slot": "main",
|
||||
"sort_order": 4,
|
||||
"content": {
|
||||
"period": "Season 2025",
|
||||
"title": "What we grew together",
|
||||
"summary": "<p>Every acre here began as a pledge from someone who wanted a piece of degraded land to come back. This is where those pledges went last season — twelve sites, one patient sequence, repeated until the ground took hold.</p>",
|
||||
"outcomes": [
|
||||
{
|
||||
"value": "840 acres",
|
||||
"label": "Native cover planted",
|
||||
"detail": "Across twelve restoration sites, raised from local tube stock."
|
||||
},
|
||||
{
|
||||
"value": "19 km",
|
||||
"label": "Creek bank mended",
|
||||
"detail": "Fenced, replanted and handed to the carers who live beside it."
|
||||
},
|
||||
{
|
||||
"value": "1,200",
|
||||
"label": "Volunteer days",
|
||||
"detail": "Given by people who live within a day's drive of the work."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "divider",
|
||||
"title": "Divider",
|
||||
"slot": "main",
|
||||
"sort_order": 5,
|
||||
"content": {
|
||||
"variant": "line",
|
||||
"weight": "thin",
|
||||
"width": "wide"
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "quote",
|
||||
"title": "Quote",
|
||||
"slot": "main",
|
||||
"sort_order": 6,
|
||||
"content": {
|
||||
"style": "pull",
|
||||
"align": "left",
|
||||
"quote": "You plant, you fence, you wait. Then the land answers back.",
|
||||
"attribution": "Mara Ellison",
|
||||
"role": "Field Lead, Wirra Creek"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/blog",
|
||||
"title": "Field Journal",
|
||||
"template_key": "blog-index",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "card",
|
||||
"title": "Post: First Rain",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"layout": "vertical",
|
||||
"media": "media:603fdac3-2fa5-4511-8357-aa65550d9a31",
|
||||
"title": "The first rain after planting",
|
||||
"text": "<p>What a dawn walk along Wirra Creek tells you about a whole season of restoration.</p>",
|
||||
"link": "/blog/the-first-rain-after-planting",
|
||||
"linkLabel": "Read the dispatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "card",
|
||||
"title": "Post: Fencing",
|
||||
"slot": "main",
|
||||
"sort_order": 2,
|
||||
"content": {
|
||||
"layout": "vertical",
|
||||
"media": "media:c71264ea-0217-4826-a796-baffab1cf041",
|
||||
"title": "Why we fence before we plant",
|
||||
"text": "<p>The least glamorous work on any site is the thing that protects everything that comes after it.</p>",
|
||||
"link": "/blog/why-we-fence-before-we-plant",
|
||||
"linkLabel": "Read the dispatch"
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "card",
|
||||
"title": "Post: Handover",
|
||||
"slot": "main",
|
||||
"sort_order": 3,
|
||||
"content": {
|
||||
"layout": "vertical",
|
||||
"media": "media:02a30527-5464-45e2-a1f9-529fd9f46787",
|
||||
"title": "Handing the land back",
|
||||
"text": "<p>Restoration is not finished when we leave. It is finished when the local carers take it on.</p>",
|
||||
"link": "/blog/handing-the-land-back",
|
||||
"linkLabel": "Read the dispatch"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/blog/the-first-rain-after-planting",
|
||||
"title": "The first rain after planting",
|
||||
"template_key": "article",
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "heading",
|
||||
"title": "Title",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"text": "The first rain after planting",
|
||||
"level": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "image",
|
||||
"title": "Creek line at dawn",
|
||||
"slot": "main",
|
||||
"sort_order": 2,
|
||||
"content": {
|
||||
"src": "media:c71264ea-0217-4826-a796-baffab1cf041",
|
||||
"alt": "Sunlight through moss-covered trees in regrown forest",
|
||||
"caption": "The oldest reach at Wirra Creek, three seasons in — canopy closing, bank holding.",
|
||||
"size": "lg",
|
||||
"aspectRatio": "wide"
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "rich-text",
|
||||
"title": "Body",
|
||||
"slot": "main",
|
||||
"sort_order": 3,
|
||||
"content": {
|
||||
"html": "<p>We walked the creek line at dawn, before the heat, the way we always do on the morning after the first real rain. The ground was dark and giving underfoot. Along the bank, where six weeks ago there had been bare clay and the stubble of old grazing, the tube stock had held — thousands of small green points, each one a native seedling with its roots now gripping the soil we had loosened for it.</p><p>The first rain after planting is the one that tells you. Everything before it is hope and hard work: you clear, you fence, you dig the holes on contour, you sink the tubes and firm the earth around each one. Then you wait, and you watch the sky, because a young planting that dries out in its first fortnight will not come back no matter how carefully it was put in. This rain came in the second week. It came slow and steady overnight, the kind that soaks in instead of running off, and by dawn the whole site had drunk it.</p><p>Restoration is slow, and it is meant to be. You cannot hurry a root system. You cannot argue a creek bank into stability. What you can do is give the land the conditions it needs and then get out of its way — fence the stock out, keep the weeds off the young growth, and let the seasons do the rest. We plant, we fence, we wait. It is the least heroic sequence imaginable, and it is the only one that works.</p><p>Wirra Creek was one of the worst sites we had taken on. Decades of stock had trodden the banks to bare earth; every flood carried another metre of topsoil downstream, and the water ran the colour of milky tea for days after any rain. When we first walked it with the carers who live along it, nobody pretended it would be quick. We agreed a ten-year horizon out loud, in the paddock, so that no one would mistake a single good season for the finish line.</p><p>The species matter more than people expect. We raise our tube stock from local seed — the sedges and wattles and paperbarks that already know this soil and this rainfall — because a plant that belongs on a site will hold on through the years we are not watching. The nursery work is unglamorous and it is where half the outcome is decided. A good seedling, hardened off properly, will take the first rain and run with it. A soft one, forced under lights and shipped in from far away, will sulk and die at the first hot week.</p><p>By mid-morning the sun was up and the carers had arrived to walk the line with us. There is a particular quiet that comes over people on a morning like that — nobody says much. You crouch by a tube, you feel the soil, you check that the guard has held and the stem is firm, and you move on to the next. Down the line someone laughs, because a seedling that was leaning last week has stood itself upright overnight, the way they do once the roots find water.</p><p>Not everything held. It never does. In the low corner where the flood sits longest we lost a run of tubes to waterlogging, and up on the dry western bank a few had been nosed out by something — a wallaby, most likely, testing the guards. We marked the gaps to replant in autumn. Losing some is part of the arithmetic; you plant knowing a portion will fail, and you plant enough that the survivors still close the canopy in time.</p><p>The fence is what makes all of it possible. Before a single tube goes in, we fence the stock out of the creek line and hold that line for years. It is the least glamorous work we do and it is the work that protects everything else. A good fence buys a young planting three quiet years to establish, and three years is usually all a native system needs to look after itself.</p><p>We finished the walk at the top of the reach, where the oldest planting — three seasons in now — has grown past head height and thrown enough shade that the bank beneath it stays damp and dark. Frogs have come back to that stretch. You can hear them after rain. It is the plainest possible proof that the work is working: you make a place liveable again, and the living things move back in without being asked.</p><p>That is the whole of it, really. You plant, you fence, you wait. Restoration keeps its own slow clock and there is no cheating it. Then one ordinary morning after rain the land answers back — a bank that holds, a creek that runs clear, a chorus of frogs where there was bare clay — and you understand that you were only ever helping the land remember what it already knew how to do.</p>"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/blog/why-we-fence-before-we-plant",
|
||||
"title": "Why we fence before we plant",
|
||||
"template_key": "article",
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "heading",
|
||||
"title": "Title",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"text": "Why we fence before we plant",
|
||||
"level": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "rich-text",
|
||||
"title": "Body",
|
||||
"slot": "main",
|
||||
"sort_order": 2,
|
||||
"content": {
|
||||
"html": "<p>Stock will find new growth before it has a chance to root. A single mob of cattle can undo a season's planting in an afternoon, and they always seem to know exactly where the softest new leaves are. So we fence first. Every time. It is the least glamorous work we do and it is the thing that protects everything that comes after.</p><p>A good fence buys a young planting three years of quiet — long enough for the roots to reach down, the canopy to start closing, and the site to stop needing us. Three years is usually all a native system asks for. Get the fence right and hold the line, and the rest of restoration is mostly patience.</p><p>People ask why we do not just plant more to make up for what gets eaten. The answer is that a fence is cheaper than a failure, and far cheaper than the heartbreak of walking a chewed-out site the morning after. We would rather spend the first budget on posts and wire than replant the same bank three times over.</p>"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/blog/handing-the-land-back",
|
||||
"title": "Handing the land back",
|
||||
"template_key": "article",
|
||||
"parent_slug": "/blog",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "heading",
|
||||
"title": "Title",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"text": "Handing the land back",
|
||||
"level": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "rich-text",
|
||||
"title": "Body",
|
||||
"slot": "main",
|
||||
"sort_order": 2,
|
||||
"content": {
|
||||
"html": "<p>Restoration is not finished when we leave. It is finished when the local carers take it on. We plant with them from the first day — not as spectators, but with hands in the soil — so the knowledge stays on the land instead of leaving in the back of our ute.</p><p>By the third season we start to step back. The watering slows, the replanting tapers off, and the walks along the line become the carers' walks, not ours. The site keeps going because the people beside it keep going, and they keep going because it was theirs the whole way through.</p><p>We measure our success by how little we are needed at the end. A site we can walk away from is the goal. It sounds strange for an organisation to aim at its own irrelevance, but that is exactly the point: land that can look after itself, in the care of people who will be there long after the last of our fences has weathered grey.</p>"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/contact",
|
||||
"title": "Get in touch",
|
||||
"template_key": "contact",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "contact-form",
|
||||
"title": "Contact Enquiry",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"heading": "Work with us",
|
||||
"description": "Tell us about your land, your region or how you would like to help. We read every message.",
|
||||
"submitLabel": "Send message",
|
||||
"successMessage": "Thank you. Your message has been received. We will reply by email.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Your name",
|
||||
"type": "text",
|
||||
"required": true,
|
||||
"width": "half"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"label": "Email",
|
||||
"type": "email",
|
||||
"required": true,
|
||||
"width": "half"
|
||||
},
|
||||
{
|
||||
"name": "region",
|
||||
"label": "Region",
|
||||
"type": "text",
|
||||
"required": false,
|
||||
"width": "half"
|
||||
},
|
||||
{
|
||||
"name": "message",
|
||||
"label": "How can we help?",
|
||||
"type": "textarea",
|
||||
"required": true,
|
||||
"width": "full"
|
||||
}
|
||||
],
|
||||
"formConfig": {
|
||||
"enabled": true,
|
||||
"captchaEnabled": false,
|
||||
"targetTable": "contact_submissions"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "hours-location",
|
||||
"title": "Where to find us",
|
||||
"slot": "aside",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"heading": "Office hours",
|
||||
"hours": [
|
||||
{
|
||||
"day": "Monday to Thursday",
|
||||
"hours": "9am to 5pm"
|
||||
},
|
||||
{
|
||||
"day": "Friday",
|
||||
"hours": "9am to 3pm"
|
||||
},
|
||||
{
|
||||
"day": "Saturday",
|
||||
"hours": "Field days only"
|
||||
},
|
||||
{
|
||||
"day": "Sunday",
|
||||
"closed": true
|
||||
}
|
||||
],
|
||||
"specialHoursNote": "Planting days run most weekends in autumn. Check the field journal.",
|
||||
"address": "3 Wattle Road\nWirra Creek",
|
||||
"phone": "+1 555 0142"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/login",
|
||||
"title": "Members",
|
||||
"template_key": "auth",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "auth-form",
|
||||
"title": "Sign in",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"default_tab": "login",
|
||||
"show_social": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "text",
|
||||
"title": "Foot",
|
||||
"slot": "footer",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"text": "<p>Members track pledges and see each site's season report first.</p>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"menu_items": [
|
||||
{
|
||||
"menu": "main",
|
||||
"label": "Home",
|
||||
"page_slug": "/",
|
||||
"sort_order": 1
|
||||
},
|
||||
{
|
||||
"menu": "main",
|
||||
"label": "Our work",
|
||||
"page_slug": "/about",
|
||||
"sort_order": 2
|
||||
},
|
||||
{
|
||||
"menu": "main",
|
||||
"label": "Field Journal",
|
||||
"page_slug": "/blog",
|
||||
"sort_order": 3
|
||||
},
|
||||
{
|
||||
"menu": "main",
|
||||
"label": "Get in touch",
|
||||
"page_slug": "/contact",
|
||||
"sort_order": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"workflows": [
|
||||
{
|
||||
"key": "contact_notify_admins",
|
||||
"name": "Contact enquiry to admins",
|
||||
"description": "On a contact-form submission, email the site admins.",
|
||||
"enabled": true,
|
||||
"trigger": { "type": "row_inserted", "table": "contact_submissions" },
|
||||
"steps": [
|
||||
{
|
||||
"type": "email",
|
||||
"name": "Email admins",
|
||||
"template": "Form Submission Notification",
|
||||
"recipients": { "mode": "admins", "scope": "all" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
273
template.templ
Normal file
@ -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) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
@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,
|
||||
})
|
||||
<body
|
||||
class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col"
|
||||
style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Spectral", Georgia, serif);"
|
||||
>
|
||||
@bn.AdminBypassBanner(data.SiteSettings)
|
||||
<header class="w-full">
|
||||
@templ.Raw(data.Slots["header"])
|
||||
</header>
|
||||
<main class="flex-grow w-full max-w-5xl mx-auto px-4 py-10">
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
@templ.Raw(main)
|
||||
} else {
|
||||
<div class="py-20 text-center" style="color: hsl(var(--muted-foreground));">
|
||||
<p>No content blocks assigned to this page.</p>
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
<footer class="w-full mt-auto">
|
||||
@templ.Raw(data.Slots["footer"])
|
||||
</footer>
|
||||
@bn.BodyEnd(data.SiteSettings)
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// EarthenLanding renders the landing template (hero, main, cta, footer).
|
||||
templ EarthenLanding(data PageData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
@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,
|
||||
})
|
||||
<body
|
||||
class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col"
|
||||
style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Spectral", Georgia, serif);"
|
||||
>
|
||||
@bn.AdminBypassBanner(data.SiteSettings)
|
||||
<section class="w-full">
|
||||
@templ.Raw(data.Slots["hero"])
|
||||
</section>
|
||||
<main class="flex-grow">
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
<div class="max-w-5xl mx-auto px-4 py-16">
|
||||
@templ.Raw(main)
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
<section class="w-full">
|
||||
@templ.Raw(data.Slots["cta"])
|
||||
</section>
|
||||
<footer class="w-full mt-auto">
|
||||
@templ.Raw(data.Slots["footer"])
|
||||
</footer>
|
||||
@bn.BodyEnd(data.SiteSettings)
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// EarthenArticle renders the article template (header, lede, main, aside, footer).
|
||||
templ EarthenArticle(data PageData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
@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,
|
||||
})
|
||||
<body
|
||||
class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col"
|
||||
style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Spectral", Georgia, serif);"
|
||||
>
|
||||
@bn.AdminBypassBanner(data.SiteSettings)
|
||||
<header class="w-full" style="border-bottom: 1px solid hsl(var(--border));">
|
||||
<div class="max-w-3xl mx-auto px-4">
|
||||
@templ.Raw(data.Slots["header"])
|
||||
</div>
|
||||
</header>
|
||||
if lede, ok := data.Slots["lede"]; ok && lede != "" {
|
||||
<section class="w-full max-w-3xl mx-auto px-4 pt-12">
|
||||
@templ.Raw(lede)
|
||||
</section>
|
||||
}
|
||||
<div class="flex-grow w-full grid grid-cols-1 lg:grid-cols-[1fr,260px] gap-12 max-w-5xl mx-auto px-4 py-12">
|
||||
<main>
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
<article class="earthen-article">
|
||||
@templ.Raw(main)
|
||||
</article>
|
||||
} else {
|
||||
<p style="color: hsl(var(--muted-foreground));">No story body yet.</p>
|
||||
}
|
||||
</main>
|
||||
if aside, ok := data.Slots["aside"]; ok && aside != "" {
|
||||
<aside class="earthen-aside text-sm">
|
||||
@templ.Raw(aside)
|
||||
</aside>
|
||||
}
|
||||
</div>
|
||||
<footer class="w-full mt-auto">
|
||||
@templ.Raw(data.Slots["footer"])
|
||||
</footer>
|
||||
@bn.BodyEnd(data.SiteSettings)
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// EarthenFullWidth renders edge-to-edge pages (header, main, footer).
|
||||
templ EarthenFullWidth(data PageData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
@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,
|
||||
})
|
||||
<body
|
||||
class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col"
|
||||
style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Spectral", Georgia, serif);"
|
||||
>
|
||||
@bn.AdminBypassBanner(data.SiteSettings)
|
||||
<header class="w-full">
|
||||
@templ.Raw(data.Slots["header"])
|
||||
</header>
|
||||
<main class="flex-grow w-full">
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
@templ.Raw(main)
|
||||
} else {
|
||||
<div class="max-w-4xl mx-auto py-20 px-4 text-center" style="color: hsl(var(--muted-foreground));">
|
||||
<p>No content blocks assigned to this page.</p>
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
<footer class="w-full mt-auto">
|
||||
@templ.Raw(data.Slots["footer"])
|
||||
</footer>
|
||||
@bn.BodyEnd(data.SiteSettings)
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
543
template_templ.go
Normal file
@ -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, "<!doctype html><html lang=\"en\" class=\"earthen\">")
|
||||
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, "<body class=\"bg-paper-grain earthen-body antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Spectral", Georgia, serif);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<header class=\"w-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</header><main class=\"flex-grow w-full max-w-5xl mx-auto px-4 py-10\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"py-20 text-center\" style=\"color: hsl(var(--muted-foreground));\"><p>No content blocks assigned to this page.</p></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</main><footer class=\"w-full mt-auto\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</footer>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// 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, "<!doctype html><html lang=\"en\" class=\"earthen\">")
|
||||
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, "<body class=\"bg-paper-grain earthen-body antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Spectral", Georgia, serif);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<section class=\"w-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Slots["hero"]).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</section><main class=\"flex-grow\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"max-w-5xl mx-auto px-4 py-16\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</main><section class=\"w-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Slots["cta"]).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</section><footer class=\"w-full mt-auto\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</footer>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// 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, "<!doctype html><html lang=\"en\" class=\"earthen\">")
|
||||
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, "<body class=\"bg-paper-grain earthen-body antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Spectral", Georgia, serif);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<header class=\"w-full\" style=\"border-bottom: 1px solid hsl(var(--border));\"><div class=\"max-w-3xl mx-auto px-4\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</div></header>")
|
||||
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, "<section class=\"w-full max-w-3xl mx-auto px-4 pt-12\">")
|
||||
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, "</section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"flex-grow w-full grid grid-cols-1 lg:grid-cols-[1fr,260px] gap-12 max-w-5xl mx-auto px-4 py-12\"><main>")
|
||||
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, "<article class=\"earthen-article\">")
|
||||
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, "</article>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<p style=\"color: hsl(var(--muted-foreground));\">No story body yet.</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</main>")
|
||||
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, "<aside class=\"earthen-aside text-sm\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(aside).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</aside>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</div><footer class=\"w-full mt-auto\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</footer>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</body></html>")
|
||||
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, "<!doctype html><html lang=\"en\" class=\"earthen\">")
|
||||
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, "<body class=\"bg-paper-grain earthen-body antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Spectral", Georgia, serif);\">")
|
||||
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, "<header class=\"w-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</header><main class=\"flex-grow w-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<div class=\"max-w-4xl mx-auto py-20 px-4 text-center\" style=\"color: hsl(var(--muted-foreground));\"><p>No content blocks assigned to this page.</p></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</main><footer class=\"w-full mt-auto\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</footer>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</body></html>")
|
||||
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
|
||||
@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<header class="w-full">{{ slots.header|safe }}</header>
|
||||
{% if slots.lede %}<section class="w-full max-w-3xl mx-auto px-4 pt-12">{{ slots.lede|safe }}</section>{% endif %}
|
||||
<div class="flex-grow w-full grid grid-cols-1 lg:grid-cols-[1fr,260px] gap-12 max-w-5xl mx-auto px-4 py-12">
|
||||
<main>{% if slots.main %}<article class="earthen-article">{{ slots.main|safe }}</article>{% else %}<p style="color: hsl(var(--muted-foreground));">No story body yet.</p>{% endif %}</main>
|
||||
{% if slots.aside %}<aside class="earthen-aside text-sm">{{ slots.aside|safe }}</aside>{% endif %}
|
||||
</div>
|
||||
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<main class="flex-grow w-full flex flex-col items-center justify-center px-4 py-12">
|
||||
<div class="w-full max-w-md">
|
||||
<a href="/" class="block text-center" aria-label="Home">
|
||||
<svg aria-hidden="true" class="mx-auto mb-4 h-11 w-11" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" style="color:hsl(var(--primary));"><path d="M24 44V8"></path><path d="M24 12c-4-2-7-2-9 0M24 12c4-2 7-2 9 0"></path><path d="M24 18c-6-2-10-1-12 1M24 18c6-2 10-1 12 1"></path><path d="M24 25c-7-1-12 0-14 3M24 25c7-1 12 0 14 3"></path><path d="M24 33c-7 0-11 2-12 5M24 33c7 0 11 2 12 5"></path></svg>
|
||||
</a>
|
||||
<div>{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p style="color:hsl(var(--muted-foreground));">No form assigned to this page.</p></div>{% endif %}</div>
|
||||
{% if slots.footer %}<div class="mt-6 text-center text-sm" style="color:hsl(var(--muted-foreground));">{{ slots.footer|safe }}</div>{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
{% if slots.header %}<header class="w-full">{{ slots.header|safe }}</header>{% endif %}
|
||||
<main class="flex-grow w-full">
|
||||
<div class="max-w-6xl mx-auto px-4 py-12 md:py-16">
|
||||
{% if title %}
|
||||
<div class="text-center">
|
||||
<svg aria-hidden="true" class="mx-auto mb-4 h-10 w-10" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" style="color:hsl(var(--primary));"><path d="M24 44V8"></path><path d="M24 12c-4-2-7-2-9 0M24 12c4-2 7-2 9 0"></path><path d="M24 18c-6-2-10-1-12 1M24 18c6-2 10-1 12 1"></path><path d="M24 25c-7-1-12 0-14 3M24 25c7-1 12 0 14 3"></path><path d="M24 33c-7 0-11 2-12 5M24 33c7 0 11 2 12 5"></path></svg>
|
||||
<h1 class="crayon-underline mx-auto w-fit text-4xl md:text-5xl font-semibold" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);color:hsl(var(--primary));">{{ title }}</h1>
|
||||
{% if page_meta.MetaDescription %}<p class="mt-4 text-lg" style="color:hsl(var(--muted-foreground));">{{ page_meta.MetaDescription }}</p>{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if slots.featured %}<section class="mt-12">{{ slots.featured|safe }}</section>{% endif %}
|
||||
{% if slots.main %}<section class="mt-12">{{ slots.main|safe }}</section>{% else %}<div class="py-20 text-center"><p style="color:hsl(var(--muted-foreground));">No posts yet. New stories from the field will appear here soon.</p></div>{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
{% if slots.footer %}<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>{% endif %}
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,22 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
{% if slots.header %}<header class="w-full">{{ slots.header|safe }}</header>{% endif %}
|
||||
<main class="flex-grow w-full">
|
||||
<div class="max-w-6xl mx-auto px-4 py-12 md:py-16">
|
||||
{% if slots.aside %}
|
||||
<div class="grid gap-12 md:grid-cols-[2fr_1fr]">
|
||||
<div>{% if slots.main %}{{ slots.main|safe }}{% endif %}</div>
|
||||
<aside class="w-full md:border-l md:border-border md:pl-10">{{ slots.aside|safe }}</aside>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="max-w-3xl mx-auto">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p style="color:hsl(var(--muted-foreground));">No content blocks assigned to this page.</p></div>{% endif %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
{% if slots.footer %}<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>{% endif %}
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<header class="w-full">{{ slots.header|safe }}</header>
|
||||
<main class="flex-grow w-full max-w-5xl mx-auto px-4 py-10">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center" style="color: hsl(var(--muted-foreground));"><p>No content blocks assigned to this page.</p></div>{% endif %}</main>
|
||||
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<header class="w-full">{{ slots.header|safe }}</header>
|
||||
<main class="flex-grow w-full">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="max-w-4xl mx-auto py-20 px-4 text-center" style="color: hsl(var(--muted-foreground));"><p>No content blocks assigned to this page.</p></div>{% endif %}</main>
|
||||
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="earthen">
|
||||
{{ head_html|safe }}
|
||||
<body class="bg-paper-grain earthen-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Work Sans", ui-sans-serif, system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<section class="w-full">{{ slots.hero|safe }}</section>
|
||||
<main class="flex-grow w-full">{% if slots.main %}{{ slots.main|safe }}{% endif %}</main>
|
||||
<section class="w-full">{{ slots.cta|safe }}</section>
|
||||
<footer class="w-full mt-auto">{{ slots.footer|safe }}</footer>
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,9 +0,0 @@
|
||||
<!doctype html><html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="x-apple-disable-message-reformatting"><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--[if mso]>
|
||||
<noscript>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
</noscript>
|
||||
<![endif]--><title>{{ site_name }}</title></head><body style="background-color: {{ colors.background|default:"#F6F1E6" }}; margin: 0; padding: 0; font-family: &#39;Work Sans&#39;, ui-sans-serif, system-ui, sans-serif; color: {{ colors.foreground|default:"#2D3A28" }};">{% if preview_text %}<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">{{ preview_text }}</div>{% endif %}<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color: {{ colors.background|default:"#F6F1E6" }};"><tr><td align="center" style="padding: 32px 12px;"><table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px; background-color: {{ colors.card|default:"#FCF9F2" }}; border: 1px solid {{ colors.border|default:"#D6D1C2" }}; border-radius: 12px; overflow: hidden;"><tr><td align="center" style="padding: 28px 32px; background-color: {{ colors.primary|default:"#3A5A36" }}; color: {{ colors.primaryForeground|default:"#F6F1E6" }};">{% if logo_url %}<img src="{{ logo_url }}" alt="{{ site_name }}" style="max-height: 44px; width: auto; display: block;">{% elif site_name %}<h1 style="margin: 0; font-family: &#39;Fraunces&#39;, &#39;Playfair Display&#39;, Georgia, serif; font-size: 26px; font-weight: 600; letter-spacing: -0.01em; color: {{ colors.primaryForeground|default:"#F6F1E6" }};">{{ site_name }}</h1>{% endif %}</td></tr><tr><td style="padding: 36px 40px; font-size: 16px; line-height: 1.7; color: {{ colors.foreground|default:"#2D3A28" }}; font-family: &#39;Work Sans&#39;, ui-sans-serif, system-ui, sans-serif;">{{ body|safe }}</td></tr><tr><td style="padding: 24px 40px; background-color: {{ colors.muted|default:"#EFEBDF" }}; border-top: 1px solid {{ colors.border|default:"#D6D1C2" }};"><table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0"><tr><td align="center"><p style="margin: 0 0 8px; font-family: &#39;Fraunces&#39;, &#39;Playfair Display&#39;, Georgia, serif; font-size: 14px; font-weight: 600; color: {{ colors.foreground|default:"#2D3A28" }};">{{ site_name }}</p><p style="margin: 0 0 6px; font-size: 11px; color: {{ colors.mutedForeground|default:"#6E7560" }};">Registered charity. We respect your inbox.</p>{% if site_url %}<p style="margin: 0 0 12px; font-size: 12px; color: {{ colors.mutedForeground|default:"#6E7560" }};"><a href="{{ site_url }}" style="color: {{ colors.primary|default:"#3A5A36" }}; text-decoration: none; border-bottom: 1px dotted {{ colors.primary|default:"#3A5A36" }};">{{ site_url }}</a></p>{% endif %}{% if unsubscribe_url %}<p style="margin: 0; font-size: 11px; color: {{ colors.mutedForeground|default:"#6E7560" }};"><a href="{{ unsubscribe_url }}" style="color: {{ colors.mutedForeground|default:"#6E7560" }}; text-decoration: underline;">Unsubscribe</a></p>{% else %}<p style="margin: 0; font-size: 11px; color: {{ colors.mutedForeground|default:"#6E7560" }};"><a href="#" style="color: {{ colors.mutedForeground|default:"#6E7560" }}; text-decoration: underline;">Unsubscribe</a></p>{% endif %}</td></tr></table></td></tr></table></td></tr></table></body></html>
|
||||
@ -1,21 +0,0 @@
|
||||
{# announcement-bar override — earthen banner on moss/clay tones. Dismiss <script>, data-bn-annkey content-hash and localStorage keying preserved verbatim from the builtin; only classes reskinned. Fields mirror builtin: variant, message, linkText, linkUrl, dismissable. #}
|
||||
{% set anncls = "bg-primary/10 text-foreground" %}
|
||||
{% if variant == "promo" %}{% set anncls = "bg-primary text-primary-foreground" %}{% elif variant == "alert" %}{% set anncls = "bg-destructive text-destructive-foreground" %}{% endif %}
|
||||
<div data-block-override="earthen:announcement-bar" data-bn-announcement data-bn-annkey="{{ variant|default:'info' }}:{{ message|slugify }}" class="bn-announcement w-full border-b border-border {{ anncls }} {{ class }}">
|
||||
<div class="max-w-7xl mx-auto px-10 py-2 relative flex items-center justify-center gap-2 text-sm text-center">
|
||||
<p class="font-medium" style="font-family:var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif);">{{ message }}{% if linkText and linkUrl %} <a href="{{ linkUrl }}" class="underline underline-offset-2 hover:opacity-80">{{ linkText }}</a>{% endif %}</p>
|
||||
{% if dismissable %}<button type="button" data-bn-ann-dismiss aria-label="Dismiss announcement" class="absolute right-3 top-1/2 -translate-y-1/2 inline-flex items-center justify-center p-1 rounded-lg hover:bg-black/10 transition-colors"><svg width="16" height="16" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></button>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if dismissable %}<script>
|
||||
(function(){
|
||||
var bars=document.querySelectorAll('[data-bn-announcement]:not([data-bn-ann-wired])');
|
||||
bars.forEach(function(bar){
|
||||
bar.setAttribute('data-bn-ann-wired','1');
|
||||
var key='bnann:'+bar.getAttribute('data-bn-annkey');
|
||||
try{if(localStorage.getItem(key)==='1'){bar.style.display='none';return;}}catch(e){}
|
||||
var btn=bar.querySelector('[data-bn-ann-dismiss]');
|
||||
if(btn)btn.addEventListener('click',function(){bar.style.display='none';try{localStorage.setItem(key,'1');}catch(e){}});
|
||||
});
|
||||
})();
|
||||
</script>{% endif %}
|
||||
@ -1,16 +0,0 @@
|
||||
{# audio-embed override — cream paper player card. Fields + facade JS mirror builtin verbatim: url, media, artwork, title, artist, download. #}
|
||||
<figure data-block-override="earthen:audio-embed" class="bn-audio earthen-panel earthen-blob-soft my-8 overflow-hidden{% if class %} {{ class }}{% endif %}" data-bn-audio data-audio-url="{{ url|default:"" }}" data-audio-media="{% if media %}{{ media|mediaURL }}{% endif %}"><div class="flex items-center gap-4 p-4">{% if artwork %}<div class="earthen-blob-soft h-16 w-16 shrink-0 overflow-hidden bg-muted">{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}</div>{% endif %}<div class="min-w-0 flex-1">{% if title %}<p class="truncate text-sm font-semibold" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);color:hsl(var(--foreground));">{{ title }}</p>{% endif %}{% if artist %}<p class="truncate text-sm italic" style="color:hsl(var(--muted-foreground));">{{ artist }}</p>{% endif %}<div class="mt-2 flex items-center gap-3" data-bn-audio-slot><button type="button" data-bn-audio-play class="earthen-cta inline-flex items-center gap-2 px-4 py-1.5 text-sm font-semibold" aria-label="Play{% if title %}: {{ title }}{% endif %}">::lucide:play:sm:: Play</button>{% if download %}<a href="{% if media %}{{ media|mediaURL }}{% else %}{{ url }}{% endif %}" download class="earthen-link inline-flex items-center gap-1 text-sm">::lucide:download:sm:: Download</a>{% endif %}</div></div></div></figure><script>
|
||||
(function(){
|
||||
if(window.__bnAudioFacade)return;window.__bnAudioFacade=true;
|
||||
document.addEventListener('click',function(e){
|
||||
var btn=e.target.closest('[data-bn-audio-play]');if(!btn)return;
|
||||
var fig=btn.closest('[data-bn-audio]');if(!fig)return;
|
||||
e.preventDefault();
|
||||
var src=fig.getAttribute('data-audio-media')||fig.getAttribute('data-audio-url')||'';
|
||||
if(!src)return;
|
||||
var slot=btn.closest('[data-bn-audio-slot]');
|
||||
var a=document.createElement('audio');a.src=src;a.controls=true;a.autoplay=true;a.preload='none';a.style.cssText='width:100%;min-width:12rem;height:2.25rem;';
|
||||
btn.remove();slot.insertBefore(a,slot.firstChild);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@ -1,33 +0,0 @@
|
||||
{# auth-form override — earthen login/register on a paper panel. Wiring is UNTOUCHABLE and reproduced exactly from builtin auth_blocks.go: single shared #auth-message, /api/auth/login (email,password) and /api/auth/register (username,email,password), hx-target #auth-message. Captcha widget injected from _captcha_widget when captchaEnabled (server enforces). Fields: default_tab, show_social (social omitted — needs Go/site config; graceful). Only classes/styles/copy reskinned to earthen. #}
|
||||
{% if context.isPublicLoggedIn %}
|
||||
<div data-block-override="earthen:auth-form" class="auth-form-logged-in mx-auto max-w-md text-center py-8">
|
||||
<p style="color:hsl(var(--muted-foreground));font-family:var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif);">You are signed in as <strong style="color:hsl(var(--foreground));">{{ context.publicUsername }}</strong></p>
|
||||
<a href="/account" class="earthen-link mt-3 inline-block">My account</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div data-block-override="earthen:auth-form" class="auth-form earthen-panel mx-auto max-w-md p-8">
|
||||
<div id="auth-message"></div>
|
||||
<div class="mb-6 flex border-b border-border">
|
||||
<button type="button" onclick="document.getElementById('login-panel').classList.remove('hidden');document.getElementById('register-panel').classList.add('hidden');this.setAttribute('data-active','1');this.nextElementSibling.removeAttribute('data-active');" data-auth-tab {% if default_tab != "register" %}data-active="1"{% endif %} class="flex-1 py-2 text-center text-sm font-medium uppercase tracking-wide border-b-2 border-transparent data-[active]:border-[hsl(var(--primary))] data-[active]:text-primary" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);">Sign in</button>
|
||||
<button type="button" onclick="document.getElementById('register-panel').classList.remove('hidden');document.getElementById('login-panel').classList.add('hidden');this.setAttribute('data-active','1');this.previousElementSibling.removeAttribute('data-active');" data-auth-tab {% if default_tab == "register" %}data-active="1"{% endif %} class="flex-1 py-2 text-center text-sm font-medium uppercase tracking-wide border-b-2 border-transparent data-[active]:border-[hsl(var(--primary))] data-[active]:text-primary" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);">Join us</button>
|
||||
</div>
|
||||
<div id="login-panel" class="{% if default_tab == "register" %}hidden{% endif %}">
|
||||
<form hx-post="/api/auth/login" hx-target="#auth-message" hx-swap="innerHTML">
|
||||
<div class="mb-3"><label class="block text-xs font-medium mb-1" style="color:hsl(var(--primary));">Email</label><input type="email" name="email" required class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
<div class="mb-4"><label class="block text-xs font-medium mb-1" style="color:hsl(var(--primary));">Password</label><input type="password" name="password" required class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
{% if _captcha_widget %}{{ _captcha_widget|safe }}{% endif %}
|
||||
<button type="submit" class="earthen-cta w-full" hx-disabled-elt="this">Sign in</button>
|
||||
<p class="text-center mt-3 text-sm"><a href="/forgot-password" class="earthen-link">Forgot password?</a></p>
|
||||
</form>
|
||||
</div>
|
||||
<div id="register-panel" class="{% if default_tab != "register" %}hidden{% endif %}">
|
||||
<form hx-post="/api/auth/register" hx-target="#auth-message" hx-swap="innerHTML">
|
||||
<div class="mb-3"><label class="block text-xs font-medium mb-1" style="color:hsl(var(--primary));">Username</label><input type="text" name="username" required pattern="[a-zA-Z0-9_-]{3,30}" class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
<div class="mb-3"><label class="block text-xs font-medium mb-1" style="color:hsl(var(--primary));">Email</label><input type="email" name="email" required class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
<div class="mb-4"><label class="block text-xs font-medium mb-1" style="color:hsl(var(--primary));">Password</label><input type="password" name="password" required minlength="8" class="w-full rounded-lg border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:border-[hsl(var(--primary))] focus:ring-1 focus:ring-ring" /></div>
|
||||
{% if _captcha_widget %}{{ _captcha_widget|safe }}{% endif %}
|
||||
<button type="submit" class="earthen-cta w-full" hx-disabled-elt="this">Create account</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -1,31 +0,0 @@
|
||||
{# auth-status override — header login state. Logged-out: Sign in / Join links. Logged-in: moss monogram + dropdown (logout posts /api/auth/logout). Wiring UNTOUCHABLE: context.isPublicLoggedIn / context.publicDisplayName / context.publicUsername reads, #auth-dropdown-menu, onclick toggle, outside-click <script>, hx-post /api/auth/logout. Only classes/styles/copy reskinned to earthen. #}
|
||||
{% if context.isPublicLoggedIn %}
|
||||
<div data-block-override="earthen:auth-status" class="auth-status relative">
|
||||
<button type="button" onclick="this.nextElementSibling.classList.toggle('hidden')" class="flex items-center gap-2 text-sm font-medium opacity-90 hover:opacity-100 px-3 py-1.5 transition-colors">
|
||||
<span class="inline-flex h-7 w-7 items-center justify-center rounded-full text-[0.7rem] font-bold" style="background-color:hsl(var(--primary));color:hsl(var(--primary-foreground));">{{ context.publicDisplayName|default:context.publicUsername|first|upper }}</span>
|
||||
<span>{{ context.publicDisplayName|default:context.publicUsername }}</span>
|
||||
<svg width="16" height="16" class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
|
||||
</button>
|
||||
<div id="auth-dropdown-menu" class="hidden absolute right-0 top-full mt-1 w-48 earthen-panel py-1 z-50">
|
||||
<a href="/account" class="block px-4 py-2 text-sm text-foreground hover:bg-muted">My profile</a>
|
||||
<a href="/account/reviews" class="block px-4 py-2 text-sm text-foreground hover:bg-muted">My reviews</a>
|
||||
<hr class="my-1 border-border"/>
|
||||
<button hx-post="/api/auth/logout" hx-swap="none" class="block w-full text-left px-4 py-2 text-sm text-destructive hover:bg-muted">Sign out</button>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
document.addEventListener('click', function(e) {
|
||||
var menu = document.getElementById('auth-dropdown-menu');
|
||||
var btn = document.querySelector('.auth-status > button');
|
||||
if (!menu || !btn) return;
|
||||
if (!btn.contains(e.target) && !menu.contains(e.target)) { menu.classList.add('hidden'); }
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
{% else %}
|
||||
<div data-block-override="earthen:auth-status" class="auth-status flex items-center gap-2">
|
||||
<a href="/login" class="text-sm px-3 py-1.5 opacity-80 hover:opacity-100 hover:text-primary transition-colors">Sign in</a>
|
||||
<a href="/login?tab=register" class="earthen-cta text-sm">Join us</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -1,22 +0,0 @@
|
||||
{# author-bio-hero (earthen) — author-page header from the `authors` provider. Guards, field reads, social icons (via <use>) and image wiring preserved; earthen skin only. #}
|
||||
{% if author %}
|
||||
<section data-block-override="earthen:author-bio-hero" class="border-b border-border bg-paper-grain py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto flex max-w-3xl flex-col items-center px-4 text-center">
|
||||
{% if showAvatar and author.avatar_url %}<div class="earthen-blob mb-6 h-24 w-24 overflow-hidden bg-muted">{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}</div>{% endif %}
|
||||
<svg class="earthen-glyph mb-4 h-8 w-8" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M24 44V8"></path><path d="M24 12c-4-2-7-2-9 0M24 12c4-2 7-2 9 0"></path><path d="M24 18c-6-2-10-1-12 1M24 18c6-2 10-1 12 1"></path><path d="M24 25c-7-1-12 0-14 3M24 25c7-1 12 0 14 3"></path><path d="M24 33c-7 0-11 2-12 5M24 33c7 0 11 2 12 5"></path></svg>
|
||||
<h1 class="text-3xl md:text-4xl" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);color:hsl(var(--primary));">{{ author.display_name }}</h1>
|
||||
{% if author.job_title or author.company %}<p class="mt-2 text-sm font-medium text-accent">{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}</p>{% endif %}
|
||||
{% if author.bio %}<p class="mt-5 max-w-2xl leading-relaxed text-muted-foreground" style="font-family:var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif);">{{ author.bio }}</p>{% endif %}
|
||||
<div class="mt-6 flex flex-wrap items-center justify-center gap-4 text-sm text-muted-foreground">
|
||||
{% if showPostCount %}<span>{{ author.post_count|default:0 }} post{% if author.post_count != 1 %}s{% endif %}</span>{% endif %}
|
||||
{% if author.location %}<span>{{ author.location }}</span>{% endif %}
|
||||
</div>
|
||||
{% if showSocial %}<div class="mt-6 flex items-center justify-center gap-4">
|
||||
{% if author.website_url %}<a href="{{ author.website_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Website"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#globe"></use></svg></a>{% endif %}
|
||||
{% if author.twitter_handle %}<a href="https://twitter.com/{{ author.twitter_handle|cut:"@" }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Twitter"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#x"></use></svg></a>{% endif %}
|
||||
{% if author.linkedin_url %}<a href="{{ author.linkedin_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="LinkedIn"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"></use></svg></a>{% endif %}
|
||||
{% if author.github_url %}<a href="{{ author.github_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="GitHub"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#github"></use></svg></a>{% endif %}
|
||||
</div>{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
@ -1,13 +0,0 @@
|
||||
{# author-bio (earthen) — byline card sourced from the `post` provider's author_* fields. Guards, field reads and image wiring preserved; earthen skin only. #}
|
||||
{% if post.author_name %}
|
||||
<aside data-block-override="earthen:author-bio" class="earthen-panel earthen-blob-soft p-6 text-card-foreground{% if class %} {{ class }}{% endif %}">
|
||||
{% if heading %}<p class="mb-4 text-xs font-semibold uppercase tracking-wider text-accent">{{ heading }}</p>{% endif %}
|
||||
<div class="flex items-start gap-4">
|
||||
{% if showAvatar and post.author_avatar %}<div class="earthen-blob-soft h-14 w-14 flex-shrink-0 overflow-hidden bg-muted">{% img post.author_avatar alt=post.author_name class="h-full w-full object-cover" %}</div>{% endif %}
|
||||
<div class="min-w-0">
|
||||
<h3 class="text-base font-semibold" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);color:hsl(var(--card-foreground));">{% if post.author_slug %}<a href="/author/{{ post.author_slug }}" class="transition-colors hover:text-primary">{{ post.author_name }}</a>{% else %}{{ post.author_name }}{% endif %}</h3>
|
||||
{% if post.author_bio %}<p class="mt-2 text-sm text-muted-foreground" style="font-family:var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif);">{{ post.author_bio }}</p>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
{% endif %}
|
||||
@ -1,24 +0,0 @@
|
||||
{# blog-hero (earthen) — blog-index header + featured posts from the `posts` provider. Wiring, field reads and loops preserved from builtin; earthen skin only. #}
|
||||
<section data-block-override="earthen:blog-hero" class="bg-paper-grain py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
<div class="mx-auto max-w-2xl text-center">
|
||||
<svg class="earthen-glyph mx-auto mb-4 h-9 w-9" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M24 44V8"></path><path d="M24 12c-4-2-7-2-9 0M24 12c4-2 7-2 9 0"></path><path d="M24 18c-6-2-10-1-12 1M24 18c6-2 10-1 12 1"></path><path d="M24 25c-7-1-12 0-14 3M24 25c7-1 12 0 14 3"></path><path d="M24 33c-7 0-11 2-12 5M24 33c7 0 11 2 12 5"></path></svg>
|
||||
{% if title %}<h1 class="crayon-underline inline-block text-4xl md:text-5xl" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);color:hsl(var(--primary));">{{ title }}</h1>{% endif %}
|
||||
{% if subtitle %}<p class="mt-4 text-lg text-muted-foreground" style="font-family:var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif);">{{ subtitle }}</p>{% endif %}
|
||||
</div>
|
||||
{% if posts %}
|
||||
<div class="mt-12 grid grid-cols-1 gap-8 md:grid-cols-3">
|
||||
{% for post in posts %}
|
||||
<article class="group flex flex-col{% if forloop.First %} md:col-span-3 md:flex-row md:items-stretch md:gap-8{% endif %}">
|
||||
{% if post.featured_image_url %}<a href="{{ post.url }}" class="earthen-blob-soft mb-4 block overflow-hidden bg-muted{% if forloop.First %} md:mb-0 md:w-1/2{% endif %}">{% img post.featured_image_url alt=post.title class="aspect-video h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" %}</a>{% endif %}
|
||||
<div class="flex flex-col justify-center{% if forloop.First %} md:w-1/2{% endif %}">
|
||||
<h2 class="{% if forloop.First %}text-2xl{% else %}text-lg{% endif %} font-semibold leading-snug" style="font-family:var(--font-heading, 'Fraunces', 'Playfair Display', Georgia, serif);color:hsl(var(--foreground));"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h2>
|
||||
{% if showExcerpt and post.excerpt %}<p class="mt-3 text-sm text-muted-foreground" style="font-family:var(--font-body, 'Work Sans', ui-sans-serif, system-ui, sans-serif);">{{ post.excerpt }}</p>{% endif %}
|
||||
<div class="mt-4 flex flex-wrap items-center gap-x-3 text-xs text-muted-foreground">{% if post.author_name %}<span class="font-medium text-accent">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"M j, Y" }}</span>{% endif %}</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||