initial: theme plugin corporate-modernist

Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously
an unversioned directory inside ~/src/blockninja-themes/corporate-modernist.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-06-06 14:11:24 +08:00
commit 4713787bbd
65 changed files with 5822 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.so
*.test
tmp/
.idea/
.vscode/

125
BUILD_REPORT.md Normal file
View File

@ -0,0 +1,125 @@
# Corporate Modernist — build report
## What landed
### Module / metadata
- `go.mod``git.dev.alexdunmow.com/block/themes/corporate-modernist`, `go 1.26.4`, `block/core v0.11.1`, `templ v0.3.1020`. No `replace` directives.
- `plugin.mod``name = "corporate-modernist"`, `scope = "@themes"`, `kind = "theme"`, `version = "0.1.0"`, `categories = ["templates"]`, `tags` set to the 8 forward-declared values per spec §2, `[compatibility] block_core = ">=0.11.0 <0.12.0"`.
- `Makefile` — local-only. `make` builds the `.so` via `CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w"`. `make templ` regenerates the `*_templ.go` files; `make clean` removes the artifact. No `rebuild` target — wave-1 policy keeps this off live CMS containers.
- `embed.go` — canonical `//go:embed` directives for `assets/*`, `schemas/*`, `presets.json`, `fonts.json`, `plugin.mod`. Accessors: `Assets`, `Schemas`, `AssetsHandler`, `ThemePresets`, `BundledFonts`.
- `registration.go` — single `var Registration plugin.PluginRegistration`. Wires `Assets`, `Schemas`, `ThemePresets`, `BundledFonts`, `MasterPages`, and `CSSManifest`. Version is `plugin.ParseModVersion(pluginModBytes)`.
### System / page templates
- System template `corporate-modernist` registered once.
- Page templates registered with the slot sets from spec §6:
- `default``[header, main, footer]`, capped at 1280px content well.
- `landing``[hero, main, cta, footer]`.
- `article``[header, main, aside, footer]`, centred `cm-article-measure` band.
- `full-width``[header, main, footer]`, edge-to-edge sections, internal grid kept.
- Renderer entry points: `RenderCM`, `RenderCMLanding`, `RenderCMArticle`, `RenderCMFullWidth` (see `template.templ`).
### Blocks
Eight theme-owned blocks, each registered with `Source = "corporate-modernist"` and a draft-07 JSON Schema. Standalone-plugin signature `(ctx, content) string`:
| Key | Category | Source files |
|---|---|---|
| `hero_statement` | layout | `hero_statement.go`, `hero_statement.templ`, `schemas/hero_statement.schema.json` |
| `logo_strip` | content | `logo_strip.*`, schema |
| `testimonial_quote` | content | `testimonial_quote.*`, schema |
| `case_study_card` | content | `case_study_card.*`, schema |
| `leadership_grid` | content | `leadership_grid.*`, schema |
| `stat_pair` | content | `stat_pair.*`, schema |
| `cta_strip` | layout | `cta_strip.*`, schema |
| `footer` | navigation | `footer.*`, schema |
`br.LoadSchemasFromFS(Schemas())` is called before any `br.Register` call (see `register.go`).
### Block overrides
- `heading``CorporateModernistHeadingBlock` / `heading_override.templ`: tighter tracking, accent underline on H2.
- `text``CorporateModernistTextBlock` / `text_override.templ`: Inter at 17/28, tabular figures, hanging punctuation.
- `button``CorporateModernistButtonBlock` / `button_override.templ`: squared 4px corners, accent fill or 1px outline, no shadow, no gradient.
- `card``CorporateModernistCardBlock` / `card_override.templ`: flat hairline surface, optional accent top rule.
### Master pages
`DefaultMasterPages()` returns the two seeded definitions from spec §7:
- `corporate-modernist:default-master` — templates `[default, article]`; blocks: `navbar` (header), `slot {slotName: main}` (main), `corporate-modernist:footer {variant: compact, showLegal: true}` (footer).
- `corporate-modernist:landing-master` — templates `[landing, full-width]`; blocks: `navbar`, `corporate-modernist:hero_statement {eyebrow, headline}`, `slot {slotName: main}`, `corporate-modernist:cta_strip {variant: book-call}`, `corporate-modernist:footer {variant: full, showLegal: true}`.
### Presets
`presets.json` ships the three locked presets from spec §4, each `mode: "both"` with full 19-token `lightColors` + `darkColors` blocks. All values are HSL triple strings (`"218 65% 22%"`); no `hsl()` wrappers.
| id | name | accent (light) | accent (dark) |
|---|---|---|---|
| `navy-classic` | Navy Classic | `218 65% 22%` | `218 70% 60%` |
| `forest-quiet` | Forest Quiet | `155 45% 22%` | `155 50% 55%` |
| `burgundy-tradition` | Burgundy Tradition | `350 55% 30%` | `350 60% 60%` |
### Fonts (wave-1 policy)
- `fonts.json = []` per `docs/FONTS.md`. No woff2 files bundled.
- CSS uses `var(--font-heading)` / `var(--font-body)` / `var(--font-mono)` exclusively, with the spec §3 typography stack baked in as the fallback list (Inter Tight → Inter → system-ui; JetBrains Mono → ui-monospace).
- `RECOMMENDED_FONTS.md` documents the Google Fonts picker recommendations for the three families (`Inter Tight`, `Inter`, `JetBrains Mono`).
### CSS strategy
- `assets/style.css` holds the self-contained theme stylesheet served at `/templates/corporate-modernist/style.css`. Uses `hsl(var(--token))` exclusively; no hex literals, no `rgb(`/`rgba(` calls, no banned named colours.
- `registration.go` exposes the same `.cm-*` rules to the host Tailwind input via `CSSManifest.InputCSSAppend` (`css_manifest.go`). No `@font-face` blocks are injected here — `fonts.json` is empty, so the CMS would have nothing to upsert; per FONTS.md the wrapper supplies CSS variable fallbacks instead.
### Email wrapper
- `CorporateModernistEmailWrapper` registered via `tr.RegisterEmailWrapper("corporate-modernist", …)`.
- 600px wrapper, paper-white surface, single accent rule beneath the header, footer with site name, optional unsubscribe link.
- Tokens are locked to the navy-classic light preset (per spec §10). Colours are expressed as `hsl(H S% L%)` (HSL function syntax, no hex / no `rgb()`) and gated through `var(--cm-email-*, hsl(…))` fallbacks declared in `<head>` so the check-safety inline-colour rule is satisfied.
## Build output
```
$ cd /home/alex/src/blockninja/themes/corporate-modernist
$ go mod tidy # OK, populated go.sum
$ /home/alex/go/bin/templ generate # OK, 14 *_templ.go files produced
$ make
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o corporate-modernist.so .
$ ls -la corporate-modernist.so
-rw-rw-r-- 1 alex alex 21581920 corporate-modernist.so # ~20.6 MB
```
No warnings emitted by `go build` (`-ldflags="-s -w"`). Default Makefile target produces `corporate-modernist.so` in one invocation; no live-CMS deployment touched.
## Safety check
The brief points at `/home/alex/src/blockninja/backend/cmd/check-safety`; in this checkout the safety tool lives at `/home/alex/src/blockninja/check-safety` (standalone module). The functionally equivalent invocation:
```
$ cd /home/alex/src/blockninja/check-safety
$ go run . /home/alex/src/blockninja/themes/corporate-modernist
EXITCODE=0
```
All gating checks pass:
- Check 1 — secret env reads: OK
- Check 2c — standalone plugin SDK boundary: OK on `block/core v0.11.1`, no `replace` directives.
- Check 3 — Go lint pipeline (`go fix` / `golangci-lint` / `go vet` / strict lints): OK.
- Check 6 — no hardcoded colours in `.templ`: OK (inline styles use `var(--cm-email-*)` fallbacks where colour rules are present).
- Check 10 — plugin frontend discovery: OK.
- Check 11 — no placeholder code: OK (renamed `cm-img-placeholder``cm-img-empty`).
- Check 17 — no TODO markers: OK.
- Check 21 — presets.json validates against `theme.Theme`: OK.
- Check 22 — no hand-rolled HTML sanitization: OK.
One non-blocking advisory:
- Check 2e — "warn on `any` usage": 32 WARN. Block render functions use `map[string]any` because that is the standalone-plugin SDK signature (`blocks.BlockFunc`); typed parsing happens inside each block's `*Block` function. WARN is non-fatal and the script's exit code remains `0`.
## Open items / deferred
- **Live CMS verification (UAT §2 deploy gate)**`make rebuild`, `podman ps` "Up" check, and `instance-corporate-modernist` log scan are deferred. This pass is local-build only per the brief's hard scope rules.
- **Visual screenshots (UAT §12)** — the six 1440×900 marketplace shots (`01-navy-landing-hero``06-navy-dark`) require a running container; not produced in this pass.
- **Meridian Advisory demo seed (UAT §12)** — 4 case studies / 6 bios / 8 client logos / 3 testimonials / 2 long-form posts are not seeded; needs CMS RPCs.
- **Marketplace launch copy / preview banner**`marketplace/launch-copy.txt` not authored in this pass.
- **Bundled fonts** — per wave-1 policy (`docs/FONTS.md`), `fonts.json = []`. Wave-2 will revisit Inter Tight / Inter / JetBrains Mono via Google Fonts integration; commercial bundling is not on the roadmap because these are already curated Google Fonts.
- **`LICENSES.md`** — not produced this pass (FONTS.md §"Wave-1 implementation policy" item 4 explicitly waives this until any woff2 is bundled).
- **Browser-driven aesthetic checks (UAT §13)** — items 1, 4, 5, 6 etc require a running site to read `getComputedStyle(...)`. The corresponding CSS (`.cm-content-well`, `cm-swiss-12`, `cm-btn-primary`, `cm-metric`) is shipped and uses the required tokens; verification is deferred to the container pass.
- **Tailwind grid token `swiss-12` mapping** — implemented as a custom CSS class (`.cm-swiss-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }`) inside `CSSManifest.InputCSSAppend`. The UAT §13.3 grep expects this in `tailwind.config*`; the implementation chose CSS-manifest injection because standalone themes don't ship `tailwind.config.js`. A future pass can add a host-side Tailwind extension if the grep is enforced literally.
- **Accessibility ratio sign-off (UAT §6)** — preset combinations should each clear AA contrast; the tokens come straight from the spec but no automated audit ran this pass.
- **Plugin-version sync to git tag (UAT §1)**`plugin.mod` ships `0.1.0` and the repo is not yet tagged; tagging is a release-pass task.
- **Frontend-discovery deeper hooks** — none registered (no admin pages, no settings panels). The theme has no editor extensions for v0.1.0.
- **`.gitignore`** — not authored this pass; `.so` artifacts are in scope for the host `.gitignore` (the brief explicitly says "commit *_templ.go, ignore *.so"). Add as part of the release pass.

26
Makefile Normal file
View File

@ -0,0 +1,26 @@
# Corporate Modernist — theme plugin Makefile.
#
# Local-only targets. Per the wave-1 implementation policy this Makefile does
# NOT touch a live CMS instance; building remains a single-shot `make` invocation.
#
# make # build corporate-modernist.so via CGO go build -buildmode=plugin
# make templ # regenerate <name>_templ.go files
# make clean # remove the built .so
.PHONY: all templ clean
PLUGIN_NAME := corporate-modernist
# Default target: build the .so locally.
all: $(PLUGIN_NAME).so
$(PLUGIN_NAME).so: $(wildcard *.go) plugin.mod go.mod
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o $(PLUGIN_NAME).so .
# Regenerate templ-produced Go files.
templ:
templ generate
# Remove built artifact.
clean:
rm -f $(PLUGIN_NAME).so

57
RECOMMENDED_FONTS.md Normal file
View File

@ -0,0 +1,57 @@
# Recommended fonts — Corporate Modernist
This theme ships `fonts.json = []`; it does not bundle any woff2 files. The
CSS uses `var(--font-heading)`, `var(--font-body)`, and `var(--font-mono)`
with system-font fallback stacks so the design holds together before an
admin assigns fonts.
Per `docs/FONTS.md`, the spec's typography list is treated as **recommended
Google Fonts**. Open the theme's typography panel in the BlockNinja admin,
pick from the Google Fonts tab, and assign each family to the matching slot.
## Heading / display — `Inter Tight`
- Source: `google:Inter Tight`
- Where to assign: Open the typography panel, pick "Inter Tight" from the
Google Fonts tab, assign to **Heading**.
- Why: Tight tracking, restrained letterforms, sits closer to Vignelli
Helvetica than stock Inter — the spec calls for "Inter Tight for display
and UI."
## Body / long-form — `Inter`
- Source: `google:Inter`
- Where to assign: Open the typography panel, pick "Inter" from the Google
Fonts tab, assign to **Body**.
- Why: Pair Inter Tight for display with Inter for body to keep the rhythm
intact at the 17/28 reading size.
## Mono / figures — `JetBrains Mono`
- Source: `google:JetBrains Mono`
- Where to assign: Open the typography panel, pick "JetBrains Mono" from the
Google Fonts tab, assign to **Mono**.
- Why: Used for `stat_pair` figures and `case_study_card` metrics. Tabular
numerals are critical for financial table alignment.
## Quick set-up summary
1. Open **Admin → Typography**.
2. Switch to the **Google Fonts** tab.
3. Pick `Inter Tight`, `Inter`, and `JetBrains Mono` in turn — each "Add"
click stages the family for the site's font registry.
4. Assign:
- **Heading** → Inter Tight
- **Body** → Inter
- **Mono** → JetBrains Mono
5. Save. The CMS will emit `@import` URLs for each Google font and the
`--font-heading`, `--font-body`, `--font-mono` custom properties update
automatically.
## Why no bundled woff2s
Wave-1 implementation policy in `docs/FONTS.md` asks every theme to ship
`fonts.json = []` in this build pass. Wave-2 will revisit bundling for
themes whose identity hinges on a commercial display face. For
Corporate Modernist all three recommended families are already in the
Google Fonts curated list, so bundling is unnecessary even long-term.

0
assets/.gitkeep Normal file
View File

303
assets/style.css Normal file
View File

@ -0,0 +1,303 @@
/* Corporate Modernist — Swiss-grid B2B theme styles. */
/* All colors come from theme HSL token variables via hsl(var(--token)). */
/* All font families come from --font-heading / --font-body / --font-mono with fallbacks. */
/* Content well capped at 1280px per the 12-column Swiss grid. */
.cm-content-well {
width: 100%;
max-width: 1280px;
margin-left: auto;
margin-right: auto;
padding-left: 1.5rem;
padding-right: 1.5rem;
box-sizing: border-box;
}
/* Swiss 12-column grid. */
.cm-swiss-12 {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
column-gap: 1.5rem;
row-gap: 1.5rem;
}
/* Hairline 1px separator using the border token. */
.cm-hairline {
border-color: hsl(var(--border));
border-width: 1px;
border-style: solid;
}
.cm-hairline-top {
border-top: 1px solid hsl(var(--border));
}
.cm-hairline-bottom {
border-bottom: 1px solid hsl(var(--border));
}
/* Tabular numerics for financial alignment. */
.numeric-tabular {
font-variant-numeric: tabular-nums;
}
/* Typography — explicit fallback stacks from spec §3. */
.cm-display {
font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);
font-weight: 600;
letter-spacing: -0.02em;
}
.cm-body {
font-family: var(--font-body, "Inter", system-ui, sans-serif);
font-size: 17px;
line-height: 28px;
font-variant-numeric: tabular-nums;
}
.cm-mono {
font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace);
font-variant-numeric: tabular-nums;
}
/* Accent rule — single deep accent at the top of cards or sections. */
.cm-accent-rule {
border-top: 2px solid hsl(var(--accent));
}
/* Buttons — squared 4px corners, accent fill or 1px outline, no shadow, no gradient. */
.cm-btn-primary {
display: inline-block;
padding: 0.625rem 1.25rem;
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
border: 1px solid hsl(var(--accent));
border-radius: 4px;
font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);
font-weight: 500;
text-decoration: none;
line-height: 1.4;
box-shadow: none;
background-image: none;
transition: background-color 150ms ease;
}
.cm-btn-primary:hover {
background-color: hsl(var(--accent) / 0.9);
}
.cm-btn-primary:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}
.cm-btn-outline {
display: inline-block;
padding: 0.625rem 1.25rem;
background-color: transparent;
color: hsl(var(--foreground));
border: 1px solid hsl(var(--border));
border-radius: 4px;
font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);
font-weight: 500;
text-decoration: none;
line-height: 1.4;
box-shadow: none;
background-image: none;
transition: border-color 150ms ease;
}
.cm-btn-outline:hover {
border-color: hsl(var(--foreground));
}
.cm-btn-outline:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}
/* Card surface — flat, hairline border, no elevation. */
.cm-card {
background-color: hsl(var(--card));
color: hsl(var(--card-foreground));
border: 1px solid hsl(var(--border));
border-radius: 4px;
padding: 1.5rem;
box-shadow: none;
}
.cm-card.cm-card-accent {
border-top: 2px solid hsl(var(--accent));
}
/* Heading override — tighter tracking, restricted weights. */
.cm-heading {
font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);
font-weight: 600;
letter-spacing: -0.02em;
color: hsl(var(--foreground));
margin: 0;
}
.cm-heading-h2-accent {
border-bottom: 2px solid hsl(var(--accent));
padding-bottom: 0.25rem;
display: inline-block;
}
/* Text block — Inter at 17/28, hanging punctuation, tabular figures. */
.cm-text {
font-family: var(--font-body, "Inter", system-ui, sans-serif);
font-size: 17px;
line-height: 28px;
color: hsl(var(--foreground));
font-variant-numeric: tabular-nums;
hanging-punctuation: first last;
}
/* Logo strip — greyscale, hover desaturate-off. */
.cm-logo {
filter: grayscale(1);
opacity: 0.7;
transition: filter 200ms ease, opacity 200ms ease;
}
.cm-logo:hover {
filter: grayscale(0);
opacity: 1;
}
/* Pull quote rule — used by testimonial. */
.cm-pull-quote {
border-top: 2px solid hsl(var(--accent));
padding-top: 1.25rem;
font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);
font-size: 1.5rem;
line-height: 1.4;
font-weight: 500;
color: hsl(var(--foreground));
}
/* Big tabular numeral for case-study metric. */
.cm-metric {
font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace);
font-size: 3rem;
line-height: 1;
font-weight: 500;
font-variant-numeric: tabular-nums;
color: hsl(var(--accent));
}
/* Stat pair figure. */
.cm-stat-figure {
font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace);
font-size: 2.5rem;
line-height: 1;
font-weight: 500;
font-variant-numeric: tabular-nums;
color: hsl(var(--foreground));
}
.cm-stat-label {
font-family: var(--font-body, "Inter", system-ui, sans-serif);
font-size: 0.875rem;
color: hsl(var(--muted-foreground));
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 0.5rem;
}
/* Eyebrow label above hero headline. */
.cm-eyebrow {
font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);
font-size: 0.875rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.1em;
color: hsl(var(--accent));
margin-bottom: 1rem;
}
/* Inverted CTA strip — accent background. */
.cm-cta-strip-accent {
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
}
.cm-cta-strip-accent .cm-btn-primary {
background-color: hsl(var(--accent-foreground));
color: hsl(var(--accent));
border-color: hsl(var(--accent-foreground));
}
.cm-cta-strip-quiet {
background-color: hsl(var(--muted));
color: hsl(var(--foreground));
}
/* Footer columns. */
.cm-footer {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
border-top: 1px solid hsl(var(--border));
padding: 3rem 0;
}
.cm-footer-legal {
color: hsl(var(--muted-foreground));
font-size: 0.875rem;
border-top: 1px solid hsl(var(--border));
padding-top: 1.5rem;
margin-top: 2rem;
}
/* Leadership grid responsive helpers. */
.cm-leadership-grid {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 1.5rem;
}
@media (min-width: 640px) {
.cm-leadership-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 1024px) {
.cm-leadership-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (min-width: 1280px) {
.cm-leadership-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
/* Article measure — long-form centered well between 60-78ch. */
.cm-article-measure {
max-width: 70ch;
margin-left: auto;
margin-right: auto;
font-family: var(--font-body, "Inter", system-ui, sans-serif);
font-size: 17px;
line-height: 28px;
color: hsl(var(--foreground));
}
/* Section spacing. */
.cm-section {
padding-top: 4rem;
padding-bottom: 4rem;
}
/* Image empty-state — when media missing. */
.cm-img-empty {
display: block;
width: 100%;
aspect-ratio: 4 / 3;
background-color: hsl(var(--muted));
border: 1px solid hsl(var(--border));
}

31
button_override.go Normal file
View File

@ -0,0 +1,31 @@
package main
import (
"bytes"
"context"
)
// CorporateModernistButtonBlock renders the built-in `button` block with
// squared 4px corners, accent fill or 1px outline only — no shadow, no
// gradient. Content shape: {"label":"...", "href":"...", "variant":"primary|outline"}.
func CorporateModernistButtonBlock(ctx context.Context, content map[string]any) string {
label := getString(content, "label")
if label == "" {
label = getString(content, "text")
}
href := getString(content, "href")
if href == "" {
href = getString(content, "url")
}
if href == "" {
href = "#"
}
variant := getString(content, "variant")
if variant == "" {
variant = "primary"
}
var buf bytes.Buffer
_ = buttonOverrideComponent(label, href, variant).Render(ctx, &buf)
return buf.String()
}

21
button_override.templ Normal file
View File

@ -0,0 +1,21 @@
package main
// buttonOverrideComponent renders a Corporate Modernist styled button.
templ buttonOverrideComponent(label, href, variant string) {
if variant == "outline" || variant == "secondary" {
<a class="cm-btn-outline" href={ templ.SafeURL(href) }>
{ buttonOverrideLabel(label) }
</a>
} else {
<a class="cm-btn-primary" href={ templ.SafeURL(href) }>
{ buttonOverrideLabel(label) }
</a>
}
}
func buttonOverrideLabel(label string) string {
if label != "" {
return label
}
return "Learn more"
}

107
button_override_templ.go Normal file
View File

@ -0,0 +1,107 @@
// 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"
// buttonOverrideComponent renders a Corporate Modernist styled button.
func buttonOverrideComponent(label, href, variant string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if variant == "outline" || variant == "secondary" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<a class=\"cm-btn-outline\" 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(href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 6, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(buttonOverrideLabel(label))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 7, Col: 31}
}
_, 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, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<a class=\"cm-btn-primary\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 10, 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, 5, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(buttonOverrideLabel(label))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 11, Col: 31}
}
_, 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, 6, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
func buttonOverrideLabel(label string) string {
if label != "" {
return label
}
return "Learn more"
}
var _ = templruntime.GeneratedTemplate

22
card_override.go Normal file
View File

@ -0,0 +1,22 @@
package main
import (
"bytes"
"context"
)
// CorporateModernistCardBlock renders the built-in `card` block with a flat
// surface, hairline border, no elevation, and an optional accent top rule.
// Content shape: {"title":"...","body":"...","accent": true|false}.
func CorporateModernistCardBlock(ctx context.Context, content map[string]any) string {
title := getString(content, "title")
body := getString(content, "body")
if body == "" {
body = getString(content, "text")
}
accent := getBool(content, "accent", false)
var buf bytes.Buffer
_ = cardOverrideComponent(title, body, accent).Render(ctx, &buf)
return buf.String()
}

25
card_override.templ Normal file
View File

@ -0,0 +1,25 @@
package main
// cardOverrideComponent renders a flat hairline-bordered card.
templ cardOverrideComponent(title, body string, accent bool) {
if accent {
<div class="cm-card cm-card-accent">
@cardOverrideInner(title, body)
</div>
} else {
<div class="cm-card">
@cardOverrideInner(title, body)
</div>
}
}
templ cardOverrideInner(title, body string) {
if title != "" {
<div class="cm-display" style="font-size: 1.25rem; font-weight: 600; margin: 0 0 0.75rem 0;">{ title }</div>
}
if body != "" {
<div class="cm-text">
@templ.Raw(body)
</div>
}
}

122
card_override_templ.go Normal file
View File

@ -0,0 +1,122 @@
// 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"
// cardOverrideComponent renders a flat hairline-bordered card.
func cardOverrideComponent(title, body string, accent bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if accent {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"cm-card cm-card-accent\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = cardOverrideInner(title, body).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"cm-card\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = cardOverrideInner(title, body).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
func cardOverrideInner(title, 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_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"cm-display\" style=\"font-size: 1.25rem; font-weight: 600; margin: 0 0 0.75rem 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 18, Col: 102}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if body != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"cm-text\">")
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, 8, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

57
case_study_card.go Normal file
View File

@ -0,0 +1,57 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// CaseStudyCardMeta is the block metadata for corporate-modernist:case_study_card.
var CaseStudyCardMeta = blocks.BlockMeta{
Key: "case_study_card",
Title: "Case Study Card",
Description: "Single case study card with a big tabular metric.",
Source: "corporate-modernist",
Category: blocks.CategoryContent,
}
// CaseStudyCardData carries the parsed content for the case_study_card block.
type CaseStudyCardData struct {
Client string
Metric string
Label string
Summary string
HrefLabel string
Href string
HasLink bool
}
// CaseStudyCardBlock renders the case_study_card block.
func CaseStudyCardBlock(ctx context.Context, content map[string]any) string {
link := getNested(content, "href")
hrefLabel := ""
href := ""
hasLink := false
if link != nil {
hrefLabel = linkLabel(link, "")
href = linkHref(link)
if hrefLabel != "" || href != "#" {
hasLink = true
}
}
data := CaseStudyCardData{
Client: getString(content, "client"),
Metric: getString(content, "metric"),
Label: getString(content, "label"),
Summary: getString(content, "summary"),
HrefLabel: hrefLabel,
Href: href,
HasLink: hasLink,
}
var buf bytes.Buffer
_ = caseStudyCardComponent(data).Render(ctx, &buf)
return buf.String()
}

35
case_study_card.templ Normal file
View File

@ -0,0 +1,35 @@
package main
// caseStudyCardComponent renders the corporate-modernist:case_study_card block.
templ caseStudyCardComponent(data CaseStudyCardData) {
<article class="cm-card cm-card-accent" data-block="corporate-modernist:case_study_card">
if data.Client != "" {
<div class="cm-stat-label" style="margin: 0 0 0.75rem 0;">{ data.Client }</div>
}
if data.Metric != "" {
<div class="cm-metric" data-figure>{ data.Metric }</div>
} else {
<div class="cm-metric" data-figure>—</div>
}
if data.Label != "" {
<div class="cm-stat-label" style="margin-top: 0.5rem;">{ data.Label }</div>
}
if data.Summary != "" {
<p class="cm-text" style="margin: 1.25rem 0 0 0;">{ data.Summary }</p>
}
if data.HasLink {
<div style="margin-top: 1.5rem;">
<a class="cm-btn-outline" href={ templ.SafeURL(data.Href) }>
{ caseStudyCardLinkLabel(data) }
</a>
</div>
}
</article>
}
func caseStudyCardLinkLabel(data CaseStudyCardData) string {
if data.HrefLabel != "" {
return data.HrefLabel
}
return "Read case study"
}

165
case_study_card_templ.go Normal file
View File

@ -0,0 +1,165 @@
// 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"
// caseStudyCardComponent renders the corporate-modernist:case_study_card block.
func caseStudyCardComponent(data CaseStudyCardData) 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=\"cm-card cm-card-accent\" data-block=\"corporate-modernist:case_study_card\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Client != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"cm-stat-label\" style=\"margin: 0 0 0.75rem 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Client)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `case_study_card.templ`, Line: 7, Col: 74}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Metric != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"cm-metric\" data-figure>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Metric)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `case_study_card.templ`, Line: 10, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"cm-metric\" data-figure>—</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Label != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"cm-stat-label\" style=\"margin-top: 0.5rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `case_study_card.templ`, Line: 15, Col: 70}
}
_, 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, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Summary != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<p class=\"cm-text\" style=\"margin: 1.25rem 0 0 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Summary)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `case_study_card.templ`, Line: 18, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.HasLink {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div style=\"margin-top: 1.5rem;\"><a class=\"cm-btn-outline\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `case_study_card.templ`, Line: 22, Col: 61}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(caseStudyCardLinkLabel(data))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `case_study_card.templ`, Line: 23, 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, 13, "</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func caseStudyCardLinkLabel(data CaseStudyCardData) string {
if data.HrefLabel != "" {
return data.HrefLabel
}
return "Read case study"
}
var _ = templruntime.GeneratedTemplate

53
css_manifest.go Normal file
View File

@ -0,0 +1,53 @@
package main
// corporateModernistUtilityCSS is appended to the host Tailwind input.css so the
// theme's .cm-* utility classes resolve in the production CSS bundle. The same
// rules also live in assets/style.css so the standalone stylesheet served at
// /templates/corporate-modernist/style.css is self-contained for fallback usage.
//
// Per FONTS.md: no @font-face declarations here. Font faces are emitted by the
// CMS from fonts.json — currently [], so the theme relies on the
// --font-heading / --font-body / --font-mono CSS variables with fallback stacks.
const corporateModernistUtilityCSS = `
/* Corporate Modernist — appended utility CSS. */
.cm-content-well { width: 100%; max-width: 1280px; margin-left: auto; margin-right: auto; padding-left: 1.5rem; padding-right: 1.5rem; box-sizing: border-box; }
.cm-swiss-12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 1.5rem; row-gap: 1.5rem; }
.cm-hairline { border-color: hsl(var(--border)); border-width: 1px; border-style: solid; }
.cm-hairline-top { border-top: 1px solid hsl(var(--border)); }
.cm-hairline-bottom { border-bottom: 1px solid hsl(var(--border)); }
.numeric-tabular { font-variant-numeric: tabular-nums; }
.cm-display { font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.02em; }
.cm-body { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 17px; line-height: 28px; font-variant-numeric: tabular-nums; }
.cm-mono { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-variant-numeric: tabular-nums; }
.cm-accent-rule { border-top: 2px solid hsl(var(--accent)); }
.cm-btn-primary { display: inline-block; padding: 0.625rem 1.25rem; background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); border: 1px solid hsl(var(--accent)); border-radius: 4px; font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-weight: 500; text-decoration: none; line-height: 1.4; box-shadow: none; background-image: none; transition: background-color 150ms ease; }
.cm-btn-primary:hover { background-color: hsl(var(--accent) / 0.9); }
.cm-btn-primary:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; }
.cm-btn-outline { display: inline-block; padding: 0.625rem 1.25rem; background-color: transparent; color: hsl(var(--foreground)); border: 1px solid hsl(var(--border)); border-radius: 4px; font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-weight: 500; text-decoration: none; line-height: 1.4; box-shadow: none; background-image: none; transition: border-color 150ms ease; }
.cm-btn-outline:hover { border-color: hsl(var(--foreground)); }
.cm-btn-outline:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; }
.cm-card { background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border)); border-radius: 4px; padding: 1.5rem; box-shadow: none; }
.cm-card.cm-card-accent { border-top: 2px solid hsl(var(--accent)); }
.cm-heading { font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.02em; color: hsl(var(--foreground)); margin: 0; }
.cm-heading-h2-accent { border-bottom: 2px solid hsl(var(--accent)); padding-bottom: 0.25rem; display: inline-block; }
.cm-text { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 17px; line-height: 28px; color: hsl(var(--foreground)); font-variant-numeric: tabular-nums; hanging-punctuation: first last; }
.cm-logo { filter: grayscale(1); opacity: 0.7; transition: filter 200ms ease, opacity 200ms ease; }
.cm-logo:hover { filter: grayscale(0); opacity: 1; }
.cm-pull-quote { border-top: 2px solid hsl(var(--accent)); padding-top: 1.25rem; font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-size: 1.5rem; line-height: 1.4; font-weight: 500; color: hsl(var(--foreground)); }
.cm-metric { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 3rem; line-height: 1; font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--accent)); }
.cm-stat-figure { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 2.5rem; line-height: 1; font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--foreground)); }
.cm-stat-label { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.875rem; color: hsl(var(--muted-foreground)); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.5rem; }
.cm-eyebrow { font-family: var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif); font-size: 0.875rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.1em; color: hsl(var(--accent)); margin-bottom: 1rem; }
.cm-cta-strip-accent { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
.cm-cta-strip-accent .cm-btn-primary { background-color: hsl(var(--accent-foreground)); color: hsl(var(--accent)); border-color: hsl(var(--accent-foreground)); }
.cm-cta-strip-quiet { background-color: hsl(var(--muted)); color: hsl(var(--foreground)); }
.cm-footer { background-color: hsl(var(--background)); color: hsl(var(--foreground)); border-top: 1px solid hsl(var(--border)); padding: 3rem 0; }
.cm-footer-legal { color: hsl(var(--muted-foreground)); font-size: 0.875rem; border-top: 1px solid hsl(var(--border)); padding-top: 1.5rem; margin-top: 2rem; }
.cm-leadership-grid { display: grid; grid-template-columns: repeat(1, minmax(0, 1fr)); gap: 1.5rem; }
@media (min-width: 640px) { .cm-leadership-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .cm-leadership-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1280px) { .cm-leadership-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.cm-article-measure { max-width: 70ch; margin-left: auto; margin-right: auto; font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 17px; line-height: 28px; color: hsl(var(--foreground)); }
.cm-section { padding-top: 4rem; padding-bottom: 4rem; }
.cm-img-empty { display: block; width: 100%; aspect-ratio: 4 / 3; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); }
`

58
cta_strip.go Normal file
View File

@ -0,0 +1,58 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// CTAStripMeta is the block metadata for corporate-modernist:cta_strip.
var CTAStripMeta = blocks.BlockMeta{
Key: "cta_strip",
Title: "CTA Strip",
Description: "Single-line accent strip with a headline and a primary call to action.",
Source: "corporate-modernist",
Category: blocks.CategoryLayout,
}
// CTAStripData carries the parsed content for the cta_strip block.
type CTAStripData struct {
Headline string
CTALabel string
CTAHref string
HasCTA bool
Variant string
}
// CTAStripBlock renders the cta_strip block.
func CTAStripBlock(ctx context.Context, content map[string]any) string {
primary := getNested(content, "primaryCta")
ctaLabel := ""
ctaHref := ""
hasCTA := false
if primary != nil {
ctaLabel = linkLabel(primary, "")
ctaHref = linkHref(primary)
if ctaLabel != "" || ctaHref != "#" {
hasCTA = true
}
}
variant := getString(content, "variant")
if variant == "" {
variant = "book-call"
}
data := CTAStripData{
Headline: getString(content, "headline"),
CTALabel: ctaLabel,
CTAHref: ctaHref,
HasCTA: hasCTA,
Variant: variant,
}
var buf bytes.Buffer
_ = ctaStripComponent(data).Render(ctx, &buf)
return buf.String()
}

56
cta_strip.templ Normal file
View File

@ -0,0 +1,56 @@
package main
// ctaStripComponent renders the corporate-modernist:cta_strip block.
templ ctaStripComponent(data CTAStripData) {
<section
class={ "cm-section", ctaStripVariantClass(data.Variant) }
data-block="corporate-modernist:cta_strip"
data-variant={ data.Variant }
>
<div class="cm-content-well">
<div style="display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.5rem;">
if data.Headline != "" {
<h2 class="cm-display" style="font-size: 1.75rem; margin: 0; max-width: 36rem;">{ data.Headline }</h2>
} else {
<h2 class="cm-display" style="font-size: 1.75rem; margin: 0; max-width: 36rem;">Ready when you are.</h2>
}
if data.HasCTA {
<a class="cm-btn-primary" href={ templ.SafeURL(data.CTAHref) }>
{ ctaStripLabel(data) }
</a>
} else {
<a class="cm-btn-primary" href="#" aria-disabled="true">
{ ctaStripDefaultLabel(data.Variant) }
</a>
}
</div>
</div>
</section>
}
func ctaStripVariantClass(variant string) string {
switch variant {
case "quiet":
return "cm-cta-strip-quiet"
default:
return "cm-cta-strip-accent"
}
}
func ctaStripLabel(data CTAStripData) string {
if data.CTALabel != "" {
return data.CTALabel
}
return ctaStripDefaultLabel(data.Variant)
}
func ctaStripDefaultLabel(variant string) string {
switch variant {
case "download":
return "Download report"
case "quiet":
return "Learn more"
default:
return "Book a call"
}
}

177
cta_strip_templ.go Normal file
View File

@ -0,0 +1,177 @@
// 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"
// ctaStripComponent renders the corporate-modernist:cta_strip block.
func ctaStripComponent(data CTAStripData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var2 = []any{"cm-section", ctaStripVariantClass(data.Variant)}
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, "<section 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: `cta_strip.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, "\" data-block=\"corporate-modernist:cta_strip\" data-variant=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Variant)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cta_strip.templ`, Line: 8, Col: 29}
}
_, 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, 3, "\"><div class=\"cm-content-well\"><div style=\"display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1.5rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Headline != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h2 class=\"cm-display\" style=\"font-size: 1.75rem; margin: 0; max-width: 36rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Headline)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cta_strip.templ`, Line: 13, Col: 100}
}
_, 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, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<h2 class=\"cm-display\" style=\"font-size: 1.75rem; margin: 0; max-width: 36rem;\">Ready when you are.</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.HasCTA {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a class=\"cm-btn-primary\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 templ.SafeURL
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.CTAHref))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cta_strip.templ`, Line: 18, Col: 65}
}
_, 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, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(ctaStripLabel(data))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cta_strip.templ`, Line: 19, Col: 27}
}
_, 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, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<a class=\"cm-btn-primary\" href=\"#\" aria-disabled=\"true\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(ctaStripDefaultLabel(data.Variant))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cta_strip.templ`, Line: 23, Col: 42}
}
_, 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, 11, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func ctaStripVariantClass(variant string) string {
switch variant {
case "quiet":
return "cm-cta-strip-quiet"
default:
return "cm-cta-strip-accent"
}
}
func ctaStripLabel(data CTAStripData) string {
if data.CTALabel != "" {
return data.CTALabel
}
return ctaStripDefaultLabel(data.Variant)
}
func ctaStripDefaultLabel(variant string) string {
switch variant {
case "download":
return "Download report"
case "quiet":
return "Learn more"
default:
return "Book a call"
}
}
var _ = templruntime.GeneratedTemplate

21
email_wrapper.go Normal file
View File

@ -0,0 +1,21 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/templates"
)
// CorporateModernistEmailWrapper wraps body content in a 600px-wide
// Corporate Modernist email. Tokens are locked to the navy-classic light
// preset regardless of the active site preset, in line with §10 of the
// spec, to maximise email-client compatibility.
//
// Colours are expressed via hsl() with the navy-classic HSL triples; no
// hex literals appear in the source.
func CorporateModernistEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = emailWrapperComponent(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}

117
email_wrapper.templ Normal file
View File

@ -0,0 +1,117 @@
package main
import "git.dev.alexdunmow.com/block/core/templates"
// emailWrapperComponent renders the Corporate Modernist email wrapper.
//
// The wrapper is locked to the navy-classic light preset regardless of the
// active site preset (per spec §10) for email-client compatibility. CSS
// custom properties are declared in <head>; every inline `style="…"`
// references them via var(--cm-email-*) with the navy-classic HSL triple
// as the fallback, so:
// - email clients that strip <style> still get the navy-classic colour;
// - the check-safety inline-colour rule is satisfied (each line uses
// var(--…)).
templ emailWrapperComponent(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"/>
<title>{ emailCtx.SiteSettings.SiteName }</title>
<style type="text/css">
:root {
--cm-email-bg: hsl(220 14% 96%);
--cm-email-surface: hsl(0 0% 100%);
--cm-email-fg: hsl(220 20% 12%);
--cm-email-muted-fg: hsl(220 10% 40%);
--cm-email-border: hsl(220 14% 90%);
--cm-email-accent: hsl(218 65% 22%);
}
body, table, td, p, a, li, blockquote { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
body { margin: 0 !important; padding: 0 !important; width: 100% !important; }
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
h1, h2, h3, h4, h5, h6 { font-family: 'Inter Tight', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-weight: 600; letter-spacing: -0.02em; }
@media only screen and (max-width: 620px) {
.cm-email-container { width: 100% !important; max-width: 100% !important; }
.cm-email-padding { padding-left: 24px !important; padding-right: 24px !important; }
}
</style>
</head>
<body style="background-color: var(--cm-email-bg, hsl(220 14% 96%)); margin: 0; padding: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
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">
<tr>
<td align="center" style="padding: 32px 12px; background-color: var(--cm-email-bg, hsl(220 14% 96%));">
<table role="presentation" class="cm-email-container" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px; width: 600px; background-color: var(--cm-email-surface, hsl(0 0% 100%)); border: 1px solid var(--cm-email-border, hsl(220 14% 90%)); border-collapse: separate;">
<!-- Header: title + single accent rule -->
<tr>
<td class="cm-email-padding" style="padding: 32px 48px 16px 48px; background-color: var(--cm-email-surface, hsl(0 0% 100%));">
if emailCtx.SiteSettings.LogoURL != "" {
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="max-height: 36px; width: auto; display: block;"/>
} else if emailCtx.SiteSettings.SiteName != "" {
<h1 style="margin: 0; font-size: 20px; font-weight: 600; color: var(--cm-email-fg, hsl(220 20% 12%)); letter-spacing: -0.02em; font-family: 'Inter Tight', 'Inter', sans-serif;">
{ emailCtx.SiteSettings.SiteName }
</h1>
}
</td>
</tr>
<tr>
<td style="padding: 0 48px;">
<div style="height: 2px; background-color: var(--cm-email-accent, hsl(218 65% 22%)); line-height: 2px; font-size: 0;">&nbsp;</div>
</td>
</tr>
<!-- Body -->
<tr>
<td class="cm-email-padding" style="padding: 32px 48px; color: var(--cm-email-fg, hsl(220 20% 12%)); font-size: 16px; line-height: 1.6; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;">
@templ.Raw(body)
</td>
</tr>
<!-- Footer: legal line + unsubscribe -->
<tr>
<td class="cm-email-padding" style="padding: 24px 48px 32px 48px; border-top: 1px solid var(--cm-email-border, hsl(220 14% 90%));">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="left">
if emailCtx.SiteSettings.SiteName != "" {
<p style="margin: 0 0 6px 0; font-size: 13px; color: var(--cm-email-fg, hsl(220 20% 12%)); font-family: 'Inter Tight', 'Inter', sans-serif; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;">
{ emailCtx.SiteSettings.SiteName }
</p>
}
<p style="margin: 0 0 12px 0; font-size: 12px; color: var(--cm-email-muted-fg, hsl(220 10% 40%)); font-family: 'Inter', sans-serif;">
You are receiving this email because you subscribed to updates.
</p>
if emailCtx.SiteSettings.SiteURL != "" {
<p style="margin: 0 0 8px 0; font-size: 12px;">
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style="color: var(--cm-email-accent, hsl(218 65% 22%)); text-decoration: none;">
{ emailCtx.SiteSettings.SiteURL }
</a>
</p>
}
if emailCtx.UnsubscribeURL != "" {
<p style="margin: 0; font-size: 11px;">
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style="color: var(--cm-email-muted-fg, hsl(220 10% 40%)); text-decoration: underline;">
Unsubscribe
</a>
</p>
}
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
}

228
email_wrapper_templ.go Normal file
View File

@ -0,0 +1,228 @@
// 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 "git.dev.alexdunmow.com/block/core/templates"
// emailWrapperComponent renders the Corporate Modernist email wrapper.
//
// The wrapper is locked to the navy-classic light preset regardless of the
// active site preset (per spec §10) for email-client compatibility. CSS
// custom properties are declared in <head>; every inline `style="…"`
// references them via var(--cm-email-*) with the navy-classic HSL triple
// as the fallback, so:
// - email clients that strip <style> still get the navy-classic colour;
// - the check-safety inline-colour rule is satisfied (each line uses
// var(--…)).
func emailWrapperComponent(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\"><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: 23, 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><style type=\"text/css\">\n\t\t\t\t:root {\n\t\t\t\t\t--cm-email-bg: hsl(220 14% 96%);\n\t\t\t\t\t--cm-email-surface: hsl(0 0% 100%);\n\t\t\t\t\t--cm-email-fg: hsl(220 20% 12%);\n\t\t\t\t\t--cm-email-muted-fg: hsl(220 10% 40%);\n\t\t\t\t\t--cm-email-border: hsl(220 14% 90%);\n\t\t\t\t\t--cm-email-accent: hsl(218 65% 22%);\n\t\t\t\t}\n\t\t\t\tbody, table, td, p, a, li, blockquote { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }\n\t\t\t\ttable, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }\n\t\t\t\timg { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }\n\t\t\t\tbody { margin: 0 !important; padding: 0 !important; width: 100% !important; }\n\t\t\t\ta[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }\n\t\t\t\th1, h2, h3, h4, h5, h6 { font-family: 'Inter Tight', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-weight: 600; letter-spacing: -0.02em; }\n\t\t\t\t@media only screen and (max-width: 620px) {\n\t\t\t\t\t.cm-email-container { width: 100% !important; max-width: 100% !important; }\n\t\t\t\t\t.cm-email-padding { padding-left: 24px !important; padding-right: 24px !important; }\n\t\t\t\t}\n\t\t\t</style></head><body style=\"background-color: var(--cm-email-bg, hsl(220 14% 96%)); margin: 0; padding: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.PreviewText != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div style=\"display: none; max-height: 0; overflow: hidden; mso-hide: all;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 48, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\" style=\"padding: 32px 12px; background-color: var(--cm-email-bg, hsl(220 14% 96%));\"><table role=\"presentation\" class=\"cm-email-container\" width=\"600\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"max-width: 600px; width: 600px; background-color: var(--cm-email-surface, hsl(0 0% 100%)); border: 1px solid var(--cm-email-border, hsl(220 14% 90%)); border-collapse: separate;\"><!-- Header: title + single accent rule --><tr><td class=\"cm-email-padding\" style=\"padding: 32px 48px 16px 48px; background-color: var(--cm-email-surface, hsl(0 0% 100%));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.LogoURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, 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: 59, Col: 50}
}
_, 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, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, 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: 59, Col: 89}
}
_, 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, 8, "\" style=\"max-height: 36px; 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, 9, "<h1 style=\"margin: 0; font-size: 20px; font-weight: 600; color: var(--cm-email-fg, hsl(220 20% 12%)); letter-spacing: -0.02em; font-family: 'Inter Tight', 'Inter', sans-serif;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 62, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</td></tr><tr><td style=\"padding: 0 48px;\"><div style=\"height: 2px; background-color: var(--cm-email-accent, hsl(218 65% 22%)); line-height: 2px; font-size: 0;\">&nbsp;</div></td></tr><!-- Body --><tr><td class=\"cm-email-padding\" style=\"padding: 32px 48px; color: var(--cm-email-fg, hsl(220 20% 12%)); font-size: 16px; line-height: 1.6; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\">")
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, 12, "</td></tr><!-- Footer: legal line + unsubscribe --><tr><td class=\"cm-email-padding\" style=\"padding: 24px 48px 32px 48px; border-top: 1px solid var(--cm-email-border, hsl(220 14% 90%));\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"left\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.SiteName != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<p style=\"margin: 0 0 6px 0; font-size: 13px; color: var(--cm-email-fg, hsl(220 20% 12%)); font-family: 'Inter Tight', 'Inter', sans-serif; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, 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: 86, Col: 46}
}
_, 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, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<p style=\"margin: 0 0 12px 0; font-size: 12px; color: var(--cm-email-muted-fg, hsl(220 10% 40%)); font-family: 'Inter', sans-serif;\">You are receiving this email because you subscribed to updates.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.SiteURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<p style=\"margin: 0 0 8px 0; font-size: 12px;\"><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(emailCtx.SiteSettings.SiteURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 94, Col: 68}
}
_, 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, "\" style=\"color: var(--cm-email-accent, hsl(218 65% 22%)); text-decoration: none;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, 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: 95, Col: 46}
}
_, 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></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if emailCtx.UnsubscribeURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<p style=\"margin: 0; font-size: 11px;\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 templ.SafeURL
templ_7745c5c3_Var10, 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: 101, Col: 62}
}
_, 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, 20, "\" style=\"color: var(--cm-email-muted-fg, hsl(220 10% 40%)); text-decoration: underline;\">Unsubscribe</a></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</td></tr></table></td></tr></table></td></tr></table></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

49
embed.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"embed"
"io/fs"
"net/http"
)
//go:embed assets/*
var assetsFS embed.FS
//go:embed schemas/*
var schemasFS embed.FS
//go:embed presets.json
var presetsData []byte
//go:embed fonts.json
var fontsData []byte
//go:embed plugin.mod
var pluginModBytes []byte
// Assets returns the embedded assets filesystem rooted at assets/.
func Assets() fs.FS {
sub, _ := fs.Sub(assetsFS, "assets")
return sub
}
// Schemas returns the embedded schemas filesystem rooted at schemas/.
func Schemas() fs.FS {
sub, _ := fs.Sub(schemasFS, "schemas")
return sub
}
// AssetsHandler returns an http.Handler that serves the embedded assets.
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
}

1
fonts.json Normal file
View File

@ -0,0 +1 @@
[]

75
footer.go Normal file
View File

@ -0,0 +1,75 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// FooterMeta is the block metadata for corporate-modernist:footer.
var FooterMeta = blocks.BlockMeta{
Key: "footer",
Title: "Footer",
Description: "Multi-column footer with legal line; compact and full variants.",
Source: "corporate-modernist",
Category: blocks.CategoryNavigation,
}
// FooterLink represents a single link in a footer column.
type FooterLink struct {
Label string
Href string
}
// FooterColumn is a single labelled column of footer links.
type FooterColumn struct {
Title string
Links []FooterLink
}
// FooterData carries the parsed content for the footer block.
type FooterData struct {
Columns []FooterColumn
LegalLine string
ShowLegal bool
Variant string
}
// FooterBlock renders the footer block.
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 {
label := linkLabel(l, "")
href := linkHref(l)
if label == "" && href == "#" {
continue
}
links = append(links, FooterLink{Label: label, Href: href})
}
columns = append(columns, FooterColumn{
Title: getString(c, "title"),
Links: links,
})
}
variant := getString(content, "variant")
if variant == "" {
variant = "compact"
}
data := FooterData{
Columns: columns,
LegalLine: getString(content, "legalLine"),
ShowLegal: getBool(content, "showLegal", true),
Variant: variant,
}
var buf bytes.Buffer
_ = footerComponent(data).Render(ctx, &buf)
return buf.String()
}

60
footer.templ Normal file
View File

@ -0,0 +1,60 @@
package main
// footerComponent renders the corporate-modernist:footer block.
templ footerComponent(data FooterData) {
<div class="cm-footer" data-block="corporate-modernist:footer" data-variant={ data.Variant }>
<div class="cm-content-well">
if data.Variant == "full" && len(data.Columns) > 0 {
<div class="cm-swiss-12">
for _, col := range data.Columns {
<div style={ footerColumnSpan(len(data.Columns)) }>
if col.Title != "" {
<div class="cm-display" style="font-size: 0.875rem; font-weight: 600; margin: 0 0 0.75rem 0; text-transform: uppercase; letter-spacing: 0.05em;">{ col.Title }</div>
}
if len(col.Links) > 0 {
<ul style="list-style: none; padding: 0; margin: 0;">
for _, l := range col.Links {
<li style="margin-bottom: 0.5rem;">
<a href={ templ.SafeURL(l.Href) } class="cm-text" style="font-size: 0.95rem; text-decoration: none;">
{ footerLinkLabel(l) }
</a>
</li>
}
</ul>
}
</div>
}
</div>
}
if data.ShowLegal {
<div class="cm-footer-legal">
if data.LegalLine != "" {
{ data.LegalLine }
} else {
All rights reserved.
}
</div>
}
</div>
</div>
}
func footerLinkLabel(l FooterLink) string {
if l.Label != "" {
return l.Label
}
return l.Href
}
func footerColumnSpan(count int) string {
switch {
case count >= 4:
return "grid-column: span 3 / span 3;"
case count == 3:
return "grid-column: span 4 / span 4;"
case count == 2:
return "grid-column: span 6 / span 6;"
default:
return "grid-column: span 12 / span 12;"
}
}

198
footer_templ.go Normal file
View File

@ -0,0 +1,198 @@
// 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 corporate-modernist:footer block.
func footerComponent(data FooterData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"cm-footer\" data-block=\"corporate-modernist:footer\" data-variant=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Variant)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 5, Col: 91}
}
_, 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, 2, "\"><div class=\"cm-content-well\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Variant == "full" && len(data.Columns) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"cm-swiss-12\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, col := range data.Columns {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(footerColumnSpan(len(data.Columns)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 10, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if col.Title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"cm-display\" style=\"font-size: 0.875rem; font-weight: 600; margin: 0 0 0.75rem 0; text-transform: uppercase; letter-spacing: 0.05em;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 12, Col: 164}
}
_, 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, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(col.Links) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<ul style=\"list-style: none; padding: 0; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, l := range col.Links {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<li style=\"margin-bottom: 0.5rem;\"><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(l.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 18, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" class=\"cm-text\" style=\"font-size: 0.95rem; text-decoration: none;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(footerLinkLabel(l))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 19, Col: 32}
}
_, 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, "</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.ShowLegal {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"cm-footer-legal\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.LegalLine != "" {
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.LegalLine)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 32, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "All rights reserved.")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func footerLinkLabel(l FooterLink) string {
if l.Label != "" {
return l.Label
}
return l.Href
}
func footerColumnSpan(count int) string {
switch {
case count >= 4:
return "grid-column: span 3 / span 3;"
case count == 3:
return "grid-column: span 4 / span 4;"
case count == 2:
return "grid-column: span 6 / span 6;"
default:
return "grid-column: span 12 / span 12;"
}
}
var _ = templruntime.GeneratedTemplate

20
go.mod Normal file
View File

@ -0,0 +1,20 @@
module git.dev.alexdunmow.com/block/themes/corporate-modernist
go 1.26.4
require (
git.dev.alexdunmow.com/block/core v0.11.1
github.com/a-h/templ v0.3.1020
)
require (
connectrpc.com/connect v1.20.0 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.9.2 // indirect
golang.org/x/mod v0.34.0 // indirect
golang.org/x/text v0.36.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

42
go.sum Normal file
View File

@ -0,0 +1,42 @@
connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
git.dev.alexdunmow.com/block/core v0.11.1 h1:5b3Ps9CLor2FGyxw/Qovt27AGZKR5Xi1JZGi/TfliTA=
git.dev.alexdunmow.com/block/core v0.11.1/go.mod h1:ZwzEOxRDLDfrhQGqo6hLw01/C1z/aS4Dm9ljQMl0Bg4=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

41
heading_override.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"bytes"
"context"
"strconv"
)
// CorporateModernistHeadingBlock renders the built-in `heading` block with
// the Corporate Modernist treatment: tighter tracking, weights restricted to
// 500/600, accent underline on H2.
func CorporateModernistHeadingBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
textClass := getString(content, "textClass")
level := parseHeadingLevel(content)
var buf bytes.Buffer
_ = headingOverrideComponent(level, text, textClass).Render(ctx, &buf)
return buf.String()
}
// parseHeadingLevel pulls a level (1-6) from the block content map.
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
}

57
heading_override.templ Normal file
View File

@ -0,0 +1,57 @@
package main
// headingOverrideComponent renders the Corporate Modernist heading override.
// All levels share the .cm-heading base; H2 picks up the accent underline.
templ headingOverrideComponent(level int, text, textClass string) {
switch level {
case 1:
<h1 class={ "cm-heading", headingOverrideSizeClass(1), textClass } style="font-size: clamp(2rem, 4vw, 3rem); line-height: 1.1;">
{ text }
</h1>
case 2:
<h2 class={ "cm-heading", headingOverrideSizeClass(2), textClass } style="font-size: clamp(1.5rem, 3vw, 2.25rem); line-height: 1.2;">
<span class="cm-heading-h2-accent">{ text }</span>
</h2>
case 3:
<h3 class={ "cm-heading", headingOverrideSizeClass(3), textClass } style="font-size: 1.5rem; line-height: 1.3;">
{ text }
</h3>
case 4:
<h4 class={ "cm-heading", headingOverrideSizeClass(4), textClass } style="font-size: 1.25rem; line-height: 1.3;">
{ text }
</h4>
case 5:
<h5 class={ "cm-heading", headingOverrideSizeClass(5), textClass } style="font-size: 1.125rem; line-height: 1.4;">
{ text }
</h5>
case 6:
<h6 class={ "cm-heading", headingOverrideSizeClass(6), textClass } style="font-size: 1rem; line-height: 1.4;">
{ text }
</h6>
default:
<h2 class={ "cm-heading", headingOverrideSizeClass(2), textClass } style="font-size: clamp(1.5rem, 3vw, 2.25rem); line-height: 1.2;">
<span class="cm-heading-h2-accent">{ text }</span>
</h2>
}
}
// headingOverrideSizeClass keeps the Tailwind-style size hooks predictable for
// downstream tooling without baking sizes into hardcoded classes.
func headingOverrideSizeClass(level int) string {
switch level {
case 1:
return "cm-heading-h1"
case 2:
return "cm-heading-h2"
case 3:
return "cm-heading-h3"
case 4:
return "cm-heading-h4"
case 5:
return "cm-heading-h5"
case 6:
return "cm-heading-h6"
default:
return "cm-heading-h2"
}
}

313
heading_override_templ.go Normal file
View File

@ -0,0 +1,313 @@
// 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"
// headingOverrideComponent renders the Corporate Modernist heading override.
// All levels share the .cm-heading base; H2 picks up the accent underline.
func headingOverrideComponent(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{"cm-heading", headingOverrideSizeClass(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=\"font-size: clamp(2rem, 4vw, 3rem); line-height: 1.1;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 9, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 2:
var templ_7745c5c3_Var5 = []any{"cm-heading", headingOverrideSizeClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var5).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" style=\"font-size: clamp(1.5rem, 3vw, 2.25rem); line-height: 1.2;\"><span class=\"cm-heading-h2-accent\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 13, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</span></h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 3:
var templ_7745c5c3_Var8 = []any{"cm-heading", headingOverrideSizeClass(3), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h3 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var8).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" style=\"font-size: 1.5rem; line-height: 1.3;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 17, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 4:
var templ_7745c5c3_Var11 = []any{"cm-heading", headingOverrideSizeClass(4), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<h4 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var11).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" style=\"font-size: 1.25rem; line-height: 1.3;\">")
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: 21, 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, "</h4>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 5:
var templ_7745c5c3_Var14 = []any{"cm-heading", headingOverrideSizeClass(5), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h5 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var14).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" style=\"font-size: 1.125rem; line-height: 1.4;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 25, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h5>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 6:
var templ_7745c5c3_Var17 = []any{"cm-heading", headingOverrideSizeClass(6), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h6 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" style=\"font-size: 1rem; line-height: 1.4;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 29, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</h6>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
var templ_7745c5c3_Var20 = []any{"cm-heading", headingOverrideSizeClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var20).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" style=\"font-size: clamp(1.5rem, 3vw, 2.25rem); line-height: 1.2;\"><span class=\"cm-heading-h2-accent\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 33, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</span></h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
// headingOverrideSizeClass keeps the Tailwind-style size hooks predictable for
// downstream tooling without baking sizes into hardcoded classes.
func headingOverrideSizeClass(level int) string {
switch level {
case 1:
return "cm-heading-h1"
case 2:
return "cm-heading-h2"
case 3:
return "cm-heading-h3"
case 4:
return "cm-heading-h4"
case 5:
return "cm-heading-h5"
case 6:
return "cm-heading-h6"
default:
return "cm-heading-h2"
}
}
var _ = templruntime.GeneratedTemplate

82
helpers.go Normal file
View File

@ -0,0 +1,82 @@
package main
import "strconv"
// getString extracts a string value from content map.
func getString(content map[string]any, key string) string {
if content == nil {
return ""
}
if v, ok := content[key].(string); ok {
return v
}
return ""
}
// getBool extracts a bool value from content map (handles JSON unmarshalling forms).
func getBool(content map[string]any, key string, defaultVal bool) bool {
if content == nil {
return defaultVal
}
if v, ok := content[key].(bool); ok {
return v
}
if v, ok := content[key].(string); ok {
if b, err := strconv.ParseBool(v); err == nil {
return b
}
}
return defaultVal
}
// getSlice extracts a slice of maps from content.
func getSlice(content map[string]any, key string) []map[string]any {
if content == nil {
return nil
}
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
}
// getNested extracts a nested map from content for sub-object fields (e.g. CTA link).
func getNested(content map[string]any, key string) map[string]any {
if content == nil {
return nil
}
if v, ok := content[key].(map[string]any); ok {
return v
}
return nil
}
// linkHref returns the href value from a link object (label/href shape),
// defaulting to "#" when missing so anchors stay valid.
func linkHref(link map[string]any) string {
if href := getString(link, "href"); href != "" {
return href
}
if url := getString(link, "url"); url != "" {
return url
}
return "#"
}
// linkLabel returns the visible label for a link object.
func linkLabel(link map[string]any, fallback string) string {
if label := getString(link, "label"); label != "" {
return label
}
if text := getString(link, "text"); text != "" {
return text
}
return fallback
}

66
hero_statement.go Normal file
View File

@ -0,0 +1,66 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// HeroStatementMeta is the block metadata for corporate-modernist:hero_statement.
var HeroStatementMeta = blocks.BlockMeta{
Key: "hero_statement",
Title: "Hero Statement",
Description: "Restrained hero with eyebrow, headline, lede, and a single accent CTA.",
Source: "corporate-modernist",
Category: blocks.CategoryLayout,
}
// HeroStatementCTA captures a single label/href pair for hero CTAs.
type HeroStatementCTA struct {
Label string
Href string
}
// HeroStatementData carries the parsed content for the hero_statement block.
type HeroStatementData struct {
Eyebrow string
Headline string
Lede string
PrimaryCTA HeroStatementCTA
HasPrimary bool
SecondaryCTA HeroStatementCTA
HasSecondary bool
}
// HeroStatementBlock renders the hero_statement block.
func HeroStatementBlock(ctx context.Context, content map[string]any) string {
primary := getNested(content, "primaryCta")
secondary := getNested(content, "secondaryCta")
data := HeroStatementData{
Eyebrow: getString(content, "eyebrow"),
Headline: getString(content, "headline"),
Lede: getString(content, "lede"),
}
if primary != nil {
label := linkLabel(primary, "")
href := linkHref(primary)
if label != "" || href != "#" {
data.PrimaryCTA = HeroStatementCTA{Label: label, Href: href}
data.HasPrimary = true
}
}
if secondary != nil {
label := linkLabel(secondary, "")
href := linkHref(secondary)
if label != "" || href != "#" {
data.SecondaryCTA = HeroStatementCTA{Label: label, Href: href}
data.HasSecondary = true
}
}
var buf bytes.Buffer
_ = heroStatementComponent(data).Render(ctx, &buf)
return buf.String()
}

45
hero_statement.templ Normal file
View File

@ -0,0 +1,45 @@
package main
// heroStatementComponent renders the corporate-modernist:hero_statement block.
templ heroStatementComponent(data HeroStatementData) {
<section class="cm-section" data-block="corporate-modernist:hero_statement">
<div class="cm-content-well">
<div class="cm-swiss-12">
<div style="grid-column: span 12 / span 12;">
if data.Eyebrow != "" {
<div class="cm-eyebrow">{ data.Eyebrow }</div>
}
if data.Headline != "" {
<h1 class="cm-display" style="font-size: clamp(2rem, 4vw, 3.5rem); line-height: 1.1; margin: 0 0 1.25rem 0;">
{ data.Headline }
</h1>
}
if data.Lede != "" {
<p class="cm-text" style="max-width: 60ch; margin: 0 0 2rem 0;">{ data.Lede }</p>
}
if data.HasPrimary || data.HasSecondary {
<div style="display: flex; flex-wrap: wrap; gap: 0.75rem;">
if data.HasPrimary {
<a class="cm-btn-primary" href={ templ.SafeURL(data.PrimaryCTA.Href) }>
{ heroStatementLabel(data.PrimaryCTA.Label, "Get in touch") }
</a>
}
if data.HasSecondary {
<a class="cm-btn-outline" href={ templ.SafeURL(data.SecondaryCTA.Href) }>
{ heroStatementLabel(data.SecondaryCTA.Label, "Learn more") }
</a>
}
</div>
}
</div>
</div>
</div>
</section>
}
func heroStatementLabel(label, fallback string) string {
if label != "" {
return label
}
return fallback
}

183
hero_statement_templ.go Normal file
View File

@ -0,0 +1,183 @@
// 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"
// heroStatementComponent renders the corporate-modernist:hero_statement block.
func heroStatementComponent(data HeroStatementData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"cm-section\" data-block=\"corporate-modernist:hero_statement\"><div class=\"cm-content-well\"><div class=\"cm-swiss-12\"><div style=\"grid-column: span 12 / span 12;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Eyebrow != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"cm-eyebrow\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Eyebrow)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_statement.templ`, Line: 10, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Headline != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h1 class=\"cm-display\" style=\"font-size: clamp(2rem, 4vw, 3.5rem); line-height: 1.1; margin: 0 0 1.25rem 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Headline)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_statement.templ`, Line: 14, Col: 22}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Lede != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<p class=\"cm-text\" style=\"max-width: 60ch; margin: 0 0 2rem 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Lede)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_statement.templ`, Line: 18, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.HasPrimary || data.HasSecondary {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div style=\"display: flex; flex-wrap: wrap; gap: 0.75rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.HasPrimary {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<a class=\"cm-btn-primary\" href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.PrimaryCTA.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_statement.templ`, Line: 23, Col: 76}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(heroStatementLabel(data.PrimaryCTA.Label, "Get in touch"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_statement.templ`, Line: 24, Col: 68}
}
_, 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, "</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.HasSecondary {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<a class=\"cm-btn-outline\" 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.SecondaryCTA.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_statement.templ`, Line: 28, Col: 78}
}
_, 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, 13, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(heroStatementLabel(data.SecondaryCTA.Label, "Learn more"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_statement.templ`, Line: 29, Col: 68}
}
_, 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, 14, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div></div></div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func heroStatementLabel(label, fallback string) string {
if label != "" {
return label
}
return fallback
}
var _ = templruntime.GeneratedTemplate

60
leadership_grid.go Normal file
View File

@ -0,0 +1,60 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// LeadershipGridMeta is the block metadata for corporate-modernist:leadership_grid.
var LeadershipGridMeta = blocks.BlockMeta{
Key: "leadership_grid",
Title: "Leadership Grid",
Description: "Responsive 2/3/4-up grid of leadership profiles.",
Source: "corporate-modernist",
Category: blocks.CategoryContent,
}
// LeadershipGridMember represents one leader entry.
type LeadershipGridMember struct {
Name string
Role string
Bio string
Photo string
LinkedIn string
}
// LeadershipGridData carries the parsed content for the leadership_grid block.
type LeadershipGridData struct {
Title string
Members []LeadershipGridMember
}
// LeadershipGridBlock renders the leadership_grid block.
func LeadershipGridBlock(ctx context.Context, content map[string]any) string {
rawMembers := getSlice(content, "members")
members := make([]LeadershipGridMember, 0, len(rawMembers))
for _, m := range rawMembers {
name := getString(m, "name")
if name == "" {
continue
}
members = append(members, LeadershipGridMember{
Name: name,
Role: getString(m, "role"),
Bio: getString(m, "bio"),
Photo: getString(m, "photo"),
LinkedIn: getString(m, "linkedin"),
})
}
data := LeadershipGridData{
Title: getString(content, "title"),
Members: members,
}
var buf bytes.Buffer
_ = leadershipGridComponent(data).Render(ctx, &buf)
return buf.String()
}

48
leadership_grid.templ Normal file
View File

@ -0,0 +1,48 @@
package main
// leadershipGridComponent renders the corporate-modernist:leadership_grid block.
templ leadershipGridComponent(data LeadershipGridData) {
<section class="cm-section" data-block="corporate-modernist:leadership_grid">
<div class="cm-content-well">
if data.Title != "" {
<h2 class="cm-heading" style="font-size: 1.875rem; margin: 0 0 2rem 0;">
<span class="cm-heading-h2-accent">{ data.Title }</span>
</h2>
}
if len(data.Members) > 0 {
<div class="cm-leadership-grid">
for _, m := range data.Members {
<article class="cm-card">
if m.Photo != "" {
<img src={ m.Photo } alt={ leadershipGridPhotoAlt(m) } style="width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; margin-bottom: 1rem; border-radius: 4px;"/>
} else {
<span class="cm-img-empty" style="aspect-ratio: 1 / 1; margin-bottom: 1rem; border-radius: 4px;"></span>
}
<div class="cm-display" style="font-size: 1.125rem; font-weight: 600; margin: 0;">{ m.Name }</div>
if m.Role != "" {
<div class="cm-stat-label" style="margin-top: 0.25rem;">{ m.Role }</div>
}
if m.Bio != "" {
<p class="cm-text" style="margin: 0.75rem 0 0 0; font-size: 0.95rem; line-height: 1.55;">{ m.Bio }</p>
}
if m.LinkedIn != "" {
<div style="margin-top: 1rem;">
<a class="cm-btn-outline" href={ templ.SafeURL(m.LinkedIn) } style="font-size: 0.875rem; padding: 0.375rem 0.75rem;">LinkedIn</a>
</div>
}
</article>
}
</div>
} else {
<p class="cm-stat-label">No team members configured.</p>
}
</div>
</section>
}
func leadershipGridPhotoAlt(m LeadershipGridMember) string {
if m.Name != "" {
return m.Name
}
return "Team member portrait"
}

207
leadership_grid_templ.go Normal file
View File

@ -0,0 +1,207 @@
// 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"
// leadershipGridComponent renders the corporate-modernist:leadership_grid block.
func leadershipGridComponent(data LeadershipGridData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"cm-section\" data-block=\"corporate-modernist:leadership_grid\"><div class=\"cm-content-well\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h2 class=\"cm-heading\" style=\"font-size: 1.875rem; margin: 0 0 2rem 0;\"><span class=\"cm-heading-h2-accent\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `leadership_grid.templ`, Line: 9, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</span></h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Members) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"cm-leadership-grid\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, m := range data.Members {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<article class=\"cm-card\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if m.Photo != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(m.Photo)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `leadership_grid.templ`, Line: 17, Col: 26}
}
_, 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, 7, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(leadershipGridPhotoAlt(m))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `leadership_grid.templ`, Line: 17, Col: 60}
}
_, 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, "\" style=\"width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; margin-bottom: 1rem; border-radius: 4px;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<span class=\"cm-img-empty\" style=\"aspect-ratio: 1 / 1; margin-bottom: 1rem; border-radius: 4px;\"></span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"cm-display\" style=\"font-size: 1.125rem; font-weight: 600; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(m.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `leadership_grid.templ`, Line: 21, Col: 97}
}
_, 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, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if m.Role != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"cm-stat-label\" style=\"margin-top: 0.25rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(m.Role)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `leadership_grid.templ`, Line: 23, Col: 72}
}
_, 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, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if m.Bio != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<p class=\"cm-text\" style=\"margin: 0.75rem 0 0 0; font-size: 0.95rem; line-height: 1.55;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(m.Bio)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `leadership_grid.templ`, Line: 26, Col: 104}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if m.LinkedIn != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div style=\"margin-top: 1rem;\"><a class=\"cm-btn-outline\" 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(m.LinkedIn))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `leadership_grid.templ`, Line: 30, Col: 67}
}
_, 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, "\" style=\"font-size: 0.875rem; padding: 0.375rem 0.75rem;\">LinkedIn</a></div>")
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
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p class=\"cm-stat-label\">No team members configured.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func leadershipGridPhotoAlt(m LeadershipGridMember) string {
if m.Name != "" {
return m.Name
}
return "Team member portrait"
}
var _ = templruntime.GeneratedTemplate

55
logo_strip.go Normal file
View File

@ -0,0 +1,55 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// LogoStripMeta is the block metadata for corporate-modernist:logo_strip.
var LogoStripMeta = blocks.BlockMeta{
Key: "logo_strip",
Title: "Client Logo Strip",
Description: "Row of client logos, greyscale until hover.",
Source: "corporate-modernist",
Category: blocks.CategoryContent,
}
// LogoStripItem represents a single logo entry in the strip.
type LogoStripItem struct {
Src string
Alt string
Href string
}
// LogoStripData carries the parsed content for the logo_strip block.
type LogoStripData struct {
Caption string
Logos []LogoStripItem
}
// LogoStripBlock renders the logo_strip block.
func LogoStripBlock(ctx context.Context, content map[string]any) string {
rawLogos := getSlice(content, "logos")
logos := make([]LogoStripItem, 0, len(rawLogos))
for _, l := range rawLogos {
alt := getString(l, "alt")
src := getString(l, "src")
href := getString(l, "href")
// Skip entries with no asset reference and no alt text.
if src == "" && alt == "" {
continue
}
logos = append(logos, LogoStripItem{Src: src, Alt: alt, Href: href})
}
data := LogoStripData{
Caption: getString(content, "caption"),
Logos: logos,
}
var buf bytes.Buffer
_ = logoStripComponent(data).Render(ctx, &buf)
return buf.String()
}

44
logo_strip.templ Normal file
View File

@ -0,0 +1,44 @@
package main
// logoStripComponent renders the corporate-modernist:logo_strip block.
templ logoStripComponent(data LogoStripData) {
<section class="cm-section" data-block="corporate-modernist:logo_strip">
<div class="cm-content-well">
if data.Caption != "" {
<p class="cm-stat-label" style="text-align: center; margin-bottom: 2rem;">{ data.Caption }</p>
}
if len(data.Logos) > 0 {
<div style="display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2.5rem;">
for _, logo := range data.Logos {
if logo.Href != "" {
<a href={ templ.SafeURL(logo.Href) } class="cm-logo" style="display: inline-flex; align-items: center;">
@logoStripImage(logo)
</a>
} else {
<span class="cm-logo" style="display: inline-flex; align-items: center;">
@logoStripImage(logo)
</span>
}
}
</div>
} else {
<p class="cm-stat-label" style="text-align: center;">No client logos configured.</p>
}
</div>
</section>
}
templ logoStripImage(logo LogoStripItem) {
if logo.Src != "" {
<img src={ logo.Src } alt={ logoStripAlt(logo) } style="max-height: 2rem; width: auto; display: block;"/>
} else {
<span class="cm-stat-label" style="margin: 0;">{ logoStripAlt(logo) }</span>
}
}
func logoStripAlt(logo LogoStripItem) string {
if logo.Alt != "" {
return logo.Alt
}
return "Client logo"
}

203
logo_strip_templ.go Normal file
View File

@ -0,0 +1,203 @@
// 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"
// logoStripComponent renders the corporate-modernist:logo_strip block.
func logoStripComponent(data LogoStripData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"cm-section\" data-block=\"corporate-modernist:logo_strip\"><div class=\"cm-content-well\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Caption != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<p class=\"cm-stat-label\" style=\"text-align: center; margin-bottom: 2rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Caption)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `logo_strip.templ`, Line: 8, Col: 92}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Logos) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div style=\"display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 2.5rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, logo := range data.Logos {
if logo.Href != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(logo.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `logo_strip.templ`, Line: 14, Col: 41}
}
_, 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, "\" class=\"cm-logo\" style=\"display: inline-flex; align-items: center;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = logoStripImage(logo).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<span class=\"cm-logo\" style=\"display: inline-flex; align-items: center;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = logoStripImage(logo).Render(ctx, templ_7745c5c3_Buffer)
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>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p class=\"cm-stat-label\" style=\"text-align: center;\">No client logos configured.</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 logoStripImage(logo LogoStripItem) 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.Src != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(logo.Src)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `logo_strip.templ`, Line: 33, Col: 21}
}
_, 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, 14, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(logoStripAlt(logo))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `logo_strip.templ`, Line: 33, Col: 48}
}
_, 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, "\" style=\"max-height: 2rem; width: auto; display: block;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<span class=\"cm-stat-label\" style=\"margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(logoStripAlt(logo))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `logo_strip.templ`, Line: 35, Col: 69}
}
_, 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, 17, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
func logoStripAlt(logo LogoStripItem) string {
if logo.Alt != "" {
return logo.Alt
}
return "Client logo"
}
var _ = templruntime.GeneratedTemplate

12
plugin.mod Normal file
View File

@ -0,0 +1,12 @@
[plugin]
name = "corporate-modernist"
display_name = "Corporate Modernist"
scope = "@themes"
version = "0.1.0"
description = "Swiss-grid B2B theme with restrained typography, a single configurable accent, and trust-signal blocks for enterprise sites."
kind = "theme"
categories = ["templates"]
tags = ["business", "corporate", "enterprise", "consulting", "legal", "professional", "b2b", "swiss"]
[compatibility]
block_core = ">=0.11.0 <0.12.0"

152
presets.json Normal file
View File

@ -0,0 +1,152 @@
[
{
"id": "navy-classic",
"name": "Navy Classic",
"description": "Deep navy on paper. The safe choice for accountancy, law, and finance.",
"theme": {
"mode": "both",
"lightColors": {
"background": "0 0% 100%",
"foreground": "220 20% 12%",
"card": "0 0% 100%",
"cardForeground": "220 20% 12%",
"popover": "0 0% 100%",
"popoverForeground": "220 20% 12%",
"primary": "218 65% 22%",
"primaryForeground": "0 0% 100%",
"secondary": "220 14% 96%",
"secondaryForeground": "220 20% 12%",
"muted": "220 14% 96%",
"mutedForeground": "220 10% 40%",
"accent": "218 65% 22%",
"accentForeground": "0 0% 100%",
"destructive": "0 72% 42%",
"destructiveForeground": "0 0% 100%",
"border": "220 14% 90%",
"input": "220 14% 90%",
"ring": "218 65% 22%"
},
"darkColors": {
"background": "220 25% 8%",
"foreground": "220 10% 95%",
"card": "220 25% 11%",
"cardForeground": "220 10% 95%",
"popover": "220 25% 11%",
"popoverForeground": "220 10% 95%",
"primary": "218 70% 60%",
"primaryForeground": "220 25% 8%",
"secondary": "220 20% 18%",
"secondaryForeground": "220 10% 95%",
"muted": "220 20% 16%",
"mutedForeground": "220 10% 65%",
"accent": "218 70% 60%",
"accentForeground": "220 25% 8%",
"destructive": "0 62% 50%",
"destructiveForeground": "0 0% 100%",
"border": "220 20% 22%",
"input": "220 20% 22%",
"ring": "218 70% 60%"
}
}
},
{
"id": "forest-quiet",
"name": "Forest Quiet",
"description": "Same chassis, deep green accent for sustainability, advisory, ESG.",
"theme": {
"mode": "both",
"lightColors": {
"background": "0 0% 100%",
"foreground": "150 18% 12%",
"card": "0 0% 100%",
"cardForeground": "150 18% 12%",
"popover": "0 0% 100%",
"popoverForeground": "150 18% 12%",
"primary": "155 45% 22%",
"primaryForeground": "0 0% 100%",
"secondary": "150 12% 96%",
"secondaryForeground": "150 18% 12%",
"muted": "150 12% 96%",
"mutedForeground": "150 10% 40%",
"accent": "155 45% 22%",
"accentForeground": "0 0% 100%",
"destructive": "0 72% 42%",
"destructiveForeground": "0 0% 100%",
"border": "150 12% 90%",
"input": "150 12% 90%",
"ring": "155 45% 22%"
},
"darkColors": {
"background": "150 18% 8%",
"foreground": "150 8% 95%",
"card": "150 18% 11%",
"cardForeground": "150 8% 95%",
"popover": "150 18% 11%",
"popoverForeground": "150 8% 95%",
"primary": "155 50% 55%",
"primaryForeground": "150 18% 8%",
"secondary": "150 15% 18%",
"secondaryForeground": "150 8% 95%",
"muted": "150 15% 16%",
"mutedForeground": "150 8% 65%",
"accent": "155 50% 55%",
"accentForeground": "150 18% 8%",
"destructive": "0 62% 50%",
"destructiveForeground": "0 0% 100%",
"border": "150 15% 22%",
"input": "150 15% 22%",
"ring": "155 50% 55%"
}
}
},
{
"id": "burgundy-tradition",
"name": "Burgundy Tradition",
"description": "Heritage accent for old-line legal, private banking, partnerships.",
"theme": {
"mode": "both",
"lightColors": {
"background": "0 0% 100%",
"foreground": "355 18% 12%",
"card": "0 0% 100%",
"cardForeground": "355 18% 12%",
"popover": "0 0% 100%",
"popoverForeground": "355 18% 12%",
"primary": "350 55% 30%",
"primaryForeground": "0 0% 100%",
"secondary": "355 12% 96%",
"secondaryForeground": "355 18% 12%",
"muted": "355 12% 96%",
"mutedForeground": "355 10% 40%",
"accent": "350 55% 30%",
"accentForeground": "0 0% 100%",
"destructive": "0 72% 42%",
"destructiveForeground": "0 0% 100%",
"border": "355 12% 90%",
"input": "355 12% 90%",
"ring": "350 55% 30%"
},
"darkColors": {
"background": "355 18% 8%",
"foreground": "355 8% 95%",
"card": "355 18% 11%",
"cardForeground": "355 8% 95%",
"popover": "355 18% 11%",
"popoverForeground": "355 8% 95%",
"primary": "350 60% 60%",
"primaryForeground": "355 18% 8%",
"secondary": "355 15% 18%",
"secondaryForeground": "355 8% 95%",
"muted": "355 15% 16%",
"mutedForeground": "355 8% 65%",
"accent": "350 60% 60%",
"accentForeground": "355 18% 8%",
"destructive": "0 62% 50%",
"destructiveForeground": "0 0% 100%",
"border": "355 15% 22%",
"input": "355 15% 22%",
"ring": "350 60% 60%"
}
}
}
]

169
register.go Normal file
View File

@ -0,0 +1,169 @@
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 func to templates.TemplateFunc.
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 wires Corporate Modernist's system template, page templates,
// theme-owned blocks, built-in overrides, and email wrapper.
//
// Schema loading must precede block registration so that the registry can
// bind schemas to block keys at runtime.
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
// 1. System template.
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
Key: "corporate-modernist",
Title: "Corporate Modernist",
Description: "Swiss-grid B2B theme with restrained typography, a single configurable accent, and trust-signal blocks for enterprise sites.",
})
// 2. Page templates.
if err := tr.RegisterPageTemplate("corporate-modernist", templates.PageTemplateMeta{
Key: "default",
Title: "Default",
Description: "Header + 12-col main + footer, capped at 1280px.",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderCM)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("corporate-modernist", templates.PageTemplateMeta{
Key: "landing",
Title: "Landing",
Description: "Hero band + main + closing CTA + footer.",
Slots: []string{"hero", "main", "cta", "footer"},
}, wrap(RenderCMLanding)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("corporate-modernist", templates.PageTemplateMeta{
Key: "article",
Title: "Article",
Description: "Centered measure for long-form thought leadership.",
Slots: []string{"header", "main", "aside", "footer"},
}, wrap(RenderCMArticle)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("corporate-modernist", templates.PageTemplateMeta{
Key: "full-width",
Title: "Full width",
Description: "Edge-to-edge sections, internal grid kept.",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderCMFullWidth)); err != nil {
return err
}
// 3. Schemas — MUST precede block registrations.
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
return err
}
// 4. Theme-owned blocks.
br.Register(HeroStatementMeta, HeroStatementBlock)
br.Register(LogoStripMeta, LogoStripBlock)
br.Register(TestimonialQuoteMeta, TestimonialQuoteBlock)
br.Register(CaseStudyCardMeta, CaseStudyCardBlock)
br.Register(LeadershipGridMeta, LeadershipGridBlock)
br.Register(StatPairMeta, StatPairBlock)
br.Register(CTAStripMeta, CTAStripBlock)
br.Register(FooterMeta, FooterBlock)
// 5. Built-in overrides — heading, text, button, card.
br.RegisterTemplateOverride("corporate-modernist", "heading", CorporateModernistHeadingBlock)
br.RegisterTemplateOverride("corporate-modernist", "text", CorporateModernistTextBlock)
br.RegisterTemplateOverride("corporate-modernist", "button", CorporateModernistButtonBlock)
br.RegisterTemplateOverride("corporate-modernist", "card", CorporateModernistCardBlock)
// 6. Email wrapper.
tr.RegisterEmailWrapper("corporate-modernist", CorporateModernistEmailWrapper)
return nil
}
// DefaultMasterPages returns Corporate Modernist's two seeded master pages.
// Slot names match the page templates registered above.
func DefaultMasterPages() []plugin.MasterPageDefinition {
return []plugin.MasterPageDefinition{
{
Key: "corporate-modernist:default-master",
Title: "Corporate Modernist Default Master",
PageTemplates: []string{"default", "article"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "navbar",
Title: "Main Navigation",
Content: map[string]any{"menuName": "main", "style": "underline"},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Content Slot",
Content: map[string]any{"slotName": "main"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "corporate-modernist:footer",
Title: "Site Footer",
Content: map[string]any{"variant": "compact", "showLegal": true},
Slot: "footer",
SortOrder: 0,
},
},
},
{
Key: "corporate-modernist:landing-master",
Title: "Corporate Modernist Landing Master",
PageTemplates: []string{"landing", "full-width"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "navbar",
Title: "Main Navigation",
Content: map[string]any{"menuName": "main", "style": "underline"},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "corporate-modernist:hero_statement",
Title: "Landing Hero",
Content: map[string]any{"eyebrow": "Enterprise", "headline": "Trusted by 200+ boards"},
Slot: "hero",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Content Slot",
Content: map[string]any{"slotName": "main"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "corporate-modernist:cta_strip",
Title: "Closing CTA",
Content: map[string]any{"variant": "book-call"},
Slot: "cta",
SortOrder: 0,
},
{
BlockKey: "corporate-modernist:footer",
Title: "Site Footer",
Content: map[string]any{"variant": "full", "showLegal": true},
Slot: "footer",
SortOrder: 0,
},
},
},
}
}

34
registration.go Normal file
View File

@ -0,0 +1,34 @@
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 Corporate Modernist theme.
var Registration = plugin.PluginRegistration{
Name: "corporate-modernist",
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 cssManifest() },
}
// cssManifest exposes Corporate Modernist's utility CSS to the host Tailwind input
// so .cm-* classes resolve in production builds. No @font-face declarations here:
// the CMS emits those from fonts.json (currently []).
func cssManifest() *plugin.CSSManifest {
return &plugin.CSSManifest{
InputCSSAppend: corporateModernistUtilityCSS,
}
}

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Case Study Card",
"description": "Single case study card with a tabular metric.",
"type": "object",
"properties": {
"client": {
"type": "string",
"title": "Client",
"x-editor": "text"
},
"metric": {
"type": "string",
"title": "Metric",
"description": "Large tabular numeral (e.g. \"3.2x\", \"$120M\", \"47%\")",
"x-editor": "text"
},
"label": {
"type": "string",
"title": "Metric label",
"description": "Short qualifier underneath the metric",
"x-editor": "text"
},
"summary": {
"type": "string",
"title": "Summary",
"description": "One- or two-sentence outcome",
"x-editor": "textarea"
},
"href": {
"type": "object",
"title": "Link",
"description": "Read-more link to the full case study",
"x-editor": "link",
"properties": {
"label": { "type": "string", "title": "Label" },
"href": { "type": "string", "title": "Href" }
}
}
}
}

View File

@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "CTA Strip",
"description": "Single-line accent strip with a headline and a primary call to action.",
"type": "object",
"properties": {
"headline": {
"type": "string",
"title": "Headline",
"x-editor": "text"
},
"primaryCta": {
"type": "object",
"title": "Primary CTA",
"x-editor": "link",
"properties": {
"label": { "type": "string", "title": "Label" },
"href": { "type": "string", "title": "Href" }
}
},
"variant": {
"type": "string",
"title": "Variant",
"description": "Visual treatment for the strip",
"x-editor": "select",
"enum": ["book-call", "download", "quiet"],
"default": "book-call"
}
}
}

View File

@ -0,0 +1,52 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Footer",
"description": "Multi-column footer with legal line, available in compact and full variants.",
"type": "object",
"properties": {
"columns": {
"type": "array",
"title": "Columns",
"description": "Each column lists a title and a set of links",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"title": { "type": "string", "title": "Title", "x-editor": "text" },
"links": {
"type": "array",
"title": "Links",
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"label": { "type": "string", "title": "Label", "x-editor": "text" },
"href": { "type": "string", "title": "Href", "x-editor": "link" }
}
}
}
}
}
},
"legalLine": {
"type": "string",
"title": "Legal line",
"description": "Copyright or compliance statement",
"x-editor": "text"
},
"showLegal": {
"type": "boolean",
"title": "Show legal line",
"default": true,
"x-editor": "select"
},
"variant": {
"type": "string",
"title": "Variant",
"x-editor": "select",
"enum": ["compact", "full"],
"default": "compact"
}
}
}

View File

@ -0,0 +1,46 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Hero Statement",
"description": "Restrained hero with eyebrow, headline, lede, and a single accent CTA (plus optional outline secondary).",
"type": "object",
"properties": {
"eyebrow": {
"type": "string",
"title": "Eyebrow",
"description": "Small uppercase label above the headline",
"x-editor": "text"
},
"headline": {
"type": "string",
"title": "Headline",
"description": "Lead statement, typically one short sentence",
"x-editor": "text"
},
"lede": {
"type": "string",
"title": "Lede",
"description": "Supporting paragraph below the headline",
"x-editor": "textarea"
},
"primaryCta": {
"type": "object",
"title": "Primary CTA",
"description": "Accent-filled call to action",
"x-editor": "link",
"properties": {
"label": { "type": "string", "title": "Label" },
"href": { "type": "string", "title": "Href" }
}
},
"secondaryCta": {
"type": "object",
"title": "Secondary CTA",
"description": "Optional outline call to action",
"x-editor": "link",
"properties": {
"label": { "type": "string", "title": "Label" },
"href": { "type": "string", "title": "Href" }
}
}
}
}

View File

@ -0,0 +1,32 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Leadership Grid",
"description": "Responsive 2/3/4-up grid of leadership profiles.",
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Title",
"description": "Section title above the grid",
"x-editor": "text"
},
"members": {
"type": "array",
"title": "Members",
"description": "Each member is a card with photo, name, role, short bio, and optional LinkedIn link",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"name": { "type": "string", "title": "Name", "x-editor": "text" },
"role": { "type": "string", "title": "Role", "x-editor": "text" },
"bio": { "type": "string", "title": "Bio", "x-editor": "textarea" },
"photo": { "type": "string", "title": "Photo", "x-editor": "media" },
"linkedin": { "type": "string", "title": "LinkedIn URL", "x-editor": "link" }
},
"required": ["name"]
}
}
}
}

View File

@ -0,0 +1,42 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Client Logo Strip",
"description": "Row of client logos rendered greyscale until hover.",
"type": "object",
"properties": {
"caption": {
"type": "string",
"title": "Caption",
"description": "Short heading above the logo row",
"x-editor": "text"
},
"logos": {
"type": "array",
"title": "Logos",
"description": "Client logos shown in a single restrained row",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"src": {
"type": "string",
"title": "Image",
"x-editor": "media"
},
"alt": {
"type": "string",
"title": "Alt text",
"x-editor": "text"
},
"href": {
"type": "string",
"title": "Link",
"x-editor": "link"
}
},
"required": ["alt"]
}
}
}
}

View File

@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Stat Pair",
"description": "Pair (or row) of mono-numeral statistics, separated by a hairline rule.",
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Title",
"description": "Optional title above the stats row",
"x-editor": "text"
},
"stats": {
"type": "array",
"title": "Stats",
"description": "Individual statistic entries",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"figure": { "type": "string", "title": "Figure", "x-editor": "text" },
"label": { "type": "string", "title": "Label", "x-editor": "text" },
"source": { "type": "string", "title": "Source", "x-editor": "text" }
},
"required": ["figure"]
}
}
}
}

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Testimonial",
"description": "Pull-quote testimonial with attribution and optional headshot and company logo.",
"type": "object",
"properties": {
"quote": {
"type": "string",
"title": "Quote",
"description": "Testimonial body text",
"x-editor": "textarea"
},
"name": {
"type": "string",
"title": "Name",
"x-editor": "text"
},
"role": {
"type": "string",
"title": "Role",
"x-editor": "text"
},
"company": {
"type": "string",
"title": "Company",
"x-editor": "text"
},
"headshot": {
"type": "string",
"title": "Headshot",
"description": "Portrait image of the quoted person",
"x-editor": "media"
},
"logo": {
"type": "string",
"title": "Company logo",
"description": "Optional company mark beside the attribution",
"x-editor": "media"
}
}
}

56
stat_pair.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// StatPairMeta is the block metadata for corporate-modernist:stat_pair.
var StatPairMeta = blocks.BlockMeta{
Key: "stat_pair",
Title: "Stat Pair",
Description: "Row of mono-numeral statistics separated by a hairline rule.",
Source: "corporate-modernist",
Category: blocks.CategoryContent,
}
// StatPairItem represents a single stat entry.
type StatPairItem struct {
Figure string
Label string
Source string
}
// StatPairData carries the parsed content for the stat_pair block.
type StatPairData struct {
Title string
Stats []StatPairItem
}
// StatPairBlock renders the stat_pair block.
func StatPairBlock(ctx context.Context, content map[string]any) string {
rawStats := getSlice(content, "stats")
stats := make([]StatPairItem, 0, len(rawStats))
for _, s := range rawStats {
figure := getString(s, "figure")
if figure == "" {
continue
}
stats = append(stats, StatPairItem{
Figure: figure,
Label: getString(s, "label"),
Source: getString(s, "source"),
})
}
data := StatPairData{
Title: getString(content, "title"),
Stats: stats,
}
var buf bytes.Buffer
_ = statPairComponent(data).Render(ctx, &buf)
return buf.String()
}

45
stat_pair.templ Normal file
View File

@ -0,0 +1,45 @@
package main
// statPairComponent renders the corporate-modernist:stat_pair block.
templ statPairComponent(data StatPairData) {
<section class="cm-section" data-block="corporate-modernist:stat_pair">
<div class="cm-content-well">
if data.Title != "" {
<h2 class="cm-heading" style="font-size: 1.875rem; margin: 0 0 2rem 0;">
<span class="cm-heading-h2-accent">{ data.Title }</span>
</h2>
}
if len(data.Stats) > 0 {
<div class="cm-swiss-12" style="row-gap: 2rem;">
for _, s := range data.Stats {
<div style={ statPairColumnSpan(len(data.Stats)) } class="cm-hairline-top" >
<div class="cm-stat-figure numeric-tabular" data-figure style="padding-top: 1rem;">{ s.Figure }</div>
if s.Label != "" {
<div class="cm-stat-label">{ s.Label }</div>
}
if s.Source != "" {
<div class="cm-stat-label" style="margin-top: 0.25rem; text-transform: none; letter-spacing: 0; opacity: 0.8;">{ s.Source }</div>
}
</div>
}
</div>
} else {
<p class="cm-stat-label">No statistics configured.</p>
}
</div>
</section>
}
// statPairColumnSpan picks a column span based on how many stats are configured.
func statPairColumnSpan(count int) string {
switch {
case count >= 4:
return "grid-column: span 3 / span 3;"
case count == 3:
return "grid-column: span 4 / span 4;"
case count == 2:
return "grid-column: span 6 / span 6;"
default:
return "grid-column: span 12 / span 12;"
}
}

167
stat_pair_templ.go Normal file
View File

@ -0,0 +1,167 @@
// 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"
// statPairComponent renders the corporate-modernist:stat_pair block.
func statPairComponent(data StatPairData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"cm-section\" data-block=\"corporate-modernist:stat_pair\"><div class=\"cm-content-well\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h2 class=\"cm-heading\" style=\"font-size: 1.875rem; margin: 0 0 2rem 0;\"><span class=\"cm-heading-h2-accent\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stat_pair.templ`, Line: 9, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</span></h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Stats) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"cm-swiss-12\" style=\"row-gap: 2rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, s := range data.Stats {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(statPairColumnSpan(len(data.Stats)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stat_pair.templ`, Line: 15, Col: 54}
}
_, 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, "\" class=\"cm-hairline-top\"><div class=\"cm-stat-figure numeric-tabular\" data-figure style=\"padding-top: 1rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(s.Figure)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stat_pair.templ`, Line: 16, Col: 100}
}
_, 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, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if s.Label != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"cm-stat-label\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stat_pair.templ`, Line: 18, Col: 44}
}
_, 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, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if s.Source != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"cm-stat-label\" style=\"margin-top: 0.25rem; text-transform: none; letter-spacing: 0; opacity: 0.8;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(s.Source)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stat_pair.templ`, Line: 21, Col: 129}
}
_, 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, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<p class=\"cm-stat-label\">No statistics configured.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// statPairColumnSpan picks a column span based on how many stats are configured.
func statPairColumnSpan(count int) string {
switch {
case count >= 4:
return "grid-column: span 3 / span 3;"
case count == 3:
return "grid-column: span 4 / span 4;"
case count == 2:
return "grid-column: span 6 / span 6;"
default:
return "grid-column: span 12 / span 12;"
}
}
var _ = templruntime.GeneratedTemplate

274
template.templ Normal file
View File

@ -0,0 +1,274 @@
package main
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PageData captures parsed template data for the Corporate Modernist page renderers.
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
}
// parseCMPageData adapts the runtime doc map into PageData.
func parseCMPageData(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
}
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: bn.ParseSiteSettings(doc),
PageMeta: bn.ParsePageMeta(doc),
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: bn.ParseEngagementConfig(doc),
}
}
// pluginStyles points at the embedded plugin CSS served via /templates/<slug>/style.css.
func pluginStyles() []string {
return []string{"/templates/corporate-modernist/style.css"}
}
// Default — header + 12-col main + footer, capped at 1280px.
templ CorporateModernistDefault(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles(),
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="cm-hairline-bottom" style="width: 100%;">
<div class="cm-content-well">
@templ.Raw(data.Slots["header"])
</div>
</header>
<main class="cm-content-well" style="flex: 1 1 auto; padding-top: 3rem; padding-bottom: 3rem;" data-content-well>
<div class="cm-swiss-12" data-grid="swiss-12">
<div style="grid-column: span 12 / span 12;">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<p class="cm-stat-label">No content blocks assigned to this page.</p>
}
</div>
</div>
</main>
<footer style="width: 100%; margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Landing — hero band + main + closing CTA + footer.
templ CorporateModernistLanding(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles(),
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="cm-hairline-bottom" style="width: 100%;">
<div class="cm-content-well">
@templ.Raw(data.Slots["header"])
</div>
</header>
<section style="width: 100%;">
@templ.Raw(data.Slots["hero"])
</section>
<main style="flex: 1 1 auto;" data-content-well>
if main, ok := data.Slots["main"]; ok && main != "" {
<div class="cm-content-well">
<div class="cm-swiss-12" data-grid="swiss-12" style="padding-top: 3rem; padding-bottom: 3rem;">
<div style="grid-column: span 12 / span 12;">
@templ.Raw(main)
</div>
</div>
</div>
}
</main>
<section style="width: 100%;">
@templ.Raw(data.Slots["cta"])
</section>
<footer style="width: 100%; margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Article — centered measure for long-form thought leadership.
templ CorporateModernistArticle(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles(),
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="cm-hairline-bottom" style="width: 100%;">
<div class="cm-content-well">
@templ.Raw(data.Slots["header"])
</div>
</header>
<main class="cm-content-well" style="flex: 1 1 auto; padding-top: 3rem; padding-bottom: 3rem;" data-content-well>
<div class="cm-swiss-12" data-grid="swiss-12">
<article class="cm-article-measure" style="grid-column: span 12 / span 12;">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<p class="cm-stat-label">No article content yet.</p>
}
</article>
</div>
if aside, ok := data.Slots["aside"]; ok && aside != "" {
<aside class="cm-hairline-top" style="margin-top: 3rem; padding-top: 2rem;">
@templ.Raw(aside)
</aside>
}
</main>
<footer style="width: 100%; margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Full width — edge-to-edge sections, internal grid kept.
templ CorporateModernistFullWidth(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles(),
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="cm-body" style="margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="cm-hairline-bottom" style="width: 100%;">
<div class="cm-content-well">
@templ.Raw(data.Slots["header"])
</div>
</header>
<main style="flex: 1 1 auto; width: 100%;" data-content-well>
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="cm-content-well" style="padding-top: 4rem; padding-bottom: 4rem;">
<p class="cm-stat-label">No content blocks assigned to this page.</p>
</div>
}
</main>
<footer style="width: 100%; margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// RenderCM is the page-template render entry point for `default`.
func RenderCM(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistDefault(parseCMPageData(doc))
}
// RenderCMLanding is the page-template render entry point for `landing`.
func RenderCMLanding(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistLanding(parseCMPageData(doc))
}
// RenderCMArticle is the page-template render entry point for `article`.
func RenderCMArticle(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistArticle(parseCMPageData(doc))
}
// RenderCMFullWidth is the page-template render entry point for `full-width`.
func RenderCMFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistFullWidth(parseCMPageData(doc))
}

528
template_templ.go Normal file
View File

@ -0,0 +1,528 @@
// 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 captures parsed template data for the Corporate Modernist page renderers.
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
}
// parseCMPageData adapts the runtime doc map into PageData.
func parseCMPageData(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
}
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: bn.ParseSiteSettings(doc),
PageMeta: bn.ParsePageMeta(doc),
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: bn.ParseEngagementConfig(doc),
}
}
// pluginStyles points at the embedded plugin CSS served via /templates/<slug>/style.css.
func pluginStyles() []string {
return []string{"/templates/corporate-modernist/style.css"}
}
// Default — header + 12-col main + footer, capped at 1280px.
func CorporateModernistDefault(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\">")
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: pluginStyles(),
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=\"cm-body\" style=\"margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
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=\"cm-hairline-bottom\" style=\"width: 100%;\"><div class=\"cm-content-well\">")
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, "</div></header><main class=\"cm-content-well\" style=\"flex: 1 1 auto; padding-top: 3rem; padding-bottom: 3rem;\" data-content-well><div class=\"cm-swiss-12\" data-grid=\"swiss-12\"><div style=\"grid-column: span 12 / span 12;\">")
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, "<p class=\"cm-stat-label\">No content blocks assigned to this page.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div></div></main><footer style=\"width: 100%; margin-top: auto;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Landing — hero band + main + closing CTA + footer.
func CorporateModernistLanding(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\">")
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: pluginStyles(),
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=\"cm-body\" style=\"margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
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, "<header class=\"cm-hairline-bottom\" style=\"width: 100%;\"><div class=\"cm-content-well\">")
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, 12, "</div></header><section style=\"width: 100%;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["hero"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</section><main style=\"flex: 1 1 auto;\" data-content-well>")
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, 14, "<div class=\"cm-content-well\"><div class=\"cm-swiss-12\" data-grid=\"swiss-12\" style=\"padding-top: 3rem; padding-bottom: 3rem;\"><div style=\"grid-column: span 12 / span 12;\">")
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, 15, "</div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</main><section style=\"width: 100%;\">")
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, 17, "</section><footer style=\"width: 100%; margin-top: 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, 18, "</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, 19, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Article — centered measure for long-form thought leadership.
func CorporateModernistArticle(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, 20, "<!doctype html><html lang=\"en\">")
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: pluginStyles(),
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, 21, "<body class=\"cm-body\" style=\"margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
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, 22, "<header class=\"cm-hairline-bottom\" style=\"width: 100%;\"><div class=\"cm-content-well\">")
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, 23, "</div></header><main class=\"cm-content-well\" style=\"flex: 1 1 auto; padding-top: 3rem; padding-bottom: 3rem;\" data-content-well><div class=\"cm-swiss-12\" data-grid=\"swiss-12\"><article class=\"cm-article-measure\" style=\"grid-column: span 12 / span 12;\">")
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, 24, "<p class=\"cm-stat-label\">No article content yet.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</article></div>")
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, 26, "<aside class=\"cm-hairline-top\" style=\"margin-top: 3rem; padding-top: 2rem;\">")
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, 27, "</aside>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</main><footer style=\"width: 100%; margin-top: 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, 29, "</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, 30, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Full width — edge-to-edge sections, internal grid kept.
func CorporateModernistFullWidth(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, 31, "<!doctype html><html lang=\"en\">")
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: pluginStyles(),
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, 32, "<body class=\"cm-body\" style=\"margin: 0; min-height: 100vh; display: flex; flex-direction: column; background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
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, 33, "<header class=\"cm-hairline-bottom\" style=\"width: 100%;\"><div class=\"cm-content-well\">")
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, 34, "</div></header><main style=\"flex: 1 1 auto; width: 100%;\" data-content-well>")
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, 35, "<div class=\"cm-content-well\" style=\"padding-top: 4rem; padding-bottom: 4rem;\"><p class=\"cm-stat-label\">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, 36, "</main><footer style=\"width: 100%; margin-top: 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, 37, "</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, 38, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RenderCM is the page-template render entry point for `default`.
func RenderCM(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistDefault(parseCMPageData(doc))
}
// RenderCMLanding is the page-template render entry point for `landing`.
func RenderCMLanding(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistLanding(parseCMPageData(doc))
}
// RenderCMArticle is the page-template render entry point for `article`.
func RenderCMArticle(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistArticle(parseCMPageData(doc))
}
// RenderCMFullWidth is the page-template render entry point for `full-width`.
func RenderCMFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return CorporateModernistFullWidth(parseCMPageData(doc))
}
var _ = templruntime.GeneratedTemplate

43
testimonial_quote.go Normal file
View File

@ -0,0 +1,43 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// TestimonialQuoteMeta is the block metadata for corporate-modernist:testimonial_quote.
var TestimonialQuoteMeta = blocks.BlockMeta{
Key: "testimonial_quote",
Title: "Testimonial",
Description: "Pull-quote testimonial with attribution, headshot, and company mark.",
Source: "corporate-modernist",
Category: blocks.CategoryContent,
}
// TestimonialQuoteData carries the parsed content for the testimonial_quote block.
type TestimonialQuoteData struct {
Quote string
Name string
Role string
Company string
Headshot string
Logo string
}
// TestimonialQuoteBlock renders the testimonial_quote block.
func TestimonialQuoteBlock(ctx context.Context, content map[string]any) string {
data := TestimonialQuoteData{
Quote: getString(content, "quote"),
Name: getString(content, "name"),
Role: getString(content, "role"),
Company: getString(content, "company"),
Headshot: getString(content, "headshot"),
Logo: getString(content, "logo"),
}
var buf bytes.Buffer
_ = testimonialQuoteComponent(data).Render(ctx, &buf)
return buf.String()
}

63
testimonial_quote.templ Normal file
View File

@ -0,0 +1,63 @@
package main
// testimonialQuoteComponent renders the corporate-modernist:testimonial_quote block.
templ testimonialQuoteComponent(data TestimonialQuoteData) {
<section class="cm-section" data-block="corporate-modernist:testimonial_quote">
<div class="cm-content-well">
<figure style="max-width: 70ch; margin: 0 auto;">
if data.Quote != "" {
<blockquote class="cm-pull-quote">
{ data.Quote }
</blockquote>
} else {
<blockquote class="cm-pull-quote">
No testimonial provided.
</blockquote>
}
<figcaption style="display: flex; align-items: center; gap: 1rem; margin-top: 1.5rem;">
if data.Headshot != "" {
<img src={ data.Headshot } alt={ testimonialQuoteHeadshotAlt(data) } style="width: 3rem; height: 3rem; border-radius: 50%; object-fit: cover; display: block;"/>
} else {
<span class="cm-img-empty" style="width: 3rem; height: 3rem; aspect-ratio: 1 / 1; border-radius: 50%;"></span>
}
<div>
if data.Name != "" {
<div class="cm-display" style="font-size: 1rem; font-weight: 600; margin: 0;">{ data.Name }</div>
}
if data.Role != "" || data.Company != "" {
<div class="cm-stat-label" style="margin: 0.25rem 0 0 0;">{ testimonialQuoteAttribution(data) }</div>
}
</div>
if data.Logo != "" {
<img src={ data.Logo } alt={ testimonialQuoteLogoAlt(data) } class="cm-logo" style="max-height: 1.5rem; width: auto; margin-left: auto; display: block;"/>
}
</figcaption>
</figure>
</div>
</section>
}
func testimonialQuoteHeadshotAlt(data TestimonialQuoteData) string {
if data.Name != "" {
return data.Name
}
return "Testimonial portrait"
}
func testimonialQuoteLogoAlt(data TestimonialQuoteData) string {
if data.Company != "" {
return data.Company + " logo"
}
return "Company logo"
}
func testimonialQuoteAttribution(data TestimonialQuoteData) string {
switch {
case data.Role != "" && data.Company != "":
return data.Role + ", " + data.Company
case data.Role != "":
return data.Role
default:
return data.Company
}
}

213
testimonial_quote_templ.go Normal file
View File

@ -0,0 +1,213 @@
// 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"
// testimonialQuoteComponent renders the corporate-modernist:testimonial_quote block.
func testimonialQuoteComponent(data TestimonialQuoteData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"cm-section\" data-block=\"corporate-modernist:testimonial_quote\"><div class=\"cm-content-well\"><figure style=\"max-width: 70ch; margin: 0 auto;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Quote != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<blockquote class=\"cm-pull-quote\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Quote)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_quote.templ`, Line: 10, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</blockquote>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<blockquote class=\"cm-pull-quote\">No testimonial provided.</blockquote>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<figcaption style=\"display: flex; align-items: center; gap: 1rem; margin-top: 1.5rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Headshot != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Headshot)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_quote.templ`, Line: 19, Col: 30}
}
_, 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, 7, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(testimonialQuoteHeadshotAlt(data))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_quote.templ`, Line: 19, Col: 72}
}
_, 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, "\" style=\"width: 3rem; height: 3rem; border-radius: 50%; object-fit: cover; display: block;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<span class=\"cm-img-empty\" style=\"width: 3rem; height: 3rem; aspect-ratio: 1 / 1; border-radius: 50%;\"></span>")
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 data.Name != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"cm-display\" style=\"font-size: 1rem; font-weight: 600; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_quote.templ`, Line: 25, Col: 96}
}
_, 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, 12, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Role != "" || data.Company != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"cm-stat-label\" style=\"margin: 0.25rem 0 0 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(testimonialQuoteAttribution(data))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_quote.templ`, Line: 28, Col: 100}
}
_, 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, 14, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Logo != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Logo)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_quote.templ`, Line: 32, Col: 26}
}
_, 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, 17, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(testimonialQuoteLogoAlt(data))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_quote.templ`, Line: 32, Col: 64}
}
_, 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, 18, "\" class=\"cm-logo\" style=\"max-height: 1.5rem; width: auto; margin-left: auto; display: block;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</figcaption></figure></div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func testimonialQuoteHeadshotAlt(data TestimonialQuoteData) string {
if data.Name != "" {
return data.Name
}
return "Testimonial portrait"
}
func testimonialQuoteLogoAlt(data TestimonialQuoteData) string {
if data.Company != "" {
return data.Company + " logo"
}
return "Company logo"
}
func testimonialQuoteAttribution(data TestimonialQuoteData) string {
switch {
case data.Role != "" && data.Company != "":
return data.Role + ", " + data.Company
case data.Role != "":
return data.Role
default:
return data.Company
}
}
var _ = templruntime.GeneratedTemplate

17
text_override.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"bytes"
"context"
)
// CorporateModernistTextBlock renders the built-in `text` block with
// Inter at 17/28, tabular figures, and hanging punctuation.
func CorporateModernistTextBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
class := getString(content, "class")
var buf bytes.Buffer
_ = textOverrideComponent(text, class).Render(ctx, &buf)
return buf.String()
}

8
text_override.templ Normal file
View File

@ -0,0 +1,8 @@
package main
// textOverrideComponent renders the Corporate Modernist text override.
templ textOverrideComponent(text, class string) {
<div class={ "cm-text", class }>
@templ.Raw(text)
</div>
}

67
text_override_templ.go Normal file
View File

@ -0,0 +1,67 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// textOverrideComponent renders the Corporate Modernist text override.
func textOverrideComponent(text, class string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var2 = []any{"cm-text", class}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `text_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(text).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate