diff --git a/Makefile b/Makefile index 4c2c1df..8faa6c4 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,27 @@ -# editorial — build & publish helpers (wasm .bnp workflow) +# editorial — build & publish helpers (CODELESS .bnp workflow, WO-WZ-021) # -# The theme compiles to a wasip1 reactor-mode wasm module packed into a .bnp -# artifact (`ninja plugin build`). The legacy .so path is retired (WO-WZ-015): -# the CMS is wasm-native and can no longer load .so plugins. Distribution is -# publish-to-registry -> InstallFromRegistry (hot-load, no restart). +# This theme is a CODELESS .bnp: pure declaration the host runs — blocks +# (blocks/blocks.yaml + .ninjatpl), page/system templates, template overrides, +# an email wrapper, master pages, presets, fonts and CSS in manifest.yaml. No +# Go, no templ, no plugin.wasm, no block/core dependency. `ninja plugin build` +# classifies the repo as codeless (no Go at the root) and packs the artifact +# with manifest.pb (codeless: true) and NO plugin.wasm. # # Usage: -# make build # compile wasm + pack editorial-.bnp -# make verify # validate the built .bnp (layout/abi/name/size) -# make archive-check # prove a clean `git archive HEAD` compiles to wasm +# make build # pack editorial-.bnp (codeless — no wasm) +# make verify # validate the built .bnp (loader-identical checks) +# make archive-check # prove a clean `git archive HEAD` still builds codeless # make publish # ninja plugin publish --bnp (to the active registry) -# make templ # regenerate templ Go files # make clean # remove build artefacts -.PHONY: build verify archive-check publish templ clean help +.PHONY: build verify archive-check publish clean help PLUGIN_NAME := editorial NINJA := ninja BNP := $(PLUGIN_NAME)-$(shell grep '^version' plugin.mod | sed 's/.*"\(.*\)"/\1/').bnp -# Compile to wasm and pack the .bnp (GOOS=wasip1 GOARCH=wasm, DESCRIBE probe, -# tar.zst of plugin.wasm + plugin.mod + manifest.pb + schemas/ + assets/). +# Pack the codeless .bnp (manifest.pb synthesized from plugin.mod + manifest.yaml +# + blocks/ + templates/; no wasm compile, no DESCRIBE probe). build: $(NINJA) plugin build --dir . @@ -28,12 +29,12 @@ build: verify: $(NINJA) plugin verify $(BNP) -# Prove a clean `git archive HEAD` (what publish ships) compiles to wasm. +# Prove a clean `git archive HEAD` (what publish ships) still packs codeless. archive-check: @T=$$(mktemp -d /tmp/archive-check-$(PLUGIN_NAME).XXXXXX) && \ trap 'rm -rf "$$T"' EXIT && \ git archive HEAD | tar -x -C "$$T" && \ - cd "$$T" && GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o "$$T/check.wasm" . && \ + cd "$$T" && $(NINJA) plugin build --dir . && \ echo "archive-check OK" # Publish the prebuilt .bnp to the active registry. @@ -41,19 +42,14 @@ archive-check: publish: build $(NINJA) plugin publish --bnp $(BNP) -# Regenerate templ Go files locally. -templ: - templ generate - # Remove build artefacts. clean: - rm -f $(PLUGIN_NAME)-*.bnp $(PLUGIN_NAME).so plugin.wasm + rm -f $(PLUGIN_NAME)-*.bnp plugin.wasm help: @echo "Targets:" - @echo " build Compile wasm + pack the .bnp" + @echo " build Pack the codeless .bnp (no wasm)" @echo " verify Validate the built .bnp (loader-identical checks)" - @echo " archive-check Prove a clean git archive HEAD compiles to wasm" + @echo " archive-check Prove a clean git archive HEAD packs codeless" @echo " publish Publish the .bnp to the active registry" - @echo " templ Regenerate templ Go files" @echo " clean Remove build artefacts" diff --git a/blocks/blocks.yaml b/blocks/blocks.yaml new file mode 100644 index 0000000..d84eae3 --- /dev/null +++ b/blocks/blocks.yaml @@ -0,0 +1,48 @@ +# Editorial theme block definitions (codeless .bnp, WO-WZ-021). +# Keys are fully qualified (editorial:) because the codeless loader +# registers definitions verbatim — there is no PluginBlockRegistry prefixing +# in the codeless path (unlike the wasm/templ path). The qualified keys match +# the hardcoded data-block attributes and the master_pages.json references. +blocks: + - key: editorial:masthead + title: Masthead + description: Newspaper-style masthead with Playfair wordmark and hairline rule + category: navigation + schema: masthead.schema.json + template: masthead.ninjatpl + - key: editorial:byline + title: Byline + description: Author byline with optional photo, dateline, and read time + category: theme + schema: byline.schema.json + template: byline.ninjatpl + - key: editorial:pullquote + title: Pull Quote + description: Large oxblood pull quote in Playfair italic + category: theme + schema: pullquote.schema.json + template: pullquote.ninjatpl + - key: editorial:dropcap_intro + title: Drop Cap Intro + description: Opening paragraph with an oxblood Playfair drop cap + category: theme + schema: dropcap_intro.schema.json + template: dropcap_intro.ninjatpl + - key: editorial:marginalia + title: Marginalia Rail + description: Right-rail italic notes for longform articles + category: theme + schema: marginalia.schema.json + template: marginalia.ninjatpl + - key: editorial:section_label + title: Section Label + description: Small-caps section label with optional hairline divider + category: theme + schema: section_label.schema.json + template: section_label.ninjatpl + - key: editorial:colophon + title: Colophon + description: Editorial footer with ISSN, subscribe stub, and link list + category: navigation + schema: colophon.schema.json + template: colophon.ninjatpl diff --git a/blocks/byline.ninjatpl b/blocks/byline.ninjatpl new file mode 100644 index 0000000..d17b1a0 --- /dev/null +++ b/blocks/byline.ninjatpl @@ -0,0 +1 @@ +
diff --git a/schemas/byline.schema.json b/blocks/byline.schema.json similarity index 100% rename from schemas/byline.schema.json rename to blocks/byline.schema.json diff --git a/blocks/colophon.ninjatpl b/blocks/colophon.ninjatpl new file mode 100644 index 0000000..cd476e5 --- /dev/null +++ b/blocks/colophon.ninjatpl @@ -0,0 +1 @@ +
{% if showSignup == true or showSignup == "true" %}
{% endif %}{% if links %}{% endif %}{% if issn %}
ISSN {{ issn }}
{% endif %}
diff --git a/schemas/colophon.schema.json b/blocks/colophon.schema.json similarity index 100% rename from schemas/colophon.schema.json rename to blocks/colophon.schema.json diff --git a/blocks/dropcap_intro.ninjatpl b/blocks/dropcap_intro.ninjatpl new file mode 100644 index 0000000..ee4ac55 --- /dev/null +++ b/blocks/dropcap_intro.ninjatpl @@ -0,0 +1 @@ +{% if lead %}
{{ lead|safe }}
{% endif %} diff --git a/schemas/dropcap_intro.schema.json b/blocks/dropcap_intro.schema.json similarity index 100% rename from schemas/dropcap_intro.schema.json rename to blocks/dropcap_intro.schema.json diff --git a/blocks/marginalia.ninjatpl b/blocks/marginalia.ninjatpl new file mode 100644 index 0000000..f00b58c --- /dev/null +++ b/blocks/marginalia.ninjatpl @@ -0,0 +1 @@ +
{% if items %}{% for note in items %}{% endfor %}{% else %}{% endif %}
diff --git a/schemas/marginalia.schema.json b/blocks/marginalia.schema.json similarity index 100% rename from schemas/marginalia.schema.json rename to blocks/marginalia.schema.json diff --git a/blocks/masthead.ninjatpl b/blocks/masthead.ninjatpl new file mode 100644 index 0000000..e2f0f96 --- /dev/null +++ b/blocks/masthead.ninjatpl @@ -0,0 +1 @@ +
{% if compact == true or compact == "true" %}
{{ title|default:"Editorial" }}{% if menuName %}{{ menuName }}{% endif %}

{% else %}
{% if kicker %}
{{ kicker }}
{% endif %}{{ title|default:"Editorial" }}
{% if menuName %}{% endif %}
{% endif %}
diff --git a/schemas/masthead.schema.json b/blocks/masthead.schema.json similarity index 100% rename from schemas/masthead.schema.json rename to blocks/masthead.schema.json diff --git a/blocks/pullquote.ninjatpl b/blocks/pullquote.ninjatpl new file mode 100644 index 0000000..e053801 --- /dev/null +++ b/blocks/pullquote.ninjatpl @@ -0,0 +1 @@ +{% if quote %}{% endif %} diff --git a/schemas/pullquote.schema.json b/blocks/pullquote.schema.json similarity index 100% rename from schemas/pullquote.schema.json rename to blocks/pullquote.schema.json diff --git a/blocks/section_label.ninjatpl b/blocks/section_label.ninjatpl new file mode 100644 index 0000000..2bb3c9f --- /dev/null +++ b/blocks/section_label.ninjatpl @@ -0,0 +1 @@ +{% if label %}
{% if divider != "false" and divider != false %}{% endif %}
{% endif %} diff --git a/schemas/section_label.schema.json b/blocks/section_label.schema.json similarity index 100% rename from schemas/section_label.schema.json rename to blocks/section_label.schema.json diff --git a/button_override.go b/button_override.go deleted file mode 100644 index de36a52..0000000 --- a/button_override.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - "bytes" - "context" -) - -// EditorialButtonBlock overrides the built-in `button` block for the -// editorial template. The editorial button is hairline-only: 1px border, -// transparent fill, oxblood text on hover (see .editorial-button CSS). -// -// Content shape: { "text", "url", "ariaLabel" }. -func EditorialButtonBlock(ctx context.Context, content map[string]any) string { - text := getString(content, "text") - url := getString(content, "url") - ariaLabel := getString(content, "ariaLabel") - - if text == "" { - return "" - } - - var buf bytes.Buffer - _ = editorialButtonComponent(text, url, ariaLabel).Render(ctx, &buf) - return buf.String() -} diff --git a/button_override.templ b/button_override.templ deleted file mode 100644 index 8e6576a..0000000 --- a/button_override.templ +++ /dev/null @@ -1,15 +0,0 @@ -package main - -// editorialButtonComponent renders an editorial-styled button. The element is -// always - - } else { - - } -} diff --git a/button_override_templ.go b/button_override_templ.go deleted file mode 100644 index 6a56424..0000000 --- a/button_override_templ.go +++ /dev/null @@ -1,116 +0,0 @@ -// 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" - -// editorialButtonComponent renders an editorial-styled button. The element is -// always ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/byline.go b/byline.go deleted file mode 100644 index a26c500..0000000 --- a/byline.go +++ /dev/null @@ -1,56 +0,0 @@ -package main - -import ( - "bytes" - "context" - - "git.dev.alexdunmow.com/block/core/blocks" -) - -// BylineBlockMeta describes the Editorial byline block: author name, optional -// avatar, dateline, and read time. -// Registered as "editorial:byline" at runtime. -var BylineBlockMeta = blocks.BlockMeta{ - Key: "byline", - Title: "Byline", - Description: "Author byline with optional photo, dateline, and read time", - Source: "editorial", - Category: blocks.CategoryTheme, -} - -// BylineData carries parsed byline content. -// -// Author resolution: this plugin does not resolve the slug to an actual author -// record (that would require ServiceDeps and a database call). Instead the -// authorSlug is rendered verbatim in the byline label. The CMS layer can -// replace the slug with the resolved name when integrating with the author -// directory. -type BylineData struct { - AuthorSlug string - ShowPhoto bool - ShowReadTime bool - Dateline string -} - -// BylineBlock renders the article byline. -// -// Content shape: -// -// { -// "authorSlug": "jane-smith", -// "showPhoto": "true", -// "showReadTime": "true", -// "dateline": "London, March 14" -// } -func BylineBlock(ctx context.Context, content map[string]any) string { - data := BylineData{ - AuthorSlug: getString(content, "authorSlug"), - ShowPhoto: getBool(content, "showPhoto", true), - ShowReadTime: getBool(content, "showReadTime", true), - Dateline: getString(content, "dateline"), - } - - var buf bytes.Buffer - _ = bylineComponent(data).Render(ctx, &buf) - return buf.String() -} diff --git a/byline.templ b/byline.templ deleted file mode 100644 index f7f98a4..0000000 --- a/byline.templ +++ /dev/null @@ -1,37 +0,0 @@ -package main - -// bylineComponent renders the byline. When showPhoto is true and an author -// slug is supplied, a neutral avatar circle is rendered (the actual avatar -// URL would be resolved server-side by the CMS author directory). When the -// dateline is empty the dateline cell is suppressed. -templ bylineComponent(data BylineData) { -
- -
-} diff --git a/byline_templ.go b/byline_templ.go deleted file mode 100644 index cd6d96e..0000000 --- a/byline_templ.go +++ /dev/null @@ -1,152 +0,0 @@ -// 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" - -// bylineComponent renders the byline. When showPhoto is true and an author -// slug is supplied, a neutral avatar circle is rendered (the actual avatar -// URL would be resolved server-side by the CMS author directory). When the -// dateline is empty the dateline cell is suppressed. -func bylineComponent(data BylineData) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/colophon.go b/colophon.go deleted file mode 100644 index b9da328..0000000 --- a/colophon.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "bytes" - "context" - - "git.dev.alexdunmow.com/block/core/blocks" -) - -// ColophonBlockMeta describes the Editorial colophon (footer) block: optional -// subscribe stub, optional ISSN field, and a list of inline footer links. -// Registered as "editorial:colophon" at runtime. -var ColophonBlockMeta = blocks.BlockMeta{ - Key: "colophon", - Title: "Colophon", - Description: "Editorial footer with ISSN, subscribe stub, and link list", - Source: "editorial", - Category: blocks.CategoryNavigation, -} - -// ColophonLink is one row in the colophon link list. -type ColophonLink struct { - Text string - URL string -} - -// ColophonData carries parsed colophon content. -type ColophonData struct { - ShowSignup bool - ISSN string - Links []ColophonLink -} - -// ColophonBlock renders the colophon. -// -// Content shape: -// -// { -// "showSignup": "true", -// "issn": "0000-0000", -// "links": [ -// { "text": "About", "url": "/about" }, -// { "text": "Style Guide", "url": "/style-guide" } -// ] -// } -func ColophonBlock(ctx context.Context, content map[string]any) string { - rawLinks := getSlice(content, "links") - links := make([]ColophonLink, 0, len(rawLinks)) - for _, l := range rawLinks { - links = append(links, ColophonLink{ - Text: getString(l, "text"), - URL: getString(l, "url"), - }) - } - - data := ColophonData{ - ShowSignup: getBool(content, "showSignup", false), - ISSN: getString(content, "issn"), - Links: links, - } - - var buf bytes.Buffer - _ = colophonComponent(data).Render(ctx, &buf) - return buf.String() -} diff --git a/colophon.templ b/colophon.templ deleted file mode 100644 index 95ee3e7..0000000 --- a/colophon.templ +++ /dev/null @@ -1,45 +0,0 @@ -package main - -// colophonComponent renders the editorial colophon (footer). Subscribe stub, -// link list, and ISSN are all optional. The stub is a non-functional stand-in -// for this build pass — the spec lists the colophon as a footer with a -// subscribe stub, but signup wiring is out of scope. -templ colophonComponent(data ColophonData) { -
- if data.ShowSignup { -
- -
- - -
-
- } - if len(data.Links) > 0 { - - } - if data.ISSN != "" { -
- { "ISSN " }{ data.ISSN } -
- } -
-} - -// linkHref returns "#" for empty URLs so the rendered href is always valid. -func linkHref(u string) string { - if u == "" { - return "#" - } - return u -} diff --git a/colophon_templ.go b/colophon_templ.go deleted file mode 100644 index 63f06a0..0000000 --- a/colophon_templ.go +++ /dev/null @@ -1,158 +0,0 @@ -// 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 editorial colophon (footer). Subscribe stub, -// link list, and ISSN are all optional. The stub is a non-functional stand-in -// for this build pass — the spec lists the colophon as a footer with a -// subscribe stub, but signup wiring is out of scope. -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, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if data.ShowSignup { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - if len(data.Links) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - if data.ISSN != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("ISSN ") - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `colophon.templ`, Line: 33, Col: 51} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.ISSN) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `colophon.templ`, Line: 33, Col: 64} - } - _, 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, 11, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -// linkHref returns "#" for empty URLs so the rendered href is always valid. -func linkHref(u string) string { - if u == "" { - return "#" - } - return u -} - -var _ = templruntime.GeneratedTemplate diff --git a/colors_email.go b/colors_email.go deleted file mode 100644 index 450b71b..0000000 --- a/colors_email.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "fmt" - - "git.dev.alexdunmow.com/block/core/templates" -) - -// Email rendering must inline colour values because most desktop email -// clients do not honour CSS custom properties (var(--token)) or modern -// hsl() syntax. The host sets EmailContext.Colors with hex strings derived -// from the active theme preset; when those are empty we fall back to the -// broadsheet preset's equivalents. -// -// The fallback colours are assembled programmatically rather than written as -// hex literals so source files stay free of `#xxxxxx` patterns (the UAT -// visual gate forbids literal hex / rgb / named colours in *.templ and *.go). -// Each byte triple corresponds to a broadsheet preset HSL token converted -// to sRGB and rounded to the nearest 8-bit channel; see BUILD_REPORT.md for -// the conversion derivation. - -// rgbHex assembles an HTML hex colour string from three byte channels. The -// returned value follows the standard CSS hex form (hash plus six hex -// digits), built at runtime via fmt.Sprintf so this source file never -// contains a literal hex triplet that would trip the UAT visual gate. -func rgbHex(r, g, b uint8) string { - return fmt.Sprintf("%s%02x%02x%02x", "#", r, g, b) -} - -// Broadsheet preset fallback channels (light mode). -// -// background 36 30% 96% → cream paper. -// card 36 30% 98% → slightly lighter cream. -// foreground 20 14% 10% → ink black. -// mutedFg 20 10% 38% → soft grey-brown. -// border 30 12% 82% → warm hairline. -// accent 0 55% 28% → oxblood. -var ( - emailFallbackBg = rgbHex(247, 241, 228) - emailFallbackCard = rgbHex(251, 248, 236) - emailFallbackFg = rgbHex(29, 23, 20) - emailFallbackMutedFg = rgbHex(110, 99, 87) - emailFallbackBorder = rgbHex(214, 205, 191) - emailFallbackAccent = rgbHex(112, 32, 33) -) - -// editorialEmailBg returns the email page background colour, preferring the -// theme-derived value from the host when present. -func editorialEmailBg(c templates.EmailContext) string { - if c.Colors.Background != "" { - return c.Colors.Background - } - return emailFallbackBg -} - -func editorialEmailCard(c templates.EmailContext) string { - if c.Colors.Card != "" { - return c.Colors.Card - } - return emailFallbackCard -} - -func editorialEmailFg(c templates.EmailContext) string { - if c.Colors.Foreground != "" { - return c.Colors.Foreground - } - return emailFallbackFg -} - -func editorialEmailMutedFg(c templates.EmailContext) string { - if c.Colors.MutedForeground != "" { - return c.Colors.MutedForeground - } - return emailFallbackMutedFg -} - -func editorialEmailBorder(c templates.EmailContext) string { - if c.Colors.Border != "" { - return c.Colors.Border - } - return emailFallbackBorder -} - -// editorialEmailAccent returns the oxblood accent. EmailColors does not -// expose an accent slot in this SDK version, so the value is always the -// preset-derived fallback. -func editorialEmailAccent(c templates.EmailContext) string { - return emailFallbackAccent -} diff --git a/dropcap_intro.go b/dropcap_intro.go deleted file mode 100644 index d378fe9..0000000 --- a/dropcap_intro.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "bytes" - "context" - - "git.dev.alexdunmow.com/block/core/blocks" -) - -// DropcapIntroBlockMeta describes the Editorial drop-cap intro block: an -// opening paragraph whose first letter is rendered as an oxblood drop cap. -// Registered as "editorial:dropcap_intro" at runtime. -var DropcapIntroBlockMeta = blocks.BlockMeta{ - Key: "dropcap_intro", - Title: "Drop Cap Intro", - Description: "Opening paragraph with an oxblood Playfair drop cap", - Source: "editorial", - Category: blocks.CategoryTheme, -} - -// DropcapIntroData carries parsed drop-cap-intro content. -type DropcapIntroData struct { - Lead string // rich-text payload, trusted upstream -} - -// DropcapIntroBlock renders the opening paragraph. -// -// The drop cap is rendered via CSS (::first-letter on the first

) rather -// than by extracting the first character server-side. This is the simpler -// path and works for ASCII openers. The spec calls out a known risk: an -// opener starting with `"` or `—` will style the punctuation. That fallback -// is acceptable for this build pass and is documented in BUILD_REPORT.md. -// -// Content shape: -// -// { "lead": "

The reform was always going to be quiet.

" } -func DropcapIntroBlock(ctx context.Context, content map[string]any) string { - data := DropcapIntroData{ - Lead: getString(content, "lead"), - } - - if data.Lead == "" { - return "" - } - - var buf bytes.Buffer - _ = dropcapIntroComponent(data).Render(ctx, &buf) - return buf.String() -} diff --git a/dropcap_intro.templ b/dropcap_intro.templ deleted file mode 100644 index 4cb7e70..0000000 --- a/dropcap_intro.templ +++ /dev/null @@ -1,11 +0,0 @@ -package main - -// dropcapIntroComponent renders the opening paragraph wrapped in the -// `editorial-dropcap` CSS hook. The hook selects `p:first-of-type::first-letter` -// inside the wrapper, so the editor can supply multiple

elements and the -// drop cap is applied to the first one only. -templ dropcapIntroComponent(data DropcapIntroData) { -

- @templ.Raw(data.Lead) -
-} diff --git a/dropcap_intro_templ.go b/dropcap_intro_templ.go deleted file mode 100644 index 12ec83d..0000000 --- a/dropcap_intro_templ.go +++ /dev/null @@ -1,52 +0,0 @@ -// 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" - -// dropcapIntroComponent renders the opening paragraph wrapped in the -// `editorial-dropcap` CSS hook. The hook selects `p:first-of-type::first-letter` -// inside the wrapper, so the editor can supply multiple

elements and the -// drop cap is applied to the first one only. -func dropcapIntroComponent(data DropcapIntroData) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Lead).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/email_wrapper.templ b/email_wrapper.templ deleted file mode 100644 index b62182b..0000000 --- a/email_wrapper.templ +++ /dev/null @@ -1,117 +0,0 @@ -package main - -import ( - "bytes" - "context" - "fmt" - "git.dev.alexdunmow.com/block/core/templates" -) - -// EditorialEmailWrapper wraps an email body in a print-broadsheet style: cream -// background, Playfair masthead, hairline rule, 580px centred column, Source -// Serif body, oxblood unsubscribe link. -func EditorialEmailWrapper(body string, emailCtx templates.EmailContext) string { - var buf bytes.Buffer - _ = editorialEmailTemplate(emailCtx, body).Render(context.Background(), &buf) - return buf.String() -} - -// editorialEmailTemplate renders the Editorial-branded email layout. All -// colours are pulled off emailCtx.Colors (the host has already converted the -// theme HSL tokens to hex for Outlook inlining); the helpers below provide -// editorial-appropriate fallbacks when the host has not provided a value. -templ editorialEmailTemplate(emailCtx templates.EmailContext, body string) { - - - - - - - - { emailCtx.SiteSettings.SiteName } - - - - if emailCtx.PreviewText != "" { -
- { emailCtx.PreviewText } -
- } - - - - -
- - - - - - - - - - - - - - - - - - -
- - -} - -// Email colour helpers. These return values from the EmailContext when set -// (the host has converted the active preset's HSL tokens into hex). When the -// host has not provided a value we fall back to broadsheet-preset-equivalent -// values, assembled programmatically in colors_email.go so no literal hex -// triplets appear in this rendering file (the UAT visual gate blocks literal -// hex/rgb in *.templ and *.go). diff --git a/email_wrapper_templ.go b/email_wrapper_templ.go deleted file mode 100644 index cf7b687..0000000 --- a/email_wrapper_templ.go +++ /dev/null @@ -1,330 +0,0 @@ -// 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 ( - "bytes" - "context" - "fmt" - "git.dev.alexdunmow.com/block/core/templates" -) - -// EditorialEmailWrapper wraps an email body in a print-broadsheet style: cream -// background, Playfair masthead, hairline rule, 580px centred column, Source -// Serif body, oxblood unsubscribe link. -func EditorialEmailWrapper(body string, emailCtx templates.EmailContext) string { - var buf bytes.Buffer - _ = editorialEmailTemplate(emailCtx, body).Render(context.Background(), &buf) - return buf.String() -} - -// editorialEmailTemplate renders the Editorial-branded email layout. All -// colours are pulled off emailCtx.Colors (the host has already converted the -// theme HSL tokens to hex for Outlook inlining); the helpers below provide -// editorial-appropriate fallbacks when the host has not provided a value. -func editorialEmailTemplate(emailCtx templates.EmailContext, body string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 31, Col: 42} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if emailCtx.PreviewText != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 50, Col: 27} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if emailCtx.SiteSettings.LogoURL != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"")") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else if emailCtx.SiteSettings.SiteName != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, 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: 43} - } - _, 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 - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
 
") - 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, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var16 string - templ_7745c5c3_Var16, 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: 91, Col: 72} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

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

Unsubscribe

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -// Email colour helpers. These return values from the EmailContext when set -// (the host has converted the active preset's HSL tokens into hex). When the -// host has not provided a value we fall back to broadsheet-preset-equivalent -// values, assembled programmatically in colors_email.go so no literal hex -// triplets appear in this rendering file (the UAT visual gate blocks literal -// hex/rgb in *.templ and *.go). -var _ = templruntime.GeneratedTemplate diff --git a/embed.go b/embed.go deleted file mode 100644 index 8d3d6e1..0000000 --- a/embed.go +++ /dev/null @@ -1,61 +0,0 @@ -package main - -import ( - "embed" - "io/fs" - "net/http" - - "git.dev.alexdunmow.com/block/core/plugin" -) - -//go:embed assets/* -var assetsFS embed.FS - -//go:embed schemas/* -var schemasFS embed.FS - -//go:embed presets.json -var presetsData []byte - -//go:embed fonts.json -var fontsData []byte - -//go:embed plugin.mod -var pluginModBytes []byte - -// Assets returns the embedded assets filesystem rooted at assets/. -func Assets() fs.FS { - sub, _ := fs.Sub(assetsFS, "assets") - return sub -} - -// Schemas returns the embedded schemas filesystem rooted at schemas/. -func Schemas() fs.FS { - sub, _ := fs.Sub(schemasFS, "schemas") - return sub -} - -// AssetsHandler returns an http.Handler that serves the embedded assets. -func AssetsHandler() http.Handler { - return http.FileServer(http.FS(Assets())) -} - -// ThemePresets returns the embedded theme presets JSON. -func ThemePresets() []byte { return presetsData } - -// BundledFonts returns the embedded fonts manifest JSON. -func BundledFonts() []byte { return fontsData } - -// ThemeCSSManifest returns the editorial-specific utility CSS that must be -// merged into the host Tailwind input layer at build time. This covers the -// prose-editorial measure, the drop-cap ::first-letter rules, the hairline -// dividers, the small-caps section labels, and the marginalia rail — all of -// which are visual contracts the UAT (§13) checks at runtime. -// -// Per docs/FONTS.md, no @font-face declarations are emitted here; the host -// already emits them from the admin font assignments + the fonts table. -func ThemeCSSManifest() *plugin.CSSManifest { - return &plugin.CSSManifest{ - InputCSSAppend: editorialUtilityCSS, - } -} diff --git a/go.mod b/go.mod deleted file mode 100644 index ad30394..0000000 --- a/go.mod +++ /dev/null @@ -1,20 +0,0 @@ -module git.dev.alexdunmow.com/block/themes/editorial - -go 1.26.4 - -require ( - git.dev.alexdunmow.com/block/core v0.15.2 - github.com/a-h/templ v0.3.1020 -) - -require ( - connectrpc.com/connect v1.20.0 // indirect - github.com/BurntSushi/toml v1.6.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.9.2 // indirect - golang.org/x/mod v0.34.0 // indirect - golang.org/x/text v0.36.0 // indirect - google.golang.org/protobuf v1.36.11 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index ebd3d11..0000000 --- a/go.sum +++ /dev/null @@ -1,46 +0,0 @@ -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.15.2 h1:qTjJxa1pClxwehkdTxXiJDpHQLr5DoXBUCa40QRO0L8= -git.dev.alexdunmow.com/block/core v0.15.2/go.mod h1:A+mgA9oeWoPhXruv4Mjpywwl2A9B5I3cKzlJjalBdlE= -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= -github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU= -github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0= -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/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= -golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/heading_override.go b/heading_override.go deleted file mode 100644 index bbea96b..0000000 --- a/heading_override.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "bytes" - "context" - "strconv" -) - -// EditorialHeadingBlock overrides the built-in `heading` block for the -// editorial template. The override only applies when the active page template -// is `editorial`; on other templates the built-in heading runs untouched. -// -// Content shape mirrors the built-in heading: { "text", "level", "kicker", -// "textClass" }. The optional "kicker" is editorial-specific (a small-caps -// label above the H1). -func EditorialHeadingBlock(ctx context.Context, content map[string]any) string { - text := getString(content, "text") - textClass := getString(content, "textClass") - kicker := getString(content, "kicker") - level := parseHeadingLevel(content) - - var buf bytes.Buffer - _ = editorialHeadingComponent(level, text, textClass, kicker).Render(ctx, &buf) - return buf.String() -} - -// parseHeadingLevel parses content.level into 1-6, defaulting to 2. Accepts -// number, integer-from-JSON (float64), and string forms. -func parseHeadingLevel(content map[string]any) int { - if level, ok := content["level"].(float64); ok { - l := int(level) - if l >= 1 && l <= 6 { - return l - } - } - if level, ok := content["level"].(int); ok { - if level >= 1 && level <= 6 { - return level - } - } - if level, ok := content["level"].(string); ok { - if l, err := strconv.Atoi(level); err == nil && l >= 1 && l <= 6 { - return l - } - } - return 2 -} diff --git a/heading_override.templ b/heading_override.templ deleted file mode 100644 index 8eb6365..0000000 --- a/heading_override.templ +++ /dev/null @@ -1,28 +0,0 @@ -package main - -// editorialHeadingComponent renders an editorial-styled heading. H1 carries -// the optional kicker. The text-size scale lifts on the landing template so -// the lead-story H1 hits ≥72px at desktop sizes (UAT §13.9). -templ editorialHeadingComponent(level int, text, textClass, kicker string) { - switch level { - case 1: -
- if kicker != "" { - { kicker } - } -

{ text }

-
- case 2: -

{ text }

- case 3: -

{ text }

- case 4: -

{ text }

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

{ text }

- } -} diff --git a/heading_override_templ.go b/heading_override_templ.go deleted file mode 100644 index 5177e64..0000000 --- a/heading_override_templ.go +++ /dev/null @@ -1,316 +0,0 @@ -// 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" - -// editorialHeadingComponent renders an editorial-styled heading. H1 carries -// the optional kicker. The text-size scale lifts on the landing template so -// the lead-story H1 hits ≥72px at desktop sizes (UAT §13.9). -func editorialHeadingComponent(level int, text, textClass, kicker 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: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if kicker != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(kicker) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 11, Col: 52} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - var templ_7745c5c3_Var3 = []any{"editorial-heading text-5xl md:text-7xl mt-2", textClass} - 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, 4, "

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

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case 2: - var templ_7745c5c3_Var6 = []any{"editorial-heading text-3xl md:text-4xl editorial-column", textClass} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

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

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case 3: - var templ_7745c5c3_Var9 = []any{"editorial-heading text-2xl md:text-3xl editorial-column", textClass} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var11 string - templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(text) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 18, Col: 92} - } - _, 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, 12, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case 4: - var templ_7745c5c3_Var12 = []any{"editorial-heading text-xl md:text-2xl editorial-column", textClass} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var14 string - templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(text) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 20, Col: 91} - } - _, 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, 15, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case 5: - var templ_7745c5c3_Var15 = []any{"editorial-heading text-lg editorial-column", textClass} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var17 string - templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(text) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 22, Col: 79} - } - _, 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, 18, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - case 6: - var templ_7745c5c3_Var18 = []any{"editorial-heading text-base editorial-column", textClass} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var18...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var20 string - templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(text) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 24, Col: 81} - } - _, 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, 21, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - default: - var templ_7745c5c3_Var21 = []any{"editorial-heading text-3xl editorial-column", textClass} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var21...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var23 string - templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(text) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 26, Col: 80} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/helpers.go b/helpers.go deleted file mode 100644 index da32abe..0000000 --- a/helpers.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -// getString extracts a string value from a content map. -func getString(content map[string]any, key string) string { - if v, ok := content[key].(string); ok { - return v - } - return "" -} - -// getStringWithDefault returns the string at key or the default when absent/empty. -func getStringWithDefault(content map[string]any, key, def string) string { - if v := getString(content, key); v != "" { - return v - } - return def -} - -// getBool extracts a bool value from a content map. Accepts true/false and -// string forms "true"/"false"/"yes"/"no"/"1"/"0" (admin select dropdowns -// commonly store booleans as strings). -func getBool(content map[string]any, key string, def bool) bool { - if v, ok := content[key].(bool); ok { - return v - } - if v, ok := content[key].(string); ok { - switch v { - case "true", "yes", "1": - return true - case "false", "no", "0": - return false - } - } - return def -} - -// getSlice extracts a slice of map values from content[key]. JSON decoding -// gives []any; we narrow to []map[string]any so block code can index into -// individual items by name. -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 -} diff --git a/image_override.go b/image_override.go deleted file mode 100644 index 714756b..0000000 --- a/image_override.go +++ /dev/null @@ -1,26 +0,0 @@ -package main - -import ( - "bytes" - "context" -) - -// EditorialImageBlock overrides the built-in `image` block for the editorial -// template. The image is wrapped in a
; the caption is rendered as -// Source Serif italic 14px, preceded by a 1px hairline rule (UAT §13.12). -// -// Content shape: { "src", "alt", "caption" }. -func EditorialImageBlock(ctx context.Context, content map[string]any) string { - src := getString(content, "src") - alt := getString(content, "alt") - caption := getString(content, "caption") - - if src == "" { - // Match built-in fallback: render nothing for an empty image. - return "" - } - - var buf bytes.Buffer - _ = editorialImageComponent(src, alt, caption).Render(ctx, &buf) - return buf.String() -} diff --git a/image_override.templ b/image_override.templ deleted file mode 100644 index d0011a5..0000000 --- a/image_override.templ +++ /dev/null @@ -1,11 +0,0 @@ -package main - -// editorialImageComponent renders an image with an editorial figcaption. -templ editorialImageComponent(src, alt, caption string) { -
- { - if caption != "" { -
{ caption }
- } -
-} diff --git a/image_override_templ.go b/image_override_templ.go deleted file mode 100644 index c01bde5..0000000 --- a/image_override_templ.go +++ /dev/null @@ -1,90 +0,0 @@ -// 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" - -// editorialImageComponent renders an image with an editorial figcaption. -func editorialImageComponent(src, alt, caption string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
\"") ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if caption != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(caption) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 8, Col: 24} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/main.go b/main.go deleted file mode 100644 index 5a2dec4..0000000 --- a/main.go +++ /dev/null @@ -1,17 +0,0 @@ -//go:build wasip1 - -// Package main compiles the editorial theme to a wasip1 reactor-mode wasm guest -// (WO-WZ-015). The .so build path is retired; `ninja plugin build` produces the -// .bnp artifact the registry serves. -// -// Reactor mode: the host runs `_initialize` once per pooled instance, which -// runs this init func (hence wasmguest.Serve) before any bn_invoke. Serve is -// non-blocking; all theme work then arrives over the ABI hook calls. main is -// never called. -package main - -import "git.dev.alexdunmow.com/block/core/plugin/wasmguest" - -func init() { wasmguest.Serve(Registration) } - -func main() {} // never called — reactor mode diff --git a/manifest.yaml b/manifest.yaml new file mode 100644 index 0000000..ba51251 --- /dev/null +++ b/manifest.yaml @@ -0,0 +1,384 @@ +# Editorial theme — codeless .bnp manifest (WO-WZ-021 Phase B). +# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm). +theme_presets: presets.json +bundled_fonts: fonts.json +master_pages: master_pages.json + +system_templates: + - key: editorial + title: Editorial + description: "Didone display type, narrow measure, and hairline rules for newsrooms, longform writers, and opinion columnists." + +page_templates: + - system: editorial + key: default + title: Default + description: "Standard page with masthead, narrow main column, footer." + slots: [header, main, footer] + - system: editorial + key: landing + title: Section Front + description: "Front-page-style stack: masthead, lead story, river, footer." + slots: [masthead, lead, river, footer] + - system: editorial + key: article + title: Article + description: "Single-column long read with byline, drop cap, marginalia rail." + slots: [header, byline, main, marginalia, footer] + - system: editorial + key: full-width + title: Full Width + description: "Edge-to-edge for photo essays and data graphics." + slots: [header, main, footer] + +template_overrides: + - { template: editorial, block: heading } + - { template: editorial, block: text } + - { template: editorial, block: button } + - { template: editorial, block: image } + +email_wrappers: + - editorial + +css: + input_css_append: | + /* === Editorial theme — utility layer === */ + + :root { + --editorial-measure: 64ch; + --editorial-rule: 1px solid hsl(var(--border)); + } + + /* --- Narrow editorial column with indented continuation paragraphs --- */ + + .prose-editorial { + max-width: var(--editorial-measure); + margin-left: auto; + margin-right: auto; + font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif); + color: hsl(var(--foreground)); + line-height: 1.65; + } + + .prose-editorial p { + margin: 0 0 1em 0; + text-indent: 0; + } + + .prose-editorial p + p { + text-indent: 1.5em; + } + + .prose-editorial h1, + .prose-editorial h2, + .prose-editorial h3, + .prose-editorial h4 { + font-family: var(--font-heading, "Playfair Display", Georgia, serif); + font-weight: 700; + letter-spacing: -0.01em; + color: hsl(var(--foreground)); + } + + .prose-editorial a { + color: hsl(var(--accent)); + text-decoration: underline; + text-decoration-thickness: 1px; + text-underline-offset: 2px; + } + + /* --- Drop cap — oxblood accent first letter, ≥ 3x body size --- */ + + .editorial-dropcap p:first-of-type::first-letter { + font-family: var(--font-heading, "Playfair Display", Georgia, serif); + font-weight: 900; + font-style: italic; + color: hsl(var(--accent)); + float: left; + font-size: 4.5em; + line-height: 0.85; + padding-right: 0.12em; + padding-top: 0.05em; + margin-bottom: -0.1em; + } + + /* --- Marginalia rail (right-side italic notes) --- */ + + .editorial-marginalia { + font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif); + font-style: italic; + font-size: 12px; + line-height: 1.5; + color: hsl(var(--muted-foreground)); + } + + .editorial-marginalia aside { + margin: 0 0 1.5rem 0; + padding-left: 0.75rem; + border-left: var(--editorial-rule); + } + + @media (max-width: 768px) { + .editorial-marginalia { + margin-top: 1.5rem; + border-top: var(--editorial-rule); + padding-top: 1rem; + } + } + + /* --- Masthead wordmark --- */ + + .editorial-masthead-wordmark { + font-family: var(--font-heading, "Playfair Display", Georgia, serif); + font-weight: 900; + font-style: italic; + letter-spacing: -0.02em; + color: hsl(var(--foreground)); + } + + .editorial-masthead-kicker { + font-family: var(--font-mono, "JetBrains Mono", "SF Mono", ui-monospace, monospace); + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.15em; + color: hsl(var(--muted-foreground)); + } + + .editorial-masthead-rule { + border: 0; + border-top: var(--editorial-rule); + margin: 0.5rem 0 0 0; + } + + /* --- Pull quote — Playfair italic in oxblood --- */ + + .editorial-pullquote { + font-family: var(--font-heading, "Playfair Display", Georgia, serif); + font-style: italic; + font-size: 38px; + line-height: 1.2; + color: hsl(var(--accent)); + margin: 2.5rem auto; + max-width: var(--editorial-measure); + border: none; + } + + .editorial-pullquote-attribution { + display: block; + margin-top: 1rem; + font-family: var(--font-body, "Source Serif 4", Georgia, serif); + font-style: normal; + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.12em; + color: hsl(var(--muted-foreground)); + } + + /* --- Section label — small-caps with hairline rule --- */ + + .editorial-section-label { + font-family: var(--font-body, "Source Serif 4", Georgia, serif); + text-transform: uppercase; + font-variant: small-caps; + font-feature-settings: "smcp"; + letter-spacing: 0.15em; + font-size: 13px; + color: hsl(var(--foreground)); + } + + .editorial-section-label-rule { + border: 0; + border-top: var(--editorial-rule); + margin: 0.5rem 0 1.5rem 0; + } + + /* --- Hairline rules --- */ + + hr.editorial-rule, + .editorial-hairline { + border: 0; + border-top: var(--editorial-rule); + margin: 1.5rem 0; + } + + /* --- Byline --- */ + + .editorial-byline { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 0; + border-top: var(--editorial-rule); + border-bottom: var(--editorial-rule); + } + + .editorial-byline-photo { + width: 36px; + height: 36px; + border-radius: 50%; + background-color: hsl(var(--muted)); + flex-shrink: 0; + object-fit: cover; + } + + .editorial-byline-author { + font-family: var(--font-mono, "JetBrains Mono", monospace), var(--font-body, "Source Serif 4", Georgia, serif); + /* The spec calls for Inter for UI bylines. Fallback to Inter then system. */ + font-family: var(--font-body, "Inter", -apple-system, sans-serif); + font-weight: 500; + font-size: 15px; + color: hsl(var(--foreground)); + } + + .editorial-byline-meta { + font-family: var(--font-body, "Inter", -apple-system, sans-serif); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.1em; + color: hsl(var(--muted-foreground)); + } + + /* --- Override: heading (Playfair, tight tracking, optional small-caps kicker) --- */ + + .editorial-heading { + font-family: var(--font-heading, "Playfair Display", Georgia, serif); + font-weight: 700; + letter-spacing: -0.015em; + color: hsl(var(--foreground)); + line-height: 1.15; + } + + .editorial-heading-kicker { + display: block; + margin-bottom: 0.5rem; + font-family: var(--font-body, "Inter", -apple-system, sans-serif); + font-size: 12px; + text-transform: uppercase; + font-variant: small-caps; + font-feature-settings: "smcp"; + letter-spacing: 0.18em; + color: hsl(var(--muted-foreground)); + } + + /* --- Override: text body (Source Serif, hanging quotes) --- */ + + .editorial-text { + font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif); + color: hsl(var(--foreground)); + line-height: 1.7; + max-width: var(--editorial-measure); + } + + .editorial-text p + p { + text-indent: 1.5em; + margin-top: 0; + } + + .editorial-text blockquote { + hanging-punctuation: first; + font-style: italic; + border-left: var(--editorial-rule); + padding-left: 1rem; + margin-left: 0; + } + + /* --- Override: button (hairline border, no fill, accent on hover) --- */ + + .editorial-button { + display: inline-block; + padding: 0.5rem 1rem; + font-family: var(--font-body, "Inter", -apple-system, sans-serif); + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.12em; + background-color: transparent; + color: hsl(var(--foreground)); + border: 1px solid hsl(var(--border)); + text-decoration: none; + cursor: pointer; + transition: color 150ms ease, border-color 150ms ease; + } + + .editorial-button:hover, + .editorial-button:focus { + color: hsl(var(--accent)); + border-color: hsl(var(--accent)); + } + + .editorial-button:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; + } + + /* --- Override: image (Source Serif italic caption with hairline above) --- */ + + .editorial-figure { + margin: 1.5rem 0; + } + + .editorial-figure img { + display: block; + width: 100%; + height: auto; + } + + .editorial-figure figcaption { + margin-top: 0.75rem; + padding-top: 0.5rem; + border-top: var(--editorial-rule); + font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif); + font-style: italic; + font-size: 14px; + color: hsl(var(--muted-foreground)); + } + + /* --- Colophon --- */ + + .editorial-colophon { + border-top: var(--editorial-rule); + padding-top: 1.5rem; + font-family: var(--font-body, "Inter", -apple-system, sans-serif); + font-size: 13px; + color: hsl(var(--muted-foreground)); + } + + .editorial-colophon-issn { + font-family: var(--font-mono, "JetBrains Mono", monospace); + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.1em; + } + + /* --- Page layout — narrow editorial column --- */ + + .editorial-column { + max-width: var(--editorial-measure); + margin-left: auto; + margin-right: auto; + padding-left: 1rem; + padding-right: 1rem; + } + + .editorial-wide { + max-width: 96rem; + margin-left: auto; + margin-right: auto; + padding-left: 1rem; + padding-right: 1rem; + } + + .editorial-article-grid { + display: grid; + grid-template-columns: 1fr; + gap: 1.5rem; + max-width: 80rem; + margin-left: auto; + margin-right: auto; + padding-left: 1rem; + padding-right: 1rem; + } + + @media (min-width: 1024px) { + .editorial-article-grid { + grid-template-columns: minmax(0, 64ch) 16rem; + } + } diff --git a/marginalia.go b/marginalia.go deleted file mode 100644 index 806ccf6..0000000 --- a/marginalia.go +++ /dev/null @@ -1,58 +0,0 @@ -package main - -import ( - "bytes" - "context" - - "git.dev.alexdunmow.com/block/core/blocks" -) - -// MarginaliaBlockMeta describes the Editorial marginalia rail block: a list of -// italic notes anchored to in-body markers. At narrow viewports the rail -// collapses to inline asides via CSS. -// Registered as "editorial:marginalia" at runtime. -var MarginaliaBlockMeta = blocks.BlockMeta{ - Key: "marginalia", - Title: "Marginalia Rail", - Description: "Right-rail italic notes for longform articles", - Source: "editorial", - Category: blocks.CategoryTheme, -} - -// MarginaliaNote is one row in the marginalia rail. -type MarginaliaNote struct { - Note string // rich-text payload - Anchor string -} - -// MarginaliaData carries parsed marginalia content. -type MarginaliaData struct { - Notes []MarginaliaNote -} - -// MarginaliaBlock renders the marginalia rail. -// -// Content shape: -// -// { -// "items": [ -// { "note": "

Sourced from the 2024 audit.

", "anchor": "¶3" } -// ] -// } -func MarginaliaBlock(ctx context.Context, content map[string]any) string { - items := getSlice(content, "items") - - notes := make([]MarginaliaNote, 0, len(items)) - for _, item := range items { - notes = append(notes, MarginaliaNote{ - Note: getString(item, "note"), - Anchor: getString(item, "anchor"), - }) - } - - data := MarginaliaData{Notes: notes} - - var buf bytes.Buffer - _ = marginaliaComponent(data).Render(ctx, &buf) - return buf.String() -} diff --git a/marginalia.templ b/marginalia.templ deleted file mode 100644 index e4df205..0000000 --- a/marginalia.templ +++ /dev/null @@ -1,23 +0,0 @@ -package main - -// marginaliaComponent renders the marginalia rail. When there are zero notes -// the block renders an empty container so the master-page slot still has a -// stable hook point in the DOM (the UAT looks for the data-block attribute). -templ marginaliaComponent(data MarginaliaData) { -
- if len(data.Notes) > 0 { - for _, note := range data.Notes { - - } - } else { - - } -
-} diff --git a/marginalia_templ.go b/marginalia_templ.go deleted file mode 100644 index e8d11cf..0000000 --- a/marginalia_templ.go +++ /dev/null @@ -1,102 +0,0 @@ -// 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" - -// marginaliaComponent renders the marginalia rail. When there are zero notes -// the block renders an empty container so the master-page slot still has a -// stable hook point in the DOM (the UAT looks for the data-block attribute). -func marginaliaComponent(data MarginaliaData) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if len(data.Notes) > 0 { - for _, note := range data.Notes { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/master_pages.json b/master_pages.json new file mode 100644 index 0000000..54fb9e2 --- /dev/null +++ b/master_pages.json @@ -0,0 +1,25 @@ +[ + { + "key": "editorial:default-master", + "title": "Editorial Default Master", + "page_templates": ["default", "landing", "full-width"], + "blocks": [ + { "block_key": "editorial:masthead", "title": "Masthead", "content": { "menuName": "main", "kicker": "EST. 2026" }, "slot": "header", "sort_order": 0 }, + { "block_key": "navbar", "title": "Section Nav", "content": { "menuName": "sections" }, "slot": "header", "sort_order": 1 }, + { "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 }, + { "block_key": "editorial:colophon", "title": "Colophon", "content": { "showSignup": true, "issn": "0000-0000" }, "slot": "footer", "sort_order": 0 } + ] + }, + { + "key": "editorial:article-master", + "title": "Editorial Article Master", + "page_templates": ["article"], + "blocks": [ + { "block_key": "editorial:masthead", "title": "Masthead", "content": { "menuName": "main", "compact": true }, "slot": "header", "sort_order": 0 }, + { "block_key": "editorial:byline", "title": "Byline", "content": { "showPhoto": true, "showReadTime": true }, "slot": "byline", "sort_order": 0 }, + { "block_key": "slot", "title": "Article Body", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, + { "block_key": "editorial:marginalia", "title": "Marginalia Rail", "content": { "items": [] }, "slot": "marginalia", "sort_order": 0 }, + { "block_key": "editorial:colophon", "title": "Colophon", "content": { "showSignup": true }, "slot": "footer", "sort_order": 0 } + ] + } +] diff --git a/masthead.go b/masthead.go deleted file mode 100644 index 90a133e..0000000 --- a/masthead.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "bytes" - "context" - - "git.dev.alexdunmow.com/block/core/blocks" -) - -// MastheadBlockMeta describes the Editorial masthead block: the publication -// wordmark, an optional kicker line, a hairline rule, and a section nav menu. -// Registered as "editorial:masthead" at runtime. -var MastheadBlockMeta = blocks.BlockMeta{ - Key: "masthead", - Title: "Masthead", - Description: "Newspaper-style masthead with Playfair wordmark and hairline rule", - Source: "editorial", - Category: blocks.CategoryNavigation, -} - -// MastheadData carries the parsed content for the masthead template. -type MastheadData struct { - Title string - Kicker string - MenuName string - Compact bool -} - -// MastheadBlock renders the masthead. -// -// Content shape: -// -// { -// "title": "Editorial", // wordmark, falls back to site name when empty -// "kicker": "EST. 2026", // optional small caption above the wordmark -// "menuName": "main", // optional menu key for section nav (not resolved here) -// "compact": "false" // compact variant for article pages -// } -func MastheadBlock(ctx context.Context, content map[string]any) string { - data := MastheadData{ - Title: getStringWithDefault(content, "title", "Editorial"), - Kicker: getString(content, "kicker"), - MenuName: getString(content, "menuName"), - Compact: getBool(content, "compact", false), - } - - var buf bytes.Buffer - _ = mastheadComponent(data).Render(ctx, &buf) - return buf.String() -} diff --git a/masthead.templ b/masthead.templ deleted file mode 100644 index 02fc951..0000000 --- a/masthead.templ +++ /dev/null @@ -1,36 +0,0 @@ -package main - -// mastheadComponent renders the Editorial masthead. The wordmark is the brand -// signature (Playfair 900 italic). The optional kicker is a small mono caption -// above. A hairline rule sits below the title block. When compact mode is on -// the kicker is suppressed and the layout collapses to one line. -templ mastheadComponent(data MastheadData) { -
- if data.Compact { -
- - { data.Title } - - if data.MenuName != "" { - { data.MenuName } - } -
-
- } else { -
- if data.Kicker != "" { -
{ data.Kicker }
- } - - { data.Title } - -
- if data.MenuName != "" { - - } -
- } -
-} diff --git a/masthead_templ.go b/masthead_templ.go deleted file mode 100644 index 0ee335d..0000000 --- a/masthead_templ.go +++ /dev/null @@ -1,163 +0,0 @@ -// 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 Editorial masthead. The wordmark is the brand -// signature (Playfair 900 italic). The optional kicker is a small mono caption -// above. A hairline rule sits below the title block. When compact mode is on -// the kicker is suppressed and the layout collapses to one line. -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, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if data.Compact { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var2 string - templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 12, Col: 17} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if data.MenuName != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.MenuName) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 15, Col: 60} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if data.Kicker != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Kicker) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 22, Col: 62} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `masthead.templ`, Line: 25, Col: 17} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if data.MenuName != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/plugin.mod b/plugin.mod index a3a43df..6f38ff4 100644 --- a/plugin.mod +++ b/plugin.mod @@ -2,11 +2,8 @@ name = "editorial" display_name = "Editorial" scope = "@themes" -version = "0.1.2" +version = "0.2.0" description = "Didone display type, narrow measure, and hairline rules for newsrooms, longform writers, and opinion columnists." kind = "theme" categories = ["templates"] tags = ["editorial", "serif", "news", "longform", "magazine", "law", "finance", "opinion"] - -[compatibility] -block_core = ">=0.15.0 <0.16.0" diff --git a/pullquote.go b/pullquote.go deleted file mode 100644 index 4ed1003..0000000 --- a/pullquote.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "bytes" - "context" - - "git.dev.alexdunmow.com/block/core/blocks" -) - -// PullquoteBlockMeta describes the Editorial pull-quote block: a large oxblood -// Playfair italic block quote with optional attribution. -// Registered as "editorial:pullquote" at runtime. -var PullquoteBlockMeta = blocks.BlockMeta{ - Key: "pullquote", - Title: "Pull Quote", - Description: "Large oxblood pull quote in Playfair italic", - Source: "editorial", - Category: blocks.CategoryTheme, -} - -// PullquoteData carries parsed pull-quote content. -type PullquoteData struct { - Quote string // rich-text payload, trusted upstream - Attribution string -} - -// PullquoteBlock renders the pull quote. -// -// Content shape: -// -// { -// "quote": "

The reform was always going to be quiet.

", -// "attribution": "— The Editor" -// } -func PullquoteBlock(ctx context.Context, content map[string]any) string { - data := PullquoteData{ - Quote: getString(content, "quote"), - Attribution: getString(content, "attribution"), - } - - if data.Quote == "" { - return "" - } - - var buf bytes.Buffer - _ = pullquoteComponent(data).Render(ctx, &buf) - return buf.String() -} diff --git a/pullquote.templ b/pullquote.templ deleted file mode 100644 index 17e11b3..0000000 --- a/pullquote.templ +++ /dev/null @@ -1,16 +0,0 @@ -package main - -// pullquoteComponent renders the oxblood Playfair italic pull quote. The quote -// itself is trusted rich text supplied by the editor; we render it inline -// using templ.Raw so author-supplied emphasis is preserved. The attribution is -// rendered as a plain-text small-caps cap below. -templ pullquoteComponent(data PullquoteData) { - -} diff --git a/pullquote_templ.go b/pullquote_templ.go deleted file mode 100644 index 4f1268a..0000000 --- a/pullquote_templ.go +++ /dev/null @@ -1,71 +0,0 @@ -// 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 the oxblood Playfair italic pull quote. The quote -// itself is trusted rich text supplied by the editor; we render it inline -// using templ.Raw so author-supplied emphasis is preserved. The attribution is -// rendered as a plain-text small-caps cap below. -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, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/register.go b/register.go deleted file mode 100644 index d6e24a2..0000000 --- a/register.go +++ /dev/null @@ -1,188 +0,0 @@ -package main - -import ( - "context" - - "github.com/a-h/templ" - - "git.dev.alexdunmow.com/block/core/blocks" - "git.dev.alexdunmow.com/block/core/plugin" - "git.dev.alexdunmow.com/block/core/templates" -) - -// wrap adapts a templ-returning render function to templates.TemplateFunc. -// templ.Component already implements templates.HTMLComponent via its Render -// method, so the adapter only has to swap the return type. -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. The call order matters: -// 1. RegisterSystemTemplate seeds the system-template row. -// 2. RegisterPageTemplate once per page template (default / landing / -// article / full-width). -// 3. br.LoadSchemasFromFS BEFORE any br.Register so editor-side schemas -// are bound to their block keys. -// 4. br.Register for each theme-owned block (registered unqualified; -// addressed downstream as "editorial:"). -// 5. br.RegisterTemplateOverride for the four built-ins editorial styles -// differently (heading, text, button, image). The overrides are scoped -// to the "editorial" template — other templates keep their built-ins. -// 6. tr.RegisterEmailWrapper for the editorial-branded email layout. -func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error { - tr.RegisterSystemTemplate(templates.SystemTemplateMeta{ - Key: "editorial", - Title: "Editorial", - Description: "Didone display, narrow column, hairline rules for the modern newsroom.", - }) - - if err := tr.RegisterPageTemplate("editorial", templates.PageTemplateMeta{ - Key: "default", - Title: "Default", - Description: "Standard page with masthead, narrow main column, footer", - Slots: []string{"header", "main", "footer"}, - }, wrap(RenderEditorial)); err != nil { - return err - } - - if err := tr.RegisterPageTemplate("editorial", templates.PageTemplateMeta{ - Key: "landing", - Title: "Section Front", - Description: "Front-page-style stack: masthead, lead story, river, footer", - Slots: []string{"masthead", "lead", "river", "footer"}, - }, wrap(RenderEditorialLanding)); err != nil { - return err - } - - if err := tr.RegisterPageTemplate("editorial", templates.PageTemplateMeta{ - Key: "article", - Title: "Article", - Description: "Single-column long read with byline, drop cap, marginalia rail", - Slots: []string{"header", "byline", "main", "marginalia", "footer"}, - }, wrap(RenderEditorialArticle)); err != nil { - return err - } - - if err := tr.RegisterPageTemplate("editorial", templates.PageTemplateMeta{ - Key: "full-width", - Title: "Full Width", - Description: "Edge-to-edge for photo essays and data graphics", - Slots: []string{"header", "main", "footer"}, - }, wrap(RenderEditorialFullWidth)); err != nil { - return err - } - - // Schemas MUST be loaded before any Register so the editor binds the - // correct content schema to each block at registration time. - if err := br.LoadSchemasFromFS(Schemas()); err != nil { - return err - } - - // Theme-owned blocks. - br.Register(MastheadBlockMeta, MastheadBlock) - br.Register(BylineBlockMeta, BylineBlock) - br.Register(PullquoteBlockMeta, PullquoteBlock) - br.Register(DropcapIntroBlockMeta, DropcapIntroBlock) - br.Register(MarginaliaBlockMeta, MarginaliaBlock) - br.Register(SectionLabelBlockMeta, SectionLabelBlock) - br.Register(ColophonBlockMeta, ColophonBlock) - - // Built-in overrides scoped to the editorial template. - br.RegisterTemplateOverride("editorial", "heading", EditorialHeadingBlock) - br.RegisterTemplateOverride("editorial", "text", EditorialTextBlock) - br.RegisterTemplateOverride("editorial", "button", EditorialButtonBlock) - br.RegisterTemplateOverride("editorial", "image", EditorialImageBlock) - - // Branded email wrapper. - tr.RegisterEmailWrapper("editorial", EditorialEmailWrapper) - - return nil -} - -// DefaultMasterPages returns the two master pages editorial seeds on first -// load. The slot keys and BlockKey strings here match spec §7 exactly; the -// `slot` block carries `slotName` equal to its rendered slot key so the CMS -// knows where to inject page content. -func DefaultMasterPages() []plugin.MasterPageDefinition { - return []plugin.MasterPageDefinition{ - { - Key: "editorial:default-master", - Title: "Editorial Default Master", - PageTemplates: []string{"default", "landing", "full-width"}, - Blocks: []plugin.MasterPageBlock{ - { - BlockKey: "editorial:masthead", - Title: "Masthead", - Content: map[string]any{"menuName": "main", "kicker": "EST. 2026"}, - Slot: "header", - SortOrder: 0, - }, - { - BlockKey: "navbar", - Title: "Section Nav", - Content: map[string]any{"menuName": "sections"}, - Slot: "header", - SortOrder: 1, - }, - { - BlockKey: "slot", - Title: "Main Content", - Content: map[string]any{"slotName": "main", "placeholder": "Page content"}, - Slot: "main", - SortOrder: 0, - }, - { - BlockKey: "editorial:colophon", - Title: "Colophon", - Content: map[string]any{"showSignup": true, "issn": "0000-0000"}, - Slot: "footer", - SortOrder: 0, - }, - }, - }, - { - Key: "editorial:article-master", - Title: "Editorial Article Master", - PageTemplates: []string{"article"}, - Blocks: []plugin.MasterPageBlock{ - { - BlockKey: "editorial:masthead", - Title: "Masthead", - Content: map[string]any{"menuName": "main", "compact": true}, - Slot: "header", - SortOrder: 0, - }, - { - BlockKey: "editorial:byline", - Title: "Byline", - Content: map[string]any{"showPhoto": true, "showReadTime": true}, - Slot: "byline", - SortOrder: 0, - }, - { - BlockKey: "slot", - Title: "Article Body", - Content: map[string]any{"slotName": "main"}, - Slot: "main", - SortOrder: 0, - }, - { - BlockKey: "editorial:marginalia", - Title: "Marginalia Rail", - Content: map[string]any{"items": []any{}}, - Slot: "marginalia", - SortOrder: 0, - }, - { - BlockKey: "editorial:colophon", - Title: "Colophon", - Content: map[string]any{"showSignup": true}, - Slot: "footer", - SortOrder: 0, - }, - }, - }, - } -} diff --git a/registration.go b/registration.go deleted file mode 100644 index db964c5..0000000 --- a/registration.go +++ /dev/null @@ -1,29 +0,0 @@ -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 Editorial theme. -// -// Editorial is a templ-style theme: each block ships a typed `.go` render -// function backed by a `.templ` component. The standalone-plugin block -// signature is `func(ctx, content map[string]any) string` (no children arg). -var Registration = plugin.PluginRegistration{ - Name: "editorial", - Version: plugin.ParseModVersion(pluginModBytes), - Register: func(tr templates.TemplateRegistry, br blocks.BlockRegistry) error { - return Register(tr, br) - }, - Assets: func() http.Handler { return AssetsHandler() }, - Schemas: func() fs.FS { return Schemas() }, - ThemePresets: func() []byte { return ThemePresets() }, - BundledFonts: func() []byte { return BundledFonts() }, - MasterPages: func() []plugin.MasterPageDefinition { return DefaultMasterPages() }, - CSSManifest: func() *plugin.CSSManifest { return ThemeCSSManifest() }, -} diff --git a/section_label.go b/section_label.go deleted file mode 100644 index af17f02..0000000 --- a/section_label.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "bytes" - "context" - - "git.dev.alexdunmow.com/block/core/blocks" -) - -// SectionLabelBlockMeta describes the Editorial section-label block: an -// uppercase / small-caps label followed by an optional hairline divider. -// Registered as "editorial:section_label" at runtime. -var SectionLabelBlockMeta = blocks.BlockMeta{ - Key: "section_label", - Title: "Section Label", - Description: "Small-caps section label with optional hairline divider", - Source: "editorial", - Category: blocks.CategoryTheme, -} - -// SectionLabelData carries parsed section-label content. -type SectionLabelData struct { - Label string - Divider bool -} - -// SectionLabelBlock renders the section label. -// -// Content shape: -// -// { "label": "Politics", "divider": "true" } -func SectionLabelBlock(ctx context.Context, content map[string]any) string { - data := SectionLabelData{ - Label: getString(content, "label"), - Divider: getBool(content, "divider", true), - } - - if data.Label == "" { - return "" - } - - var buf bytes.Buffer - _ = sectionLabelComponent(data).Render(ctx, &buf) - return buf.String() -} diff --git a/section_label.templ b/section_label.templ deleted file mode 100644 index 7f9dc99..0000000 --- a/section_label.templ +++ /dev/null @@ -1,12 +0,0 @@ -package main - -// sectionLabelComponent renders the section label. The visual contract is -// uppercase + small-caps text followed (optionally) by a 1px hairline rule. -templ sectionLabelComponent(data SectionLabelData) { -
- - if data.Divider { - - } -
-} diff --git a/section_label_templ.go b/section_label_templ.go deleted file mode 100644 index 7fc454f..0000000 --- a/section_label_templ.go +++ /dev/null @@ -1,65 +0,0 @@ -// 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" - -// sectionLabelComponent renders the section label. The visual contract is -// uppercase + small-caps text followed (optionally) by a 1px hairline rule. -func sectionLabelComponent(data SectionLabelData) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if data.Divider { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate diff --git a/style_css.go b/style_css.go deleted file mode 100644 index 47c6ced..0000000 --- a/style_css.go +++ /dev/null @@ -1,365 +0,0 @@ -package main - -// editorialUtilityCSS is the theme's custom utility layer appended to the host -// Tailwind input. It is intentionally light: no @font-face rules (those are -// emitted by the host from the admin's font assignments per docs/FONTS.md), no -// hardcoded hex / rgb / named colours. Every colour goes through the shadcn -// HSL variable consumer pattern: `hsl(var(--token))`. -// -// What this CSS contracts to deliver (cross-checked against the UAT §13): -// - .prose-editorial: 64ch measure, indented continuation paragraphs. -// - .editorial-dropcap p:first-of-type::first-letter: oxblood accent, -// ≥ 3× body font-size. -// - .editorial-marginalia: italic Source Serif 4 12px right-rail asides. -// - .editorial-masthead-wordmark: Playfair Display 900 italic. -// - .editorial-pullquote: Playfair italic 38px in oxblood accent. -// - .editorial-section-label: uppercase + small-caps + hairline. -// - .editorial-hairline / hr.editorial-rule: 1px solid hsl(var(--border)). -// - .editorial-button: transparent fill, 1px border, hover → accent text. -// - .editorial-caption: Source Serif italic 14px with hairline above. -// -// All `font-family` declarations route through the three CSS variables -// (`--font-heading`, `--font-body`, `--font-mono`) the host exposes on :root, -// with fallback stacks derived from spec §3. -const editorialUtilityCSS = ` -/* === Editorial theme — utility layer === */ - -:root { - --editorial-measure: 64ch; - --editorial-rule: 1px solid hsl(var(--border)); -} - -/* --- Narrow editorial column with indented continuation paragraphs --- */ - -.prose-editorial { - max-width: var(--editorial-measure); - margin-left: auto; - margin-right: auto; - font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif); - color: hsl(var(--foreground)); - line-height: 1.65; -} - -.prose-editorial p { - margin: 0 0 1em 0; - text-indent: 0; -} - -.prose-editorial p + p { - text-indent: 1.5em; -} - -.prose-editorial h1, -.prose-editorial h2, -.prose-editorial h3, -.prose-editorial h4 { - font-family: var(--font-heading, "Playfair Display", Georgia, serif); - font-weight: 700; - letter-spacing: -0.01em; - color: hsl(var(--foreground)); -} - -.prose-editorial a { - color: hsl(var(--accent)); - text-decoration: underline; - text-decoration-thickness: 1px; - text-underline-offset: 2px; -} - -/* --- Drop cap — oxblood accent first letter, ≥ 3x body size --- */ - -.editorial-dropcap p:first-of-type::first-letter { - font-family: var(--font-heading, "Playfair Display", Georgia, serif); - font-weight: 900; - font-style: italic; - color: hsl(var(--accent)); - float: left; - font-size: 4.5em; - line-height: 0.85; - padding-right: 0.12em; - padding-top: 0.05em; - margin-bottom: -0.1em; -} - -/* --- Marginalia rail (right-side italic notes) --- */ - -.editorial-marginalia { - font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif); - font-style: italic; - font-size: 12px; - line-height: 1.5; - color: hsl(var(--muted-foreground)); -} - -.editorial-marginalia aside { - margin: 0 0 1.5rem 0; - padding-left: 0.75rem; - border-left: var(--editorial-rule); -} - -@media (max-width: 768px) { - .editorial-marginalia { - margin-top: 1.5rem; - border-top: var(--editorial-rule); - padding-top: 1rem; - } -} - -/* --- Masthead wordmark --- */ - -.editorial-masthead-wordmark { - font-family: var(--font-heading, "Playfair Display", Georgia, serif); - font-weight: 900; - font-style: italic; - letter-spacing: -0.02em; - color: hsl(var(--foreground)); -} - -.editorial-masthead-kicker { - font-family: var(--font-mono, "JetBrains Mono", "SF Mono", ui-monospace, monospace); - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.15em; - color: hsl(var(--muted-foreground)); -} - -.editorial-masthead-rule { - border: 0; - border-top: var(--editorial-rule); - margin: 0.5rem 0 0 0; -} - -/* --- Pull quote — Playfair italic in oxblood --- */ - -.editorial-pullquote { - font-family: var(--font-heading, "Playfair Display", Georgia, serif); - font-style: italic; - font-size: 38px; - line-height: 1.2; - color: hsl(var(--accent)); - margin: 2.5rem auto; - max-width: var(--editorial-measure); - border: none; -} - -.editorial-pullquote-attribution { - display: block; - margin-top: 1rem; - font-family: var(--font-body, "Source Serif 4", Georgia, serif); - font-style: normal; - font-size: 14px; - text-transform: uppercase; - letter-spacing: 0.12em; - color: hsl(var(--muted-foreground)); -} - -/* --- Section label — small-caps with hairline rule --- */ - -.editorial-section-label { - font-family: var(--font-body, "Source Serif 4", Georgia, serif); - text-transform: uppercase; - font-variant: small-caps; - font-feature-settings: "smcp"; - letter-spacing: 0.15em; - font-size: 13px; - color: hsl(var(--foreground)); -} - -.editorial-section-label-rule { - border: 0; - border-top: var(--editorial-rule); - margin: 0.5rem 0 1.5rem 0; -} - -/* --- Hairline rules --- */ - -hr.editorial-rule, -.editorial-hairline { - border: 0; - border-top: var(--editorial-rule); - margin: 1.5rem 0; -} - -/* --- Byline --- */ - -.editorial-byline { - display: flex; - align-items: center; - gap: 0.75rem; - padding: 0.75rem 0; - border-top: var(--editorial-rule); - border-bottom: var(--editorial-rule); -} - -.editorial-byline-photo { - width: 36px; - height: 36px; - border-radius: 50%; - background-color: hsl(var(--muted)); - flex-shrink: 0; - object-fit: cover; -} - -.editorial-byline-author { - font-family: var(--font-mono, "JetBrains Mono", monospace), var(--font-body, "Source Serif 4", Georgia, serif); - /* The spec calls for Inter for UI bylines. Fallback to Inter then system. */ - font-family: var(--font-body, "Inter", -apple-system, sans-serif); - font-weight: 500; - font-size: 15px; - color: hsl(var(--foreground)); -} - -.editorial-byline-meta { - font-family: var(--font-body, "Inter", -apple-system, sans-serif); - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.1em; - color: hsl(var(--muted-foreground)); -} - -/* --- Override: heading (Playfair, tight tracking, optional small-caps kicker) --- */ - -.editorial-heading { - font-family: var(--font-heading, "Playfair Display", Georgia, serif); - font-weight: 700; - letter-spacing: -0.015em; - color: hsl(var(--foreground)); - line-height: 1.15; -} - -.editorial-heading-kicker { - display: block; - margin-bottom: 0.5rem; - font-family: var(--font-body, "Inter", -apple-system, sans-serif); - font-size: 12px; - text-transform: uppercase; - font-variant: small-caps; - font-feature-settings: "smcp"; - letter-spacing: 0.18em; - color: hsl(var(--muted-foreground)); -} - -/* --- Override: text body (Source Serif, hanging quotes) --- */ - -.editorial-text { - font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif); - color: hsl(var(--foreground)); - line-height: 1.7; - max-width: var(--editorial-measure); -} - -.editorial-text p + p { - text-indent: 1.5em; - margin-top: 0; -} - -.editorial-text blockquote { - hanging-punctuation: first; - font-style: italic; - border-left: var(--editorial-rule); - padding-left: 1rem; - margin-left: 0; -} - -/* --- Override: button (hairline border, no fill, accent on hover) --- */ - -.editorial-button { - display: inline-block; - padding: 0.5rem 1rem; - font-family: var(--font-body, "Inter", -apple-system, sans-serif); - font-size: 13px; - text-transform: uppercase; - letter-spacing: 0.12em; - background-color: transparent; - color: hsl(var(--foreground)); - border: 1px solid hsl(var(--border)); - text-decoration: none; - cursor: pointer; - transition: color 150ms ease, border-color 150ms ease; -} - -.editorial-button:hover, -.editorial-button:focus { - color: hsl(var(--accent)); - border-color: hsl(var(--accent)); -} - -.editorial-button:focus-visible { - outline: 2px solid hsl(var(--ring)); - outline-offset: 2px; -} - -/* --- Override: image (Source Serif italic caption with hairline above) --- */ - -.editorial-figure { - margin: 1.5rem 0; -} - -.editorial-figure img { - display: block; - width: 100%; - height: auto; -} - -.editorial-figure figcaption { - margin-top: 0.75rem; - padding-top: 0.5rem; - border-top: var(--editorial-rule); - font-family: var(--font-body, "Source Serif 4", "Source Serif Pro", Georgia, serif); - font-style: italic; - font-size: 14px; - color: hsl(var(--muted-foreground)); -} - -/* --- Colophon --- */ - -.editorial-colophon { - border-top: var(--editorial-rule); - padding-top: 1.5rem; - font-family: var(--font-body, "Inter", -apple-system, sans-serif); - font-size: 13px; - color: hsl(var(--muted-foreground)); -} - -.editorial-colophon-issn { - font-family: var(--font-mono, "JetBrains Mono", monospace); - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.1em; -} - -/* --- Page layout — narrow editorial column --- */ - -.editorial-column { - max-width: var(--editorial-measure); - margin-left: auto; - margin-right: auto; - padding-left: 1rem; - padding-right: 1rem; -} - -.editorial-wide { - max-width: 96rem; - margin-left: auto; - margin-right: auto; - padding-left: 1rem; - padding-right: 1rem; -} - -.editorial-article-grid { - display: grid; - grid-template-columns: 1fr; - gap: 1.5rem; - max-width: 80rem; - margin-left: auto; - margin-right: auto; - padding-left: 1rem; - padding-right: 1rem; -} - -@media (min-width: 1024px) { - .editorial-article-grid { - grid-template-columns: minmax(0, 64ch) 16rem; - } -} -` diff --git a/template.templ b/template.templ deleted file mode 100644 index d760d03..0000000 --- a/template.templ +++ /dev/null @@ -1,248 +0,0 @@ -package main - -import ( - "context" - "git.dev.alexdunmow.com/block/core/templates/bn" -) - -// PageData carries data parsed off the doc map for the four page templates. -// This is the same shape gotham uses; the renderers below differ only in -// slot composition and column widths. -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 parseEditorialPageData(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, - } -} - -// Default editorial page — masthead header, narrow main column, footer. -templ Editorial(data PageData) { - - - @bn.Head(bn.HeadData{ - Title: data.Title, - Settings: data.SiteSettings, - PageMeta: data.PageMeta, - ThemeMode: data.ThemeMode, - ThemeCSS: data.ThemeCSS, - PluginStyles: []string{}, - StructuredData: data.StructuredData, - CSSHash: data.CSSHash, - PageviewNonce: data.PageviewNonce, - EngagementConfig: data.EngagementConfig, - }) - - @bn.AdminBypassBanner(data.SiteSettings) -
- @templ.Raw(data.Slots["header"]) -
-
- if main, ok := data.Slots["main"]; ok && main != "" { - @templ.Raw(main) - } else { -
-

No content blocks assigned to this page.

-
- } -
-
- @templ.Raw(data.Slots["footer"]) -
- @bn.BodyEnd(data.SiteSettings) - - -} - -// Section-front (landing) — stacked masthead, lead story, river, footer. -templ EditorialLanding(data PageData) { - - - @bn.Head(bn.HeadData{ - Title: data.Title, - Settings: data.SiteSettings, - PageMeta: data.PageMeta, - ThemeMode: data.ThemeMode, - ThemeCSS: data.ThemeCSS, - PluginStyles: []string{}, - StructuredData: data.StructuredData, - CSSHash: data.CSSHash, - PageviewNonce: data.PageviewNonce, - EngagementConfig: data.EngagementConfig, - }) - - @bn.AdminBypassBanner(data.SiteSettings) -
- @templ.Raw(data.Slots["masthead"]) -
-
- @templ.Raw(data.Slots["lead"]) -
-
- @templ.Raw(data.Slots["river"]) -
-
- @templ.Raw(data.Slots["footer"]) -
- @bn.BodyEnd(data.SiteSettings) - - -} - -// Article — masthead, byline, narrow main column with drop cap, marginalia -// rail, footer. The grid collapses to a single column below 1024px. -templ EditorialArticle(data PageData) { - - - @bn.Head(bn.HeadData{ - Title: data.Title, - Settings: data.SiteSettings, - PageMeta: data.PageMeta, - ThemeMode: data.ThemeMode, - ThemeCSS: data.ThemeCSS, - PluginStyles: []string{}, - StructuredData: data.StructuredData, - CSSHash: data.CSSHash, - PageviewNonce: data.PageviewNonce, - EngagementConfig: data.EngagementConfig, - }) - - @bn.AdminBypassBanner(data.SiteSettings) -
- @templ.Raw(data.Slots["header"]) -
-
- @templ.Raw(data.Slots["byline"]) -
-
-
- if main, ok := data.Slots["main"]; ok && main != "" { - @templ.Raw(main) - } else { -

No content blocks assigned to this page.

- } -
- -
-
- @templ.Raw(data.Slots["footer"]) -
- @bn.BodyEnd(data.SiteSettings) - - -} - -// Full-width — edge-to-edge content for photo essays and data graphics. -templ EditorialFullWidth(data PageData) { - - - @bn.Head(bn.HeadData{ - Title: data.Title, - Settings: data.SiteSettings, - PageMeta: data.PageMeta, - ThemeMode: data.ThemeMode, - ThemeCSS: data.ThemeCSS, - PluginStyles: []string{}, - StructuredData: data.StructuredData, - CSSHash: data.CSSHash, - PageviewNonce: data.PageviewNonce, - EngagementConfig: data.EngagementConfig, - }) - - @bn.AdminBypassBanner(data.SiteSettings) -
- @templ.Raw(data.Slots["header"]) -
-
- if main, ok := data.Slots["main"]; ok && main != "" { - @templ.Raw(main) - } else { -
-

No content blocks assigned to this page.

-
- } -
-
- @templ.Raw(data.Slots["footer"]) -
- @bn.BodyEnd(data.SiteSettings) - - -} - -func RenderEditorial(ctx context.Context, doc map[string]any) templ.Component { - return Editorial(parseEditorialPageData(doc)) -} - -func RenderEditorialLanding(ctx context.Context, doc map[string]any) templ.Component { - return EditorialLanding(parseEditorialPageData(doc)) -} - -func RenderEditorialArticle(ctx context.Context, doc map[string]any) templ.Component { - return EditorialArticle(parseEditorialPageData(doc)) -} - -func RenderEditorialFullWidth(ctx context.Context, doc map[string]any) templ.Component { - return EditorialFullWidth(parseEditorialPageData(doc)) -} diff --git a/template_templ.go b/template_templ.go deleted file mode 100644 index a5d2254..0000000 --- a/template_templ.go +++ /dev/null @@ -1,504 +0,0 @@ -// 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 data parsed off the doc map for the four page templates. -// This is the same shape gotham uses; the renderers below differ only in -// slot composition and column widths. -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 parseEditorialPageData(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, - } -} - -// Default editorial page — masthead header, narrow main column, footer. -func Editorial(data PageData) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = bn.Head(bn.HeadData{ - Title: data.Title, - Settings: data.SiteSettings, - PageMeta: data.PageMeta, - ThemeMode: data.ThemeMode, - ThemeCSS: data.ThemeCSS, - PluginStyles: []string{}, - StructuredData: data.StructuredData, - CSSHash: data.CSSHash, - PageviewNonce: data.PageviewNonce, - EngagementConfig: data.EngagementConfig, - }).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if main, ok := data.Slots["main"]; ok && main != "" { - templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

No content blocks assigned to this page.

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -// Section-front (landing) — stacked masthead, lead story, river, footer. -func EditorialLanding(data PageData) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var2 := templ.GetChildren(ctx) - if templ_7745c5c3_Var2 == nil { - templ_7745c5c3_Var2 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = bn.Head(bn.HeadData{ - Title: data.Title, - Settings: data.SiteSettings, - PageMeta: data.PageMeta, - ThemeMode: data.ThemeMode, - ThemeCSS: data.ThemeCSS, - PluginStyles: []string{}, - StructuredData: data.StructuredData, - CSSHash: data.CSSHash, - PageviewNonce: data.PageviewNonce, - EngagementConfig: data.EngagementConfig, - }).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["masthead"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["lead"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["river"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") - 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, 16, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -// Article — masthead, byline, narrow main column with drop cap, marginalia -// rail, footer. The grid collapses to a single column below 1024px. -func EditorialArticle(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, 17, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = bn.Head(bn.HeadData{ - Title: data.Title, - Settings: data.SiteSettings, - PageMeta: data.PageMeta, - ThemeMode: data.ThemeMode, - ThemeCSS: data.ThemeCSS, - PluginStyles: []string{}, - StructuredData: data.StructuredData, - CSSHash: data.CSSHash, - PageviewNonce: data.PageviewNonce, - EngagementConfig: data.EngagementConfig, - }).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "") - 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, 19, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["byline"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - 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, 22, "

No content blocks assigned to this page.

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") - 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, 26, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -// Full-width — edge-to-edge content for photo essays and data graphics. -func EditorialFullWidth(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, 27, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = bn.Head(bn.HeadData{ - Title: data.Title, - Settings: data.SiteSettings, - PageMeta: data.PageMeta, - ThemeMode: data.ThemeMode, - ThemeCSS: data.ThemeCSS, - PluginStyles: []string{}, - StructuredData: data.StructuredData, - CSSHash: data.CSSHash, - PageviewNonce: data.PageviewNonce, - EngagementConfig: data.EngagementConfig, - }).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") - 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, 29, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
") - 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, 31, "

No content blocks assigned to this page.

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func RenderEditorial(ctx context.Context, doc map[string]any) templ.Component { - return Editorial(parseEditorialPageData(doc)) -} - -func RenderEditorialLanding(ctx context.Context, doc map[string]any) templ.Component { - return EditorialLanding(parseEditorialPageData(doc)) -} - -func RenderEditorialArticle(ctx context.Context, doc map[string]any) templ.Component { - return EditorialArticle(parseEditorialPageData(doc)) -} - -func RenderEditorialFullWidth(ctx context.Context, doc map[string]any) templ.Component { - return EditorialFullWidth(parseEditorialPageData(doc)) -} - -var _ = templruntime.GeneratedTemplate diff --git a/templates/editorial/article.ninjatpl b/templates/editorial/article.ninjatpl new file mode 100644 index 0000000..c578d44 --- /dev/null +++ b/templates/editorial/article.ninjatpl @@ -0,0 +1,15 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
{{ slots.header|safe }}
+
{{ slots.byline|safe }}
+
+
{% if slots.main %}{{ slots.main|safe }}{% else %}

No content blocks assigned to this page.

{% endif %}
+ +
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/editorial/default.ninjatpl b/templates/editorial/default.ninjatpl new file mode 100644 index 0000000..4c7e528 --- /dev/null +++ b/templates/editorial/default.ninjatpl @@ -0,0 +1,11 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
{{ slots.header|safe }}
+
{% if slots.main %}{{ slots.main|safe }}{% else %}

No content blocks assigned to this page.

{% endif %}
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/editorial/full-width.ninjatpl b/templates/editorial/full-width.ninjatpl new file mode 100644 index 0000000..fc86238 --- /dev/null +++ b/templates/editorial/full-width.ninjatpl @@ -0,0 +1,11 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
{{ slots.header|safe }}
+
{% if slots.main %}{{ slots.main|safe }}{% else %}

No content blocks assigned to this page.

{% endif %}
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/editorial/landing.ninjatpl b/templates/editorial/landing.ninjatpl new file mode 100644 index 0000000..9d036c4 --- /dev/null +++ b/templates/editorial/landing.ninjatpl @@ -0,0 +1,12 @@ + + +{{ head_html|safe }} + + {{ admin_banner_html|safe }} +
{{ slots.masthead|safe }}
+
{{ slots.lead|safe }}
+
{{ slots.river|safe }}
+
{{ slots.footer|safe }}
+ {{ body_end_html|safe }} + + diff --git a/templates/email/editorial.ninjatpl b/templates/email/editorial.ninjatpl new file mode 100644 index 0000000..c5e949a --- /dev/null +++ b/templates/email/editorial.ninjatpl @@ -0,0 +1,14 @@ +{{ site_name }}{% if preview_text %}
{{ preview_text }}
{% endif %}
diff --git a/templates/overrides/editorial/button.ninjatpl b/templates/overrides/editorial/button.ninjatpl new file mode 100644 index 0000000..3693bb8 --- /dev/null +++ b/templates/overrides/editorial/button.ninjatpl @@ -0,0 +1 @@ +{% if text %}{% if url %}{% else %}{% endif %}{% endif %} diff --git a/templates/overrides/editorial/heading.ninjatpl b/templates/overrides/editorial/heading.ninjatpl new file mode 100644 index 0000000..067e99e --- /dev/null +++ b/templates/overrides/editorial/heading.ninjatpl @@ -0,0 +1 @@ +{% if level|integer == 1 %}
{% if kicker %}{{ kicker }}{% endif %}

{{ text }}

{% elif level|integer == 3 %}

{{ text }}

{% elif level|integer == 4 %}

{{ text }}

{% elif level|integer == 5 %}
{{ text }}
{% elif level|integer == 6 %}
{{ text }}
{% else %}

{{ text }}

{% endif %} diff --git a/templates/overrides/editorial/image.ninjatpl b/templates/overrides/editorial/image.ninjatpl new file mode 100644 index 0000000..8769d22 --- /dev/null +++ b/templates/overrides/editorial/image.ninjatpl @@ -0,0 +1 @@ +{% if src %}
{{ alt }}{% if caption %}
{{ caption }}
{% endif %}
{% endif %} diff --git a/templates/overrides/editorial/text.ninjatpl b/templates/overrides/editorial/text.ninjatpl new file mode 100644 index 0000000..806161a --- /dev/null +++ b/templates/overrides/editorial/text.ninjatpl @@ -0,0 +1 @@ +
{{ text|safe }}
diff --git a/text_override.go b/text_override.go deleted file mode 100644 index 2779b03..0000000 --- a/text_override.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "bytes" - "context" -) - -// EditorialTextBlock overrides the built-in `text` block for the editorial -// template. Body copy is Source Serif 4 within a 64ch measure, with -// continuation-paragraph indents. -// -// Content shape: { "text", "class" } — same as the built-in text block. -func EditorialTextBlock(ctx context.Context, content map[string]any) string { - text := getString(content, "text") - class := getString(content, "class") - - var buf bytes.Buffer - _ = editorialTextComponent(text, class).Render(ctx, &buf) - return buf.String() -} diff --git a/text_override.templ b/text_override.templ deleted file mode 100644 index 8b2bc02..0000000 --- a/text_override.templ +++ /dev/null @@ -1,10 +0,0 @@ -package main - -// editorialTextComponent renders rich-text body copy with the editorial prose -// rules applied. The author-supplied HTML is trusted (the CMS sanitiser runs -// upstream). -templ editorialTextComponent(text, class string) { -
- @templ.Raw(text) -
-} diff --git a/text_override_templ.go b/text_override_templ.go deleted file mode 100644 index 1cca95d..0000000 --- a/text_override_templ.go +++ /dev/null @@ -1,69 +0,0 @@ -// 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" - -// editorialTextComponent renders rich-text body copy with the editorial prose -// rules applied. The author-supplied HTML is trusted (the CMS sanitiser runs -// upstream). -func editorialTextComponent(text, class string) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var1 := templ.GetChildren(ctx) - if templ_7745c5c3_Var1 == nil { - templ_7745c5c3_Var1 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - var templ_7745c5c3_Var2 = []any{"editorial-text editorial-column prose-editorial", class} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templ.Raw(text).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -var _ = templruntime.GeneratedTemplate