initial: theme plugin magazine-bold

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

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

5
.gitignore vendored Normal file
View File

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

193
BUILD_REPORT.md Normal file
View File

@ -0,0 +1,193 @@
# Magazine Bold — Build Report
Generated from a clean wave-1 implementation pass against the spec at
`themes/docs/works/magazine-bold.md` (§§115) and the UAT at
`themes/docs/uat/magazine-bold.md`, with `themes/docs/FONTS.md` overriding
spec §5 and UAT §11.
## What landed
### Identity & metadata
- `plugin.mod``name = magazine-bold`, `kind = theme`, `scope = @themes`,
`version = 0.1.0`, `categories = ["templates", "media"]`,
`tags = [fashion, lifestyle, streetwear, magazine, bold, display, music,
editorial, art]` (9 entries, all from the whitelist), `block_core
compatibility = ">=0.11.0 <0.12.0"`, description verbatim from spec §1.
- `go.mod` — module `git.dev.alexdunmow.com/block/themes/magazine-bold`,
Go directive `1.26.4`, requires `block/core v0.11.1` and `a-h/templ
v0.3.1020`, no `replace` directives.
### File layout (templ-style)
```
magazine-bold/
plugin.mod go.mod go.sum
Makefile registration.go register.go
embed.go helpers.go css.go
template.templ + _templ.go # 4 page renderers
masthead.{go,templ} + _templ.go
cover_story.{go,templ} + _templ.go
photo_essay.{go,templ} + _templ.go
pull_quote.{go,templ} + _templ.go
issue_archive.{go,templ} + _templ.go
colophon.{go,templ} + _templ.go
heading_override.{go,templ} + _templ.go
text_override.{go,templ} + _templ.go
image_override.{go,templ} + _templ.go
button_override.{go,templ} + _templ.go
email_wrapper.{go,templ} + _templ.go
schemas/masthead.schema.json
schemas/cover_story.schema.json
schemas/photo_essay.schema.json
schemas/pull_quote.schema.json
schemas/issue_archive.schema.json
schemas/colophon.schema.json
presets.json fonts.json assets/.gitkeep
RECOMMENDED_FONTS.md BUILD_REPORT.md
```
### Registration
- One `tr.RegisterSystemTemplate({Key: "magazine-bold"})` call.
- Four `tr.RegisterPageTemplate("magazine-bold", ...)` calls with the slots:
- `default`: `["masthead", "main", "colophon"]`
- `landing`: `["cover", "secondary", "main", "colophon"]`
- `article`: `["masthead", "deck", "main", "colophon"]`
- `full-width`: `["masthead", "main", "colophon"]`
- `br.LoadSchemasFromFS(Schemas())` called BEFORE the first `br.Register(...)`.
- Six theme blocks registered with `Source: "magazine-bold"`:
- `masthead` → CategoryLayout
- `cover_story` → CategoryContent
- `photo_essay` → CategoryContent
- `pull_quote` → CategoryContent
- `issue_archive` → CategoryNavigation
- `colophon` → CategoryLayout
- Four built-in overrides via `br.RegisterTemplateOverride("magazine-bold", …)`:
`heading`, `text`, `image`, `button`.
- One `tr.RegisterEmailWrapper("magazine-bold", MagazineBoldEmailWrapper)`.
### Master pages
Both master pages from spec §7 are seeded in `DefaultMasterPages()`:
- `magazine-bold:default-master``PageTemplates: [default, article]`,
blocks: `navbar`@masthead/0`magazine-bold:masthead`@masthead/1
`slot`@main/0`magazine-bold:colophon`@colophon/0.
- `magazine-bold:landing-master``PageTemplates: [landing, full-width]`,
blocks: `navbar`@masthead/0`magazine-bold:cover_story`@cover/0
`magazine-bold:issue_archive`@secondary/0`slot`@main/0
`magazine-bold:colophon`@colophon/0.
- Every `slot` block carries `Content["slotName"] == "main"`.
### Presets (presets.json)
Three presets, each `mode: "both"`, each with both `lightColors` and
`darkColors` blocks carrying all 19 theme tokens:
- `paper-pink` — Paper & Hot Pink (default), spec §4 verbatim.
- `ink-blue` — same neutrals, accent swapped to `220 100% 56%` (light) /
`220 100% 62%` (dark), ring matches.
- `chalk-lime` — cool chalk background (`60 15% 97%` light / `0 0% 6%` dark),
lime accent (`78 90% 50%` / `78 90% 55%`), accentForeground reads ink
in both modes for ≥ 4.5:1 contrast on lime.
All HSL triples are bare strings — no `hsl(…)` wrappers anywhere.
### Schemas
All six schemas are draft-07, list properties matching the spec §8 field
table, and use only `x-editor` types from the allowed set. `pull_quote.accent`
enum is `["pink", "blue", "lime"]`. `photo_essay.frames` items use
`x-editor: collection` with item-level `media`, `text` and `select` (span
enum `["half", "full", "tall"]`).
### CSS strategy
`CSSManifest.InputCSSAppend` ships a single utility-CSS block at
`magazine-bold/css.go` (`magazineBoldUtilityCSS`). It defines:
- `.font-display`, `.font-serif-sub`, `.font-sans`, `.font-mono` — each
consumes `var(--font-heading|body|mono, <fallback stack>)` per FONTS.md.
- `.text-folio` clamp (220pt-equivalent at 1440px → ~80px at 360px).
- `.text-deck`, `.text-kicker` display-size utilities.
- `.mb-hairline*` for the 1px ink rules.
- `.mb-grid-12`, `.mb-grid-asym-cover`, `.mb-col-folio`, `.mb-col-headline`,
`.mb-col-deck` for the 12-column asymmetric layout.
- `.mb-photo-grid`, `.mb-frame-{half,full,tall}` for the photo-essay spans
(collapsing to 1-col at ≤ 768px).
- `.mb-dropcap` for the heading-override drop-cap.
- `.mb-button` squared-ink button with accent hover and a `:focus-visible`
outline using `hsl(var(--ring))`.
- `.mb-caption`, `.mb-image-folio` mono caption strip.
- `.colorblock-accent` + per-accent `[data-mb-accent="pink|blue|lime"]`
variants for pull quotes (using literal HSL triples that match the spec
§4 accent values so a pinned accent reads correctly across presets).
No hardcoded hex, rgb(), or named colors are used in `.templ` / `.go` /
`.html` files outside the email wrapper (see "Open items" below).
### Email wrapper
`RegisterEmailWrapper("magazine-bold", MagazineBoldEmailWrapper)` ships a
600px `<table>` with a paper-pink default background, an ink hairline below
the PP-Editorial-style wordmark (with Georgia / Times New Roman / serif
fallback for Outlook 365 web), a 16/24 Inter body, and a mono colophon
strip echoing the web colophon. Hex defaults inlined in `email_wrapper.go`
are derived from the paper-pink light preset so the layout reads correctly
in clients that strip the host CSS.
### Build output
- `make` produced `magazine-bold.so` at 21 MB (CGO `-buildmode=plugin
-ldflags="-s -w"`).
- `nm --dynamic magazine-bold.so | grep Registration` shows
`D git.dev.alexdunmow.com/block/themes/magazine-bold.Registration`
exported as the Go-plugin entry point.
- Zero `warning:` lines in the build output.
### Safety check
- `cd ~/src/blockninja/check-safety && go run . ~/src/blockninja/cms
--plugin-dir ~/src/blockninja/themes/magazine-bold` → **exit 0**, all 22
checks `OK` or `SKIP` (the SKIPs are frontend / orchestrator checks that
don't apply to a templ-only theme plugin).
- The task brief referenced the older path
`~/src/blockninja/backend/cmd/check-safety`, but the repo has been
re-organised so `check-safety/` now lives at the top level as its own
Go module. The equivalent invocation is the one above; behaviour is
identical and the plugin passes cleanly.
## Open items / deferred
- **Fonts (wave-2):** per `docs/FONTS.md`, this pass ships
`fonts.json = []` and no woff2 files. PP Editorial New, Migra, Inter and
JetBrains Mono are documented as recommended picks in
`RECOMMENDED_FONTS.md`. Wave-2 will commission / licence the commercial
faces (PP Editorial New + Migra) and bundle them with a `LICENSES.md`.
- **Email wrapper hex defaults:** the wrapper inlines six hex literals
(`#F7F2EB`, `#141414`, `#EEE9E0`, `#666666`, `#E0E0E0`, `#FFFFFF`) as
client-strip-proof defaults for the paper-pink preset. Mail clients
ignore `var()` and (often) strip `<style>` blocks, so these literals are
load-bearing — they only fire when the host doesn't populate
`EmailContext.Colors.*`. This is a deliberate exception to the "no
hardcoded colors" rule, scoped to `email_wrapper.go`. Spec §10 / UAT §10
call for this paper-pink fallback explicitly.
- **Marketplace assets:** the six 1440×900 screenshots, the demo content
seed (Vol. 1 / Spring + five posts + three pull quotes + an 8-frame photo
essay + an `issues` bucket), and the launch copy live in the
marketplace-listing pass that follows the build pass. Not produced here.
- **`make rebuild` / live deploy:** the task brief forbids running
`make rebuild` in this pass. The local `make` target builds the `.so`
and that's verified above.
- **`make logs` / `instance-magazine-bold` container:** depends on
`make rebuild`, so deferred with the live-deploy step.
- **UAT §11 `LICENSES.md`:** intentionally omitted per the wave-1 fonts
policy ("no `LICENSES.md` needed in this pass").
- **`nm | grep ' T main.Registration'` UAT check:** the Go-plugin runtime
exports the registration symbol under the module path
(`git.dev.alexdunmow.com/block/themes/magazine-bold.Registration` in
the dynamic symbol table), not as `main.Registration`. The loader still
resolves it correctly. UAT §3 line 35 would need wording-only tweak to
match the real Go-plugin symbol format; functionally the plugin is fine.
- **Per-page accent override (spec §15 open question):** kept at preset
scope. A pinned per-pull-quote accent is supported via
`data-mb-accent="pink|blue|lime"`. Per-page accent rotation is a v0.2.0
candidate.
## Counts
- Page templates: 4
- Theme blocks: 6
- Built-in overrides: 4 (heading, text, image, button)
- Email wrappers: 1
- Master pages: 2
- Presets: 3 (× 2 modes each)
- Tokens per preset block: 19

33
Makefile Normal file
View File

@ -0,0 +1,33 @@
# Magazine Bold — local build helpers
#
# The plugin compiles to a .so shared object loaded by the CMS at runtime.
#
# Usage:
# make # Build magazine-bold.so locally (default)
# make templ # Regenerate templ Go files
# make clean # Remove the built .so
.PHONY: all templ clean help
PLUGIN_NAME := magazine-bold
TEMPL := $(HOME)/go/bin/templ
# Default target: build the .so locally for development.
all: $(PLUGIN_NAME).so
# Local plugin build (no container). Useful for CI / quick checks.
$(PLUGIN_NAME).so: $(wildcard *.go) plugin.mod go.mod
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o $(PLUGIN_NAME).so .
# Regenerate templ Go files locally (for development).
templ:
$(TEMPL) generate
clean:
rm -f $(PLUGIN_NAME).so
help:
@echo "Targets:"
@echo " all Build $(PLUGIN_NAME).so locally (default)"
@echo " templ Regenerate templ Go files locally"
@echo " clean Remove the built .so"

56
RECOMMENDED_FONTS.md Normal file
View File

@ -0,0 +1,56 @@
# Magazine Bold — Recommended fonts
Per `themes/docs/FONTS.md` (wave-1 policy), this theme ships `fonts.json = []`
and provides CSS fallback stacks via `--font-heading`, `--font-body`,
`--font-mono`. The site admin assigns real fonts via the typography settings
panel; the recommendations below match the spec §5 intent.
## Display (heading slot)
Spec calls for **PP Editorial New** (Regular Italic + Ultralight) at 180280pt
for cover-story hits, with **Migra** as a secondary serif drop-in. Both are
commercial; the open-source picks below render the same italic-display energy.
Open-source picks (assign one to **Heading**):
- `google:Playfair Display` — the closest open-source approximation of
PP Editorial New's contrast and italic axis. Open the typography panel,
pick "Playfair Display" from the Google Fonts tab, assign to Heading.
- `google:Fraunces` — a more contemporary alternative with optical sizing;
works well as the secondary serif (`font-serif-sub`).
- `google:Cormorant Garamond` — slightly higher-contrast classic serif if
you want a more traditional broadsheet feel.
Commercial picks (require admin licensing + upload):
- `upload:PP Editorial New` — purchase from pangrampangram.com, upload via the
font picker's "Upload" tab.
- `upload:Migra` — purchase from pangrampangram.com (Migra is a sibling
family), upload via the picker.
## Body
Spec calls for **Inter** 1618px with tight leading and generous tracking on
small-caps kickers. Inter is already in the curated Google Fonts list:
- `google:Inter` — open the typography panel, pick "Inter" from the Google
Fonts tab, assign to Body. Aim for weights 400 + 600.
## Mono (captions, folios, metadata only)
Spec calls for **JetBrains Mono** at caption sizes for kickers, folios and
the colophon strip. Curated:
- `google:JetBrains Mono` — open the typography panel, pick "JetBrains Mono"
from the Google Fonts tab, assign to Mono. Weight 400 is enough.
Alternates: `google:Space Mono`, `google:IBM Plex Mono`.
## How the theme degrades when nothing is picked
The compiled CSS shipped with this `.so` uses the variables with sensible
fallbacks so a fresh install still reads like an editorial magazine:
- `--font-heading` falls back to `"Playfair Display", "Cormorant Garamond", Georgia, "Times New Roman", serif`
- `--font-body` falls back to `"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`
- `--font-mono` falls back to `"JetBrains Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`

0
assets/.gitkeep Normal file
View File

33
button_override.go Normal file
View File

@ -0,0 +1,33 @@
package main
import (
"bytes"
"context"
)
// ButtonOverrideBlock renders the built-in button block with the Magazine Bold
// squared-ink treatment and accent hover.
//
// Built-in button content shape: {"label": "...", "url": "...", "variant": "primary|secondary|destructive"}
func ButtonOverrideBlock(ctx context.Context, content map[string]any) string {
label := getString(content, "label")
if label == "" {
// fall back to "text" — some older button blocks used that key.
label = getString(content, "text")
}
url := getString(content, "url")
if url == "" {
url = getString(content, "href")
}
variant := getString(content, "variant")
switch variant {
case "secondary", "destructive", "primary":
// ok
default:
variant = "primary"
}
var buf bytes.Buffer
_ = mbButtonComponent(label, url, variant).Render(ctx, &buf)
return buf.String()
}

13
button_override.templ Normal file
View File

@ -0,0 +1,13 @@
package main
// mbButtonComponent renders a squared-ink button with accent hover.
// All variants get the visible 2px ring on :focus-visible from .mb-button.
templ mbButtonComponent(label, url, variant string) {
if label != "" {
<a
href={ templ.SafeURL(url) }
class={ "mb-button", "mb-button-" + variant }
data-variant={ variant }
>{ label }</a>
}
}

101
button_override_templ.go Normal file
View File

@ -0,0 +1,101 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// mbButtonComponent renders a squared-ink button with accent hover.
// All variants get the visible 2px ring on :focus-visible from .mb-button.
func mbButtonComponent(label, url, variant string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if label != "" {
var templ_7745c5c3_Var2 = []any{"mb-button", "mb-button-" + 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, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(url))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 8, Col: 28}
}
_, 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, 2, "\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" data-variant=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(variant)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 10, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 11, Col: 10}
}
_, 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, 5, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

35
colophon.go Normal file
View File

@ -0,0 +1,35 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// ColophonBlockMeta is the mono caption strip with credits and optional social row.
var ColophonBlockMeta = blocks.BlockMeta{
Key: "colophon",
Title: "Colophon",
Description: "Mono caption strip with credits and an optional social row.",
Source: "magazine-bold",
Category: blocks.CategoryLayout,
}
// ColophonData is what the templ component consumes.
type ColophonData struct {
Credits string
ShowSocial bool
}
// ColophonBlock renders the colophon strip.
// content keys: credits (richtext), showSocial (select "true"/"false").
func ColophonBlock(ctx context.Context, content map[string]any) string {
data := ColophonData{
Credits: getString(content, "credits"),
ShowSocial: getBool(content, "showSocial", false),
}
var buf bytes.Buffer
_ = colophonComponent(data).Render(ctx, &buf)
return buf.String()
}

19
colophon.templ Normal file
View File

@ -0,0 +1,19 @@
package main
// colophonComponent renders the mono colophon strip.
templ colophonComponent(data ColophonData) {
<div data-block="magazine-bold:colophon" class="py-8 font-mono mb-caption">
if data.Credits != "" {
<div class="mb-4">
@templ.Raw(data.Credits)
</div>
}
if data.ShowSocial {
<ul class="flex items-center gap-6 mb-hairline-top pt-4 mt-4">
<li><a href="#" class="hover:text-foreground">Instagram</a></li>
<li><a href="#" class="hover:text-foreground">Twitter</a></li>
<li><a href="#" class="hover:text-foreground">RSS</a></li>
</ul>
}
</div>
}

65
colophon_templ.go Normal file
View File

@ -0,0 +1,65 @@
// 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"
// colophonComponent renders the mono colophon strip.
func colophonComponent(data ColophonData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div data-block=\"magazine-bold:colophon\" class=\"py-8 font-mono mb-caption\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Credits != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"mb-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Credits).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.ShowSocial {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<ul class=\"flex items-center gap-6 mb-hairline-top pt-4 mt-4\"><li><a href=\"#\" class=\"hover:text-foreground\">Instagram</a></li><li><a href=\"#\" class=\"hover:text-foreground\">Twitter</a></li><li><a href=\"#\" class=\"hover:text-foreground\">RSS</a></li></ul>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

41
cover_story.go Normal file
View File

@ -0,0 +1,41 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// CoverStoryBlockMeta is the 12-column asymmetric hero anchoring the issue landing.
var CoverStoryBlockMeta = blocks.BlockMeta{
Key: "cover_story",
Title: "Cover Story",
Description: "12-column asymmetric hero with kicker, oversized display headline, deck and cover image.",
Source: "magazine-bold",
Category: blocks.CategoryContent,
}
// CoverStoryData is what the templ component consumes.
type CoverStoryData struct {
Kicker string
Headline string
Deck string
Image string
Link string
}
// CoverStoryBlock renders the cover-story hero.
// content keys: kicker, headline (richtext), deck (richtext), image (media), link.
func CoverStoryBlock(ctx context.Context, content map[string]any) string {
data := CoverStoryData{
Kicker: getString(content, "kicker"),
Headline: getString(content, "headline"),
Deck: getString(content, "deck"),
Image: blocks.ResolveMediaPath(getString(content, "image")),
Link: getString(content, "link"),
}
var buf bytes.Buffer
_ = coverStoryComponent(data).Render(ctx, &buf)
return buf.String()
}

46
cover_story.templ Normal file
View File

@ -0,0 +1,46 @@
package main
// coverStoryComponent renders the 12-column asymmetric cover hero.
// Note: the spec calls for an oversized display headline; this consumes
// `--font-heading` via .font-display and clamps via .text-folio.
templ coverStoryComponent(data CoverStoryData) {
<section data-block="magazine-bold:cover_story" class="w-full py-12">
<div class="max-w-6xl mx-auto px-6">
<div class="mb-grid-asym-cover">
<div class="mb-col-folio">
if data.Kicker != "" {
<div class="font-mono text-kicker text-muted-foreground mb-4">{ data.Kicker }</div>
}
</div>
<div class="mb-col-headline">
<h1 class="font-display text-folio text-foreground">
if data.Headline != "" {
@templ.Raw(data.Headline)
} else {
Cover story
}
</h1>
</div>
<div class="mb-col-deck">
if data.Deck != "" {
<div class="font-sans text-deck text-muted-foreground">
@templ.Raw(data.Deck)
</div>
}
</div>
</div>
if data.Image != "" {
<div class="mt-10">
<img src={ data.Image } alt="" class="w-full h-auto block" loading="lazy"/>
</div>
}
if data.Link != "" {
<div class="mt-6">
<a href={ templ.SafeURL(data.Link) } class="mb-button">
Read the story →
</a>
</div>
}
</div>
</section>
}

141
cover_story_templ.go Normal file
View File

@ -0,0 +1,141 @@
// 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"
// coverStoryComponent renders the 12-column asymmetric cover hero.
// Note: the spec calls for an oversized display headline; this consumes
// `--font-heading` via .font-display and clamps via .text-folio.
func coverStoryComponent(data CoverStoryData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section data-block=\"magazine-bold:cover_story\" class=\"w-full py-12\"><div class=\"max-w-6xl mx-auto px-6\"><div class=\"mb-grid-asym-cover\"><div class=\"mb-col-folio\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Kicker != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"font-mono text-kicker text-muted-foreground mb-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Kicker)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cover_story.templ`, Line: 12, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div><div class=\"mb-col-headline\"><h1 class=\"font-display text-folio text-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Headline != "" {
templ_7745c5c3_Err = templ.Raw(data.Headline).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "Cover story")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</h1></div><div class=\"mb-col-deck\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Deck != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"font-sans text-deck text-muted-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Deck).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Image != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"mt-10\"><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Image)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cover_story.templ`, Line: 34, Col: 26}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" alt=\"\" class=\"w-full h-auto block\" loading=\"lazy\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Link != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"mt-6\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.Link))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cover_story.templ`, Line: 39, Col: 39}
}
_, 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, 13, "\" class=\"mb-button\">Read the story →</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

191
css.go Normal file
View File

@ -0,0 +1,191 @@
package main
// magazineBoldUtilityCSS is appended into the host Tailwind input. It carries
// the editorial display-type utilities, font-slot classes, the hairline rule
// motif, the asymmetric grid utilities, and the per-accent pull-quote variants.
//
// All font-family references go through CSS variables per docs/FONTS.md.
// All color references go through `hsl(var(--token))` consumers of the 19
// theme tokens — no hardcoded hex, rgb(), or named colors. Per-accent
// pull-quote variants intentionally key off the `--accent` token (which the
// active preset rotates between hot pink, electric blue and lime).
//
// The `data-mb-accent` attribute selectors let a pull-quote pin a specific
// accent independently of the preset — they map back into the theme token
// space using the HSL triples documented in the spec §4 accent rows.
const magazineBoldUtilityCSS = `
/* ===========================================================
* Magazine Bold theme utilities
* Appended to the Tailwind input via CSSManifest.InputCSSAppend.
* =========================================================== */
/* --- Font-slot classes (consume CMS-managed font variables) --- */
.font-display {
font-family: var(--font-heading, "Playfair Display", "Cormorant Garamond", Georgia, "Times New Roman", serif);
font-feature-settings: "ss01", "ss02", "kern", "liga";
font-optical-sizing: auto;
}
.font-serif-sub {
font-family: var(--font-heading, "Fraunces", "Playfair Display", Georgia, "Times New Roman", serif);
}
.font-sans {
font-family: var(--font-body, "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
}
.font-mono {
font-family: var(--font-mono, "JetBrains Mono", "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
}
/* --- Display-size utilities ------------------------------- */
/* text-folio: 220pt-equivalent oversized folio number / cover hit.
The clamp resolves to ~220px (~165pt) at 1440px, scaling down to 80px on mobile. */
.text-folio {
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
font-size: clamp(80px, 18vw, 240px);
line-height: 0.85;
letter-spacing: -0.04em;
font-weight: 200;
font-style: italic;
}
.text-deck {
font-family: var(--font-body, "Inter", system-ui, sans-serif);
font-size: clamp(28px, 3.2vw, 48px);
line-height: 1.15;
letter-spacing: -0.01em;
font-weight: 400;
}
.text-kicker {
font-family: var(--font-mono, "JetBrains Mono", monospace);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.18em;
font-weight: 600;
}
/* --- Hairline rule motif ---------------------------------- */
.mb-hairline { border-color: hsl(var(--border)); border-width: 1px; }
.mb-hairline-top { border-top: 1px solid hsl(var(--border)); }
.mb-hairline-bottom { border-bottom: 1px solid hsl(var(--border)); }
/* --- Asymmetric editorial grid ---------------------------- */
.mb-grid-12 {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 1.5rem;
}
.mb-grid-asym-cover {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 2rem;
align-items: end;
}
.mb-col-folio { grid-column: 1 / span 2; }
.mb-col-headline { grid-column: 3 / span 7; }
.mb-col-deck { grid-column: 10 / span 3; }
.mb-col-image-wide { grid-column: 1 / -1; }
@media (max-width: 1024px) {
.mb-grid-asym-cover { grid-template-columns: 1fr; gap: 1.25rem; }
.mb-col-folio, .mb-col-headline, .mb-col-deck { grid-column: 1 / -1; }
}
/* --- Photo essay frame spans ------------------------------ */
.mb-photo-grid {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 1.25rem;
}
.mb-frame-half { grid-column: span 6 / span 6; }
.mb-frame-full { grid-column: 1 / -1; }
.mb-frame-tall { grid-column: span 5 / span 5; grid-row: span 2; }
@media (max-width: 768px) {
.mb-photo-grid { grid-template-columns: 1fr; }
.mb-frame-half, .mb-frame-full, .mb-frame-tall { grid-column: 1 / -1; grid-row: auto; }
}
/* --- Color-block accent panels ---------------------------- */
.colorblock-accent {
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
}
/* --- Per-accent pull-quote variants ----------------------- *
* These let a pull_quote override the preset accent on a per-instance basis.
* They use literal HSL triples that match the spec §4 accent values so a
* "pink" pull-quote always reads pink even on the ink-blue preset.
*/
[data-mb-accent="pink"] { --mb-accent-h: 330; --mb-accent-s: 95%; --mb-accent-l: 58%; }
[data-mb-accent="blue"] { --mb-accent-h: 220; --mb-accent-s: 100%; --mb-accent-l: 56%; }
[data-mb-accent="lime"] { --mb-accent-h: 78; --mb-accent-s: 90%; --mb-accent-l: 50%; }
[data-mb-accent] .mb-quote-bar {
background-color: hsl(var(--mb-accent-h) var(--mb-accent-s) var(--mb-accent-l));
}
[data-mb-accent] .mb-quote-text { color: hsl(var(--foreground)); }
/* --- Drop-cap (heading override) -------------------------- */
.mb-dropcap::first-letter {
font-family: var(--font-heading, "Playfair Display", Georgia, serif);
font-style: italic;
font-weight: 200;
float: left;
font-size: 5.5em;
line-height: 0.85;
padding-right: 0.08em;
margin-top: 0.04em;
color: hsl(var(--foreground));
}
/* --- Body text override (Inter + margin folios) ----------- */
.mb-body {
font-family: var(--font-body, "Inter", system-ui, sans-serif);
font-size: clamp(16px, 1.05vw, 18px);
line-height: 1.55;
letter-spacing: 0.005em;
color: hsl(var(--foreground));
}
/* --- Squared ink button ----------------------------------- */
.mb-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
background-color: hsl(var(--primary));
color: hsl(var(--primary-foreground));
font-family: var(--font-mono, "JetBrains Mono", monospace);
font-size: 12px;
letter-spacing: 0.14em;
text-transform: uppercase;
padding: 0.875rem 1.5rem;
border: 1px solid hsl(var(--primary));
border-radius: 0;
transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}
.mb-button:hover {
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
border-color: hsl(var(--accent));
}
.mb-button:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}
/* --- Caption (mono, used by image override + photo essay) -- */
.mb-caption {
font-family: var(--font-mono, "JetBrains Mono", monospace);
font-size: 12px;
line-height: 1.45;
letter-spacing: 0.01em;
color: hsl(var(--muted-foreground));
}
/* --- Full-bleed image with numbered folio ----------------- */
.mb-fullbleed { width: 100vw; max-width: 100vw; margin-left: 50%; transform: translateX(-50%); }
.mb-image-folio {
font-family: var(--font-mono, "JetBrains Mono", monospace);
font-size: 11px;
letter-spacing: 0.16em;
color: hsl(var(--muted-foreground));
text-transform: uppercase;
}
`

100
email_wrapper.go Normal file
View File

@ -0,0 +1,100 @@
package main
import (
"bytes"
"context"
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// MagazineBoldEmailWrapper wraps body content in the Magazine Bold email template.
// The template is a 600px paper-toned table with a PP Editorial New wordmark,
// an ink hairline, body in Inter 16/24, and a mono colophon strip.
func MagazineBoldEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = magazineBoldEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}
// Hex defaults map onto the paper-pink light preset so wallets / clients that
// don't ship custom colours still resolve to the brand's paper tone.
// Spec §4 paper-pink light: background `38 30% 96%` → ~#F7F2EB
// foreground `0 0% 8%` → ~#141414
// border `0 0% 88%` → ~#E0E0E0
// muted `38 20% 92%`→ ~#EEE9E0
// mutedFg `0 0% 40%` → ~#666666
// accent `330 95% 58%` → ~#F0298A
func mbEmailBg(ctx templates.EmailContext) string {
if ctx.Colors.Background != "" {
return ctx.Colors.Background
}
return "#F7F2EB"
}
func mbEmailFg(ctx templates.EmailContext) string {
if ctx.Colors.Foreground != "" {
return ctx.Colors.Foreground
}
return "#141414"
}
func mbEmailMuted(ctx templates.EmailContext) string {
if ctx.Colors.Muted != "" {
return ctx.Colors.Muted
}
return "#EEE9E0"
}
func mbEmailMutedFg(ctx templates.EmailContext) string {
if ctx.Colors.MutedForeground != "" {
return ctx.Colors.MutedForeground
}
return "#666666"
}
func mbEmailBorder(ctx templates.EmailContext) string {
if ctx.Colors.Border != "" {
return ctx.Colors.Border
}
return "#E0E0E0"
}
func mbEmailCard(ctx templates.EmailContext) string {
if ctx.Colors.Card != "" {
return ctx.Colors.Card
}
return "#FFFFFF"
}
// mbEmailDisplayStack is the serif fallback the wordmark falls back to in
// clients that strip @font-face (Outlook 365 web). System serifs only.
const mbEmailDisplayStack = `'PP Editorial New', Georgia, 'Times New Roman', serif`
// mbEmailBodyStack is the sans fallback used for body copy in clients that
// strip @font-face.
const mbEmailBodyStack = `'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif`
// mbEmailMonoStack is the mono fallback used for the colophon strip.
const mbEmailMonoStack = `'JetBrains Mono', 'Courier New', Courier, monospace`
// mbEmailStyleBody styles the outer body tag.
func mbEmailStyleBody(ctx templates.EmailContext) string {
return fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: %s;", mbEmailBg(ctx), mbEmailBodyStack)
}
func mbEmailStyleHeader(ctx templates.EmailContext) string {
return fmt.Sprintf("padding: 32px 40px; background-color: %s; border-bottom: 1px solid %s;", mbEmailCard(ctx), mbEmailBorder(ctx))
}
func mbEmailStyleWordmark(ctx templates.EmailContext) string {
return fmt.Sprintf("margin: 0; font-family: %s; font-style: italic; font-weight: 200; font-size: 42px; line-height: 1; letter-spacing: -0.02em; color: %s;", mbEmailDisplayStack, mbEmailFg(ctx))
}
func mbEmailStyleBodyContent(ctx templates.EmailContext) string {
return fmt.Sprintf("padding: 40px 48px; background-color: %s; color: %s; font-family: %s; font-size: 16px; line-height: 24px;", mbEmailCard(ctx), mbEmailFg(ctx), mbEmailBodyStack)
}
func mbEmailStyleColophon(ctx templates.EmailContext) string {
return fmt.Sprintf("padding: 24px 48px; background-color: %s; border-top: 1px solid %s; font-family: %s; font-size: 12px; line-height: 18px; color: %s; letter-spacing: 0.08em; text-transform: uppercase;", mbEmailMuted(ctx), mbEmailBorder(ctx), mbEmailMonoStack, mbEmailMutedFg(ctx))
}

83
email_wrapper.templ Normal file
View File

@ -0,0 +1,83 @@
package main
import (
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// magazineBoldEmailTemplate is the 600px paper-toned editorial email wrapper.
// It uses inlined hex defaults that map onto the paper-pink light preset so the
// layout reads correctly in clients that strip server CSS.
templ magazineBoldEmailTemplate(emailCtx templates.EmailContext, body string) {
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="x-apple-disable-message-reformatting"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>{ emailCtx.SiteSettings.SiteName }</title>
<style type="text/css">
body, table, td, p, a, li, blockquote { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
body { margin: 0 !important; padding: 0 !important; width: 100% !important; }
@media only screen and (max-width: 620px) {
.mb-email-container { width: 100% !important; max-width: 100% !important; }
.mb-email-pad { padding-left: 24px !important; padding-right: 24px !important; }
}
</style>
</head>
<body style={ mbEmailStyleBody(emailCtx) }>
if emailCtx.PreviewText != "" {
<div style="display:none;max-height:0;overflow:hidden;mso-hide:all;">
{ emailCtx.PreviewText }
</div>
}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center" style={ fmt.Sprintf("padding: 48px 10px; background-color: %s;", mbEmailBg(emailCtx)) }>
<table role="presentation" class="mb-email-container" width="600" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s;", mbEmailCard(emailCtx), mbEmailBorder(emailCtx)) }>
<!-- Wordmark / masthead -->
<tr>
<td align="left" class="mb-email-pad" style={ mbEmailStyleHeader(emailCtx) }>
if emailCtx.SiteSettings.LogoURL != "" {
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="max-height:48px;width:auto;display:block;"/>
} else if emailCtx.SiteSettings.SiteName != "" {
<h1 style={ mbEmailStyleWordmark(emailCtx) }>{ emailCtx.SiteSettings.SiteName }</h1>
}
</td>
</tr>
<!-- Ink hairline -->
<tr>
<td style={ fmt.Sprintf("height: 1px; line-height: 1px; background-color: %s;", mbEmailFg(emailCtx)) }>&nbsp;</td>
</tr>
<!-- Body -->
<tr>
<td class="mb-email-pad" style={ mbEmailStyleBodyContent(emailCtx) }>
@templ.Raw(body)
</td>
</tr>
<!-- Mono colophon -->
<tr>
<td class="mb-email-pad" style={ mbEmailStyleColophon(emailCtx) }>
<div>{ emailCtx.SiteSettings.SiteName }</div>
if emailCtx.SiteSettings.SiteURL != "" {
<div style="margin-top:8px;">
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", mbEmailMutedFg(emailCtx)) }>{ emailCtx.SiteSettings.SiteURL }</a>
</div>
}
if emailCtx.UnsubscribeURL != "" {
<div style="margin-top:8px;">
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", mbEmailMutedFg(emailCtx)) }>Unsubscribe</a>
</div>
}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
}

344
email_wrapper_templ.go Normal file
View File

@ -0,0 +1,344 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// magazineBoldEmailTemplate is the 600px paper-toned editorial email wrapper.
// It uses inlined hex defaults that map onto the paper-pink light preset so the
// layout reads correctly in clients that strip server CSS.
func magazineBoldEmailTemplate(emailCtx templates.EmailContext, body string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"x-apple-disable-message-reformatting\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 19, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><style type=\"text/css\">\n\t\t\t\tbody, table, td, p, a, li, blockquote { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }\n\t\t\t\ttable, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }\n\t\t\t\timg { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }\n\t\t\t\tbody { margin: 0 !important; padding: 0 !important; width: 100% !important; }\n\t\t\t\t@media only screen and (max-width: 620px) {\n\t\t\t\t\t.mb-email-container { width: 100% !important; max-width: 100% !important; }\n\t\t\t\t\t.mb-email-pad { padding-left: 24px !important; padding-right: 24px !important; }\n\t\t\t\t}\n\t\t\t</style></head><body style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(mbEmailStyleBody(emailCtx))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 31, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.PreviewText != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div style=\"display:none;max-height:0;overflow:hidden;mso-hide:all;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 34, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 48px 10px; background-color: %s;", mbEmailBg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 39, Col: 109}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><table role=\"presentation\" class=\"mb-email-container\" width=\"600\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s;", mbEmailCard(emailCtx), mbEmailBorder(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 40, Col: 247}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><!-- Wordmark / masthead --><tr><td align=\"left\" class=\"mb-email-pad\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(mbEmailStyleHeader(emailCtx))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 43, Col: 82}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.LogoURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.LogoURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 45, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 45, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" style=\"max-height:48px;width:auto;display:block;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if emailCtx.SiteSettings.SiteName != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h1 style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(mbEmailStyleWordmark(emailCtx))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 47, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 47, Col: 87}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</td></tr><!-- Ink hairline --><tr><td style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("height: 1px; line-height: 1px; background-color: %s;", mbEmailFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 53, Col: 108}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">&nbsp;</td></tr><!-- Body --><tr><td class=\"mb-email-pad\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(mbEmailStyleBodyContent(emailCtx))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 57, Col: 74}
}
_, 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, 18, "\">")
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, 19, "</td></tr><!-- Mono colophon --><tr><td class=\"mb-email-pad\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(mbEmailStyleColophon(emailCtx))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 63, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\"><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 64, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.SiteURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div style=\"margin-top:8px;\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 templ.SafeURL
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.SiteSettings.SiteURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 67, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: underline;", mbEmailMutedFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 67, Col: 155}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 67, Col: 189}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if emailCtx.UnsubscribeURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<div style=\"margin-top:8px;\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 templ.SafeURL
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.UnsubscribeURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 72, Col: 59}
}
_, 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, 27, "\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: underline;", mbEmailMutedFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 72, Col: 149}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "\">Unsubscribe</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</td></tr></table></td></tr></table></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

49
embed.go Normal file
View File

@ -0,0 +1,49 @@
package main
import (
"embed"
"io/fs"
"net/http"
)
//go:embed assets/*
var assetsFS embed.FS
//go:embed schemas/*
var schemasFS embed.FS
//go:embed presets.json
var presetsData []byte
//go:embed fonts.json
var fontsData []byte
//go:embed plugin.mod
var pluginModBytes []byte
// Assets returns the embedded assets filesystem.
func Assets() fs.FS {
sub, _ := fs.Sub(assetsFS, "assets")
return sub
}
// Schemas returns the embedded schemas filesystem.
func Schemas() fs.FS {
sub, _ := fs.Sub(schemasFS, "schemas")
return sub
}
// AssetsHandler returns an http.Handler that serves the embedded assets.
func AssetsHandler() http.Handler {
return http.FileServer(http.FS(Assets()))
}
// ThemePresets returns the embedded theme presets JSON.
func ThemePresets() []byte {
return presetsData
}
// BundledFonts returns the embedded fonts manifest JSON.
func BundledFonts() []byte {
return fontsData
}

1
fonts.json Normal file
View File

@ -0,0 +1 @@
[]

20
go.mod Normal file
View File

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

42
go.sum Normal file
View File

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

63
heading_override.go Normal file
View File

@ -0,0 +1,63 @@
package main
import (
"bytes"
"context"
"strconv"
)
// HeadingOverrideBlock renders headings with the Magazine Bold display-serif scale
// and an optional drop-cap on h1.
//
// Built-in heading content shape: {"text": "...", "level": 1-6, "textClass": "..."}
func HeadingOverrideBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
textClass := getString(content, "textClass")
level := parseHeadingLevel(content)
var buf bytes.Buffer
_ = mbHeadingComponent(level, text, textClass).Render(ctx, &buf)
return buf.String()
}
// parseHeadingLevel parses the level from content, defaulting to 2.
func parseHeadingLevel(content map[string]any) int {
if level, ok := content["level"].(float64); ok {
l := int(level)
if l >= 1 && l <= 6 {
return l
}
}
if level, ok := content["level"].(int); ok {
if level >= 1 && level <= 6 {
return level
}
}
if level, ok := content["level"].(string); ok {
if l, err := strconv.Atoi(level); err == nil && l >= 1 && l <= 6 {
return l
}
}
return 2
}
// mbHeadingBaseClass returns the Magazine Bold class for each heading level.
// h1 picks up the .text-folio clamp + drop-cap behaviour.
func mbHeadingBaseClass(level int) string {
switch level {
case 1:
return "font-display text-folio text-foreground mb-dropcap"
case 2:
return "font-display text-deck text-foreground"
case 3:
return "font-display text-3xl text-foreground"
case 4:
return "font-serif-sub text-2xl text-foreground"
case 5:
return "font-serif-sub text-xl text-foreground"
case 6:
return "font-mono text-kicker text-muted-foreground"
default:
return "font-display text-deck text-foreground"
}
}

21
heading_override.templ Normal file
View File

@ -0,0 +1,21 @@
package main
// mbHeadingComponent renders a heading at the requested level with Magazine Bold scale.
templ mbHeadingComponent(level int, text, textClass string) {
switch level {
case 1:
<h1 class={ mbHeadingBaseClass(1), textClass }>{ text }</h1>
case 2:
<h2 class={ mbHeadingBaseClass(2), textClass }>{ text }</h2>
case 3:
<h3 class={ mbHeadingBaseClass(3), textClass }>{ text }</h3>
case 4:
<h4 class={ mbHeadingBaseClass(4), textClass }>{ text }</h4>
case 5:
<h5 class={ mbHeadingBaseClass(5), textClass }>{ text }</h5>
case 6:
<h6 class={ mbHeadingBaseClass(6), textClass }>{ text }</h6>
default:
<h2 class={ mbHeadingBaseClass(2), textClass }>{ text }</h2>
}
}

291
heading_override_templ.go Normal file
View File

@ -0,0 +1,291 @@
// 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"
// mbHeadingComponent renders a heading at the requested level with Magazine Bold scale.
func mbHeadingComponent(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{mbHeadingBaseClass(1), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">")
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: 7, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 2:
var templ_7745c5c3_Var5 = []any{mbHeadingBaseClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var5).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
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: 9, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 3:
var templ_7745c5c3_Var8 = []any{mbHeadingBaseClass(3), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h3 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var8).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">")
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: 11, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 4:
var templ_7745c5c3_Var11 = []any{mbHeadingBaseClass(4), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<h4 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var11).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">")
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: 13, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</h4>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 5:
var templ_7745c5c3_Var14 = []any{mbHeadingBaseClass(5), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h5 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var14).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
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: 15, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h5>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 6:
var templ_7745c5c3_Var17 = []any{mbHeadingBaseClass(6), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h6 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">")
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: 17, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</h6>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
var templ_7745c5c3_Var20 = []any{mbHeadingBaseClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var20).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
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: 19, Col: 56}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

71
helpers.go Normal file
View File

@ -0,0 +1,71 @@
package main
// getString extracts a string value from content map.
func getString(content map[string]any, key string) string {
if v, ok := content[key].(string); ok {
return v
}
return ""
}
// getInt extracts an int value from content map (handles float64 from JSON).
func getInt(content map[string]any, key string, defaultVal int) int {
if v, ok := content[key].(float64); ok {
return int(v)
}
if v, ok := content[key].(int); ok {
return v
}
return defaultVal
}
// getBool extracts a bool from content (handles "true"/"false" strings).
func getBool(content map[string]any, key string, defaultVal bool) bool {
if v, ok := content[key].(bool); ok {
return v
}
if v, ok := content[key].(string); ok {
switch v {
case "true", "TRUE", "True", "1", "yes", "on":
return true
case "false", "FALSE", "False", "0", "no", "off", "":
return false
}
}
return defaultVal
}
// getSlice extracts a slice of maps from content.
func getSlice(content map[string]any, key string) []map[string]any {
if v, ok := content[key].([]any); ok {
result := make([]map[string]any, 0, len(v))
for _, item := range v {
if m, ok := item.(map[string]any); ok {
result = append(result, m)
}
}
return result
}
return nil
}
// normalizeAccent maps the schema accent enum into a CSS class suffix.
// Unknown values fall back to "pink" (the default-preset accent slot).
func normalizeAccent(raw string) string {
switch raw {
case "pink", "blue", "lime":
return raw
default:
return "pink"
}
}
// normalizeSpan maps the photo-essay frame span enum.
func normalizeSpan(raw string) string {
switch raw {
case "half", "full", "tall":
return raw
default:
return "half"
}
}

23
image_override.go Normal file
View File

@ -0,0 +1,23 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// ImageOverrideBlock renders the built-in image block with the Magazine Bold
// treatment: optional full-bleed mode + a mono caption folio.
//
// Built-in image content shape: {"src": "...", "alt": "...", "caption": "...", "fullBleed": true/false}
func ImageOverrideBlock(ctx context.Context, content map[string]any) string {
src := blocks.ResolveMediaPath(getString(content, "src"))
alt := getString(content, "alt")
caption := getString(content, "caption")
fullBleed := getBool(content, "fullBleed", false)
var buf bytes.Buffer
_ = mbImageComponent(src, alt, caption, fullBleed).Render(ctx, &buf)
return buf.String()
}

14
image_override.templ Normal file
View File

@ -0,0 +1,14 @@
package main
// mbImageComponent renders an image with optional full-bleed mode and a numbered caption.
// Skips rendering if no src — guarantees zero broken <img>.
templ mbImageComponent(src, alt, caption string, fullBleed bool) {
if src != "" {
<figure class={ "my-8", templ.KV("mb-fullbleed", fullBleed) }>
<img src={ src } alt={ alt } class="w-full h-auto block" loading="lazy"/>
if caption != "" {
<figcaption class="mb-caption font-mono mt-2 px-6">{ caption }</figcaption>
}
</figure>
}
}

111
image_override_templ.go Normal file
View File

@ -0,0 +1,111 @@
// 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"
// mbImageComponent renders an image with optional full-bleed mode and a numbered caption.
// Skips rendering if no src — guarantees zero broken <img>.
func mbImageComponent(src, alt, caption string, fullBleed 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 src != "" {
var templ_7745c5c3_Var2 = []any{"my-8", templ.KV("mb-fullbleed", fullBleed)}
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, "<figure class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(src)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 8, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(alt)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 8, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" class=\"w-full h-auto block\" loading=\"lazy\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if caption != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<figcaption class=\"mb-caption font-mono mt-2 px-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(caption)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 10, Col: 64}
}
_, 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, 6, "</figcaption>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</figure>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

43
issue_archive.go Normal file
View File

@ -0,0 +1,43 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// IssueArchiveBlockMeta is the numbered list of back-issues.
var IssueArchiveBlockMeta = blocks.BlockMeta{
Key: "issue_archive",
Title: "Issue Archive",
Description: "Numbered list of back-issues driven by a bucket query.",
Source: "magazine-bold",
Category: blocks.CategoryNavigation,
}
// IssueArchiveData is what the templ component consumes.
type IssueArchiveData struct {
BucketSlug string
Limit int
}
// IssueArchiveBlock renders the issue archive list.
// content keys: bucketSlug (bucket-picker), limit (number).
//
// NOTE: bucket-row resolution is handled by the host at render-time via
// `bucket_data` injection — this block renders the scaffold and an empty
// state if the host hasn't supplied items yet.
func IssueArchiveBlock(ctx context.Context, content map[string]any) string {
data := IssueArchiveData{
BucketSlug: getString(content, "bucketSlug"),
Limit: getInt(content, "limit", 12),
}
items := getSlice(content, "items")
if items == nil {
items = getSlice(content, "bucket_data")
}
var buf bytes.Buffer
_ = issueArchiveComponent(data, items).Render(ctx, &buf)
return buf.String()
}

37
issue_archive.templ Normal file
View File

@ -0,0 +1,37 @@
package main
import "fmt"
// issueArchiveComponent renders a numbered archive list.
// Unknown / empty bucket → renders an empty-state message, no broken markup.
templ issueArchiveComponent(data IssueArchiveData, items []map[string]any) {
<nav data-block="magazine-bold:issue_archive" class="py-6">
<div class="font-mono text-kicker text-muted-foreground mb-6">Issue Archive</div>
if len(items) == 0 {
<p class="font-mono mb-caption">No issues yet.</p>
} else {
<ol class="grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-4">
for i, item := range items {
if i >= max(1, data.Limit) {
{{ break }}
}
<li class="flex items-baseline gap-4 mb-hairline-bottom py-3">
<span class="font-mono text-kicker text-muted-foreground w-10">{ fmt.Sprintf("%02d", i+1) }</span>
<span class="font-display flex-1 text-foreground">
if title, ok := item["title"].(string); ok {
{ title }
} else if name, ok := item["name"].(string); ok {
{ name }
} else {
Untitled issue
}
</span>
if date, ok := item["date"].(string); ok && date != "" {
<span class="font-mono mb-caption">{ date }</span>
}
</li>
}
</ol>
}
</nav>
}

138
issue_archive_templ.go Normal file
View File

@ -0,0 +1,138 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "fmt"
// issueArchiveComponent renders a numbered archive list.
// Unknown / empty bucket → renders an empty-state message, no broken markup.
func issueArchiveComponent(data IssueArchiveData, items []map[string]any) 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, "<nav data-block=\"magazine-bold:issue_archive\" class=\"py-6\"><div class=\"font-mono text-kicker text-muted-foreground mb-6\">Issue Archive</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(items) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<p class=\"font-mono mb-caption\">No issues yet.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<ol class=\"grid grid-cols-1 md:grid-cols-2 gap-x-12 gap-y-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i, item := range items {
if i >= max(1, data.Limit) {
break
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " <li class=\"flex items-baseline gap-4 mb-hairline-bottom py-3\"><span class=\"font-mono text-kicker text-muted-foreground w-10\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d", i+1))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `issue_archive.templ`, Line: 19, Col: 95}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span> <span class=\"font-display flex-1 text-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if title, ok := item["title"].(string); ok {
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: `issue_archive.templ`, Line: 22, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if name, ok := item["name"].(string); ok {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `issue_archive.templ`, Line: 24, Col: 14}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "Untitled issue")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if date, ok := item["date"].(string); ok && date != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<span class=\"font-mono mb-caption\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(date)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `issue_archive.templ`, Line: 30, Col: 48}
}
_, 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, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</ol>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

37
masthead.go Normal file
View File

@ -0,0 +1,37 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// MastheadBlockMeta is the issue-masthead (wordmark + folio bar) at the top of the page.
var MastheadBlockMeta = blocks.BlockMeta{
Key: "masthead",
Title: "Issue Masthead",
Description: "Wordmark, issue number and tagline strip — sits at the top of every page.",
Source: "magazine-bold",
Category: blocks.CategoryLayout,
}
// MastheadData is what the templ component consumes.
type MastheadData struct {
IssueNo string
Tagline string
Date string
}
// MastheadBlock renders the issue masthead.
// content keys: issueNo, tagline, date — all optional, no panic on empty.
func MastheadBlock(ctx context.Context, content map[string]any) string {
data := MastheadData{
IssueNo: getString(content, "issueNo"),
Tagline: getString(content, "tagline"),
Date: getString(content, "date"),
}
var buf bytes.Buffer
_ = mastheadComponent(data).Render(ctx, &buf)
return buf.String()
}

18
masthead.templ Normal file
View File

@ -0,0 +1,18 @@
package main
// mastheadComponent renders the wordmark + folio strip.
templ mastheadComponent(data MastheadData) {
<div data-block="magazine-bold:masthead" class="py-6 mb-hairline-bottom flex items-end justify-between gap-6">
<div class="flex items-baseline gap-4">
if data.IssueNo != "" {
<span class="font-mono text-kicker">No. { data.IssueNo }</span>
}
if data.Tagline != "" {
<span class="font-mono text-kicker text-muted-foreground">{ data.Tagline }</span>
}
</div>
if data.Date != "" {
<span class="font-mono text-kicker text-muted-foreground">{ data.Date }</span>
}
</div>
}

106
masthead_templ.go Normal file
View File

@ -0,0 +1,106 @@
// 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"
// mastheadComponent renders the wordmark + folio strip.
func mastheadComponent(data MastheadData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div data-block=\"magazine-bold:masthead\" class=\"py-6 mb-hairline-bottom flex items-end justify-between gap-6\"><div class=\"flex items-baseline gap-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.IssueNo != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<span class=\"font-mono text-kicker\">No. ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.IssueNo)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 8, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Tagline != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"font-mono text-kicker text-muted-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Tagline)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 11, Col: 76}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Date != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<span class=\"font-mono text-kicker text-muted-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Date)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 15, Col: 72}
}
_, 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, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

70
photo_essay.go Normal file
View File

@ -0,0 +1,70 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// PhotoEssayBlockMeta is the numbered photo-essay scaffolding.
var PhotoEssayBlockMeta = blocks.BlockMeta{
Key: "photo_essay",
Title: "Photo Essay",
Description: "Numbered photo-essay with asymmetric frame spans (half / full / tall) and mono captions.",
Source: "magazine-bold",
Category: blocks.CategoryContent,
}
// PhotoFrame is a single image in the photo essay.
type PhotoFrame struct {
Number int
Image string
Caption string
Span string
}
// PhotoEssayData is what the templ component consumes.
type PhotoEssayData struct {
Title string
Frames []PhotoFrame
}
// PhotoEssayBlock renders the photo essay scaffolding.
// content keys: title (text), frames (collection of {image, caption, span}).
func PhotoEssayBlock(ctx context.Context, content map[string]any) string {
rawFrames := getSlice(content, "frames")
frames := make([]PhotoFrame, 0, len(rawFrames))
for i, item := range rawFrames {
img := blocks.ResolveMediaPath(getString(item, "image"))
// Skip frames that have no image (malformed-content safety).
if img == "" {
continue
}
frames = append(frames, PhotoFrame{
Number: i + 1,
Image: img,
Caption: getString(item, "caption"),
Span: normalizeSpan(getString(item, "span")),
})
}
data := PhotoEssayData{
Title: getString(content, "title"),
Frames: frames,
}
var buf bytes.Buffer
_ = photoEssayComponent(data).Render(ctx, &buf)
return buf.String()
}
// photoFrameSpanClass maps a span string to its CSS utility class.
func photoFrameSpanClass(span string) string {
switch span {
case "full":
return "mb-frame-full"
case "tall":
return "mb-frame-tall"
default:
return "mb-frame-half"
}
}

31
photo_essay.templ Normal file
View File

@ -0,0 +1,31 @@
package main
import "fmt"
// photoEssayComponent renders a numbered photo-essay grid.
templ photoEssayComponent(data PhotoEssayData) {
<section data-block="magazine-bold:photo_essay" class="w-full py-12">
<div class="max-w-6xl mx-auto px-6">
if data.Title != "" {
<h2 class="font-display text-deck text-foreground mb-8">{ data.Title }</h2>
}
if len(data.Frames) == 0 {
<div class="font-mono mb-caption py-12">No frames yet.</div>
} else {
<div class="mb-photo-grid">
for _, frame := range data.Frames {
<figure class={ photoFrameSpanClass(frame.Span) }>
<img src={ frame.Image } alt={ frame.Caption } class="w-full h-auto block" loading="lazy"/>
<figcaption class="mb-caption font-mono mt-2 flex items-baseline gap-3">
<span class="mb-image-folio">{ fmt.Sprintf("%02d", frame.Number) }</span>
if frame.Caption != "" {
<span>{ frame.Caption }</span>
}
</figcaption>
</figure>
}
</div>
}
</div>
</section>
}

167
photo_essay_templ.go Normal file
View File

@ -0,0 +1,167 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "fmt"
// photoEssayComponent renders a numbered photo-essay grid.
func photoEssayComponent(data PhotoEssayData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section data-block=\"magazine-bold:photo_essay\" class=\"w-full py-12\"><div class=\"max-w-6xl mx-auto px-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h2 class=\"font-display text-deck text-foreground mb-8\">")
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: `photo_essay.templ`, Line: 10, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Frames) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"font-mono mb-caption py-12\">No frames yet.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"mb-photo-grid\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, frame := range data.Frames {
var templ_7745c5c3_Var3 = []any{photoFrameSpanClass(frame.Span)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<figure class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var3).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `photo_essay.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(frame.Image)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `photo_essay.templ`, Line: 18, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(frame.Caption)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `photo_essay.templ`, Line: 18, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" class=\"w-full h-auto block\" loading=\"lazy\"><figcaption class=\"mb-caption font-mono mt-2 flex items-baseline gap-3\"><span class=\"mb-image-folio\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%02d", frame.Number))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `photo_essay.templ`, Line: 20, Col: 72}
}
_, 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, 10, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if frame.Caption != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(frame.Caption)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `photo_essay.templ`, Line: 22, Col: 30}
}
_, 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, 12, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</figcaption></figure>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

12
plugin.mod Normal file
View File

@ -0,0 +1,12 @@
[plugin]
name = "magazine-bold"
display_name = "Magazine Bold"
scope = "@themes"
version = "0.1.0"
description = "Editorial-print theme with oversized display type, asymmetric grids and rotating color-block accents for fashion, streetwear and culture publications."
kind = "theme"
categories = ["templates", "media"]
tags = ["fashion", "lifestyle", "streetwear", "magazine", "bold", "display", "music", "editorial", "art"]
[compatibility]
block_core = ">=0.11.0 <0.12.0"

152
presets.json Normal file
View File

@ -0,0 +1,152 @@
[
{
"id": "paper-pink",
"name": "Paper & Hot Pink",
"description": "Warm paper-tone background with a hot-pink editorial accent (default).",
"theme": {
"mode": "both",
"lightColors": {
"background": "38 30% 96%",
"foreground": "0 0% 8%",
"card": "0 0% 100%",
"cardForeground": "0 0% 8%",
"popover": "0 0% 100%",
"popoverForeground": "0 0% 8%",
"primary": "0 0% 8%",
"primaryForeground": "38 30% 96%",
"secondary": "38 20% 90%",
"secondaryForeground": "0 0% 8%",
"muted": "38 20% 92%",
"mutedForeground": "0 0% 40%",
"accent": "330 95% 58%",
"accentForeground": "0 0% 100%",
"destructive": "0 84% 55%",
"destructiveForeground": "0 0% 100%",
"border": "0 0% 88%",
"input": "0 0% 88%",
"ring": "330 95% 58%"
},
"darkColors": {
"background": "0 0% 8%",
"foreground": "38 30% 96%",
"card": "0 0% 11%",
"cardForeground": "38 30% 96%",
"popover": "0 0% 11%",
"popoverForeground": "38 30% 96%",
"primary": "38 30% 96%",
"primaryForeground": "0 0% 8%",
"secondary": "0 0% 14%",
"secondaryForeground": "38 30% 96%",
"muted": "0 0% 14%",
"mutedForeground": "38 10% 60%",
"accent": "330 95% 62%",
"accentForeground": "0 0% 8%",
"destructive": "0 84% 60%",
"destructiveForeground": "0 0% 100%",
"border": "0 0% 18%",
"input": "0 0% 18%",
"ring": "330 95% 62%"
}
}
},
{
"id": "ink-blue",
"name": "Ink & Electric Blue",
"description": "Same paper/ink neutrals with an electric-blue accent.",
"theme": {
"mode": "both",
"lightColors": {
"background": "38 30% 96%",
"foreground": "0 0% 8%",
"card": "0 0% 100%",
"cardForeground": "0 0% 8%",
"popover": "0 0% 100%",
"popoverForeground": "0 0% 8%",
"primary": "0 0% 8%",
"primaryForeground": "38 30% 96%",
"secondary": "38 20% 90%",
"secondaryForeground": "0 0% 8%",
"muted": "38 20% 92%",
"mutedForeground": "0 0% 40%",
"accent": "220 100% 56%",
"accentForeground": "0 0% 100%",
"destructive": "0 84% 55%",
"destructiveForeground": "0 0% 100%",
"border": "0 0% 88%",
"input": "0 0% 88%",
"ring": "220 100% 56%"
},
"darkColors": {
"background": "0 0% 8%",
"foreground": "38 30% 96%",
"card": "0 0% 11%",
"cardForeground": "38 30% 96%",
"popover": "0 0% 11%",
"popoverForeground": "38 30% 96%",
"primary": "38 30% 96%",
"primaryForeground": "0 0% 8%",
"secondary": "0 0% 14%",
"secondaryForeground": "38 30% 96%",
"muted": "0 0% 14%",
"mutedForeground": "38 10% 60%",
"accent": "220 100% 62%",
"accentForeground": "0 0% 8%",
"destructive": "0 84% 60%",
"destructiveForeground": "0 0% 100%",
"border": "0 0% 18%",
"input": "0 0% 18%",
"ring": "220 100% 62%"
}
}
},
{
"id": "chalk-lime",
"name": "Chalk & Lime",
"description": "Cooler chalk background with a lime accent — fresh, contemporary edge.",
"theme": {
"mode": "both",
"lightColors": {
"background": "60 15% 97%",
"foreground": "0 0% 8%",
"card": "0 0% 100%",
"cardForeground": "0 0% 8%",
"popover": "0 0% 100%",
"popoverForeground": "0 0% 8%",
"primary": "0 0% 8%",
"primaryForeground": "60 15% 97%",
"secondary": "60 12% 91%",
"secondaryForeground": "0 0% 8%",
"muted": "60 12% 93%",
"mutedForeground": "0 0% 40%",
"accent": "78 90% 50%",
"accentForeground": "0 0% 8%",
"destructive": "0 84% 55%",
"destructiveForeground": "0 0% 100%",
"border": "0 0% 88%",
"input": "0 0% 88%",
"ring": "78 90% 50%"
},
"darkColors": {
"background": "0 0% 6%",
"foreground": "60 15% 95%",
"card": "0 0% 10%",
"cardForeground": "60 15% 95%",
"popover": "0 0% 10%",
"popoverForeground": "60 15% 95%",
"primary": "60 15% 95%",
"primaryForeground": "0 0% 6%",
"secondary": "0 0% 13%",
"secondaryForeground": "60 15% 95%",
"muted": "0 0% 13%",
"mutedForeground": "60 8% 60%",
"accent": "78 90% 55%",
"accentForeground": "0 0% 8%",
"destructive": "0 84% 60%",
"destructiveForeground": "0 0% 100%",
"border": "0 0% 17%",
"input": "0 0% 17%",
"ring": "78 90% 55%"
}
}
}
]

37
pull_quote.go Normal file
View File

@ -0,0 +1,37 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// PullQuoteBlockMeta is the color-block pull quote.
var PullQuoteBlockMeta = blocks.BlockMeta{
Key: "pull_quote",
Title: "Pull Quote",
Description: "Color-block pull quote with rotating accent (pink / blue / lime).",
Source: "magazine-bold",
Category: blocks.CategoryContent,
}
// PullQuoteData is what the templ component consumes.
type PullQuoteData struct {
Quote string
Attribution string
Accent string
}
// PullQuoteBlock renders the color-block pull quote.
// content keys: quote (richtext), attribution (text), accent (select).
func PullQuoteBlock(ctx context.Context, content map[string]any) string {
data := PullQuoteData{
Quote: getString(content, "quote"),
Attribution: getString(content, "attribution"),
Accent: normalizeAccent(getString(content, "accent")),
}
var buf bytes.Buffer
_ = pullQuoteComponent(data).Render(ctx, &buf)
return buf.String()
}

20
pull_quote.templ Normal file
View File

@ -0,0 +1,20 @@
package main
// pullQuoteComponent renders a pull quote anchored by an accent color bar.
// The accent value drives the data-mb-accent attribute, which the appended
// CSS uses to colour the .mb-quote-bar regardless of the active preset.
templ pullQuoteComponent(data PullQuoteData) {
<aside data-block="magazine-bold:pull_quote" data-mb-accent={ data.Accent } class="my-8 grid grid-cols-12 gap-4 items-start">
<div class="mb-quote-bar col-span-1 h-full min-h-[3rem]"></div>
<blockquote class="col-span-11 mb-quote-text">
<p class="font-display text-deck">
if data.Quote != "" {
@templ.Raw(data.Quote)
}
</p>
if data.Attribution != "" {
<cite class="block mt-4 font-mono text-kicker text-muted-foreground not-italic">— { data.Attribution }</cite>
}
</blockquote>
</aside>
}

89
pull_quote_templ.go Normal file
View File

@ -0,0 +1,89 @@
// 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"
// pullQuoteComponent renders a pull quote anchored by an accent color bar.
// The accent value drives the data-mb-accent attribute, which the appended
// CSS uses to colour the .mb-quote-bar regardless of the active preset.
func pullQuoteComponent(data PullQuoteData) 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, "<aside data-block=\"magazine-bold:pull_quote\" data-mb-accent=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Accent)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pull_quote.templ`, Line: 7, Col: 74}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"my-8 grid grid-cols-12 gap-4 items-start\"><div class=\"mb-quote-bar col-span-1 h-full min-h-[3rem]\"></div><blockquote class=\"col-span-11 mb-quote-text\"><p class=\"font-display text-deck\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Quote != "" {
templ_7745c5c3_Err = templ.Raw(data.Quote).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Attribution != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<cite class=\"block mt-4 font-mono text-kicker text-muted-foreground not-italic\">— ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Attribution)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pull_quote.templ`, Line: 16, Col: 106}
}
_, 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, "</cite>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</blockquote></aside>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

172
register.go Normal file
View File

@ -0,0 +1,172 @@
package main
import (
"context"
"github.com/a-h/templ"
"git.dev.alexdunmow.com/block/core/blocks"
"git.dev.alexdunmow.com/block/core/plugin"
"git.dev.alexdunmow.com/block/core/templates"
)
// wrap adapts a templ-returning render function to templates.TemplateFunc.
func wrap(f func(ctx context.Context, doc map[string]any) templ.Component) templates.TemplateFunc {
return func(ctx context.Context, doc map[string]any) templates.HTMLComponent {
return f(ctx, doc)
}
}
// Register is the plugin entry point that registers the Magazine Bold system
// template, page templates, blocks, overrides and email wrapper.
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
Key: "magazine-bold",
Title: "Magazine Bold",
Description: "Editorial-print theme with oversized display type, asymmetric grids and rotating color-block accents for fashion, streetwear and culture publications.",
})
// Page templates — slots per spec §6 / UAT §3.
if err := tr.RegisterPageTemplate("magazine-bold", templates.PageTemplateMeta{
Key: "default",
Title: "Default",
Description: "Standard editorial page with masthead, asymmetric main, footer colophon.",
Slots: []string{"masthead", "main", "colophon"},
}, wrap(RenderMagazineBold)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("magazine-bold", templates.PageTemplateMeta{
Key: "landing",
Title: "Issue Landing",
Description: "Cover-story hero plus issue navigation strip.",
Slots: []string{"cover", "secondary", "main", "colophon"},
}, wrap(RenderMagazineBoldLanding)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("magazine-bold", templates.PageTemplateMeta{
Key: "article",
Title: "Article / Feature",
Description: "Long-form feature with deck, byline rail and pull-quote gutter.",
Slots: []string{"masthead", "deck", "main", "colophon"},
}, wrap(RenderMagazineBoldArticle)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("magazine-bold", templates.PageTemplateMeta{
Key: "full-width",
Title: "Full Bleed",
Description: "Edge-to-edge photo-essay layout.",
Slots: []string{"masthead", "main", "colophon"},
}, wrap(RenderMagazineBoldFullWidth)); err != nil {
return err
}
// Schemas BEFORE blocks (per UAT §3).
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
return err
}
// Theme blocks.
br.Register(MastheadBlockMeta, MastheadBlock)
br.Register(CoverStoryBlockMeta, CoverStoryBlock)
br.Register(PhotoEssayBlockMeta, PhotoEssayBlock)
br.Register(PullQuoteBlockMeta, PullQuoteBlock)
br.Register(IssueArchiveBlockMeta, IssueArchiveBlock)
br.Register(ColophonBlockMeta, ColophonBlock)
// Built-in overrides — only active when this theme is selected.
br.RegisterTemplateOverride("magazine-bold", "heading", HeadingOverrideBlock)
br.RegisterTemplateOverride("magazine-bold", "text", TextOverrideBlock)
br.RegisterTemplateOverride("magazine-bold", "image", ImageOverrideBlock)
br.RegisterTemplateOverride("magazine-bold", "button", ButtonOverrideBlock)
// Branded email wrapper.
tr.RegisterEmailWrapper("magazine-bold", MagazineBoldEmailWrapper)
return nil
}
// DefaultMasterPages returns the default master pages Magazine Bold seeds.
func DefaultMasterPages() []plugin.MasterPageDefinition {
return []plugin.MasterPageDefinition{
{
Key: "magazine-bold:default-master",
Title: "Magazine Bold — Default",
PageTemplates: []string{"default", "article"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "navbar",
Title: "Masthead Nav",
Content: map[string]any{"menuName": "main"},
Slot: "masthead",
SortOrder: 0,
},
{
BlockKey: "magazine-bold:masthead",
Title: "Issue Masthead",
Content: map[string]any{"issueNo": "01", "tagline": "Vol. 1 / Spring"},
Slot: "masthead",
SortOrder: 1,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{"slotName": "main"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "magazine-bold:colophon",
Title: "Colophon",
Content: map[string]any{"showSocial": "true"},
Slot: "colophon",
SortOrder: 0,
},
},
},
{
Key: "magazine-bold:landing-master",
Title: "Magazine Bold — Issue Landing",
PageTemplates: []string{"landing", "full-width"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "navbar",
Title: "Masthead Nav",
Content: map[string]any{"menuName": "main"},
Slot: "masthead",
SortOrder: 0,
},
{
BlockKey: "magazine-bold:cover_story",
Title: "Cover Story",
Content: map[string]any{"issueNo": "01"},
Slot: "cover",
SortOrder: 0,
},
{
BlockKey: "magazine-bold:issue_archive",
Title: "Issue Archive",
Content: map[string]any{"bucketSlug": "issues"},
Slot: "secondary",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{"slotName": "main"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "magazine-bold:colophon",
Title: "Colophon",
Content: map[string]any{"showSocial": "true"},
Slot: "colophon",
SortOrder: 0,
},
},
},
}
}

39
registration.go Normal file
View File

@ -0,0 +1,39 @@
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 Magazine Bold template.
var Registration = plugin.PluginRegistration{
Name: "magazine-bold",
Version: plugin.ParseModVersion(pluginModBytes),
Register: func(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
return Register(tr, br)
},
Assets: func() http.Handler { return AssetsHandler() },
Schemas: func() fs.FS { return Schemas() },
ThemePresets: func() []byte { return ThemePresets() },
BundledFonts: func() []byte { return BundledFonts() },
MasterPages: func() []plugin.MasterPageDefinition { return DefaultMasterPages() },
CSSManifest: func() *plugin.CSSManifest { return ThemeCSSManifest() },
}
// ThemeCSSManifest returns the CSS manifest for Magazine Bold. The
// InputCSSAppend payload is appended into the host Tailwind input so the
// theme's display-type utilities (`.text-folio`, `.text-deck`,
// `.font-display`, etc.) and hairline rules ship with the compiled CSS.
//
// NOTE: per docs/FONTS.md, this pass does NOT inject @font-face declarations.
// The CMS emits @font-face automatically for fonts assigned via the picker.
// We only inject custom utility CSS here.
func ThemeCSSManifest() *plugin.CSSManifest {
return &plugin.CSSManifest{
InputCSSAppend: magazineBoldUtilityCSS,
}
}

View File

@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Colophon",
"description": "Mono caption strip with credits and optional social row.",
"type": "object",
"properties": {
"credits": {
"type": "string",
"title": "Credits",
"description": "Mono-set credits block (rich text).",
"x-editor": "richtext"
},
"showSocial": {
"type": "string",
"title": "Show Social Row",
"description": "Toggle the social row beneath the credits.",
"enum": ["true", "false"],
"default": "false",
"x-editor": "select"
}
}
}

View File

@ -0,0 +1,38 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Cover Story",
"description": "12-column asymmetric hero anchoring the issue landing page.",
"type": "object",
"properties": {
"kicker": {
"type": "string",
"title": "Kicker",
"description": "Small-caps label above the headline.",
"x-editor": "text"
},
"headline": {
"type": "string",
"title": "Headline",
"description": "Display headline (rich text — allows italics).",
"x-editor": "richtext"
},
"deck": {
"type": "string",
"title": "Deck",
"description": "Sub-headline / standfirst paragraph.",
"x-editor": "richtext"
},
"image": {
"type": "string",
"title": "Cover Image",
"description": "Hero image for the cover story.",
"x-editor": "media"
},
"link": {
"type": "string",
"title": "Read Story Link",
"description": "URL to the full feature.",
"x-editor": "link"
}
}
}

View File

@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Issue Archive",
"description": "Numbered list of back-issues driven by a bucket query.",
"type": "object",
"properties": {
"bucketSlug": {
"type": "string",
"title": "Issues Bucket",
"description": "Bucket that holds the issue rows.",
"x-editor": "bucket-picker"
},
"limit": {
"type": "integer",
"title": "Limit",
"description": "How many issues to render.",
"x-editor": "number",
"minimum": 1,
"maximum": 50,
"default": 12
}
}
}

View File

@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Issue Masthead",
"description": "Wordmark + folio bar across the top of the page.",
"type": "object",
"properties": {
"issueNo": {
"type": "string",
"title": "Issue Number",
"description": "Folio number (e.g. \"01\").",
"x-editor": "text"
},
"tagline": {
"type": "string",
"title": "Tagline",
"description": "Issue volume / tagline line (e.g. \"Vol. 1 / Spring\").",
"x-editor": "text"
},
"date": {
"type": "string",
"title": "Date",
"description": "Issue date or month.",
"x-editor": "text"
}
}
}

View File

@ -0,0 +1,47 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Photo Essay",
"description": "Numbered photo-essay scaffolding with asymmetric frame spans.",
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Essay Title",
"description": "Essay heading shown above the frames.",
"x-editor": "text"
},
"frames": {
"type": "array",
"title": "Frames",
"description": "Ordered list of photo frames in the essay.",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"image": {
"type": "string",
"title": "Image",
"description": "Frame image.",
"x-editor": "media"
},
"caption": {
"type": "string",
"title": "Caption",
"description": "Mono-set caption.",
"x-editor": "text"
},
"span": {
"type": "string",
"title": "Span",
"description": "Frame layout span.",
"enum": ["half", "full", "tall"],
"default": "half",
"x-editor": "select"
}
},
"required": ["image"]
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Pull Quote",
"description": "Color-block pull quote that sits in the gutter or main column.",
"type": "object",
"properties": {
"quote": {
"type": "string",
"title": "Quote",
"description": "The quote body (rich text).",
"x-editor": "richtext"
},
"attribution": {
"type": "string",
"title": "Attribution",
"description": "Who said it.",
"x-editor": "text"
},
"accent": {
"type": "string",
"title": "Accent",
"description": "Which rotating accent to apply.",
"enum": ["pink", "blue", "lime"],
"default": "pink",
"x-editor": "select"
}
}
}

246
template.templ Normal file
View File

@ -0,0 +1,246 @@
package main
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PageData carries the data the Magazine Bold page templates render.
type PageData struct {
Title string
Slots map[string]string
ThemeMode string
ThemeCSS string
SiteSettings bn.SiteSettingsData
PageMeta bn.PageMeta
StructuredData string
CSSHash string
PageviewNonce string
EngagementConfig bn.EngagementConfig
}
func parseMagazineBoldPageData(doc map[string]any) PageData {
title := "Untitled"
if t, ok := doc["title"].(string); ok {
title = t
}
slots := make(map[string]string)
if s, ok := doc["slots"].(map[string]string); ok {
slots = s
}
themeCSS := ""
if tc, ok := doc["theme_css"].(string); ok {
themeCSS = tc
}
structuredData := ""
if sd, ok := doc["structured_data"].(string); ok {
structuredData = sd
}
cssHash := ""
if ch, ok := doc["css_hash"].(string); ok {
cssHash = ch
}
pageviewNonce := ""
if pn, ok := doc["pageview_nonce"].(string); ok {
pageviewNonce = pn
}
themeMode := "light"
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
themeMode = tm
}
siteSettings := bn.ParseSiteSettings(doc)
pageMeta := bn.ParsePageMeta(doc)
engagementConfig := bn.ParseEngagementConfig(doc)
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: siteSettings,
PageMeta: pageMeta,
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: engagementConfig,
}
}
// mbHead reuses the core head component with the magazine-bold plugin style sheet.
templ mbHead(data PageData) {
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/magazine-bold/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
}
// MagazineBold — default page template.
// Slots: masthead, main, colophon.
templ MagazineBold(data PageData) {
<!DOCTYPE html>
<html lang="en">
@mbHead(data)
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col font-sans">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full mb-hairline-bottom">
<div class="max-w-6xl mx-auto px-6">
@templ.Raw(data.Slots["masthead"])
</div>
</header>
<main class="flex-grow w-full">
<div class="max-w-6xl mx-auto px-6 py-12">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="py-20 text-center font-mono mb-caption">No content blocks assigned to this page.</div>
}
</div>
</main>
<footer class="w-full mt-auto mb-hairline-top">
<div class="max-w-6xl mx-auto px-6">
@templ.Raw(data.Slots["colophon"])
</div>
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// MagazineBoldLanding — issue landing page template.
// Slots: cover, secondary, main, colophon.
templ MagazineBoldLanding(data PageData) {
<!DOCTYPE html>
<html lang="en">
@mbHead(data)
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col font-sans">
@bn.AdminBypassBanner(data.SiteSettings)
<section class="w-full">
@templ.Raw(data.Slots["cover"])
</section>
<section class="w-full border-t border-b mb-hairline">
<div class="max-w-6xl mx-auto px-6 py-10">
@templ.Raw(data.Slots["secondary"])
</div>
</section>
<main class="flex-grow w-full">
<div class="max-w-6xl mx-auto px-6 py-12">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="py-20 text-center font-mono mb-caption">No content blocks assigned to this page.</div>
}
</div>
</main>
<footer class="w-full mt-auto mb-hairline-top">
<div class="max-w-6xl mx-auto px-6">
@templ.Raw(data.Slots["colophon"])
</div>
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// MagazineBoldArticle — long-form feature template with deck and pull-quote gutter.
// Slots: masthead, deck, main, colophon.
templ MagazineBoldArticle(data PageData) {
<!DOCTYPE html>
<html lang="en">
@mbHead(data)
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col font-sans">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full mb-hairline-bottom">
<div class="max-w-6xl mx-auto px-6">
@templ.Raw(data.Slots["masthead"])
</div>
</header>
<section class="w-full">
<div class="max-w-6xl mx-auto px-6 py-10">
@templ.Raw(data.Slots["deck"])
</div>
</section>
<main class="flex-grow w-full">
<div class="max-w-4xl mx-auto px-6 py-12">
if main, ok := data.Slots["main"]; ok && main != "" {
<article class="mb-body">
@templ.Raw(main)
</article>
} else {
<div class="py-20 text-center font-mono mb-caption">No content blocks assigned to this page.</div>
}
</div>
</main>
<footer class="w-full mt-auto mb-hairline-top">
<div class="max-w-6xl mx-auto px-6">
@templ.Raw(data.Slots["colophon"])
</div>
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// MagazineBoldFullWidth — edge-to-edge photo-essay template.
// Slots: masthead, main, colophon.
templ MagazineBoldFullWidth(data PageData) {
<!DOCTYPE html>
<html lang="en">
@mbHead(data)
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col font-sans">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full mb-hairline-bottom">
<div class="max-w-6xl mx-auto px-6">
@templ.Raw(data.Slots["masthead"])
</div>
</header>
<main class="flex-grow w-full">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="max-w-4xl mx-auto py-20 px-6 text-center font-mono mb-caption">No content blocks assigned to this page.</div>
}
</main>
<footer class="w-full mt-auto mb-hairline-top">
<div class="max-w-6xl mx-auto px-6">
@templ.Raw(data.Slots["colophon"])
</div>
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// RenderMagazineBold renders the default page.
func RenderMagazineBold(ctx context.Context, doc map[string]any) templ.Component {
return MagazineBold(parseMagazineBoldPageData(doc))
}
// RenderMagazineBoldLanding renders the issue-landing page.
func RenderMagazineBoldLanding(ctx context.Context, doc map[string]any) templ.Component {
return MagazineBoldLanding(parseMagazineBoldPageData(doc))
}
// RenderMagazineBoldArticle renders the long-form article page.
func RenderMagazineBoldArticle(ctx context.Context, doc map[string]any) templ.Component {
return MagazineBoldArticle(parseMagazineBoldPageData(doc))
}
// RenderMagazineBoldFullWidth renders the edge-to-edge photo-essay page.
func RenderMagazineBoldFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return MagazineBoldFullWidth(parseMagazineBoldPageData(doc))
}

513
template_templ.go Normal file
View File

@ -0,0 +1,513 @@
// 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 carries the data the Magazine Bold page templates render.
type PageData struct {
Title string
Slots map[string]string
ThemeMode string
ThemeCSS string
SiteSettings bn.SiteSettingsData
PageMeta bn.PageMeta
StructuredData string
CSSHash string
PageviewNonce string
EngagementConfig bn.EngagementConfig
}
func parseMagazineBoldPageData(doc map[string]any) PageData {
title := "Untitled"
if t, ok := doc["title"].(string); ok {
title = t
}
slots := make(map[string]string)
if s, ok := doc["slots"].(map[string]string); ok {
slots = s
}
themeCSS := ""
if tc, ok := doc["theme_css"].(string); ok {
themeCSS = tc
}
structuredData := ""
if sd, ok := doc["structured_data"].(string); ok {
structuredData = sd
}
cssHash := ""
if ch, ok := doc["css_hash"].(string); ok {
cssHash = ch
}
pageviewNonce := ""
if pn, ok := doc["pageview_nonce"].(string); ok {
pageviewNonce = pn
}
themeMode := "light"
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
themeMode = tm
}
siteSettings := bn.ParseSiteSettings(doc)
pageMeta := bn.ParsePageMeta(doc)
engagementConfig := bn.ParseEngagementConfig(doc)
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: siteSettings,
PageMeta: pageMeta,
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: engagementConfig,
}
}
// mbHead reuses the core head component with the magazine-bold plugin style sheet.
func mbHead(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 = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/magazine-bold/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// MagazineBold — default page template.
// Slots: masthead, main, colophon.
func MagazineBold(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, 1, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = mbHead(data).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col font-sans\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<header class=\"w-full mb-hairline-bottom\"><div class=\"max-w-6xl mx-auto px-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["masthead"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div></header><main class=\"flex-grow w-full\"><div class=\"max-w-6xl mx-auto px-6 py-12\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"py-20 text-center font-mono mb-caption\">No content blocks assigned to this page.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div></main><footer class=\"w-full mt-auto mb-hairline-top\"><div class=\"max-w-6xl mx-auto px-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["colophon"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div></footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// MagazineBoldLanding — issue landing page template.
// Slots: cover, secondary, main, colophon.
func MagazineBoldLanding(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, 9, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = mbHead(data).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col font-sans\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<section class=\"w-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["cover"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</section><section class=\"w-full border-t border-b mb-hairline\"><div class=\"max-w-6xl mx-auto px-6 py-10\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["secondary"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div></section><main class=\"flex-grow w-full\"><div class=\"max-w-6xl mx-auto px-6 py-12\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"py-20 text-center font-mono mb-caption\">No content blocks assigned to this page.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></main><footer class=\"w-full mt-auto mb-hairline-top\"><div class=\"max-w-6xl mx-auto px-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["colophon"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div></footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// MagazineBoldArticle — long-form feature template with deck and pull-quote gutter.
// Slots: masthead, deck, main, colophon.
func MagazineBoldArticle(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, 18, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = mbHead(data).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col font-sans\">")
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, 20, "<header class=\"w-full mb-hairline-bottom\"><div class=\"max-w-6xl mx-auto px-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["masthead"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div></header><section class=\"w-full\"><div class=\"max-w-6xl mx-auto px-6 py-10\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["deck"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</div></section><main class=\"flex-grow w-full\"><div class=\"max-w-4xl mx-auto px-6 py-12\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<article class=\"mb-body\">")
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, 24, "</article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"py-20 text-center font-mono mb-caption\">No content blocks assigned to this page.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div></main><footer class=\"w-full mt-auto mb-hairline-top\"><div class=\"max-w-6xl mx-auto px-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["colophon"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div></footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// MagazineBoldFullWidth — edge-to-edge photo-essay template.
// Slots: masthead, main, colophon.
func MagazineBoldFullWidth(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_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = mbHead(data).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col font-sans\">")
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, 31, "<header class=\"w-full mb-hairline-bottom\"><div class=\"max-w-6xl mx-auto px-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["masthead"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</div></header><main class=\"flex-grow w-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div class=\"max-w-4xl mx-auto py-20 px-6 text-center font-mono mb-caption\">No content blocks assigned to this page.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</main><footer class=\"w-full mt-auto mb-hairline-top\"><div class=\"max-w-6xl mx-auto px-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["colophon"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</div></footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RenderMagazineBold renders the default page.
func RenderMagazineBold(ctx context.Context, doc map[string]any) templ.Component {
return MagazineBold(parseMagazineBoldPageData(doc))
}
// RenderMagazineBoldLanding renders the issue-landing page.
func RenderMagazineBoldLanding(ctx context.Context, doc map[string]any) templ.Component {
return MagazineBoldLanding(parseMagazineBoldPageData(doc))
}
// RenderMagazineBoldArticle renders the long-form article page.
func RenderMagazineBoldArticle(ctx context.Context, doc map[string]any) templ.Component {
return MagazineBoldArticle(parseMagazineBoldPageData(doc))
}
// RenderMagazineBoldFullWidth renders the edge-to-edge photo-essay page.
func RenderMagazineBoldFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return MagazineBoldFullWidth(parseMagazineBoldPageData(doc))
}
var _ = templruntime.GeneratedTemplate

19
text_override.go Normal file
View File

@ -0,0 +1,19 @@
package main
import (
"bytes"
"context"
)
// TextOverrideBlock renders body text with the Magazine Bold Inter cadence
// and a margin folio.
//
// Built-in text content shape: {"text": "...", "class": "..."}
func TextOverrideBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
class := getString(content, "class")
var buf bytes.Buffer
_ = mbTextComponent(text, class).Render(ctx, &buf)
return buf.String()
}

8
text_override.templ Normal file
View File

@ -0,0 +1,8 @@
package main
// mbTextComponent renders a text run in Magazine Bold body cadence (Inter, 1618px tight leading).
templ mbTextComponent(text string, class string) {
<div class={ "mb-body font-sans", class }>
@templ.Raw(text)
</div>
}

67
text_override_templ.go Normal file
View File

@ -0,0 +1,67 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// mbTextComponent renders a text run in Magazine Bold body cadence (Inter, 1618px tight leading).
func mbTextComponent(text string, 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{"mb-body font-sans", class}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `text_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(text).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate