feat: convert to codeless .bnp — templates + manifest, Go/templ deleted (WO-WZ-021)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-07-04 15:17:12 +08:00
parent b38c0d3a57
commit dccd269ab2
78 changed files with 767 additions and 4711 deletions

View File

@ -1,26 +1,27 @@
# kindergarten — build & publish helpers (wasm .bnp workflow) # kindergarten — build & publish helpers (CODELESS .bnp workflow, WO-WZ-021)
# #
# The theme compiles to a wasip1 reactor-mode wasm module packed into a .bnp # This theme is a CODELESS .bnp: pure declaration the host runs — blocks
# artifact (`ninja plugin build`). The legacy .so path is retired (WO-WZ-015): # (blocks/blocks.yaml + .ninjatpl), page/system templates, template overrides,
# the CMS is wasm-native and can no longer load .so plugins. Distribution is # an email wrapper, master pages, presets, fonts and CSS in manifest.yaml. No
# publish-to-registry -> InstallFromRegistry (hot-load, no restart). # 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: # Usage:
# make build # compile wasm + pack kindergarten-<version>.bnp # make build # pack kindergarten-<version>.bnp (codeless — no wasm)
# make verify # validate the built .bnp (layout/abi/name/size) # make verify # validate the built .bnp (loader-identical checks)
# make archive-check # prove a clean `git archive HEAD` compiles to wasm # make archive-check # prove a clean `git archive HEAD` still builds codeless
# make publish # ninja plugin publish --bnp (to the active registry) # make publish # ninja plugin publish --bnp (to the active registry)
# make templ # regenerate templ Go files
# make clean # remove build artefacts # make clean # remove build artefacts
.PHONY: build verify archive-check publish templ clean help .PHONY: build verify archive-check publish clean help
PLUGIN_NAME := kindergarten PLUGIN_NAME := kindergarten
NINJA := ninja NINJA := ninja
BNP := $(PLUGIN_NAME)-$(shell grep '^version' plugin.mod | sed 's/.*"\(.*\)"/\1/').bnp 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, # Pack the codeless .bnp (manifest.pb synthesized from plugin.mod + manifest.yaml
# tar.zst of plugin.wasm + plugin.mod + manifest.pb + schemas/ + assets/). # + blocks/ + templates/; no wasm compile, no DESCRIBE probe).
build: build:
$(NINJA) plugin build --dir . $(NINJA) plugin build --dir .
@ -28,12 +29,12 @@ build:
verify: verify:
$(NINJA) plugin verify $(BNP) $(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: archive-check:
@T=$$(mktemp -d /tmp/archive-check-$(PLUGIN_NAME).XXXXXX) && \ @T=$$(mktemp -d /tmp/archive-check-$(PLUGIN_NAME).XXXXXX) && \
trap 'rm -rf "$$T"' EXIT && \ trap 'rm -rf "$$T"' EXIT && \
git archive HEAD | tar -x -C "$$T" && \ 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" echo "archive-check OK"
# Publish the prebuilt .bnp to the active registry. # Publish the prebuilt .bnp to the active registry.
@ -41,19 +42,14 @@ archive-check:
publish: build publish: build
$(NINJA) plugin publish --bnp $(BNP) $(NINJA) plugin publish --bnp $(BNP)
# Regenerate templ Go files locally.
templ:
templ generate
# Remove build artefacts. # Remove build artefacts.
clean: clean:
rm -f $(PLUGIN_NAME)-*.bnp $(PLUGIN_NAME).so plugin.wasm rm -f $(PLUGIN_NAME)-*.bnp plugin.wasm
help: help:
@echo "Targets:" @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 " 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 " publish Publish the .bnp to the active registry"
@echo " templ Regenerate templ Go files"
@echo " clean Remove build artefacts" @echo " clean Remove build artefacts"

View File

@ -1,64 +0,0 @@
package main
import (
"bytes"
"context"
"strings"
"git.dev.alexdunmow.com/block/core/blocks"
)
// AlphabetStripBlockMeta defines metadata for the alphabet strip block.
var AlphabetStripBlockMeta = blocks.BlockMeta{
Key: "alphabet_strip",
Title: "Alphabet Strip",
Description: "Decorative letter band; jumps to anchors when used as TOC.",
Category: blocks.CategoryNavigation,
Source: "kindergarten",
}
// AlphabetStripData contains the letters + colorway for the rendered strip.
type AlphabetStripData struct {
Letters []string
ColorMode string
}
// AlphabetStripBlock renders a decorative letter band.
// Content shape: {letters,colorMode}.
func AlphabetStripBlock(ctx context.Context, content map[string]any) string {
raw := getStringDefault(content, "letters", "")
colorMode := getStringDefault(content, "colorMode", "rainbow")
// Validate colorMode against allowed set.
switch colorMode {
case "primary", "rainbow", "mono":
default:
colorMode = "rainbow"
}
letters := splitLetters(raw)
data := AlphabetStripData{
Letters: letters,
ColorMode: colorMode,
}
var buf bytes.Buffer
_ = alphabetStripComponent(data).Render(ctx, &buf)
return buf.String()
}
// splitLetters returns one entry per visible rune. Whitespace is trimmed.
func splitLetters(s string) []string {
s = strings.TrimSpace(s)
if s == "" {
return nil
}
letters := make([]string, 0, len(s))
for _, r := range s {
if r == ' ' || r == '\t' || r == '\n' {
continue
}
letters = append(letters, string(r))
}
return letters
}

View File

@ -1,25 +0,0 @@
package main
// alphabetStripComponent renders the letter band.
templ alphabetStripComponent(data AlphabetStripData) {
<nav class={ "kg-alphabet-strip", alphabetClass(data.ColorMode) } aria-label="Alphabet strip" data-block="kindergarten:alphabet_strip">
if len(data.Letters) == 0 {
<span class="kg-empty" data-empty="true">No letters configured</span>
}
for _, letter := range data.Letters {
<span class="kg-alphabet-letter">{ letter }</span>
}
</nav>
}
// alphabetClass returns the modifier class for the chosen colorway.
func alphabetClass(mode string) string {
switch mode {
case "primary":
return "kg-alphabet-primary"
case "mono":
return "kg-alphabet-mono"
default:
return ""
}
}

View File

@ -1,100 +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"
// alphabetStripComponent renders the letter band.
func alphabetStripComponent(data AlphabetStripData) 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{"kg-alphabet-strip", alphabetClass(data.ColorMode)}
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, "<nav class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `alphabet_strip.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" aria-label=\"Alphabet strip\" data-block=\"kindergarten:alphabet_strip\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(data.Letters) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<span class=\"kg-empty\" data-empty=\"true\">No letters configured</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
for _, letter := range data.Letters {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"kg-alphabet-letter\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(letter)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `alphabet_strip.templ`, Line: 10, Col: 44}
}
_, 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, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// alphabetClass returns the modifier class for the chosen colorway.
func alphabetClass(mode string) string {
switch mode {
case "primary":
return "kg-alphabet-primary"
case "mono":
return "kg-alphabet-mono"
default:
return ""
}
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,45 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// BigCTABlockMeta defines metadata for the oversized CTA block.
var BigCTABlockMeta = blocks.BlockMeta{
Key: "big_cta",
Title: "Big CTA",
Description: "Oversized pill button with crayon underline.",
Category: blocks.CategoryContent,
Source: "kindergarten",
}
// BigCTAData is the renderer input.
type BigCTAData struct {
Label string
Href string
ColorVariant string
}
// BigCTABlock renders the oversized pill button.
// Content shape: {label,href,colorVariant}.
func BigCTABlock(ctx context.Context, content map[string]any) string {
color := getStringDefault(content, "colorVariant", "yellow")
switch color {
case "red", "blue", "yellow", "green":
default:
color = "yellow"
}
data := BigCTAData{
Label: getString(content, "label"),
Href: getStringDefault(content, "href", "#"),
ColorVariant: color,
}
var buf bytes.Buffer
_ = bigCTAComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,28 +0,0 @@
package main
// bigCTAComponent renders the oversized pill CTA.
templ bigCTAComponent(data BigCTAData) {
<section class="kg-big-cta" data-block="kindergarten:big_cta">
<div class="kg-container">
if data.Label != "" {
<a href={ templ.SafeURL(data.Href) } class={ "kg-pill", "kg-crayon-underline", ctaColorClass(data.ColorVariant) }>{ data.Label }</a>
} else {
<span class="kg-empty" data-empty="true">Add a label and link to publish this call-to-action.</span>
}
</div>
</section>
}
// ctaColorClass picks the modifier class for the four-color CTA variants.
func ctaColorClass(variant string) string {
switch variant {
case "red":
return "kg-pill-red"
case "blue":
return "kg-pill-blue"
case "green":
return "kg-pill-green"
default:
return "kg-pill-yellow"
}
}

View File

@ -1,114 +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"
// bigCTAComponent renders the oversized pill CTA.
func bigCTAComponent(data BigCTAData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"kg-big-cta\" data-block=\"kindergarten:big_cta\"><div class=\"kg-container\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Label != "" {
var templ_7745c5c3_Var2 = []any{"kg-pill", "kg-crayon-underline", ctaColorClass(data.ColorVariant)}
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, 2, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `big_cta.templ`, Line: 8, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `big_cta.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `big_cta.templ`, Line: 8, Col: 130}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"kg-empty\" data-empty=\"true\">Add a label and link to publish this call-to-action.</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// ctaColorClass picks the modifier class for the four-color CTA variants.
func ctaColorClass(variant string) string {
switch variant {
case "red":
return "kg-pill-red"
case "blue":
return "kg-pill-blue"
case "green":
return "kg-pill-green"
default:
return "kg-pill-yellow"
}
}
var _ = templruntime.GeneratedTemplate

View File

@ -0,0 +1 @@
<nav class="kg-alphabet-strip {% if colorMode == "primary" %}kg-alphabet-primary{% elif colorMode == "mono" %}kg-alphabet-mono{% endif %}" aria-label="Alphabet strip" data-block="kindergarten:alphabet_strip">{% if not letters|cut:" " %}<span class="kg-empty" data-empty="true">No letters configured</span>{% endif %}{% for letter in letters|cut:" " %}<span class="kg-alphabet-letter">{{ letter }}</span>{% endfor %}</nav>

1
blocks/big_cta.ninjatpl Normal file
View File

@ -0,0 +1 @@
<section class="kg-big-cta" data-block="kindergarten:big_cta"><div class="kg-container">{% if label %}<a href="{{ href|default:'#' }}" class="kg-pill kg-crayon-underline {% if colorVariant == "red" %}kg-pill-red{% elif colorVariant == "blue" %}kg-pill-blue{% elif colorVariant == "green" %}kg-pill-green{% else %}kg-pill-yellow{% endif %}">{{ label }}</a>{% else %}<span class="kg-empty" data-empty="true">Add a label and link to publish this call-to-action.</span>{% endif %}</div></section>

54
blocks/blocks.yaml Normal file
View File

@ -0,0 +1,54 @@
# Kindergarten theme block definitions (codeless .bnp, WO-WZ-021).
# Keys are fully qualified (kindergarten:<key>) 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: kindergarten:mascot_hero
title: Mascot Hero
description: Big rounded panel with mascot SVG and primary-colored confetti.
category: content
schema: mascot_hero.schema.json
template: mascot_hero.ninjatpl
- key: kindergarten:alphabet_strip
title: Alphabet Strip
description: Decorative letter band; jumps to anchors when used as TOC.
category: navigation
schema: alphabet_strip.schema.json
template: alphabet_strip.ninjatpl
- key: kindergarten:schedule
title: Day Schedule
description: Daycare/classroom day plan with crayon time-pills.
category: content
schema: schedule.schema.json
template: schedule.ninjatpl
- key: kindergarten:gallery_of_art
title: Gallery of Art
description: Polaroid-style grid for kid artwork.
category: content
schema: gallery_of_art.schema.json
template: gallery_of_art.ninjatpl
- key: kindergarten:numbers_counter
title: Numbers Counter
description: Big circular numeral badges for stats (e.g. '12 teachers').
category: content
schema: numbers_counter.schema.json
template: numbers_counter.ninjatpl
- key: kindergarten:storybook_quote
title: Storybook Quote
description: Picture-book page treatment for a quote with optional illustration.
category: content
schema: storybook_quote.schema.json
template: storybook_quote.ninjatpl
- key: kindergarten:big_cta
title: Big CTA
description: Oversized pill button with crayon underline.
category: content
schema: big_cta.schema.json
template: big_cta.ninjatpl
- key: kindergarten:footer
title: Friendly Footer
description: Mascot wave, signup, contact, crayon-rule divider.
category: layout
schema: footer.schema.json
template: footer.ninjatpl

1
blocks/footer.ninjatpl Normal file
View File

@ -0,0 +1 @@
<div class="kg-footer" data-block="kindergarten:footer"><div class="kg-container"><div class="kg-footer-mascot"><span aria-hidden="true">👋</span><span>{{ mascotName|default:'Pip' }} says hi!</span></div>{% if showSignup != false %}<form class="kg-footer-signup" aria-label="Newsletter signup" method="post" action="/subscribe"><input type="email" name="email" placeholder="Your email" aria-label="Email address" required><button type="submit" class="kg-pill kg-pill-yellow">Sign up</button></form>{% endif %}{% if socialLinks %}<div class="kg-social-row" aria-label="Social media">{% for link in socialLinks %}<a href="{{ link.href }}" class="kg-social-link" aria-label="{% if link.platform == "facebook" %}Visit our Facebook{% elif link.platform == "instagram" %}Visit our Instagram{% elif link.platform == "youtube" %}Visit our YouTube{% elif link.platform == "tiktok" %}Visit our TikTok{% elif link.platform == "email" %}Send us an email{% else %}Open social link{% endif %}">{% if link.platform == "facebook" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path></svg>{% elif link.platform == "instagram" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line></svg>{% elif link.platform == "youtube" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z"></path><polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02"></polygon></svg>{% elif link.platform == "tiktok" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 12a4 4 0 1 0 4 4V4a5 5 0 0 0 5 5"></path></svg>{% elif link.platform == "email" %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>{% else %}<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle></svg>{% endif %}</a>{% endfor %}</div>{% endif %}</div></div>

View File

@ -0,0 +1 @@
<section class="kg-section" data-block="kindergarten:gallery_of_art"><div class="kg-container">{% set showNames = true %}{% if showChildName == false %}{% set showNames = false %}{% endif %}{% if title %}<h2 class="kg-display kg-crayon-underline" style="font-size: 2rem; margin-bottom: 1.5rem;">{{ title }}</h2>{% endif %}{% if not items %}<div class="kg-empty" data-empty="true">No artworks yet — add one to fill the gallery wall.</div>{% else %}<div class="kg-gallery-grid">{% for art in items %}<figure class="kg-polaroid">{% if art.image %}<img src="{{ art.image|mediaURL }}" alt="{% if showNames and art.childName %}Artwork by {{ art.childName }}{% else %}Children&#39;s artwork{% endif %}">{% else %}<span class="kg-polaroid-image" role="img" aria-label="Placeholder for child artwork"></span>{% endif %}<figcaption class="kg-polaroid-caption">{% if showNames and art.childName %}<span>{{ art.childName }}</span>{% endif %}{% if showNames and art.childName and art.age|integer > 0 %}, {% endif %}{% if art.age|integer > 0 %}<span>age {{ art.age|integer }}</span>{% endif %}</figcaption></figure>{% endfor %}</div>{% endif %}</div></section>

View File

@ -0,0 +1 @@
<section class="kg-section" data-block="kindergarten:mascot_hero"><div class="kg-container"><div class="kg-hero-panel"><div class="kg-confetti" aria-hidden="true"></div><div style="position: relative; z-index: 1; display: grid; gap: 2rem; grid-template-columns: 1fr; align-items: center;" class="kg-mascot-hero-grid"><div>{% if headline %}<h1 class="kg-display kg-crayon-underline" style="font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;">{{ headline }}</h1>{% else %}<h1 class="kg-display" style="font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;" data-empty="true">Welcome!</h1>{% endif %}{% if tagline %}<p class="kg-text" style="font-size: 1.25rem; margin-top: 1.5rem;">{{ tagline }}</p>{% endif %}{% if ctaLabel and ctaHref %}<div style="margin-top: 2rem;"><a href="{{ ctaHref }}" class="kg-pill">{{ ctaLabel }}</a></div>{% endif %}</div><div style="display: flex; justify-content: center; align-items: center;">{% if mascot == "blocks" %}<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Alphabet blocks mascot"><rect x="20" y="60" width="60" height="60" rx="12" fill="hsl(var(--primary))"></rect><rect x="90" y="40" width="60" height="60" rx="12" fill="hsl(var(--secondary))"></rect><rect x="60" y="120" width="60" height="60" rx="12" fill="hsl(var(--accent))"></rect><text x="50" y="98" text-anchor="middle" font-family="var(--font-heading, Quicksand)" font-weight="700" font-size="32" fill="hsl(var(--primary-foreground))">A</text><text x="120" y="78" text-anchor="middle" font-family="var(--font-heading, Quicksand)" font-weight="700" font-size="32" fill="hsl(var(--secondary-foreground))">B</text><text x="90" y="158" text-anchor="middle" font-family="var(--font-heading, Quicksand)" font-weight="700" font-size="32" fill="hsl(var(--accent-foreground))">C</text></svg>{% elif mascot == "star" %}<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Friendly star mascot"><polygon points="100,10 122,75 190,75 135,115 155,180 100,140 45,180 65,115 10,75 78,75" fill="hsl(var(--accent))" stroke="hsl(var(--foreground))" stroke-width="4" stroke-linejoin="round"></polygon><circle cx="80" cy="95" r="6" fill="hsl(var(--foreground))"></circle><circle cx="120" cy="95" r="6" fill="hsl(var(--foreground))"></circle><path d="M 80 120 Q 100 135 120 120" stroke="hsl(var(--foreground))" stroke-width="4" fill="none" stroke-linecap="round"></path></svg>{% elif mascot == "balloon" %}<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Balloon mascot"><ellipse cx="100" cy="80" rx="50" ry="60" fill="hsl(var(--primary))" stroke="hsl(var(--foreground))" stroke-width="4"></ellipse><path d="M 100 140 L 95 150 L 100 145 L 105 150 Z" fill="hsl(var(--foreground))"></path><path d="M 100 150 Q 90 175 100 195" stroke="hsl(var(--foreground))" stroke-width="2" fill="none"></path><circle cx="85" cy="65" r="6" fill="hsl(var(--foreground))"></circle><circle cx="115" cy="65" r="6" fill="hsl(var(--foreground))"></circle><path d="M 85 90 Q 100 105 115 90" stroke="hsl(var(--foreground))" stroke-width="4" fill="none" stroke-linecap="round"></path></svg>{% else %}<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Pip the mascot"><circle cx="100" cy="115" r="70" fill="hsl(var(--accent))" stroke="hsl(var(--foreground))" stroke-width="4"></circle><circle cx="78" cy="100" r="8" fill="hsl(var(--foreground))"></circle><circle cx="122" cy="100" r="8" fill="hsl(var(--foreground))"></circle><circle cx="78" cy="98" r="3" fill="hsl(var(--background))"></circle><circle cx="122" cy="98" r="3" fill="hsl(var(--background))"></circle><path d="M 75 130 Q 100 155 125 130" stroke="hsl(var(--foreground))" stroke-width="5" fill="none" stroke-linecap="round"></path><circle cx="60" cy="115" r="6" fill="hsl(var(--secondary))"></circle><circle cx="140" cy="115" r="6" fill="hsl(var(--secondary))"></circle><path d="M 50 60 L 60 35 L 80 50 Z" fill="hsl(var(--primary))"></path><path d="M 150 60 L 140 35 L 120 50 Z" fill="hsl(var(--primary))"></path></svg>{% endif %}</div></div></div></div></section>

View File

@ -0,0 +1 @@
<section class="kg-section" data-block="kindergarten:numbers_counter"><div class="kg-container">{% if not items %}<div class="kg-empty" data-empty="true">No numbers yet — add a few stats to celebrate.</div>{% else %}<div class="kg-numbers-grid">{% for item in items %}<div class="kg-numbers-item"><span class="kg-numeral" data-number="{{ item.number|integer }}">{{ item.number|integer }}</span>{% if item.label %}<span class="kg-numbers-label">{{ item.label }}</span>{% endif %}</div>{% endfor %}</div>{% endif %}</div></section>

1
blocks/schedule.ninjatpl Normal file
View File

@ -0,0 +1 @@
<section class="kg-section" data-block="kindergarten:schedule"><div class="kg-container">{% if title %}<h2 class="kg-display kg-crayon-underline" style="font-size: 2rem; margin-bottom: 1.5rem;">{{ title }}</h2>{% endif %}{% if not items %}<div class="kg-empty" data-empty="true">No schedule items yet — add a time block to get started.</div>{% else %}<ol style="list-style: none; padding: 0; margin: 0;">{% for item in items %}<li class="kg-schedule-item"><span class="kg-schedule-time">{% if item.time %}{{ item.time }}{% else %}--:--{% endif %}</span><span class="kg-schedule-activity">{% if item.activity %}{{ item.activity }}{% else %}Activity{% endif %}</span><span class="kg-schedule-icon" aria-hidden="true">{% if item.icon == "sun" %}<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path><path d="m4.93 19.07 1.41-1.41"></path><path d="m17.66 6.34 1.41-1.41"></path></svg>{% elif item.icon == "book" %}<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>{% elif item.icon == "paint" %}<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 11h2m-1 -1v2"></path><circle cx="13" cy="6" r="2"></circle><circle cx="19" cy="11" r="2"></circle><circle cx="6" cy="14" r="2"></circle><circle cx="10" cy="20" r="2"></circle><path d="M7.4 12.4l3.6 -2.4"></path><path d="M11.6 8.6l1.4 -0.6"></path><path d="M15 6l4 5"></path><path d="M8 16l2 4"></path></svg>{% elif item.icon == "snack" %}<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>{% elif item.icon == "play" %}<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>{% elif item.icon == "nap" %}<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>{% else %}<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.8 5.8 21 7 14 2 9.3 9 8.5 12 2"></polygon></svg>{% endif %}</span></li>{% endfor %}</ol>{% endif %}</div></section>

View File

@ -0,0 +1 @@
<section class="kg-section" data-block="kindergarten:storybook_quote"><div class="kg-container"><figure class="kg-storybook"><blockquote style="margin: 0;">{% if quote %}<p class="kg-storybook-quote">"{{ quote }}"</p>{% else %}<p class="kg-storybook-quote" data-empty="true">Add a quote to inspire your readers.</p>{% endif %}{% if author %}<figcaption class="kg-storybook-author">— {{ author }}</figcaption>{% endif %}</blockquote><div>{% if illustration %}<img class="kg-storybook-illustration" src="{{ illustration|mediaURL }}" alt="Illustration accompanying the quote">{% else %}<svg viewBox="0 0 200 150" width="100%" height="auto" class="kg-storybook-illustration" role="img" aria-label="Storybook illustration"><rect width="200" height="150" rx="24" fill="hsl(var(--muted))"></rect><circle cx="60" cy="60" r="20" fill="hsl(var(--accent))"></circle><rect x="100" y="40" width="60" height="40" rx="8" fill="hsl(var(--primary))"></rect><path d="M 30 110 Q 100 90 170 110 L 170 130 L 30 130 Z" fill="hsl(var(--secondary))"></path></svg>{% endif %}</div></figure></div></section>

View File

@ -1,31 +0,0 @@
package main
import (
"bytes"
"context"
)
// KindergartenButtonBlock renders a pill (border-radius 9999px) button with
// the sticker drop-shadow effect from spec §9.
//
// Content shape (matches built-in button): {"text": "...", "href": "...", "variant": "..."}.
func KindergartenButtonBlock(ctx context.Context, content map[string]any) string {
label := getString(content, "text")
if label == "" {
// Some installs may store the label under "label" instead.
label = getString(content, "label")
}
href := getStringDefault(content, "href", "#")
variant := getStringDefault(content, "variant", "yellow")
switch variant {
case "red", "blue", "yellow", "green":
default:
variant = "yellow"
}
data := BigCTAData{Label: label, Href: href, ColorVariant: variant}
var buf bytes.Buffer
_ = kgButtonComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,10 +0,0 @@
package main
// kgButtonComponent renders a pill-shaped button override.
templ kgButtonComponent(data BigCTAData) {
if data.Label != "" {
<a href={ templ.SafeURL(data.Href) } class={ "kg-pill", ctaColorClass(data.ColorVariant) }>{ data.Label }</a>
} else {
<span class="kg-empty" data-empty="true">Add label and link to render this button.</span>
}
}

View File

@ -1,92 +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"
// kgButtonComponent renders a pill-shaped button override.
func kgButtonComponent(data BigCTAData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if data.Label != "" {
var templ_7745c5c3_Var2 = []any{"kg-pill", ctaColorClass(data.ColorVariant)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 6, Col: 36}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 6, Col: 105}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<span class=\"kg-empty\" data-empty=\"true\">Add label and link to render this button.</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,21 +0,0 @@
package main
import (
"bytes"
"context"
)
// KindergartenCardBlock renders a soft cream card with a 4px primary-colored
// top stripe (spec §9).
//
// Content shape: {"title": "...", "body": "...", "class": "..."}. The body
// is rendered as raw HTML so it can host inline formatting.
func KindergartenCardBlock(ctx context.Context, content map[string]any) string {
title := getString(content, "title")
body := getString(content, "body")
class := getString(content, "class")
var buf bytes.Buffer
_ = kgCardComponent(title, body, class).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,18 +0,0 @@
package main
// kgCardComponent renders the rounded card override.
templ kgCardComponent(title, body, class string) {
<div class={ "kg-card", class }>
if title != "" {
<h3 class="kg-display" style="margin-top: 0; margin-bottom: 1rem; font-size: 1.5rem;">{ title }</h3>
}
if body != "" {
<div class="kg-text">
@templ.Raw(body)
</div>
}
if title == "" && body == "" {
<div data-empty="true" class="kg-empty">Add a title or body to fill this card.</div>
}
</div>
}

View File

@ -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"
// kgCardComponent renders the rounded card override.
func kgCardComponent(title, body, 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{"kg-card", class}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<h3 class=\"kg-display\" style=\"margin-top: 0; margin-bottom: 1rem; font-size: 1.5rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 7, Col: 96}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if body != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"kg-text\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(body).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if title == "" && body == "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div data-empty=\"true\" class=\"kg-empty\">Add a title or body to fill this card.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,49 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/templates"
)
// KindergartenEmailWrapper wraps email body content in a Kindergarten-branded
// rounded card. Uses a centered 600px table with a 32px border-radius card for
// clients that support it; Outlook degrades gracefully to a square card.
func KindergartenEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = kindergartenEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}
// kgEmailColor returns the supplied hex color or the cream/foreground/etc.
// fallback when the engine has not resolved a value for the token.
func kgEmailColor(value, fallback string) string {
if value != "" {
return value
}
return fallback
}
// kgEmailBg / etc. group fallbacks for the cream/navy palette per spec §10.
func kgEmailBg(ctx templates.EmailContext) string {
return kgEmailColor(ctx.Colors.Background, "#fdf9ed")
}
func kgEmailCard(ctx templates.EmailContext) string {
return kgEmailColor(ctx.Colors.Card, "#ffffff")
}
func kgEmailFg(ctx templates.EmailContext) string {
return kgEmailColor(ctx.Colors.Foreground, "#1a253b")
}
func kgEmailMuted(ctx templates.EmailContext) string {
return kgEmailColor(ctx.Colors.MutedForeground, "#5a6480")
}
func kgEmailAccent(ctx templates.EmailContext) string {
return kgEmailColor(ctx.Colors.Primary, "#fbcd1f")
}
func kgEmailAccentFg(ctx templates.EmailContext) string {
return kgEmailColor(ctx.Colors.PrimaryForeground, "#1a253b")
}
func kgEmailBorder(ctx templates.EmailContext) string {
return kgEmailColor(ctx.Colors.Border, "#e6d9b3")
}

View File

@ -1,108 +0,0 @@
package main
import (
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// kindergartenEmailTemplate is the Kindergarten-branded email wrapper. Renders
// a centered 600px table with a 32px-radius card. Outlook degrades to square.
templ kindergartenEmailTemplate(emailCtx templates.EmailContext, body string) {
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="x-apple-disable-message-reformatting"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<![endif]-->
<title>{ emailCtx.SiteSettings.SiteName }</title>
<style type="text/css">
body, table, td, p, a, li, blockquote { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
body { margin: 0 !important; padding: 0 !important; width: 100% !important; }
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
h1, h2, h3, h4, h5, h6 { font-family: 'Quicksand', 'Nunito', -apple-system, sans-serif; font-weight: 700; }
@media only screen and (max-width: 620px) {
.email-container { width: 100% !important; max-width: 100% !important; }
.content-padding { padding-left: 24px !important; padding-right: 24px !important; }
}
</style>
</head>
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Nunito', 'Quicksand', -apple-system, sans-serif;", kgEmailBg(emailCtx)) }>
if emailCtx.PreviewText != "" {
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">
{ emailCtx.PreviewText }
</div>
}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center" style={ fmt.Sprintf("padding: 48px 10px; background-color: %s;", kgEmailBg(emailCtx)) }>
<table role="presentation" class="email-container" width="600" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s; border-radius: 32px;", kgEmailCard(emailCtx), kgEmailBorder(emailCtx)) }>
<tr>
<td align="center" style={ fmt.Sprintf("padding: 32px 40px; background-color: %s; border-bottom: 2px dashed %s; border-radius: 32px 32px 0 0;", kgEmailCard(emailCtx), kgEmailBorder(emailCtx)) }>
if emailCtx.SiteSettings.LogoURL != "" {
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="max-height: 48px; width: auto; display: block;"/>
} else if emailCtx.SiteSettings.SiteName != "" {
<h1 style={ fmt.Sprintf("margin: 0; font-size: 28px; font-weight: 700; color: %s; letter-spacing: -0.5px; font-family: 'Quicksand', 'Nunito', sans-serif;", kgEmailFg(emailCtx)) }>
{ emailCtx.SiteSettings.SiteName }
</h1>
}
</td>
</tr>
<tr>
<td class="content-padding" style={ fmt.Sprintf("padding: 40px 48px; color: %s; font-size: 16px; line-height: 1.7; font-family: 'Nunito', sans-serif;", kgEmailFg(emailCtx)) }>
@templ.Raw(body)
</td>
</tr>
<tr>
<td align="center" style={ fmt.Sprintf("padding: 0 48px 32px 48px; background-color: %s;", kgEmailCard(emailCtx)) }>
if emailCtx.SiteSettings.SiteURL != "" {
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style={ fmt.Sprintf("display: inline-block; padding: 16px 32px; border-radius: 9999px; background-color: %s; color: %s; text-decoration: none; font-family: 'Quicksand', sans-serif; font-weight: 700; font-size: 16px;", kgEmailAccent(emailCtx), kgEmailAccentFg(emailCtx)) }>
Visit { emailCtx.SiteSettings.SiteName }
</a>
}
</td>
</tr>
<tr>
<td style={ fmt.Sprintf("padding: 32px 48px; border-top: 2px dashed %s; border-radius: 0 0 32px 32px; background-color: %s;", kgEmailBorder(emailCtx), kgEmailCard(emailCtx)) }>
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center">
<p style={ fmt.Sprintf("margin: 0 0 8px; font-size: 14px; font-weight: 700; color: %s; font-family: 'Quicksand', sans-serif;", kgEmailFg(emailCtx)) }>
👋 { emailCtx.SiteSettings.SiteName }
</p>
if emailCtx.SiteSettings.SupportEmail != "" {
<p style={ fmt.Sprintf("margin: 0 0 8px; font-size: 13px; color: %s;", kgEmailMuted(emailCtx)) }>
Need a hand? { emailCtx.SiteSettings.SupportEmail }
</p>
}
if emailCtx.UnsubscribeURL != "" {
<p style={ fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", kgEmailMuted(emailCtx)) }>
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: none;", kgEmailMuted(emailCtx)) }>
Unsubscribe
</a>
</p>
}
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
}

View File

@ -1,406 +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 (
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// kindergartenEmailTemplate is the Kindergarten-branded email wrapper. Renders
// a centered 600px table with a 32px-radius card. Outlook degrades to square.
func kindergartenEmailTemplate(emailCtx templates.EmailContext, body string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"x-apple-disable-message-reformatting\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><!--[if mso]>\n\t\t\t<noscript>\n\t\t\t\t<xml>\n\t\t\t\t\t<o:OfficeDocumentSettings>\n\t\t\t\t\t\t<o:PixelsPerInch>96</o:PixelsPerInch>\n\t\t\t\t\t</o:OfficeDocumentSettings>\n\t\t\t\t</xml>\n\t\t\t</noscript>\n\t\t\t<![endif]--><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 28, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><style type=\"text/css\">\n\t\t\t\tbody, table, td, p, a, li, blockquote { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }\n\t\t\t\ttable, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }\n\t\t\t\timg { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }\n\t\t\t\tbody { margin: 0 !important; padding: 0 !important; width: 100% !important; }\n\t\t\t\ta[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }\n\t\t\t\th1, h2, h3, h4, h5, h6 { font-family: 'Quicksand', 'Nunito', -apple-system, sans-serif; font-weight: 700; }\n\t\t\t\t@media only screen and (max-width: 620px) {\n\t\t\t\t\t.email-container { width: 100% !important; max-width: 100% !important; }\n\t\t\t\t\t.content-padding { padding-left: 24px !important; padding-right: 24px !important; }\n\t\t\t\t}\n\t\t\t</style></head><body style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Nunito', 'Quicksand', -apple-system, sans-serif;", kgEmailBg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 42, Col: 159}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.PreviewText != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div style=\"display: none; max-height: 0; overflow: hidden; mso-hide: all;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 45, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 48px 10px; background-color: %s;", kgEmailBg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 50, Col: 109}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><table role=\"presentation\" class=\"email-container\" width=\"600\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s; border-radius: 32px;", kgEmailCard(emailCtx), kgEmailBorder(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 51, Col: 265}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><tr><td align=\"center\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 32px 40px; background-color: %s; border-bottom: 2px dashed %s; border-radius: 32px 32px 0 0;", kgEmailCard(emailCtx), kgEmailBorder(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 53, Col: 199}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.LogoURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.LogoURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 55, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 55, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" style=\"max-height: 48px; width: auto; display: block;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if emailCtx.SiteSettings.SiteName != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h1 style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0; font-size: 28px; font-weight: 700; color: %s; letter-spacing: -0.5px; font-family: 'Quicksand', 'Nunito', sans-serif;", kgEmailFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 57, Col: 186}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 58, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</td></tr><tr><td class=\"content-padding\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 40px 48px; color: %s; font-size: 16px; line-height: 1.7; font-family: 'Nunito', sans-serif;", kgEmailFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 64, Col: 180}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(body).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</td></tr><tr><td align=\"center\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 0 48px 32px 48px; background-color: %s;", kgEmailCard(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 69, Col: 121}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.SiteURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 templ.SafeURL
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.SiteSettings.SiteURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 71, Col: 64}
}
_, 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, 21, "\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("display: inline-block; padding: 16px 32px; border-radius: 9999px; background-color: %s; color: %s; text-decoration: none; font-family: 'Quicksand', sans-serif; font-weight: 700; font-size: 16px;", kgEmailAccent(emailCtx), kgEmailAccentFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 71, Col: 336}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">Visit ")
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: 72, Col: 49}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</td></tr><tr><td style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 32px 48px; border-top: 2px dashed %s; border-radius: 0 0 32px 32px; background-color: %s;", kgEmailBorder(emailCtx), kgEmailCard(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 78, Col: 181}
}
_, 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, 25, "\"><table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\"><p style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0 0 8px; font-size: 14px; font-weight: 700; color: %s; font-family: 'Quicksand', sans-serif;", kgEmailFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 82, Col: 159}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\">👋 ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, 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: 83, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.SupportEmail != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<p style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0 0 8px; font-size: 13px; color: %s;", kgEmailMuted(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 86, Col: 107}
}
_, 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, 29, "\">Need a hand? ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SupportEmail)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 87, Col: 63}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if emailCtx.UnsubscribeURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<p style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", kgEmailMuted(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 91, Col: 101}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 templ.SafeURL
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.UnsubscribeURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 92, Col: 62}
}
_, 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, 33, "\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: none;", kgEmailMuted(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 92, Col: 145}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\">Unsubscribe</a></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</td></tr></table></td></tr></table></td></tr></table></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,64 +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.
func Assets() fs.FS {
sub, _ := fs.Sub(assetsFS, "assets")
return sub
}
// Schemas returns the embedded schemas filesystem.
func Schemas() fs.FS {
sub, _ := fs.Sub(schemasFS, "schemas")
return sub
}
// AssetsHandler returns an http.Handler that serves the embedded assets.
func AssetsHandler() http.Handler {
return http.FileServer(http.FS(Assets()))
}
// ThemePresets returns the embedded theme presets JSON.
func ThemePresets() []byte {
return presetsData
}
// BundledFonts returns the embedded fonts manifest JSON.
func BundledFonts() []byte {
return fontsData
}
// ThemeCSSManifest returns the CSS manifest used by host Tailwind to pick up
// theme-owned utility classes (crayon underline, dotted-paper background,
// sticker drop-shadow, font-family fallback stacks, etc.).
func ThemeCSSManifest() *plugin.CSSManifest {
css, err := assetsFS.ReadFile("assets/style.css")
if err != nil {
return &plugin.CSSManifest{}
}
return &plugin.CSSManifest{
InputCSSAppend: string(css),
}
}

View File

@ -1,53 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// FooterBlockMeta defines metadata for the friendly footer block.
var FooterBlockMeta = blocks.BlockMeta{
Key: "footer",
Title: "Friendly Footer",
Description: "Mascot wave, signup, contact, crayon-rule divider.",
Category: blocks.CategoryLayout,
Source: "kindergarten",
}
// FooterSocial is a single social link entry.
type FooterSocial struct {
Platform string
Href string
}
// FooterData is the renderer input.
type FooterData struct {
ShowSignup bool
MascotName string
SocialLinks []FooterSocial
}
// FooterBlock renders the friendly footer.
// Content shape: {showSignup,mascotName,socialLinks}.
func FooterBlock(ctx context.Context, content map[string]any) string {
raw := getSlice(content, "socialLinks")
links := make([]FooterSocial, 0, len(raw))
for _, m := range raw {
links = append(links, FooterSocial{
Platform: getString(m, "platform"),
Href: getString(m, "href"),
})
}
data := FooterData{
ShowSignup: getBool(content, "showSignup", true),
MascotName: getStringDefault(content, "mascotName", "Pip"),
SocialLinks: links,
}
var buf bytes.Buffer
_ = footerComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,64 +0,0 @@
package main
// footerComponent renders the friendly footer.
templ footerComponent(data FooterData) {
<div class="kg-footer" data-block="kindergarten:footer">
<div class="kg-container">
<div class="kg-footer-mascot">
<span aria-hidden="true">👋</span>
<span>{ data.MascotName } says hi!</span>
</div>
if data.ShowSignup {
<form class="kg-footer-signup" aria-label={ "Newsletter signup" } method="post" action="/subscribe">
<input type="email" name="email" placeholder="Your email" aria-label="Email address" required />
<button type="submit" class="kg-pill kg-pill-yellow">Sign up</button>
</form>
}
if len(data.SocialLinks) > 0 {
<div class="kg-social-row" aria-label={ "Social media" }>
for _, link := range data.SocialLinks {
<a href={ templ.SafeURL(link.Href) } class="kg-social-link" aria-label={ socialLabel(link.Platform) }>
@socialIcon(link.Platform)
</a>
}
</div>
}
</div>
</div>
}
// socialLabel renders an accessible label for the icon link.
func socialLabel(platform string) string {
switch platform {
case "facebook":
return "Visit our Facebook"
case "instagram":
return "Visit our Instagram"
case "youtube":
return "Visit our YouTube"
case "tiktok":
return "Visit our TikTok"
case "email":
return "Send us an email"
default:
return "Open social link"
}
}
// socialIcon renders a small social icon (Lucide-style stroke).
templ socialIcon(platform string) {
switch platform {
case "facebook":
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"/></svg>
case "instagram":
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="5" ry="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"/></svg>
case "youtube":
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z"/><polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02"/></svg>
case "tiktok":
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 12a4 4 0 1 0 4 4V4a5 5 0 0 0 5 5"/></svg>
case "email":
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
default:
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/></svg>
}
}

View File

@ -1,216 +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"
// footerComponent renders the friendly footer.
func footerComponent(data FooterData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"kg-footer\" data-block=\"kindergarten:footer\"><div class=\"kg-container\"><div class=\"kg-footer-mascot\"><span aria-hidden=\"true\">👋</span> <span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.MascotName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 9, Col: 27}
}
_, 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, " says hi!</span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.ShowSignup {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<form class=\"kg-footer-signup\" aria-label=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue("Newsletter signup")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 12, Col: 67}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" method=\"post\" action=\"/subscribe\"><input type=\"email\" name=\"email\" placeholder=\"Your email\" aria-label=\"Email address\" required> <button type=\"submit\" class=\"kg-pill kg-pill-yellow\">Sign up</button></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.SocialLinks) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"kg-social-row\" aria-label=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue("Social media")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 18, Col: 58}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, link := range data.SocialLinks {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(link.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 20, Col: 40}
}
_, 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, 8, "\" class=\"kg-social-link\" aria-label=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(socialLabel(link.Platform))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 20, Col: 105}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = socialIcon(link.Platform).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// socialLabel renders an accessible label for the icon link.
func socialLabel(platform string) string {
switch platform {
case "facebook":
return "Visit our Facebook"
case "instagram":
return "Visit our Instagram"
case "youtube":
return "Visit our YouTube"
case "tiktok":
return "Visit our TikTok"
case "email":
return "Send us an email"
default:
return "Open social link"
}
}
// socialIcon renders a small social icon (Lucide-style stroke).
func socialIcon(platform 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_Var7 := templ.GetChildren(ctx)
if templ_7745c5c3_Var7 == nil {
templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch platform {
case "facebook":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "instagram":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"2\" y=\"2\" width=\"20\" height=\"20\" rx=\"5\" ry=\"5\"></rect><path d=\"M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z\"></path><line x1=\"17.5\" y1=\"6.5\" x2=\"17.51\" y2=\"6.5\"></line></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "youtube":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z\"></path><polygon points=\"9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02\"></polygon></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "tiktok":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 12a4 4 0 1 0 4 4V4a5 5 0 0 0 5 5\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "email":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z\"></path><polyline points=\"22,6 12,13 2,6\"></polyline></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,57 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// GalleryOfArtBlockMeta defines metadata for the gallery of art block.
var GalleryOfArtBlockMeta = blocks.BlockMeta{
Key: "gallery_of_art",
Title: "Gallery of Art",
Description: "Polaroid-style grid for kid artwork.",
Category: blocks.CategoryContent,
Source: "kindergarten",
}
// GalleryArt represents a single artwork entry.
type GalleryArt struct {
Image string
ChildName string
Age int
}
// GalleryData is the renderer input.
type GalleryData struct {
Title string
ShowChildName bool
Items []GalleryArt
}
// GalleryOfArtBlock renders the polaroid grid.
// Content shape: {title,showChildName,items:[{image,childName,age}]}.
//
// Privacy: when showChildName=false, no child names are rendered.
func GalleryOfArtBlock(ctx context.Context, content map[string]any) string {
raw := getSlice(content, "items")
items := make([]GalleryArt, 0, len(raw))
for _, m := range raw {
items = append(items, GalleryArt{
Image: getString(m, "image"),
ChildName: getString(m, "childName"),
Age: getInt(m, "age", 0),
})
}
data := GalleryData{
Title: getString(content, "title"),
ShowChildName: getBool(content, "showChildName", true),
Items: items,
}
var buf bytes.Buffer
_ = galleryOfArtComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,52 +0,0 @@
package main
import (
"fmt"
"git.dev.alexdunmow.com/block/core/blocks"
)
// galleryOfArtComponent renders a polaroid-style grid of artworks.
templ galleryOfArtComponent(data GalleryData) {
<section class="kg-section" data-block="kindergarten:gallery_of_art">
<div class="kg-container">
if data.Title != "" {
<h2 class="kg-display kg-crayon-underline" style="font-size: 2rem; margin-bottom: 1.5rem;">{ data.Title }</h2>
}
if len(data.Items) == 0 {
<div class="kg-empty" data-empty="true">No artworks yet — add one to fill the gallery wall.</div>
} else {
<div class="kg-gallery-grid">
for _, art := range data.Items {
<figure class="kg-polaroid">
if art.Image != "" {
<img src={ blocks.ResolveMediaPath(art.Image) } alt={ polaroidAlt(art, data.ShowChildName) } />
} else {
<span class="kg-polaroid-image" role="img" aria-label="Placeholder for child artwork"></span>
}
<figcaption class="kg-polaroid-caption">
if data.ShowChildName && art.ChildName != "" {
<span>{ art.ChildName }</span>
}
if data.ShowChildName && art.ChildName != "" && art.Age > 0 {
{ ", " }
}
if art.Age > 0 {
<span>{ fmt.Sprintf("age %d", art.Age) }</span>
}
</figcaption>
</figure>
}
</div>
}
</div>
</section>
}
// polaroidAlt produces a respectful alt-text that honours the privacy toggle.
func polaroidAlt(art GalleryArt, showChildName bool) string {
if showChildName && art.ChildName != "" {
return fmt.Sprintf("Artwork by %s", art.ChildName)
}
return "Children's artwork"
}

View File

@ -1,197 +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 (
"fmt"
"git.dev.alexdunmow.com/block/core/blocks"
)
// galleryOfArtComponent renders a polaroid-style grid of artworks.
func galleryOfArtComponent(data GalleryData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"kg-section\" data-block=\"kindergarten:gallery_of_art\"><div class=\"kg-container\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h2 class=\"kg-display kg-crayon-underline\" style=\"font-size: 2rem; margin-bottom: 1.5rem;\">")
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: `gallery_of_art.templ`, Line: 14, Col: 107}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Items) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"kg-empty\" data-empty=\"true\">No artworks yet — add one to fill the gallery wall.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"kg-gallery-grid\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, art := range data.Items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<figure class=\"kg-polaroid\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if art.Image != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(blocks.ResolveMediaPath(art.Image))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `gallery_of_art.templ`, Line: 23, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(polaroidAlt(art, data.ShowChildName))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `gallery_of_art.templ`, Line: 23, Col: 98}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<span class=\"kg-polaroid-image\" role=\"img\" aria-label=\"Placeholder for child artwork\"></span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<figcaption class=\"kg-polaroid-caption\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.ShowChildName && art.ChildName != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(art.ChildName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `gallery_of_art.templ`, Line: 29, Col: 30}
}
_, 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, 13, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.ShowChildName && art.ChildName != "" && art.Age > 0 {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(", ")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `gallery_of_art.templ`, Line: 32, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if art.Age > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("age %d", art.Age))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `gallery_of_art.templ`, Line: 35, Col: 47}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</figcaption></figure>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// polaroidAlt produces a respectful alt-text that honours the privacy toggle.
func polaroidAlt(art GalleryArt, showChildName bool) string {
if showChildName && art.ChildName != "" {
return fmt.Sprintf("Artwork by %s", art.ChildName)
}
return "Children's artwork"
}
var _ = templruntime.GeneratedTemplate

20
go.mod
View File

@ -1,20 +0,0 @@
module git.dev.alexdunmow.com/block/themes/kindergarten
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
)

46
go.sum
View File

@ -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=

View File

@ -1,65 +0,0 @@
package main
import (
"bytes"
"context"
"strconv"
)
// KindergartenHeadingBlock renders a heading with the Kindergarten crayon-underline
// accent and an optional numeral step badge.
//
// Content expects: {"text": "...", "level": 1-6, "textClass": "...", "step": 1-9}
func KindergartenHeadingBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
textClass := getString(content, "textClass")
level := parseHeadingLevel(content)
step := parseStep(content)
var buf bytes.Buffer
_ = kgHeadingComponent(level, text, textClass, step).Render(ctx, &buf)
return buf.String()
}
// parseHeadingLevel parses the level from content, defaulting to 2.
func parseHeadingLevel(content map[string]any) int {
if level, ok := content["level"].(float64); ok {
l := int(level)
if l >= 1 && l <= 6 {
return l
}
}
if level, ok := content["level"].(int); ok {
if level >= 1 && level <= 6 {
return level
}
}
if level, ok := content["level"].(string); ok {
if l, err := strconv.Atoi(level); err == nil && l >= 1 && l <= 6 {
return l
}
}
return 2
}
// parseStep parses an optional step badge (1-9). Returns 0 if not provided
// or out of range; renderer skips the badge when step == 0.
func parseStep(content map[string]any) int {
if v, ok := content["step"].(float64); ok {
s := int(v)
if s >= 1 && s <= 9 {
return s
}
}
if v, ok := content["step"].(int); ok {
if v >= 1 && v <= 9 {
return v
}
}
if v, ok := content["step"].(string); ok {
if s, err := strconv.Atoi(v); err == nil && s >= 1 && s <= 9 {
return s
}
}
return 0
}

View File

@ -1,57 +0,0 @@
package main
import "fmt"
// kgHeadingBaseClass returns size classes per heading level (Tailwind-like
// utilities scoped to the kindergarten CSS).
func kgHeadingBaseClass(level int) string {
switch level {
case 1:
return "kg-display kg-crayon-underline"
case 2:
return "kg-display kg-crayon-underline"
case 3:
return "kg-display"
case 4:
return "kg-display"
case 5:
return "kg-display"
case 6:
return "kg-display"
default:
return "kg-display"
}
}
// kgHeadingComponent renders a heading with Kindergarten accents, optionally
// preceded by a circular step badge (e.g. for "Step 1", "Step 2" sections).
templ kgHeadingComponent(level int, text, textClass string, step int) {
if step > 0 {
<div class="kg-heading-stepped">
<span class="kg-heading-step-badge" aria-hidden="true">{ fmt.Sprintf("%d", step) }</span>
@kgHeadingTag(level, text, textClass)
</div>
} else {
@kgHeadingTag(level, text, textClass)
}
}
// kgHeadingTag emits the actual <h1>..<h6> tag at the requested level.
templ kgHeadingTag(level int, text, textClass string) {
switch level {
case 1:
<h1 class={ kgHeadingBaseClass(1), textClass } style="font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;">{ text }</h1>
case 2:
<h2 class={ kgHeadingBaseClass(2), textClass } style="font-size: 2.25rem; margin: 0;">{ text }</h2>
case 3:
<h3 class={ kgHeadingBaseClass(3), textClass } style="font-size: 1.75rem; margin: 0;">{ text }</h3>
case 4:
<h4 class={ kgHeadingBaseClass(4), textClass } style="font-size: 1.5rem; margin: 0;">{ text }</h4>
case 5:
<h5 class={ kgHeadingBaseClass(5), textClass } style="font-size: 1.25rem; margin: 0;">{ text }</h5>
case 6:
<h6 class={ kgHeadingBaseClass(6), textClass } style="font-size: 1.125rem; margin: 0;">{ text }</h6>
default:
<h2 class={ kgHeadingBaseClass(2), textClass } style="font-size: 2.25rem; margin: 0;">{ text }</h2>
}
}

View File

@ -1,373 +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 "fmt"
// kgHeadingBaseClass returns size classes per heading level (Tailwind-like
// utilities scoped to the kindergarten CSS).
func kgHeadingBaseClass(level int) string {
switch level {
case 1:
return "kg-display kg-crayon-underline"
case 2:
return "kg-display kg-crayon-underline"
case 3:
return "kg-display"
case 4:
return "kg-display"
case 5:
return "kg-display"
case 6:
return "kg-display"
default:
return "kg-display"
}
}
// kgHeadingComponent renders a heading with Kindergarten accents, optionally
// preceded by a circular step badge (e.g. for "Step 1", "Step 2" sections).
func kgHeadingComponent(level int, text, textClass string, step int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if step > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"kg-heading-stepped\"><span class=\"kg-heading-step-badge\" aria-hidden=\"true\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", step))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 31, Col: 83}
}
_, 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, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = kgHeadingTag(level, text, textClass).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = kgHeadingTag(level, text, textClass).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
// kgHeadingTag emits the actual <h1>..<h6> tag at the requested level.
func kgHeadingTag(level int, text, textClass string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch level {
case 1:
var templ_7745c5c3_Var4 = []any{kgHeadingBaseClass(1), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h1 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var4).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" style=\"font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 43, Col: 112}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 2:
var templ_7745c5c3_Var7 = []any{kgHeadingBaseClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var7).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" style=\"font-size: 2.25rem; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 45, Col: 95}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 3:
var templ_7745c5c3_Var10 = []any{kgHeadingBaseClass(3), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<h3 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var10).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" style=\"font-size: 1.75rem; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 47, Col: 95}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 4:
var templ_7745c5c3_Var13 = []any{kgHeadingBaseClass(4), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var13...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h4 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var13).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" style=\"font-size: 1.5rem; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 49, Col: 94}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h4>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 5:
var templ_7745c5c3_Var16 = []any{kgHeadingBaseClass(5), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h5 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var16).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" style=\"font-size: 1.25rem; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 51, Col: 95}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</h5>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 6:
var templ_7745c5c3_Var19 = []any{kgHeadingBaseClass(6), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h6 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var19).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" style=\"font-size: 1.125rem; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 53, Col: 96}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</h6>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
var templ_7745c5c3_Var22 = []any{kgHeadingBaseClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var22).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" style=\"font-size: 2.25rem; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 55, Col: 95}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,63 +0,0 @@
package main
// getString extracts a string value from content map.
func getString(content map[string]any, key string) string {
if v, ok := content[key].(string); ok {
return v
}
return ""
}
// getStringDefault extracts a string value from content with a fallback.
func getStringDefault(content map[string]any, key, def string) string {
if v, ok := content[key].(string); ok && v != "" {
return v
}
return def
}
// getBool extracts a bool value from content. Accepts native bool, "true"/"false"
// strings, and the JSON-encoded float forms ("true" via 1, 0). Defaults to def.
func getBool(content map[string]any, key string, def bool) bool {
switch v := content[key].(type) {
case bool:
return v
case string:
switch v {
case "true":
return true
case "false":
return false
}
case float64:
return v != 0
}
return def
}
// getInt extracts an int value from content map (handles float64 from JSON).
func getInt(content map[string]any, key string, defaultVal int) int {
if v, ok := content[key].(float64); ok {
return int(v)
}
if v, ok := content[key].(int); ok {
return v
}
return defaultVal
}
// getSlice extracts a slice of maps from content. If the underlying value is
// not a JSON array of objects (e.g. malformed content provides a bare string),
// the function returns nil so renderers can fall back to their empty state.
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
}

17
main.go
View File

@ -1,17 +0,0 @@
//go:build wasip1
// Package main compiles the kindergarten 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

532
manifest.yaml Normal file
View File

@ -0,0 +1,532 @@
# Kindergarten 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: kindergarten
title: Kindergarten
description: "Primary-colored, hand-lettered theme for schools, daycares, toy shops, museums, and kids' publishers."
page_templates:
- system: kindergarten
key: default
title: Default
description: "Standard rounded layout with header, main, footer"
slots: [header, main, footer]
- system: kindergarten
key: landing
title: Landing
description: "Mascot-led hero plus stacked feature sections"
slots: [hero, main, cta, footer]
- system: kindergarten
key: article
title: Story Page
description: "Picture-book reading layout for blog posts and stories"
slots: [header, main, aside, footer]
- system: kindergarten
key: full-width
title: Full Width
description: "Edge-to-edge gallery / classroom showcase"
slots: [header, main, footer]
template_overrides:
- { template: kindergarten, block: heading }
- { template: kindergarten, block: text }
- { template: kindergarten, block: button }
- { template: kindergarten, block: card }
email_wrappers:
- kindergarten
css:
input_css_append: |
/* ============================================================
Kindergarten — Primary-colored, hand-lettered theme
------------------------------------------------------------
Token strategy: every color reference goes through
`hsl(var(--<token>))` and font-family goes through
`var(--font-{heading|body|mono}, <fallback-stack>)`.
============================================================ */
/* ---- Typography (CSS-variable consumers + fallbacks) ------- */
.kg-page {
font-family: var(--font-body, "Nunito", "Quicksand", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
line-height: 1.7;
background-color: hsl(var(--background));
color: hsl(var(--foreground));
}
.kg-display,
.kg-page h1,
.kg-page h2,
.kg-page h3,
.kg-page h4,
.kg-page h5,
.kg-page h6 {
font-family: var(--font-heading, "Quicksand", "Sniglet", "Nunito", -apple-system, sans-serif);
font-weight: 700;
letter-spacing: -0.01em;
}
.kg-mono {
font-family: var(--font-mono, "Fira Code", "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
}
/* ---- Layout utilities -------------------------------------- */
.kg-section {
padding-block: 4rem;
}
.kg-container {
max-width: 72rem;
margin-inline: auto;
padding-inline: 1.5rem;
}
/* ---- Rounded primitives ------------------------------------ */
.kg-chip {
border-radius: 24px;
padding: 0.5rem 1rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.kg-card {
border-radius: 32px;
background-color: hsl(var(--card));
color: hsl(var(--card-foreground, var(--foreground)));
padding: 2rem;
border: 1px solid hsl(var(--border));
position: relative;
overflow: hidden;
}
.kg-card::before {
content: "";
position: absolute;
inset: 0 0 auto 0;
height: 4px;
background-color: hsl(var(--primary));
}
.kg-hero-panel {
border-radius: 48px;
padding: clamp(2rem, 6vw, 4rem);
position: relative;
overflow: hidden;
background-color: hsl(var(--accent) / 0.15);
}
.kg-pill {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1.25rem 2.5rem;
border-radius: 9999px;
font-family: var(--font-heading, "Quicksand", "Sniglet", "Nunito", sans-serif);
font-weight: 700;
font-size: 1.125rem;
background-color: hsl(var(--primary));
color: hsl(var(--primary-foreground));
border: 0;
cursor: pointer;
box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.15);
text-decoration: none;
transition: transform 120ms ease, box-shadow 120ms ease;
}
.kg-pill:hover {
transform: translateY(-1px);
box-shadow: 0 5px 0 0 hsl(var(--foreground) / 0.15);
}
.kg-pill:active {
transform: translateY(2px);
box-shadow: 0 2px 0 0 hsl(var(--foreground) / 0.15);
}
.kg-pill:focus-visible {
outline: 3px solid hsl(var(--ring));
outline-offset: 3px;
}
/* CTA colorways */
.kg-pill-red { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); }
.kg-pill-blue { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); }
.kg-pill-yellow { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
.kg-pill-green { background-color: hsl(145 65% 45%); color: hsl(0 0% 100%); }
/* ---- Crayon underline (utility on headings) ---------------- */
.kg-crayon-underline {
position: relative;
display: inline-block;
}
.kg-crayon-underline::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -6px;
height: 8px;
background: linear-gradient(90deg,
hsl(var(--primary)) 0 22%,
hsl(var(--accent)) 22% 50%,
hsl(var(--secondary)) 50% 78%,
currentColor 78% 100%);
border-radius: 4px;
transform: rotate(-1deg);
z-index: -1;
}
/* ---- Numeral badges ---------------------------------------- */
.kg-numeral {
display: inline-flex;
align-items: center;
justify-content: center;
width: 96px;
height: 96px;
border-radius: 50%;
aspect-ratio: 1 / 1;
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif);
font-weight: 700;
font-size: 2.5rem;
box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.15);
}
/* ---- Polaroid frame for gallery_of_art --------------------- */
.kg-polaroid {
background-color: hsl(var(--card));
border-radius: 16px;
padding-top: 12px;
padding-inline: 12px;
padding-bottom: 36px;
box-shadow: 0 6px 24px hsl(var(--foreground) / 0.12);
transform: rotate(-1deg);
transition: transform 200ms ease;
}
.kg-polaroid:nth-child(2n) { transform: rotate(1.5deg); }
.kg-polaroid:nth-child(3n) { transform: rotate(-0.5deg); }
.kg-polaroid:hover { transform: rotate(0deg); }
.kg-polaroid > img,
.kg-polaroid > .kg-polaroid-image {
display: block;
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
border-radius: 8px;
background-color: hsl(var(--muted));
}
.kg-polaroid-caption {
text-align: center;
margin-top: 0.75rem;
font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif);
color: hsl(var(--foreground));
}
/* ---- Alphabet strip ---------------------------------------- */
.kg-alphabet-strip {
display: flex;
overflow-x: auto;
gap: 0.5rem;
padding: 1rem;
scrollbar-width: thin;
}
.kg-alphabet-letter {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
border-radius: 24px;
background-color: hsl(var(--primary));
color: hsl(var(--primary-foreground));
font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif);
font-weight: 700;
font-size: 1.5rem;
box-shadow: 0 4px 0 0 hsl(var(--foreground) / 0.12);
}
.kg-alphabet-letter:nth-child(4n+1) { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
.kg-alphabet-letter:nth-child(4n+2) { background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground)); }
.kg-alphabet-letter:nth-child(4n+3) { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
.kg-alphabet-letter:nth-child(4n+4) { background-color: hsl(145 65% 45%); color: hsl(0 0% 100%); }
.kg-alphabet-mono .kg-alphabet-letter {
background-color: hsl(var(--muted));
color: hsl(var(--muted-foreground));
}
.kg-alphabet-primary .kg-alphabet-letter {
background-color: hsl(var(--primary));
color: hsl(var(--primary-foreground));
}
/* ---- Day schedule pills ------------------------------------ */
.kg-schedule-item {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 1rem;
align-items: center;
padding: 1rem;
border-radius: 24px;
background-color: hsl(var(--card));
border: 1px solid hsl(var(--border));
margin-bottom: 0.75rem;
}
.kg-schedule-time {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
font-family: var(--font-mono, "Fira Code", monospace);
font-weight: 700;
}
.kg-schedule-activity {
font-family: var(--font-body, "Nunito", sans-serif);
color: hsl(var(--foreground));
}
.kg-schedule-icon {
color: hsl(var(--primary));
}
/* ---- Storybook quote --------------------------------------- */
.kg-storybook {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
padding: 2rem;
border-radius: 32px;
background-color: hsl(var(--card));
border: 1px solid hsl(var(--border));
}
@media (min-width: 768px) {
.kg-storybook {
grid-template-columns: 1fr 1fr;
align-items: center;
}
}
.kg-storybook-quote {
font-family: var(--font-heading, "Quicksand", "Sniglet", serif);
font-size: clamp(1.25rem, 3vw, 2rem);
line-height: 1.4;
color: hsl(var(--foreground));
}
.kg-storybook-author {
margin-top: 1rem;
color: hsl(var(--muted-foreground));
font-family: var(--font-body, "Nunito", sans-serif);
}
.kg-storybook-illustration {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 24px;
background-color: hsl(var(--muted));
}
/* ---- Friendly footer --------------------------------------- */
.kg-footer {
padding: 3rem 1.5rem;
background-color: hsl(var(--muted));
color: hsl(var(--foreground));
border-top: 6px dashed hsl(var(--primary));
border-radius: 48px 48px 0 0;
}
.kg-footer-mascot {
display: inline-flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
font-family: var(--font-heading, "Quicksand", sans-serif);
}
.kg-footer-signup {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-block: 1.5rem;
max-width: 32rem;
}
.kg-footer-signup input {
flex: 1 1 12rem;
padding: 0.75rem 1rem;
border-radius: 9999px;
border: 2px solid hsl(var(--border));
background-color: hsl(var(--input));
color: hsl(var(--foreground));
font-family: var(--font-body, "Nunito", sans-serif);
}
.kg-footer-signup input:focus-visible {
outline: 3px solid hsl(var(--ring));
outline-offset: 2px;
}
.kg-social-row {
display: flex;
gap: 0.75rem;
margin-top: 1rem;
}
.kg-social-link {
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 9999px;
background-color: hsl(var(--card));
color: hsl(var(--primary));
border: 1px solid hsl(var(--border));
}
.kg-social-link:focus-visible {
outline: 3px solid hsl(var(--ring));
outline-offset: 2px;
}
/* ---- Mascot hero confetti dots ----------------------------- */
.kg-confetti {
position: absolute;
inset: 0;
pointer-events: none;
background-image:
radial-gradient(circle, hsl(var(--primary) / 0.5) 8px, transparent 9px),
radial-gradient(circle, hsl(var(--secondary) / 0.5) 6px, transparent 7px),
radial-gradient(circle, hsl(var(--accent) / 0.5) 5px, transparent 6px);
background-size: 80px 80px, 50px 50px, 30px 30px;
background-position: 10% 20%, 70% 30%, 40% 80%;
opacity: 0.6;
}
/* ---- Dotted paper layer (page background) ------------------ */
.kg-dotted-paper {
background-color: hsl(var(--background));
background-image: radial-gradient(circle, hsl(var(--muted-foreground) / 0.15) 1px, transparent 1.5px);
background-size: 24px 24px;
}
/* ---- Heading override (numeral badge variant) -------------- */
.kg-heading-stepped {
display: flex;
align-items: center;
gap: 1rem;
}
.kg-heading-step-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
border-radius: 50%;
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
font-family: var(--font-heading, "Quicksand", "Sniglet", sans-serif);
font-weight: 700;
font-size: 1.5rem;
flex: 0 0 auto;
}
/* ---- Text override: rounded selection highlight ------------ */
.kg-text ::selection {
background-color: hsl(var(--accent));
color: hsl(var(--accent-foreground));
}
.kg-text {
font-family: var(--font-body, "Nunito", sans-serif);
line-height: 1.7;
color: hsl(var(--foreground));
}
/* ---- Numbers counter grid ---------------------------------- */
.kg-numbers-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 2rem;
text-align: center;
}
.kg-numbers-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
}
.kg-numbers-label {
font-family: var(--font-body, "Nunito", sans-serif);
color: hsl(var(--muted-foreground));
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.875rem;
}
/* ---- Gallery grid ------------------------------------------ */
.kg-gallery-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
@media (min-width: 640px) { .kg-gallery-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .kg-gallery-grid { grid-template-columns: repeat(3, 1fr); } }
/* ---- Big CTA panel (spec §9 button override) --------------- */
.kg-big-cta {
text-align: center;
padding: 3rem 1.5rem;
}
/* ---- Empty-state placeholder ------------------------------- */
.kg-empty {
text-align: center;
padding: 2rem;
border-radius: 24px;
border: 2px dashed hsl(var(--border));
color: hsl(var(--muted-foreground));
font-family: var(--font-body, "Nunito", sans-serif);
}

View File

@ -1,44 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// MascotHeroBlockMeta defines metadata for the mascot hero block.
var MascotHeroBlockMeta = blocks.BlockMeta{
Key: "mascot_hero",
Title: "Mascot Hero",
Description: "Big rounded panel with mascot SVG and primary-colored confetti.",
Category: blocks.CategoryContent,
Source: "kindergarten",
}
// MascotHeroData carries the strongly-typed shape consumed by the templ component.
type MascotHeroData struct {
Mascot string
Headline string
Tagline string
CTALabel string
CTAHref string
BgColor string
}
// MascotHeroBlock renders the mascot hero block from the unstructured content map.
// Content shape: {mascot,headline,tagline,ctaLabel,ctaHref,bgColor}.
func MascotHeroBlock(ctx context.Context, content map[string]any) string {
data := MascotHeroData{
Mascot: getStringDefault(content, "mascot", "pip"),
Headline: getString(content, "headline"),
Tagline: getString(content, "tagline"),
CTALabel: getString(content, "ctaLabel"),
CTAHref: getString(content, "ctaHref"),
BgColor: getString(content, "bgColor"),
}
var buf bytes.Buffer
_ = mascotHeroComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,78 +0,0 @@
package main
// mascotHeroComponent renders the mascot hero panel.
templ mascotHeroComponent(data MascotHeroData) {
<section class="kg-section" data-block="kindergarten:mascot_hero">
<div class="kg-container">
<div class="kg-hero-panel">
<div class="kg-confetti" aria-hidden="true"></div>
<div style="position: relative; z-index: 1; display: grid; gap: 2rem; grid-template-columns: 1fr; align-items: center;" class="kg-mascot-hero-grid">
<div>
if data.Headline != "" {
<h1 class="kg-display kg-crayon-underline" style="font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;">{ data.Headline }</h1>
} else {
<h1 class="kg-display" style="font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;" data-empty="true">Welcome!</h1>
}
if data.Tagline != "" {
<p class="kg-text" style="font-size: 1.25rem; margin-top: 1.5rem;">{ data.Tagline }</p>
}
if data.CTALabel != "" && data.CTAHref != "" {
<div style="margin-top: 2rem;">
<a href={ templ.SafeURL(data.CTAHref) } class="kg-pill">{ data.CTALabel }</a>
</div>
}
</div>
<div style="display: flex; justify-content: center; align-items: center;">
@mascotSVG(data.Mascot)
</div>
</div>
</div>
</div>
</section>
}
// mascotSVG renders a primitive, license-free mascot illustration.
// The SVGs intentionally use `currentColor` (and the theme primary) so they
// inherit colorways from the active preset.
templ mascotSVG(name string) {
switch name {
case "blocks":
<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Alphabet blocks mascot">
<rect x="20" y="60" width="60" height="60" rx="12" fill="hsl(var(--primary))" />
<rect x="90" y="40" width="60" height="60" rx="12" fill="hsl(var(--secondary))" />
<rect x="60" y="120" width="60" height="60" rx="12" fill="hsl(var(--accent))" />
<text x="50" y="98" text-anchor="middle" font-family="var(--font-heading, Quicksand)" font-weight="700" font-size="32" fill="hsl(var(--primary-foreground))">A</text>
<text x="120" y="78" text-anchor="middle" font-family="var(--font-heading, Quicksand)" font-weight="700" font-size="32" fill="hsl(var(--secondary-foreground))">B</text>
<text x="90" y="158" text-anchor="middle" font-family="var(--font-heading, Quicksand)" font-weight="700" font-size="32" fill="hsl(var(--accent-foreground))">C</text>
</svg>
case "star":
<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Friendly star mascot">
<polygon points="100,10 122,75 190,75 135,115 155,180 100,140 45,180 65,115 10,75 78,75" fill="hsl(var(--accent))" stroke="hsl(var(--foreground))" stroke-width="4" stroke-linejoin="round" />
<circle cx="80" cy="95" r="6" fill="hsl(var(--foreground))" />
<circle cx="120" cy="95" r="6" fill="hsl(var(--foreground))" />
<path d="M 80 120 Q 100 135 120 120" stroke="hsl(var(--foreground))" stroke-width="4" fill="none" stroke-linecap="round" />
</svg>
case "balloon":
<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Balloon mascot">
<ellipse cx="100" cy="80" rx="50" ry="60" fill="hsl(var(--primary))" stroke="hsl(var(--foreground))" stroke-width="4" />
<path d="M 100 140 L 95 150 L 100 145 L 105 150 Z" fill="hsl(var(--foreground))" />
<path d="M 100 150 Q 90 175 100 195" stroke="hsl(var(--foreground))" stroke-width="2" fill="none" />
<circle cx="85" cy="65" r="6" fill="hsl(var(--foreground))" />
<circle cx="115" cy="65" r="6" fill="hsl(var(--foreground))" />
<path d="M 85 90 Q 100 105 115 90" stroke="hsl(var(--foreground))" stroke-width="4" fill="none" stroke-linecap="round" />
</svg>
default:
<svg viewBox="0 0 200 200" width="200" height="200" role="img" aria-label="Pip the mascot">
<circle cx="100" cy="115" r="70" fill="hsl(var(--accent))" stroke="hsl(var(--foreground))" stroke-width="4" />
<circle cx="78" cy="100" r="8" fill="hsl(var(--foreground))" />
<circle cx="122" cy="100" r="8" fill="hsl(var(--foreground))" />
<circle cx="78" cy="98" r="3" fill="hsl(var(--background))" />
<circle cx="122" cy="98" r="3" fill="hsl(var(--background))" />
<path d="M 75 130 Q 100 155 125 130" stroke="hsl(var(--foreground))" stroke-width="5" fill="none" stroke-linecap="round" />
<circle cx="60" cy="115" r="6" fill="hsl(var(--secondary))" />
<circle cx="140" cy="115" r="6" fill="hsl(var(--secondary))" />
<path d="M 50 60 L 60 35 L 80 50 Z" fill="hsl(var(--primary))" />
<path d="M 150 60 L 140 35 L 120 50 Z" fill="hsl(var(--primary))" />
</svg>
}
}

View File

@ -1,178 +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"
// mascotHeroComponent renders the mascot hero panel.
func mascotHeroComponent(data MascotHeroData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"kg-section\" data-block=\"kindergarten:mascot_hero\"><div class=\"kg-container\"><div class=\"kg-hero-panel\"><div class=\"kg-confetti\" aria-hidden=\"true\"></div><div style=\"position: relative; z-index: 1; display: grid; gap: 2rem; grid-template-columns: 1fr; align-items: center;\" class=\"kg-mascot-hero-grid\"><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Headline != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h1 class=\"kg-display kg-crayon-underline\" style=\"font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Headline)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mascot_hero.templ`, Line: 12, Col: 121}
}
_, 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, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h1 class=\"kg-display\" style=\"font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;\" data-empty=\"true\">Welcome!</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Tagline != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<p class=\"kg-text\" style=\"font-size: 1.25rem; margin-top: 1.5rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Tagline)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mascot_hero.templ`, Line: 17, Col: 88}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.CTALabel != "" && data.CTAHref != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div style=\"margin-top: 2rem;\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.CTAHref))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mascot_hero.templ`, Line: 21, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" class=\"kg-pill\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.CTALabel)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mascot_hero.templ`, Line: 21, Col: 79}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div><div style=\"display: flex; justify-content: center; align-items: center;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = mascotSVG(data.Mascot).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></div></div></div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// mascotSVG renders a primitive, license-free mascot illustration.
// The SVGs intentionally use `currentColor` (and the theme primary) so they
// inherit colorways from the active preset.
func mascotSVG(name 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_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch name {
case "blocks":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<svg viewBox=\"0 0 200 200\" width=\"200\" height=\"200\" role=\"img\" aria-label=\"Alphabet blocks mascot\"><rect x=\"20\" y=\"60\" width=\"60\" height=\"60\" rx=\"12\" fill=\"hsl(var(--primary))\"></rect> <rect x=\"90\" y=\"40\" width=\"60\" height=\"60\" rx=\"12\" fill=\"hsl(var(--secondary))\"></rect> <rect x=\"60\" y=\"120\" width=\"60\" height=\"60\" rx=\"12\" fill=\"hsl(var(--accent))\"></rect> <text x=\"50\" y=\"98\" text-anchor=\"middle\" font-family=\"var(--font-heading, Quicksand)\" font-weight=\"700\" font-size=\"32\" fill=\"hsl(var(--primary-foreground))\">A</text> <text x=\"120\" y=\"78\" text-anchor=\"middle\" font-family=\"var(--font-heading, Quicksand)\" font-weight=\"700\" font-size=\"32\" fill=\"hsl(var(--secondary-foreground))\">B</text> <text x=\"90\" y=\"158\" text-anchor=\"middle\" font-family=\"var(--font-heading, Quicksand)\" font-weight=\"700\" font-size=\"32\" fill=\"hsl(var(--accent-foreground))\">C</text></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "star":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<svg viewBox=\"0 0 200 200\" width=\"200\" height=\"200\" role=\"img\" aria-label=\"Friendly star mascot\"><polygon points=\"100,10 122,75 190,75 135,115 155,180 100,140 45,180 65,115 10,75 78,75\" fill=\"hsl(var(--accent))\" stroke=\"hsl(var(--foreground))\" stroke-width=\"4\" stroke-linejoin=\"round\"></polygon> <circle cx=\"80\" cy=\"95\" r=\"6\" fill=\"hsl(var(--foreground))\"></circle> <circle cx=\"120\" cy=\"95\" r=\"6\" fill=\"hsl(var(--foreground))\"></circle> <path d=\"M 80 120 Q 100 135 120 120\" stroke=\"hsl(var(--foreground))\" stroke-width=\"4\" fill=\"none\" stroke-linecap=\"round\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "balloon":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<svg viewBox=\"0 0 200 200\" width=\"200\" height=\"200\" role=\"img\" aria-label=\"Balloon mascot\"><ellipse cx=\"100\" cy=\"80\" rx=\"50\" ry=\"60\" fill=\"hsl(var(--primary))\" stroke=\"hsl(var(--foreground))\" stroke-width=\"4\"></ellipse> <path d=\"M 100 140 L 95 150 L 100 145 L 105 150 Z\" fill=\"hsl(var(--foreground))\"></path> <path d=\"M 100 150 Q 90 175 100 195\" stroke=\"hsl(var(--foreground))\" stroke-width=\"2\" fill=\"none\"></path> <circle cx=\"85\" cy=\"65\" r=\"6\" fill=\"hsl(var(--foreground))\"></circle> <circle cx=\"115\" cy=\"65\" r=\"6\" fill=\"hsl(var(--foreground))\"></circle> <path d=\"M 85 90 Q 100 105 115 90\" stroke=\"hsl(var(--foreground))\" stroke-width=\"4\" fill=\"none\" stroke-linecap=\"round\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<svg viewBox=\"0 0 200 200\" width=\"200\" height=\"200\" role=\"img\" aria-label=\"Pip the mascot\"><circle cx=\"100\" cy=\"115\" r=\"70\" fill=\"hsl(var(--accent))\" stroke=\"hsl(var(--foreground))\" stroke-width=\"4\"></circle> <circle cx=\"78\" cy=\"100\" r=\"8\" fill=\"hsl(var(--foreground))\"></circle> <circle cx=\"122\" cy=\"100\" r=\"8\" fill=\"hsl(var(--foreground))\"></circle> <circle cx=\"78\" cy=\"98\" r=\"3\" fill=\"hsl(var(--background))\"></circle> <circle cx=\"122\" cy=\"98\" r=\"3\" fill=\"hsl(var(--background))\"></circle> <path d=\"M 75 130 Q 100 155 125 130\" stroke=\"hsl(var(--foreground))\" stroke-width=\"5\" fill=\"none\" stroke-linecap=\"round\"></path> <circle cx=\"60\" cy=\"115\" r=\"6\" fill=\"hsl(var(--secondary))\"></circle> <circle cx=\"140\" cy=\"115\" r=\"6\" fill=\"hsl(var(--secondary))\"></circle> <path d=\"M 50 60 L 60 35 L 80 50 Z\" fill=\"hsl(var(--primary))\"></path> <path d=\"M 150 60 L 140 35 L 120 50 Z\" fill=\"hsl(var(--primary))\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

25
master_pages.json Normal file
View File

@ -0,0 +1,25 @@
[
{
"key": "kindergarten:default-master",
"title": "Kindergarten Default Master",
"page_templates": ["default", "article"],
"blocks": [
{ "block_key": "navbar", "title": "Crayon Nav", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 },
{ "block_key": "kindergarten:alphabet_strip", "title": "ABC Strip", "content": { "letters": "ABCDEFGHIJ", "colorMode": "rainbow" }, "slot": "header", "sort_order": 1 },
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 },
{ "block_key": "kindergarten:footer", "title": "Friendly Footer", "content": { "showSignup": "true", "mascotName": "Pip" }, "slot": "footer", "sort_order": 0 }
]
},
{
"key": "kindergarten:landing-master",
"title": "Kindergarten Landing Master",
"page_templates": ["landing", "full-width"],
"blocks": [
{ "block_key": "navbar", "title": "Crayon Nav", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 },
{ "block_key": "kindergarten:mascot_hero", "title": "Mascot Hero", "content": { "mascot": "pip", "headline": "Welcome to school!", "tagline": "Big fun, small humans." }, "slot": "hero", "sort_order": 0 },
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
{ "block_key": "kindergarten:big_cta", "title": "Big Yellow CTA", "content": { "label": "Enrol today", "href": "/enrol", "colorVariant": "yellow" }, "slot": "cta", "sort_order": 0 },
{ "block_key": "kindergarten:footer", "title": "Friendly Footer", "content": { "showSignup": "true" }, "slot": "footer", "sort_order": 0 }
]
}
]

View File

@ -1,47 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// NumbersCounterBlockMeta defines metadata for the numbers counter block.
var NumbersCounterBlockMeta = blocks.BlockMeta{
Key: "numbers_counter",
Title: "Numbers Counter",
Description: "Big circular numeral badges for stats (e.g. '12 teachers').",
Category: blocks.CategoryContent,
Source: "kindergarten",
}
// NumberItem is a single numeral + label pair.
type NumberItem struct {
Number int
Label string
}
// NumbersData is the renderer input.
type NumbersData struct {
Items []NumberItem
}
// NumbersCounterBlock renders the round numeral grid.
// Content shape: {items:[{number,label}]}.
func NumbersCounterBlock(ctx context.Context, content map[string]any) string {
raw := getSlice(content, "items")
items := make([]NumberItem, 0, len(raw))
for _, m := range raw {
items = append(items, NumberItem{
Number: getInt(m, "number", 0),
Label: getString(m, "label"),
})
}
data := NumbersData{Items: items}
var buf bytes.Buffer
_ = numbersCounterComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,25 +0,0 @@
package main
import "fmt"
// numbersCounterComponent renders circular numeral badges.
templ numbersCounterComponent(data NumbersData) {
<section class="kg-section" data-block="kindergarten:numbers_counter">
<div class="kg-container">
if len(data.Items) == 0 {
<div class="kg-empty" data-empty="true">No numbers yet — add a few stats to celebrate.</div>
} else {
<div class="kg-numbers-grid">
for _, item := range data.Items {
<div class="kg-numbers-item">
<span class="kg-numeral" data-number={ fmt.Sprintf("%d", item.Number) }>{ fmt.Sprintf("%d", item.Number) }</span>
if item.Label != "" {
<span class="kg-numbers-label">{ item.Label }</span>
}
</div>
}
</div>
}
</div>
</section>
}

View File

@ -1,117 +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 "fmt"
// numbersCounterComponent renders circular numeral badges.
func numbersCounterComponent(data NumbersData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"kg-section\" data-block=\"kindergarten:numbers_counter\"><div class=\"kg-container\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(data.Items) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"kg-empty\" data-empty=\"true\">No numbers yet — add a few stats to celebrate.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"kg-numbers-grid\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, item := range data.Items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"kg-numbers-item\"><span class=\"kg-numeral\" data-number=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", item.Number))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `numbers_counter.templ`, Line: 15, Col: 76}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Number))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `numbers_counter.templ`, Line: 15, Col: 111}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.Label != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<span class=\"kg-numbers-label\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(item.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `numbers_counter.templ`, Line: 17, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -2,11 +2,8 @@
name = "kindergarten" name = "kindergarten"
display_name = "Kindergarten" display_name = "Kindergarten"
scope = "@themes" scope = "@themes"
version = "0.1.2" version = "0.2.0"
description = "Primary-colored, hand-lettered theme for schools, daycares, toy shops, museums, and kids' publishers." description = "Primary-colored, hand-lettered theme for schools, daycares, toy shops, museums, and kids' publishers."
kind = "theme" kind = "theme"
categories = ["templates"] categories = ["templates"]
tags = ["kids", "education", "playful", "family", "school", "museum", "friendly", "daycare", "publisher"] tags = ["kids", "education", "playful", "family", "school", "museum", "friendly", "daycare", "publisher"]
[compatibility]
block_core = ">=0.15.0 <0.16.0"

View File

@ -1,181 +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 satisfies templates.HTMLComponent via Render.
func wrap(f func(ctx context.Context, doc map[string]any) templ.Component) templates.TemplateFunc {
return func(ctx context.Context, doc map[string]any) templates.HTMLComponent {
return f(ctx, doc)
}
}
// Register wires the Kindergarten theme into the host registries. The call
// order matters: the system template + page templates are registered first,
// then schemas are loaded before any block registrations so the schema
// bindings resolve.
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
Key: "kindergarten",
Title: "Kindergarten",
Description: "Primary-colored, hand-lettered theme for schools, daycares, toy shops, museums, and kids' publishers.",
})
if err := tr.RegisterPageTemplate("kindergarten", templates.PageTemplateMeta{
Key: "default",
Title: "Default",
Description: "Standard rounded layout with header, main, footer",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderKindergarten)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("kindergarten", templates.PageTemplateMeta{
Key: "landing",
Title: "Landing",
Description: "Mascot-led hero plus stacked feature sections",
Slots: []string{"hero", "main", "cta", "footer"},
}, wrap(RenderKindergartenLanding)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("kindergarten", templates.PageTemplateMeta{
Key: "article",
Title: "Story Page",
Description: "Picture-book reading layout for blog posts and stories",
Slots: []string{"header", "main", "aside", "footer"},
}, wrap(RenderKindergartenArticle)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("kindergarten", templates.PageTemplateMeta{
Key: "full-width",
Title: "Full Width",
Description: "Edge-to-edge gallery / classroom showcase",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderKindergartenFullWidth)); err != nil {
return err
}
// Schemas must be loaded before block registrations so each Register()
// call can bind to its declared schema.
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
return err
}
// Theme-owned blocks (eight per spec §8). Registered unqualified; the host
// addresses them as "kindergarten:<key>" downstream.
br.Register(MascotHeroBlockMeta, MascotHeroBlock)
br.Register(AlphabetStripBlockMeta, AlphabetStripBlock)
br.Register(ScheduleBlockMeta, ScheduleBlock)
br.Register(GalleryOfArtBlockMeta, GalleryOfArtBlock)
br.Register(NumbersCounterBlockMeta, NumbersCounterBlock)
br.Register(StorybookQuoteBlockMeta, StorybookQuoteBlock)
br.Register(BigCTABlockMeta, BigCTABlock)
br.Register(FooterBlockMeta, FooterBlock)
// Built-in overrides (spec §9). Each is scoped to the kindergarten template,
// so they only apply when this theme is active.
br.RegisterTemplateOverride("kindergarten", "heading", KindergartenHeadingBlock)
br.RegisterTemplateOverride("kindergarten", "text", KindergartenTextBlock)
br.RegisterTemplateOverride("kindergarten", "button", KindergartenButtonBlock)
br.RegisterTemplateOverride("kindergarten", "card", KindergartenCardBlock)
// Branded email wrapper.
tr.RegisterEmailWrapper("kindergarten", KindergartenEmailWrapper)
return nil
}
// DefaultMasterPages returns the default master pages that Kindergarten ships.
// Per spec §7, two masters: default-master (used by default + article page
// templates) and landing-master (used by landing + full-width).
func DefaultMasterPages() []plugin.MasterPageDefinition {
return []plugin.MasterPageDefinition{
{
Key: "kindergarten:default-master",
Title: "Kindergarten Default Master",
PageTemplates: []string{"default", "article"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "navbar",
Title: "Crayon Nav",
Content: map[string]any{"menuName": "main"},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "kindergarten:alphabet_strip",
Title: "ABC Strip",
Content: map[string]any{"letters": "ABCDEFGHIJ", "colorMode": "rainbow"},
Slot: "header",
SortOrder: 1,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{"slotName": "main", "placeholder": "Page content"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "kindergarten:footer",
Title: "Friendly Footer",
Content: map[string]any{"showSignup": "true", "mascotName": "Pip"},
Slot: "footer",
SortOrder: 0,
},
},
},
{
Key: "kindergarten:landing-master",
Title: "Kindergarten Landing Master",
PageTemplates: []string{"landing", "full-width"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "navbar",
Title: "Crayon Nav",
Content: map[string]any{"menuName": "main"},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "kindergarten:mascot_hero",
Title: "Mascot Hero",
Content: map[string]any{"mascot": "pip", "headline": "Welcome to school!", "tagline": "Big fun, small humans."},
Slot: "hero",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{"slotName": "main"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "kindergarten:big_cta",
Title: "Big Yellow CTA",
Content: map[string]any{"label": "Enrol today", "href": "/enrol", "colorVariant": "yellow"},
Slot: "cta",
SortOrder: 0,
},
{
BlockKey: "kindergarten:footer",
Title: "Friendly Footer",
Content: map[string]any{"showSignup": "true"},
Slot: "footer",
SortOrder: 0,
},
},
},
}
}

View File

@ -1,25 +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 Kindergarten theme.
var Registration = plugin.PluginRegistration{
Name: "kindergarten",
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() },
}

View File

@ -1,53 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// ScheduleBlockMeta defines metadata for the day schedule block.
var ScheduleBlockMeta = blocks.BlockMeta{
Key: "schedule",
Title: "Day Schedule",
Description: "Daycare/classroom day plan with crayon time-pills.",
Category: blocks.CategoryContent,
Source: "kindergarten",
}
// ScheduleItem is a single time/activity row.
type ScheduleItem struct {
Time string
Activity string
Icon string
}
// ScheduleData is the renderer's strongly-typed input.
type ScheduleData struct {
Title string
Items []ScheduleItem
}
// ScheduleBlock renders the day-schedule block.
// Content shape: {title,items:[{time,activity,icon}]}.
func ScheduleBlock(ctx context.Context, content map[string]any) string {
raw := getSlice(content, "items")
items := make([]ScheduleItem, 0, len(raw))
for _, m := range raw {
items = append(items, ScheduleItem{
Time: getString(m, "time"),
Activity: getString(m, "activity"),
Icon: getString(m, "icon"),
})
}
data := ScheduleData{
Title: getString(content, "title"),
Items: items,
}
var buf bytes.Buffer
_ = scheduleComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,60 +0,0 @@
package main
// scheduleComponent renders the day schedule.
templ scheduleComponent(data ScheduleData) {
<section class="kg-section" data-block="kindergarten:schedule">
<div class="kg-container">
if data.Title != "" {
<h2 class="kg-display kg-crayon-underline" style="font-size: 2rem; margin-bottom: 1.5rem;">{ data.Title }</h2>
}
if len(data.Items) == 0 {
<div class="kg-empty" data-empty="true">No schedule items yet — add a time block to get started.</div>
} else {
<ol style="list-style: none; padding: 0; margin: 0;">
for _, item := range data.Items {
<li class="kg-schedule-item">
<span class="kg-schedule-time">
if item.Time != "" {
{ item.Time }
} else {
--:--
}
</span>
<span class="kg-schedule-activity">
if item.Activity != "" {
{ item.Activity }
} else {
Activity
}
</span>
<span class="kg-schedule-icon" aria-hidden="true">
@scheduleIcon(item.Icon)
</span>
</li>
}
</ol>
}
</div>
</section>
}
// scheduleIcon renders a small SVG matching the icon name. Defaults to a star
// shape when the icon is missing or unknown so we never render a broken image.
templ scheduleIcon(name string) {
switch name {
case "sun":
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m4.93 19.07 1.41-1.41"/><path d="m17.66 6.34 1.41-1.41"/></svg>
case "book":
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
case "paint":
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 11h2m-1 -1v2"/><circle cx="13" cy="6" r="2"/><circle cx="19" cy="11" r="2"/><circle cx="6" cy="14" r="2"/><circle cx="10" cy="20" r="2"/><path d="M7.4 12.4l3.6 -2.4"/><path d="M11.6 8.6l1.4 -0.6"/><path d="M15 6l4 5"/><path d="M8 16l2 4"/></svg>
case "snack":
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>
case "play":
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
case "nap":
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
default:
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.8 5.8 21 7 14 2 9.3 9 8.5 12 2"/></svg>
}
}

View File

@ -1,197 +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"
// scheduleComponent renders the day schedule.
func scheduleComponent(data ScheduleData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"kg-section\" data-block=\"kindergarten:schedule\"><div class=\"kg-container\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h2 class=\"kg-display kg-crayon-underline\" style=\"font-size: 2rem; margin-bottom: 1.5rem;\">")
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: `schedule.templ`, Line: 8, Col: 107}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Items) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"kg-empty\" data-empty=\"true\">No schedule items yet — add a time block to get started.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<ol style=\"list-style: none; padding: 0; margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, item := range data.Items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<li class=\"kg-schedule-item\"><span class=\"kg-schedule-time\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.Time != "" {
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(item.Time)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schedule.templ`, Line: 18, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
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
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</span> <span class=\"kg-schedule-activity\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.Activity != "" {
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(item.Activity)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schedule.templ`, Line: 25, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "Activity")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</span> <span class=\"kg-schedule-icon\" aria-hidden=\"true\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = scheduleIcon(item.Icon).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</span></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</ol>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// scheduleIcon renders a small SVG matching the icon name. Defaults to a star
// shape when the icon is missing or unknown so we never render a broken image.
func scheduleIcon(name 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_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch name {
case "sun":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4\"></circle><path d=\"M12 2v2\"></path><path d=\"M12 20v2\"></path><path d=\"m4.93 4.93 1.41 1.41\"></path><path d=\"m17.66 17.66 1.41 1.41\"></path><path d=\"M2 12h2\"></path><path d=\"M20 12h2\"></path><path d=\"m4.93 19.07 1.41-1.41\"></path><path d=\"m17.66 6.34 1.41-1.41\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "book":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 19.5A2.5 2.5 0 0 1 6.5 17H20\"></path><path d=\"M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "paint":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M19 11h2m-1 -1v2\"></path><circle cx=\"13\" cy=\"6\" r=\"2\"></circle><circle cx=\"19\" cy=\"11\" r=\"2\"></circle><circle cx=\"6\" cy=\"14\" r=\"2\"></circle><circle cx=\"10\" cy=\"20\" r=\"2\"></circle><path d=\"M7.4 12.4l3.6 -2.4\"></path><path d=\"M11.6 8.6l1.4 -0.6\"></path><path d=\"M15 6l4 5\"></path><path d=\"M8 16l2 4\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "snack":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><path d=\"M8 14s1.5 2 4 2 4-2 4-2\"></path><line x1=\"9\" y1=\"9\" x2=\"9.01\" y2=\"9\"></line><line x1=\"15\" y1=\"9\" x2=\"15.01\" y2=\"9\"></line></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "play":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"5 3 19 12 5 21 5 3\"></polygon></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "nap":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.8 5.8 21 7 14 2 9.3 9 8.5 12 2\"></polygon></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,38 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// StorybookQuoteBlockMeta defines metadata for the storybook quote block.
var StorybookQuoteBlockMeta = blocks.BlockMeta{
Key: "storybook_quote",
Title: "Storybook Quote",
Description: "Picture-book page treatment for a quote with optional illustration.",
Category: blocks.CategoryContent,
Source: "kindergarten",
}
// StorybookQuoteData is the renderer input.
type StorybookQuoteData struct {
Quote string
Author string
Illustration string
}
// StorybookQuoteBlock renders the quote panel.
// Content shape: {quote,author,illustration}.
func StorybookQuoteBlock(ctx context.Context, content map[string]any) string {
data := StorybookQuoteData{
Quote: getString(content, "quote"),
Author: getString(content, "author"),
Illustration: getString(content, "illustration"),
}
var buf bytes.Buffer
_ = storybookQuoteComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,42 +0,0 @@
package main
import "git.dev.alexdunmow.com/block/core/blocks"
// storybookQuoteComponent renders a quote with optional illustration.
templ storybookQuoteComponent(data StorybookQuoteData) {
<section class="kg-section" data-block="kindergarten:storybook_quote">
<div class="kg-container">
<figure class="kg-storybook">
<blockquote style="margin: 0;">
if data.Quote != "" {
<p class="kg-storybook-quote">"{ data.Quote }"</p>
} else {
<p class="kg-storybook-quote" data-empty="true">Add a quote to inspire your readers.</p>
}
if data.Author != "" {
<figcaption class="kg-storybook-author">— { data.Author }</figcaption>
}
</blockquote>
<div>
if data.Illustration != "" {
<img class="kg-storybook-illustration" src={ blocks.ResolveMediaPath(data.Illustration) } alt={ "Illustration accompanying the quote" } />
} else {
@storybookFallbackIllustration()
}
</div>
</figure>
</div>
</section>
}
// storybookFallbackIllustration renders a friendly SVG when no illustration
// is supplied. It guarantees a visible, themed graphic instead of a broken
// image icon.
templ storybookFallbackIllustration() {
<svg viewBox="0 0 200 150" width="100%" height="auto" class="kg-storybook-illustration" role="img" aria-label="Storybook illustration">
<rect width="200" height="150" rx="24" fill="hsl(var(--muted))" />
<circle cx="60" cy="60" r="20" fill="hsl(var(--accent))" />
<rect x="100" y="40" width="60" height="40" rx="8" fill="hsl(var(--primary))" />
<path d="M 30 110 Q 100 90 170 110 L 170 130 L 30 130 Z" fill="hsl(var(--secondary))" />
</svg>
}

View File

@ -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"
import "git.dev.alexdunmow.com/block/core/blocks"
// storybookQuoteComponent renders a quote with optional illustration.
func storybookQuoteComponent(data StorybookQuoteData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section class=\"kg-section\" data-block=\"kindergarten:storybook_quote\"><div class=\"kg-container\"><figure class=\"kg-storybook\"><blockquote style=\"margin: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Quote != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<p class=\"kg-storybook-quote\">\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Quote)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `storybook_quote.templ`, Line: 12, Col: 49}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<p class=\"kg-storybook-quote\" data-empty=\"true\">Add a quote to inspire your readers.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Author != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<figcaption class=\"kg-storybook-author\">— ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Author)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `storybook_quote.templ`, Line: 17, Col: 63}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</figcaption>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</blockquote><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Illustration != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<img class=\"kg-storybook-illustration\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(blocks.ResolveMediaPath(data.Illustration))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `storybook_quote.templ`, Line: 22, Col: 93}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue("Illustration accompanying the quote")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `storybook_quote.templ`, Line: 22, Col: 139}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = storybookFallbackIllustration().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div></figure></div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// storybookFallbackIllustration renders a friendly SVG when no illustration
// is supplied. It guarantees a visible, themed graphic instead of a broken
// image icon.
func storybookFallbackIllustration() 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_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<svg viewBox=\"0 0 200 150\" width=\"100%\" height=\"auto\" class=\"kg-storybook-illustration\" role=\"img\" aria-label=\"Storybook illustration\"><rect width=\"200\" height=\"150\" rx=\"24\" fill=\"hsl(var(--muted))\"></rect> <circle cx=\"60\" cy=\"60\" r=\"20\" fill=\"hsl(var(--accent))\"></circle> <rect x=\"100\" y=\"40\" width=\"60\" height=\"40\" rx=\"8\" fill=\"hsl(var(--primary))\"></rect> <path d=\"M 30 110 Q 100 90 170 110 L 170 130 L 30 130 Z\" fill=\"hsl(var(--secondary))\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,257 +0,0 @@
package main
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PageData carries everything the page templates need to render.
type PageData struct {
Title string
Slots map[string]string
ThemeMode string
ThemeCSS string
SiteSettings bn.SiteSettingsData
PageMeta bn.PageMeta
StructuredData string
CSSHash string
PageviewNonce string
EngagementConfig bn.EngagementConfig
}
// parseKindergartenPageData converts the unstructured doc map the engine
// passes in into the strongly-typed PageData used by the templ components.
func parseKindergartenPageData(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,
}
}
// pluginStyles is the canonical list of theme stylesheet URLs each page sends to <head>.
var pluginStyles = []string{"/templates/kindergarten/style.css"}
// Kindergarten — default template.
templ Kindergarten(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="kg-page kg-dotted-paper">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="kg-section" style="padding-block: 1.5rem;">
@templ.Raw(data.Slots["header"])
</header>
<main class="kg-container" style="padding-block: 2rem; flex: 1;">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="kg-empty">No content blocks assigned to this page.</div>
}
</main>
<footer style="margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Kindergarten — landing template with mascot hero + CTA strip.
templ KindergartenLanding(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="kg-page kg-dotted-paper">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="kg-section" style="padding-block: 1.5rem;">
@templ.Raw(data.Slots["header"])
</header>
<section class="kg-section" style="padding-top: 0;">
@templ.Raw(data.Slots["hero"])
</section>
<main class="kg-container">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
}
</main>
<section>
@templ.Raw(data.Slots["cta"])
</section>
<footer style="margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Kindergarten — article (storybook reading) template.
templ KindergartenArticle(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="kg-page kg-dotted-paper">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="kg-section" style="padding-block: 1.5rem;">
@templ.Raw(data.Slots["header"])
</header>
<div class="kg-container" style="display: grid; grid-template-columns: 1fr; gap: 2rem; padding-block: 2rem;">
<main>
if main, ok := data.Slots["main"]; ok && main != "" {
<article class="kg-text">
@templ.Raw(main)
</article>
} else {
<div class="kg-empty">No content yet — start a story.</div>
}
</main>
if aside, ok := data.Slots["aside"]; ok && aside != "" {
<aside class="kg-card">
@templ.Raw(aside)
</aside>
}
</div>
<footer style="margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Kindergarten — full-width gallery / showcase template.
templ KindergartenFullWidth(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="kg-page kg-dotted-paper">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="kg-section" style="padding-block: 1.5rem;">
@templ.Raw(data.Slots["header"])
</header>
<main style="flex: 1;">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="kg-container">
<div class="kg-empty">No content blocks assigned to this page.</div>
</div>
}
</main>
<footer style="margin-top: auto;">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// Render functions invoked by the registry.
func RenderKindergarten(ctx context.Context, doc map[string]any) templ.Component {
return Kindergarten(parseKindergartenPageData(doc))
}
func RenderKindergartenLanding(ctx context.Context, doc map[string]any) templ.Component {
return KindergartenLanding(parseKindergartenPageData(doc))
}
func RenderKindergartenArticle(ctx context.Context, doc map[string]any) templ.Component {
return KindergartenArticle(parseKindergartenPageData(doc))
}
func RenderKindergartenFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return KindergartenFullWidth(parseKindergartenPageData(doc))
}

View File

@ -1,529 +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 everything the page templates need to render.
type PageData struct {
Title string
Slots map[string]string
ThemeMode string
ThemeCSS string
SiteSettings bn.SiteSettingsData
PageMeta bn.PageMeta
StructuredData string
CSSHash string
PageviewNonce string
EngagementConfig bn.EngagementConfig
}
// parseKindergartenPageData converts the unstructured doc map the engine
// passes in into the strongly-typed PageData used by the templ components.
func parseKindergartenPageData(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,
}
}
// pluginStyles is the canonical list of theme stylesheet URLs each page sends to <head>.
var pluginStyles = []string{"/templates/kindergarten/style.css"}
// Kindergarten — default template.
func Kindergarten(data PageData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"kg-page kg-dotted-paper\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<header class=\"kg-section\" style=\"padding-block: 1.5rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</header><main class=\"kg-container\" style=\"padding-block: 2rem; flex: 1;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"kg-empty\">No content blocks assigned to this page.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</main><footer style=\"margin-top: auto;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Kindergarten — landing template with mascot hero + CTA strip.
func KindergartenLanding(data PageData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<body class=\"kg-page kg-dotted-paper\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<header class=\"kg-section\" style=\"padding-block: 1.5rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</header><section class=\"kg-section\" style=\"padding-top: 0;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["hero"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</section><main class=\"kg-container\">")
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
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</main><section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["cta"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</section><footer style=\"margin-top: auto;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Kindergarten — article (storybook reading) template.
func KindergartenArticle(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, 18, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<body class=\"kg-page kg-dotted-paper\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<header class=\"kg-section\" style=\"padding-block: 1.5rem;\">")
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, 21, "</header><div class=\"kg-container\" style=\"display: grid; grid-template-columns: 1fr; gap: 2rem; padding-block: 2rem;\"><main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<article class=\"kg-text\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<div class=\"kg-empty\">No content yet — start a story.</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</main>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if aside, ok := data.Slots["aside"]; ok && aside != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<aside class=\"kg-card\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(aside).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</aside>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</div><footer style=\"margin-top: auto;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Kindergarten — full-width gallery / showcase template.
func KindergartenFullWidth(data PageData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: pluginStyles,
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<body class=\"kg-page kg-dotted-paper\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<header class=\"kg-section\" style=\"padding-block: 1.5rem;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</header><main style=\"flex: 1;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<div class=\"kg-container\"><div class=\"kg-empty\">No content blocks assigned to this page.</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</main><footer style=\"margin-top: auto;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Render functions invoked by the registry.
func RenderKindergarten(ctx context.Context, doc map[string]any) templ.Component {
return Kindergarten(parseKindergartenPageData(doc))
}
func RenderKindergartenLanding(ctx context.Context, doc map[string]any) templ.Component {
return KindergartenLanding(parseKindergartenPageData(doc))
}
func RenderKindergartenArticle(ctx context.Context, doc map[string]any) templ.Component {
return KindergartenArticle(parseKindergartenPageData(doc))
}
func RenderKindergartenFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return KindergartenFullWidth(parseKindergartenPageData(doc))
}
var _ = templruntime.GeneratedTemplate

View File

@ -0,0 +1,68 @@
<!doctype html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="x-apple-disable-message-reformatting">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--[if mso]>
<noscript>
<xml>
<o:OfficeDocumentSettings>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
</noscript>
<![endif]-->
<title>{{ site_name }}</title>
<style type="text/css">
body, table, td, p, a, li, blockquote { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
body { margin: 0 !important; padding: 0 !important; width: 100% !important; }
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
h1, h2, h3, h4, h5, h6 { font-family: 'Quicksand', 'Nunito', -apple-system, sans-serif; font-weight: 700; }
@media only screen and (max-width: 620px) {
.email-container { width: 100% !important; max-width: 100% !important; }
.content-padding { padding-left: 24px !important; padding-right: 24px !important; }
}
</style>
</head>
<body style="background-color: {{ colors.background|default:'#fdf9ed' }}; margin: 0; padding: 0; font-family: &amp;#39;Nunito&amp;#39;, &amp;#39;Quicksand&amp;#39;, -apple-system, sans-serif;">
{% if preview_text %}<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">{{ preview_text }}</div>{% endif %}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center" style="padding: 48px 10px; background-color: {{ colors.background|default:'#fdf9ed' }};">
<table role="presentation" class="email-container" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px; background-color: {{ colors.card|default:'#ffffff' }}; border: 1px solid {{ colors.border|default:'#e6d9b3' }}; border-radius: 32px;">
<tr>
<td align="center" style="padding: 32px 40px; background-color: {{ colors.card|default:'#ffffff' }}; border-bottom: 2px dashed {{ colors.border|default:'#e6d9b3' }}; border-radius: 32px 32px 0 0;">
{% if logo_url %}<img src="{{ logo_url }}" alt="{{ site_name }}" style="max-height: 48px; width: auto; display: block;">{% elif site_name %}<h1 style="margin: 0; font-size: 28px; font-weight: 700; color: {{ colors.foreground|default:'#1a253b' }}; letter-spacing: -0.5px; font-family: &amp;#39;Quicksand&amp;#39;, &amp;#39;Nunito&amp;#39;, sans-serif;">{{ site_name }}</h1>{% endif %}
</td>
</tr>
<tr>
<td class="content-padding" style="padding: 40px 48px; color: {{ colors.foreground|default:'#1a253b' }}; font-size: 16px; line-height: 1.7; font-family: &amp;#39;Nunito&amp;#39;, sans-serif;">{{ body|safe }}</td>
</tr>
<tr>
<td align="center" style="padding: 0 48px 32px 48px; background-color: {{ colors.card|default:'#ffffff' }};">
{% if site_url %}<a href="{{ site_url }}" style="display: inline-block; padding: 16px 32px; border-radius: 9999px; background-color: {{ colors.primary|default:'#fbcd1f' }}; color: {{ colors.primaryForeground|default:'#1a253b' }}; text-decoration: none; font-family: &amp;#39;Quicksand&amp;#39;, sans-serif; font-weight: 700; font-size: 16px;">Visit {{ site_name }}</a>{% endif %}
</td>
</tr>
<tr>
<td style="padding: 32px 48px; border-top: 2px dashed {{ colors.border|default:'#e6d9b3' }}; border-radius: 0 0 32px 32px; background-color: {{ colors.card|default:'#ffffff' }};">
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center">
<p style="margin: 0 0 8px; font-size: 14px; font-weight: 700; color: {{ colors.foreground|default:'#1a253b' }}; font-family: &amp;#39;Quicksand&amp;#39;, sans-serif;">👋 {{ site_name }}</p>
{% if support_email %}<p style="margin: 0 0 8px; font-size: 13px; color: {{ colors.mutedForeground|default:'#5a6480' }};">Need a hand? {{ support_email }}</p>{% endif %}
{% if unsubscribe_url %}<p style="margin: 0; font-size: 11px; color: {{ colors.mutedForeground|default:'#5a6480' }};"><a href="{{ unsubscribe_url }}" style="color: {{ colors.mutedForeground|default:'#5a6480' }}; text-decoration: none;">Unsubscribe</a></p>{% endif %}
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper">
{{ admin_banner_html|safe }}
<header class="kg-section" style="padding-block: 1.5rem;">{{ slots.header|safe }}</header>
<div class="kg-container" style="display: grid; grid-template-columns: 1fr; gap: 2rem; padding-block: 2rem;">
<main>
{% if slots.main %}<article class="kg-text">{{ slots.main|safe }}</article>{% else %}<div class="kg-empty">No content yet — start a story.</div>{% endif %}
</main>
{% if slots.aside %}<aside class="kg-card">{{ slots.aside|safe }}</aside>{% endif %}
</div>
<footer style="margin-top: auto;">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper">
{{ admin_banner_html|safe }}
<header class="kg-section" style="padding-block: 1.5rem;">{{ slots.header|safe }}</header>
<main class="kg-container" style="padding-block: 2rem; flex: 1;">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="kg-empty">No content blocks assigned to this page.</div>{% endif %}
</main>
<footer style="margin-top: auto;">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper">
{{ admin_banner_html|safe }}
<header class="kg-section" style="padding-block: 1.5rem;">{{ slots.header|safe }}</header>
<main style="flex: 1;">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="kg-container"><div class="kg-empty">No content blocks assigned to this page.</div></div>{% endif %}
</main>
<footer style="margin-top: auto;">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="kg-page kg-dotted-paper">
{{ admin_banner_html|safe }}
<header class="kg-section" style="padding-block: 1.5rem;">{{ slots.header|safe }}</header>
<section class="kg-section" style="padding-top: 0;">{{ slots.hero|safe }}</section>
<main class="kg-container">
{% if slots.main %}{{ slots.main|safe }}{% endif %}
</main>
<section>{{ slots.cta|safe }}</section>
<footer style="margin-top: auto;">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1 @@
{% if text %}{% set lbl = text %}{% else %}{% set lbl = label %}{% endif %}{% if lbl %}<a href="{{ href|default:'#' }}" class="kg-pill {% if variant == "red" %}kg-pill-red{% elif variant == "blue" %}kg-pill-blue{% elif variant == "green" %}kg-pill-green{% else %}kg-pill-yellow{% endif %}">{{ lbl }}</a>{% else %}<span class="kg-empty" data-empty="true">Add label and link to render this button.</span>{% endif %}

View File

@ -0,0 +1 @@
<div class="kg-card {{ class }}">{% if title %}<h3 class="kg-display" style="margin-top: 0; margin-bottom: 1rem; font-size: 1.5rem;">{{ title }}</h3>{% endif %}{% if body %}<div class="kg-text">{{ body|safe }}</div>{% endif %}{% if not title and not body %}<div data-empty="true" class="kg-empty">Add a title or body to fill this card.</div>{% endif %}</div>

View File

@ -0,0 +1 @@
{% if step|integer >= 1 and step|integer <= 9 %}<div class="kg-heading-stepped"><span class="kg-heading-step-badge" aria-hidden="true">{{ step|integer }}</span>{% endif %}{% if level|integer == 1 %}<h1 class="kg-display kg-crayon-underline {{ textClass }}" style="font-size: clamp(2.5rem, 6vw, 4rem); margin: 0;">{{ text }}</h1>{% elif level|integer == 3 %}<h3 class="kg-display {{ textClass }}" style="font-size: 1.75rem; margin: 0;">{{ text }}</h3>{% elif level|integer == 4 %}<h4 class="kg-display {{ textClass }}" style="font-size: 1.5rem; margin: 0;">{{ text }}</h4>{% elif level|integer == 5 %}<h5 class="kg-display {{ textClass }}" style="font-size: 1.25rem; margin: 0;">{{ text }}</h5>{% elif level|integer == 6 %}<h6 class="kg-display {{ textClass }}" style="font-size: 1.125rem; margin: 0;">{{ text }}</h6>{% else %}<h2 class="kg-display kg-crayon-underline {{ textClass }}" style="font-size: 2.25rem; margin: 0;">{{ text }}</h2>{% endif %}{% if step|integer >= 1 and step|integer <= 9 %}</div>{% endif %}

View File

@ -0,0 +1 @@
<div class="kg-text {{ class }}">{{ text|safe }}</div>

View File

@ -1,17 +0,0 @@
package main
import (
"bytes"
"context"
)
// KindergartenTextBlock renders text with Nunito body, 1.7 line-height,
// and the rounded ::selection highlight in primary yellow.
func KindergartenTextBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
class := getString(content, "class")
var buf bytes.Buffer
_ = kgTextComponent(text, class).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,9 +0,0 @@
package main
// kgTextComponent renders rich text body styled to match the Kindergarten
// reading rhythm.
templ kgTextComponent(text, class string) {
<div class={ "kg-text", class }>
@templ.Raw(text)
</div>
}

View File

@ -1,68 +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"
// kgTextComponent renders rich text body styled to match the Kindergarten
// reading rhythm.
func kgTextComponent(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{"kg-text", class}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `text_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(text).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate