commit 4713787bbda0e2e8991cfdb7b964d65e902d4c6a Author: Alex Dunmow Date: Sat Jun 6 14:11:24 2026 +0800 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f780e6f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.so +*.test +tmp/ +.idea/ +.vscode/ diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md new file mode 100644 index 0000000..6260341 --- /dev/null +++ b/BUILD_REPORT.md @@ -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 `` 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5aef0a5 --- /dev/null +++ b/Makefile @@ -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 _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 diff --git a/RECOMMENDED_FONTS.md b/RECOMMENDED_FONTS.md new file mode 100644 index 0000000..e8682ad --- /dev/null +++ b/RECOMMENDED_FONTS.md @@ -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. diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..1f11b20 --- /dev/null +++ b/assets/style.css @@ -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)); +} diff --git a/button_override.go b/button_override.go new file mode 100644 index 0000000..6cd406b --- /dev/null +++ b/button_override.go @@ -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() +} diff --git a/button_override.templ b/button_override.templ new file mode 100644 index 0000000..bcc4b8e --- /dev/null +++ b/button_override.templ @@ -0,0 +1,21 @@ +package main + +// buttonOverrideComponent renders a Corporate Modernist styled button. +templ buttonOverrideComponent(label, href, variant string) { + if variant == "outline" || variant == "secondary" { + + { buttonOverrideLabel(label) } + + } else { + + { buttonOverrideLabel(label) } + + } +} + +func buttonOverrideLabel(label string) string { + if label != "" { + return label + } + return "Learn more" +} diff --git a/button_override_templ.go b/button_override_templ.go new file mode 100644 index 0000000..6ce611e --- /dev/null +++ b/button_override_templ.go @@ -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, "") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(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, "") + 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 diff --git a/card_override.go b/card_override.go new file mode 100644 index 0000000..5676950 --- /dev/null +++ b/card_override.go @@ -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() +} diff --git a/card_override.templ b/card_override.templ new file mode 100644 index 0000000..f2bcd5c --- /dev/null +++ b/card_override.templ @@ -0,0 +1,25 @@ +package main + +// cardOverrideComponent renders a flat hairline-bordered card. +templ cardOverrideComponent(title, body string, accent bool) { + if accent { +
+ @cardOverrideInner(title, body) +
+ } else { +
+ @cardOverrideInner(title, body) +
+ } +} + +templ cardOverrideInner(title, body string) { + if title != "" { +
{ title }
+ } + if body != "" { +
+ @templ.Raw(body) +
+ } +} diff --git a/card_override_templ.go b/card_override_templ.go new file mode 100644 index 0000000..38ea3a9 --- /dev/null +++ b/card_override_templ.go @@ -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, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + 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, "
") + 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, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if body != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/case_study_card.go b/case_study_card.go new file mode 100644 index 0000000..e4f56d2 --- /dev/null +++ b/case_study_card.go @@ -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() +} diff --git a/case_study_card.templ b/case_study_card.templ new file mode 100644 index 0000000..bd3063b --- /dev/null +++ b/case_study_card.templ @@ -0,0 +1,35 @@ +package main + +// caseStudyCardComponent renders the corporate-modernist:case_study_card block. +templ caseStudyCardComponent(data CaseStudyCardData) { +
+ if data.Client != "" { +
{ data.Client }
+ } + if data.Metric != "" { +
{ data.Metric }
+ } else { +
+ } + if data.Label != "" { +
{ data.Label }
+ } + if data.Summary != "" { +

{ data.Summary }

+ } + if data.HasLink { +
+ + { caseStudyCardLinkLabel(data) } + +
+ } +
+} + +func caseStudyCardLinkLabel(data CaseStudyCardData) string { + if data.HrefLabel != "" { + return data.HrefLabel + } + return "Read case study" +} diff --git a/case_study_card_templ.go b/case_study_card_templ.go new file mode 100644 index 0000000..c5f7fc0 --- /dev/null +++ b/case_study_card_templ.go @@ -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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Client != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Metric != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Label != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Summary != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.HasLink { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func caseStudyCardLinkLabel(data CaseStudyCardData) string { + if data.HrefLabel != "" { + return data.HrefLabel + } + return "Read case study" +} + +var _ = templruntime.GeneratedTemplate diff --git a/css_manifest.go b/css_manifest.go new file mode 100644 index 0000000..252e282 --- /dev/null +++ b/css_manifest.go @@ -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)); } +` diff --git a/cta_strip.go b/cta_strip.go new file mode 100644 index 0000000..c78aca8 --- /dev/null +++ b/cta_strip.go @@ -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() +} diff --git a/cta_strip.templ b/cta_strip.templ new file mode 100644 index 0000000..f443abc --- /dev/null +++ b/cta_strip.templ @@ -0,0 +1,56 @@ +package main + +// ctaStripComponent renders the corporate-modernist:cta_strip block. +templ ctaStripComponent(data CTAStripData) { +
+
+
+ if data.Headline != "" { +

{ data.Headline }

+ } else { +

Ready when you are.

+ } + if data.HasCTA { + + { ctaStripLabel(data) } + + } else { + + { ctaStripDefaultLabel(data.Variant) } + + } +
+
+
+} + +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" + } +} diff --git a/cta_strip_templ.go b/cta_strip_templ.go new file mode 100644 index 0000000..059a1de --- /dev/null +++ b/cta_strip_templ.go @@ -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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Headline != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

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

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

Ready when you are.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.HasCTA { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func 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 diff --git a/email_wrapper.go b/email_wrapper.go new file mode 100644 index 0000000..55f4d4e --- /dev/null +++ b/email_wrapper.go @@ -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() +} diff --git a/email_wrapper.templ b/email_wrapper.templ new file mode 100644 index 0000000..e0fd515 --- /dev/null +++ b/email_wrapper.templ @@ -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 ; every inline `style="…"` +// references them via var(--cm-email-*) with the navy-classic HSL triple +// as the fallback, so: +// - email clients that strip + + + if emailCtx.PreviewText != "" { +
+ { emailCtx.PreviewText } +
+ } + + + + +
+ + + + + + + + + + + + + + + + + +
+ + +} diff --git a/email_wrapper_templ.go b/email_wrapper_templ.go new file mode 100644 index 0000000..1d51fee --- /dev/null +++ b/email_wrapper_templ.go @@ -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 ; every inline `style="…"` +// references them via var(--cm-email-*) with the navy-classic HSL triple +// as the fallback, so: +// - email clients that strip ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if emailCtx.PreviewText != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/embed.go b/embed.go new file mode 100644 index 0000000..70bc542 --- /dev/null +++ b/embed.go @@ -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 +} diff --git a/fonts.json b/fonts.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/fonts.json @@ -0,0 +1 @@ +[] diff --git a/footer.go b/footer.go new file mode 100644 index 0000000..63adf01 --- /dev/null +++ b/footer.go @@ -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() +} diff --git a/footer.templ b/footer.templ new file mode 100644 index 0000000..1161f47 --- /dev/null +++ b/footer.templ @@ -0,0 +1,60 @@ +package main + +// footerComponent renders the corporate-modernist:footer block. +templ footerComponent(data FooterData) { + +} + +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;" + } +} diff --git a/footer_templ.go b/footer_templ.go new file mode 100644 index 0000000..54144a1 --- /dev/null +++ b/footer_templ.go @@ -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, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, col := range data.Columns { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if col.Title != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(col.Links) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.ShowLegal { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + 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 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..455cf75 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..26aea2c --- /dev/null +++ b/go.sum @@ -0,0 +1,42 @@ +connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= +connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= +git.dev.alexdunmow.com/block/core v0.11.1 h1:5b3Ps9CLor2FGyxw/Qovt27AGZKR5Xi1JZGi/TfliTA= +git.dev.alexdunmow.com/block/core v0.11.1/go.mod h1:ZwzEOxRDLDfrhQGqo6hLw01/C1z/aS4Dm9ljQMl0Bg4= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw= +github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/heading_override.go b/heading_override.go new file mode 100644 index 0000000..e8d704a --- /dev/null +++ b/heading_override.go @@ -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 +} diff --git a/heading_override.templ b/heading_override.templ new file mode 100644 index 0000000..8285c2e --- /dev/null +++ b/heading_override.templ @@ -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: +

+ { text } +

+ case 2: +

+ { text } +

+ case 3: +

+ { text } +

+ case 4: +

+ { text } +

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

+ { text } +

+ } +} + +// 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" + } +} diff --git a/heading_override_templ.go b/heading_override_templ.go new file mode 100644 index 0000000..a3e4ac3 --- /dev/null +++ b/heading_override_templ.go @@ -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, "

") + 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, "

") + 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, "

") + 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, "

") + 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, "

") + 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, "

") + 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, "

") + 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, "

") + 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, "
") + 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, "
") + 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, "
") + 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, "
") + 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, "

") + 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, "

") + 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 diff --git a/helpers.go b/helpers.go new file mode 100644 index 0000000..0f55f88 --- /dev/null +++ b/helpers.go @@ -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 +} + diff --git a/hero_statement.go b/hero_statement.go new file mode 100644 index 0000000..5cb842c --- /dev/null +++ b/hero_statement.go @@ -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() +} diff --git a/hero_statement.templ b/hero_statement.templ new file mode 100644 index 0000000..08cbe39 --- /dev/null +++ b/hero_statement.templ @@ -0,0 +1,45 @@ +package main + +// heroStatementComponent renders the corporate-modernist:hero_statement block. +templ heroStatementComponent(data HeroStatementData) { +
+
+
+
+ if data.Eyebrow != "" { +
{ data.Eyebrow }
+ } + if data.Headline != "" { +

+ { data.Headline } +

+ } + if data.Lede != "" { +

{ data.Lede }

+ } + if data.HasPrimary || data.HasSecondary { + + } +
+
+
+
+} + +func heroStatementLabel(label, fallback string) string { + if label != "" { + return label + } + return fallback +} diff --git a/hero_statement_templ.go b/hero_statement_templ.go new file mode 100644 index 0000000..505306f --- /dev/null +++ b/hero_statement_templ.go @@ -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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Eyebrow != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Headline != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

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

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Lede != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.HasPrimary || data.HasSecondary { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.HasPrimary { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + 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, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.HasSecondary { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func heroStatementLabel(label, fallback string) string { + if label != "" { + return label + } + return fallback +} + +var _ = templruntime.GeneratedTemplate diff --git a/leadership_grid.go b/leadership_grid.go new file mode 100644 index 0000000..7b70386 --- /dev/null +++ b/leadership_grid.go @@ -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() +} diff --git a/leadership_grid.templ b/leadership_grid.templ new file mode 100644 index 0000000..269a39b --- /dev/null +++ b/leadership_grid.templ @@ -0,0 +1,48 @@ +package main + +// leadershipGridComponent renders the corporate-modernist:leadership_grid block. +templ leadershipGridComponent(data LeadershipGridData) { +
+
+ if data.Title != "" { +

+ { data.Title } +

+ } + if len(data.Members) > 0 { +
+ for _, m := range data.Members { +
+ if m.Photo != "" { + { + } else { + + } +
{ m.Name }
+ if m.Role != "" { +
{ m.Role }
+ } + if m.Bio != "" { +

{ m.Bio }

+ } + if m.LinkedIn != "" { +
+ LinkedIn +
+ } +
+ } +
+ } else { +

No team members configured.

+ } +
+
+} + +func leadershipGridPhotoAlt(m LeadershipGridMember) string { + if m.Name != "" { + return m.Name + } + return "Team member portrait" +} diff --git a/leadership_grid_templ.go b/leadership_grid_templ.go new file mode 100644 index 0000000..6585f2e --- /dev/null +++ b/leadership_grid_templ.go @@ -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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Title != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(data.Members) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, m := range data.Members { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if m.Photo != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if m.Role != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if m.Bio != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

") + 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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if m.LinkedIn != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

No team members configured.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func leadershipGridPhotoAlt(m LeadershipGridMember) string { + if m.Name != "" { + return m.Name + } + return "Team member portrait" +} + +var _ = templruntime.GeneratedTemplate diff --git a/logo_strip.go b/logo_strip.go new file mode 100644 index 0000000..a83d2fc --- /dev/null +++ b/logo_strip.go @@ -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() +} diff --git a/logo_strip.templ b/logo_strip.templ new file mode 100644 index 0000000..ecf9ad3 --- /dev/null +++ b/logo_strip.templ @@ -0,0 +1,44 @@ +package main + +// logoStripComponent renders the corporate-modernist:logo_strip block. +templ logoStripComponent(data LogoStripData) { +
+
+ if data.Caption != "" { +

{ data.Caption }

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

No client logos configured.

+ } +
+
+} + +templ logoStripImage(logo LogoStripItem) { + if logo.Src != "" { + { + } else { + { logoStripAlt(logo) } + } +} + +func logoStripAlt(logo LogoStripItem) string { + if logo.Alt != "" { + return logo.Alt + } + return "Client logo" +} diff --git a/logo_strip_templ.go b/logo_strip_templ.go new file mode 100644 index 0000000..7039b8c --- /dev/null +++ b/logo_strip_templ.go @@ -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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Caption != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(data.Logos) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + 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, "") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "

No client logos configured.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func 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, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "") + 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, "") + 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 diff --git a/plugin.mod b/plugin.mod new file mode 100644 index 0000000..9f9fd2f --- /dev/null +++ b/plugin.mod @@ -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" diff --git a/presets.json b/presets.json new file mode 100644 index 0000000..94e05a8 --- /dev/null +++ b/presets.json @@ -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%" + } + } + } +] diff --git a/register.go b/register.go new file mode 100644 index 0000000..91b74ab --- /dev/null +++ b/register.go @@ -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, + }, + }, + }, + } +} diff --git a/registration.go b/registration.go new file mode 100644 index 0000000..024ecdd --- /dev/null +++ b/registration.go @@ -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, + } +} diff --git a/schemas/case_study_card.schema.json b/schemas/case_study_card.schema.json new file mode 100644 index 0000000..a359cc5 --- /dev/null +++ b/schemas/case_study_card.schema.json @@ -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" } + } + } + } +} diff --git a/schemas/cta_strip.schema.json b/schemas/cta_strip.schema.json new file mode 100644 index 0000000..6778448 --- /dev/null +++ b/schemas/cta_strip.schema.json @@ -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" + } + } +} diff --git a/schemas/footer.schema.json b/schemas/footer.schema.json new file mode 100644 index 0000000..559cd91 --- /dev/null +++ b/schemas/footer.schema.json @@ -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" + } + } +} diff --git a/schemas/hero_statement.schema.json b/schemas/hero_statement.schema.json new file mode 100644 index 0000000..e1578bf --- /dev/null +++ b/schemas/hero_statement.schema.json @@ -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" } + } + } + } +} diff --git a/schemas/leadership_grid.schema.json b/schemas/leadership_grid.schema.json new file mode 100644 index 0000000..1922497 --- /dev/null +++ b/schemas/leadership_grid.schema.json @@ -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"] + } + } + } +} diff --git a/schemas/logo_strip.schema.json b/schemas/logo_strip.schema.json new file mode 100644 index 0000000..a87e925 --- /dev/null +++ b/schemas/logo_strip.schema.json @@ -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"] + } + } + } +} diff --git a/schemas/stat_pair.schema.json b/schemas/stat_pair.schema.json new file mode 100644 index 0000000..4ca74a1 --- /dev/null +++ b/schemas/stat_pair.schema.json @@ -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"] + } + } + } +} diff --git a/schemas/testimonial_quote.schema.json b/schemas/testimonial_quote.schema.json new file mode 100644 index 0000000..e7346ca --- /dev/null +++ b/schemas/testimonial_quote.schema.json @@ -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" + } + } +} diff --git a/stat_pair.go b/stat_pair.go new file mode 100644 index 0000000..2fb508b --- /dev/null +++ b/stat_pair.go @@ -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() +} diff --git a/stat_pair.templ b/stat_pair.templ new file mode 100644 index 0000000..8d19d78 --- /dev/null +++ b/stat_pair.templ @@ -0,0 +1,45 @@ +package main + +// statPairComponent renders the corporate-modernist:stat_pair block. +templ statPairComponent(data StatPairData) { +
+
+ if data.Title != "" { +

+ { data.Title } +

+ } + if len(data.Stats) > 0 { +
+ for _, s := range data.Stats { +
+
{ s.Figure }
+ if s.Label != "" { +
{ s.Label }
+ } + if s.Source != "" { +
{ s.Source }
+ } +
+ } +
+ } else { +

No statistics configured.

+ } +
+
+} + +// 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;" + } +} diff --git a/stat_pair_templ.go b/stat_pair_templ.go new file mode 100644 index 0000000..1c55f58 --- /dev/null +++ b/stat_pair_templ.go @@ -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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Title != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.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, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if len(data.Stats) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, s := range data.Stats { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if s.Label != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if s.Source != "" { + 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(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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "

No statistics configured.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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 diff --git a/template.templ b/template.templ new file mode 100644 index 0000000..b56baea --- /dev/null +++ b/template.templ @@ -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//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) { + + + @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, + }) + + @bn.AdminBypassBanner(data.SiteSettings) +
+
+ @templ.Raw(data.Slots["header"]) +
+
+
+
+
+ if main, ok := data.Slots["main"]; ok && main != "" { + @templ.Raw(main) + } else { +

No content blocks assigned to this page.

+ } +
+
+
+
+ @templ.Raw(data.Slots["footer"]) +
+ @bn.BodyEnd(data.SiteSettings) + + +} + +// Landing — hero band + main + closing CTA + footer. +templ CorporateModernistLanding(data PageData) { + + + @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, + }) + + @bn.AdminBypassBanner(data.SiteSettings) +
+
+ @templ.Raw(data.Slots["header"]) +
+
+
+ @templ.Raw(data.Slots["hero"]) +
+
+ if main, ok := data.Slots["main"]; ok && main != "" { +
+
+
+ @templ.Raw(main) +
+
+
+ } +
+
+ @templ.Raw(data.Slots["cta"]) +
+
+ @templ.Raw(data.Slots["footer"]) +
+ @bn.BodyEnd(data.SiteSettings) + + +} + +// Article — centered measure for long-form thought leadership. +templ CorporateModernistArticle(data PageData) { + + + @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, + }) + + @bn.AdminBypassBanner(data.SiteSettings) +
+
+ @templ.Raw(data.Slots["header"]) +
+
+
+
+
+ if main, ok := data.Slots["main"]; ok && main != "" { + @templ.Raw(main) + } else { +

No article content yet.

+ } +
+
+ if aside, ok := data.Slots["aside"]; ok && aside != "" { + + } +
+
+ @templ.Raw(data.Slots["footer"]) +
+ @bn.BodyEnd(data.SiteSettings) + + +} + +// Full width — edge-to-edge sections, internal grid kept. +templ CorporateModernistFullWidth(data PageData) { + + + @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, + }) + + @bn.AdminBypassBanner(data.SiteSettings) +
+
+ @templ.Raw(data.Slots["header"]) +
+
+
+ if main, ok := data.Slots["main"]; ok && main != "" { + @templ.Raw(main) + } else { +
+

No content blocks assigned to this page.

+
+ } +
+
+ @templ.Raw(data.Slots["footer"]) +
+ @bn.BodyEnd(data.SiteSettings) + + +} + +// 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)) +} diff --git a/template_templ.go b/template_templ.go new file mode 100644 index 0000000..d2fd33c --- /dev/null +++ b/template_templ.go @@ -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//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, "") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if main, ok := data.Slots["main"]; ok && main != "" { + templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

No content blocks assigned to this page.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// 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, "") + 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, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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, "
") + 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, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(data.Slots["cta"]).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = 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, "
") + 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, "") + 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, "") + 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, "") + 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, "
") + 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, "
") + 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, "

No article content yet.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if aside, ok := data.Slots["aside"]; ok && aside != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + 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, "
") + 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, "") + 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, "") + 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, "") + 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, "
") + 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, "
") + 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, "

No content blocks assigned to this page.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = 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, "
") + 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, "") + 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 diff --git a/testimonial_quote.go b/testimonial_quote.go new file mode 100644 index 0000000..06207a7 --- /dev/null +++ b/testimonial_quote.go @@ -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() +} diff --git a/testimonial_quote.templ b/testimonial_quote.templ new file mode 100644 index 0000000..e12ba74 --- /dev/null +++ b/testimonial_quote.templ @@ -0,0 +1,63 @@ +package main + +// testimonialQuoteComponent renders the corporate-modernist:testimonial_quote block. +templ testimonialQuoteComponent(data TestimonialQuoteData) { +
+
+
+ if data.Quote != "" { +
+ { data.Quote } +
+ } else { +
+ No testimonial provided. +
+ } +
+ if data.Headshot != "" { + { + } else { + + } +
+ if data.Name != "" { +
{ data.Name }
+ } + if data.Role != "" || data.Company != "" { +
{ testimonialQuoteAttribution(data) }
+ } +
+ if data.Logo != "" { + + } +
+
+
+
+} + +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 + } +} diff --git a/testimonial_quote_templ.go b/testimonial_quote_templ.go new file mode 100644 index 0000000..6951230 --- /dev/null +++ b/testimonial_quote_templ.go @@ -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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Quote != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
No testimonial provided.
") + 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 data.Headshot != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Name != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if data.Role != "" || data.Company != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Logo != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"")") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +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 diff --git a/text_override.go b/text_override.go new file mode 100644 index 0000000..7dcf903 --- /dev/null +++ b/text_override.go @@ -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() +} diff --git a/text_override.templ b/text_override.templ new file mode 100644 index 0000000..020463f --- /dev/null +++ b/text_override.templ @@ -0,0 +1,8 @@ +package main + +// textOverrideComponent renders the Corporate Modernist text override. +templ textOverrideComponent(text, class string) { +
+ @templ.Raw(text) +
+} diff --git a/text_override_templ.go b/text_override_templ.go new file mode 100644 index 0000000..423d908 --- /dev/null +++ b/text_override_templ.go @@ -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, "
") + 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate