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:27:47 +08:00
parent 8b2f726fbc
commit 5dfc1cd4be
67 changed files with 646 additions and 3982 deletions

View File

@ -1,26 +1,27 @@
# pastel-dream — build & publish helpers (wasm .bnp workflow) # pastel-dream — 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 pastel-dream-<version>.bnp # make build # pack pastel-dream-<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 := pastel-dream PLUGIN_NAME := pastel-dream
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,46 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// AffirmationMeta defines the affirmation strip block.
var AffirmationMeta = blocks.BlockMeta{
Key: "affirmation",
Title: "Affirmation Strip",
Description: "Slow-shimmer band carrying a short calming line in display type.",
Source: "pastel-dream",
Category: blocks.CategoryTheme,
}
// AffirmationBlock renders the affirmation strip.
// Content shape: {quote, author, palette}
func AffirmationBlock(ctx context.Context, content map[string]any) string {
palette := getStringOr(content, "palette", "blush")
switch palette {
case "blush", "mint", "butter", "sky":
// valid
default:
palette = "blush"
}
data := AffirmationData{
Quote: getStringOr(content, "quote", "You are doing enough."),
Author: getString(content, "author"),
Palette: palette,
}
var buf bytes.Buffer
_ = affirmationComponent(data).Render(ctx, &buf)
return buf.String()
}
// AffirmationData is the rendered view of the affirmation block.
type AffirmationData struct {
Quote string
Author string
Palette string
}

View File

@ -1,37 +0,0 @@
package main
// affirmationPaletteVar maps a palette name to a token reference. All four
// names resolve to a theme token so the strip never fails to colorize.
func affirmationPaletteVar(palette string) string {
switch palette {
case "mint":
return "hsl(var(--secondary) / 0.55)"
case "butter":
return "hsl(var(--accent) / 0.55)"
case "sky":
return "hsl(var(--secondary) / 0.40)"
default:
return "hsl(var(--primary) / 0.35)"
}
}
// affirmationComponent renders the shimmering affirmation strip.
templ affirmationComponent(data AffirmationData) {
<section
class="relative py-16 overflow-hidden animate-pastel-shimmer"
style={ "background-image: linear-gradient(90deg, hsl(var(--background)) 0%, " + affirmationPaletteVar(data.Palette) + " 50%, hsl(var(--background)) 100%);" }
data-block="pastel-dream:affirmation"
data-palette={ data.Palette }
>
<div class="relative z-10 max-w-3xl mx-auto px-6 text-center">
<p class="font-display text-3xl md:text-4xl leading-snug" style="color: hsl(var(--foreground));">
&ldquo;{ data.Quote }&rdquo;
</p>
if data.Author != "" {
<p class="font-body mt-4 text-sm uppercase tracking-[0.2em]" style="color: hsl(var(--muted-foreground));">
{ data.Author }
</p>
}
</div>
</section>
}

View File

@ -1,118 +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"
// affirmationPaletteVar maps a palette name to a token reference. All four
// names resolve to a theme token so the strip never fails to colorize.
func affirmationPaletteVar(palette string) string {
switch palette {
case "mint":
return "hsl(var(--secondary) / 0.55)"
case "butter":
return "hsl(var(--accent) / 0.55)"
case "sky":
return "hsl(var(--secondary) / 0.40)"
default:
return "hsl(var(--primary) / 0.35)"
}
}
// affirmationComponent renders the shimmering affirmation strip.
func affirmationComponent(data AffirmationData) 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=\"relative py-16 overflow-hidden animate-pastel-shimmer\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("background-image: linear-gradient(90deg, hsl(var(--background)) 0%, " + affirmationPaletteVar(data.Palette) + " 50%, hsl(var(--background)) 100%);")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `affirmation.templ`, Line: 22, Col: 158}
}
_, 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, "\" data-block=\"pastel-dream:affirmation\" data-palette=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Palette)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `affirmation.templ`, Line: 24, Col: 29}
}
_, 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, 3, "\"><div class=\"relative z-10 max-w-3xl mx-auto px-6 text-center\"><p class=\"font-display text-3xl md:text-4xl leading-snug\" style=\"color: hsl(var(--foreground));\">&ldquo;")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Quote)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `affirmation.templ`, Line: 28, Col: 23}
}
_, 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, "&rdquo;</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Author != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<p class=\"font-body mt-4 text-sm uppercase tracking-[0.2em]\" style=\"color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Author)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `affirmation.templ`, Line: 32, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</p>")
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
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -0,0 +1,10 @@
<section class="relative py-16 overflow-hidden animate-pastel-shimmer" style="background-image: linear-gradient(90deg, hsl(var(--background)) 0%, {% if palette == "mint" %}hsl(var(--secondary) / 0.55){% elif palette == "butter" %}hsl(var(--accent) / 0.55){% elif palette == "sky" %}hsl(var(--secondary) / 0.40){% else %}hsl(var(--primary) / 0.35){% endif %} 50%, hsl(var(--background)) 100%);" data-block="pastel-dream:affirmation" data-palette="{% if palette == "mint" or palette == "butter" or palette == "sky" %}{{ palette }}{% else %}blush{% endif %}">
<div class="relative z-10 max-w-3xl mx-auto px-6 text-center">
<p class="font-display text-3xl md:text-4xl leading-snug" style="color: hsl(var(--foreground));">
&ldquo;{{ quote|default:"You are doing enough." }}&rdquo;
</p>
{% if author %}<p class="font-body mt-4 text-sm uppercase tracking-[0.2em]" style="color: hsl(var(--muted-foreground));">
{{ author }}
</p>{% endif %}
</div>
</section>

42
blocks/blocks.yaml Normal file
View File

@ -0,0 +1,42 @@
# Pastel Dream theme block definitions (codeless .bnp, WO-WZ-021).
# Keys are fully qualified (pastel-dream:<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: pastel-dream:soft-navbar
title: Soft Navbar
description: Rounded pill navigation with a watercolor blob behind the logo.
category: navigation
schema: soft-navbar.schema.json
template: soft-navbar.ninjatpl
- key: pastel-dream:watercolor-hero
title: Watercolor Hero
description: Hero with two-tone watercolor blobs layered behind soft display type.
category: theme
schema: watercolor-hero.schema.json
template: watercolor-hero.ninjatpl
- key: pastel-dream:affirmation
title: Affirmation Strip
description: Slow-shimmer band carrying a short calming line in display type.
category: theme
schema: affirmation.schema.json
template: affirmation.ninjatpl
- key: pastel-dream:testimonial-soft
title: Soft Testimonial
description: Quote card with a deckle paper edge, avatar, and rating.
category: theme
schema: testimonial-soft.schema.json
template: testimonial-soft.ninjatpl
- key: pastel-dream:feature-grid-soft
title: Feature Trio
description: Three-up card grid for services, offerings, or core values.
category: layout
schema: feature-grid-soft.schema.json
template: feature-grid-soft.ninjatpl
- key: pastel-dream:cozy-footer
title: Cozy Footer
description: Newsletter signup, closing affirmation, menu column, and social pills.
category: navigation
schema: cozy-footer.schema.json
template: cozy-footer.ninjatpl

View File

@ -0,0 +1,23 @@
<section class="relative py-16 md:py-20 overflow-hidden bg-watercolor-mint" data-block="pastel-dream:cozy-footer">
<svg class="absolute -top-8 -right-8 w-64 h-64 -z-0" viewBox="0 0 300 300" aria-hidden="true">
<path d="M150,30 C220,30 270,90 270,150 C270,220 200,270 140,270 C80,270 30,210 30,150 C30,90 80,30 150,30 Z" fill="hsl(var(--primary) / 0.16)"></path>
</svg>
<div class="relative z-10 max-w-4xl mx-auto px-4 text-center">
<p class="font-display text-3xl mb-6" style="color: hsl(var(--foreground));">
{{ affirmation|default:"Be gentle with yourself today." }}
</p>
{% if showSignup != "no" and showSignup != "false" and showSignup != "0" and showSignup != "off" and showSignup != false %}<form class="flex flex-col sm:flex-row gap-3 max-w-md mx-auto mb-8" data-pastel-signup="true" onsubmit="event.preventDefault();">
<label class="sr-only" for="pastel-newsletter-email">Email</label>
<input id="pastel-newsletter-email" type="email" placeholder="you@calm.day" class="flex-1 px-5 py-3 font-body rounded-full" style="background-color: hsl(var(--input)); color: hsl(var(--foreground)); border: 1px solid hsl(var(--border));">
<button type="submit" class="pastel-pill">Stay in touch</button>
</form>{% endif %}
{% if social %}<div class="flex justify-center gap-3 flex-wrap mt-6">
{% for href in social %}<a href="{{ href }}" class="px-4 py-2 font-body text-xs uppercase tracking-[0.2em] rounded-full" style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));">
Visit
</a>{% endfor %}
</div>{% endif %}
<p class="font-mono text-xs mt-10" style="color: hsl(var(--muted-foreground));">
Pastel Dream &middot; with care
</p>
</div>
</section>

View File

@ -0,0 +1,18 @@
<section class="py-16 md:py-20" data-block="pastel-dream:feature-grid-soft">
<div class="max-w-6xl mx-auto px-4">
{% if intro %}<div class="font-body text-center max-w-2xl mx-auto mb-12 text-lg" style="color: hsl(var(--foreground) / 0.78);">
{{ intro|safe }}
</div>{% endif %}
{% if items %}<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for item in items %}<article class="pastel-card p-8 transition-transform" style="transition-timing-function: cubic-bezier(.22,1,.36,1); transition-duration: 240ms;">
{% if item.icon %}<div class="mb-4">
<img src="{{ item.icon }}" alt="" class="w-12 h-12 object-contain">
</div>{% else %}<div class="mb-4 w-12 h-12 rounded-full" style="background-color: hsl(var(--accent) / 0.5);" aria-hidden="true"></div>{% endif %}
{% if item.title %}<h3 class="font-display text-2xl mb-3" style="color: hsl(var(--foreground));">{{ item.title }}</h3>{% endif %}
{% if item.body %}<p class="font-body text-base" style="color: hsl(var(--foreground) / 0.78); line-height: 1.75;">{{ item.body }}</p>{% endif %}
</article>{% endfor %}
</div>{% else %}<div class="text-center font-body text-base" style="color: hsl(var(--muted-foreground));">
<p>Add up to three cards to the trio.</p>
</div>{% endif %}
</div>
</section>

View File

@ -0,0 +1,23 @@
<nav class="w-full px-4 py-4" data-block="pastel-dream:soft-navbar" data-menu="{{ menuName }}">
<div class="max-w-6xl mx-auto flex items-center justify-between gap-4 px-2 py-2 rounded-full bg-watercolor-blush" style="border-radius: 9999px;">
<a href="/" class="font-display text-2xl tracking-tight px-4 py-2 rounded-full" style="color: hsl(var(--primary-foreground)); background-color: hsl(var(--primary) / 0.15);">
{{ logo|default:"Pastel Dream" }}
</a>
<div class="hidden md:flex items-center gap-6 font-body text-sm" data-pastel-menu="{{ menuName }}">
<a href="/" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">Home</a>
<a href="/about" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">About</a>
<a href="/journal" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">Journal</a>
<a href="/contact" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">Contact</a>
</div>
{% if ctaText %}<a href="{{ ctaHref|default:"#" }}" class="pastel-pill text-sm">
{{ ctaText }}
</a>{% endif %}
<button class="md:hidden p-2 rounded-full" aria-label="Open menu" style="color: hsl(var(--foreground));">
<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">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
</div>
</nav>

View File

@ -0,0 +1,27 @@
<figure class="pastel-card deckle-edge p-8 max-w-xl mx-auto" data-block="pastel-dream:testimonial-soft">
{% if rating|integer > 0 %}<div class="flex gap-1 mb-4" aria-label="Rating">
{% if rating|integer >= 1 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>{% endif %}{% if rating|integer >= 2 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>{% endif %}{% if rating|integer >= 3 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>{% endif %}{% if rating|integer >= 4 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>{% endif %}{% if rating|integer >= 5 %}<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>{% endif %}
</div>{% endif %}
<blockquote class="font-body text-lg leading-relaxed" style="color: hsl(var(--card-foreground));">
{{ quote|safe }}
</blockquote>
{% if name or role %}<figcaption class="flex items-center gap-4 mt-6 pt-6 border-t" style="border-color: hsl(var(--border));">
{% if avatar %}<img src="{{ avatar }}" alt="{{ name }}" class="w-12 h-12 rounded-full object-cover">{% elif name %}<span class="w-12 h-12 rounded-full flex items-center justify-center font-display text-lg" style="background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground));">
{% for w in name|split:" " %}{% if w and forloop.Counter <= 2 %}{{ w|first|upper }}{% endif %}{% endfor %}
</span>{% endif %}
<div>
{% if name %}<p class="font-display text-xl leading-tight" style="color: hsl(var(--foreground));">{{ name }}</p>{% endif %}
{% if role %}<p class="font-body text-sm" style="color: hsl(var(--muted-foreground));">{{ role }}</p>{% endif %}
</div>
</figcaption>{% endif %}
</figure>

View File

@ -0,0 +1,35 @@
<section class="relative overflow-hidden py-20 md:py-28 bg-watercolor-blush" data-block="pastel-dream:watercolor-hero">
<svg class="absolute -top-12 -left-12 w-[28rem] h-[28rem] -z-0 animate-breathe" viewBox="0 0 400 400" aria-hidden="true">
<path d="M200,40 C290,40 360,120 360,200 C360,290 280,360 200,360 C110,360 40,280 40,200 C40,110 110,40 200,40 Z" fill="hsl(var(--primary) / 0.22)"></path>
</svg>
<svg class="absolute -bottom-16 -right-8 w-[24rem] h-[24rem] -z-0 animate-breathe" viewBox="0 0 400 400" aria-hidden="true">
<path d="M120,60 C200,40 320,80 360,180 C400,280 320,360 220,360 C120,360 40,300 40,220 C40,160 60,80 120,60 Z" fill="hsl(var(--accent) / 0.30)"></path>
</svg>
<div class="relative z-10 max-w-6xl mx-auto px-4 grid md:grid-cols-2 gap-12 items-center">
<div>
{% if eyebrow %}<p class="font-mono text-xs uppercase tracking-[0.2em] mb-4" style="color: hsl(var(--muted-foreground));">
{{ eyebrow }}
</p>{% endif %}
<h1 class="font-display brush-underline text-5xl md:text-6xl leading-tight mb-6" style="color: hsl(var(--foreground));">
{{ headline|default:"Soft starts"|safe }}
</h1>
{% if body %}<div class="font-body text-lg max-w-prose mb-8" style="color: hsl(var(--foreground) / 0.78);">
{{ body|safe }}
</div>{% endif %}
{% if ctaText %}<a href="{{ ctaHref|default:"#" }}" class="pastel-pill text-base animate-breathe">
{{ ctaText }}
</a>{% endif %}
</div>
{% if image %}<div class="relative">
<div class="pastel-card overflow-hidden">
<img src="{{ image }}" alt="" class="w-full h-auto block">
</div>
</div>{% else %}<div class="relative h-64 md:h-80" aria-hidden="true">
<svg viewBox="0 0 300 240" class="w-full h-full">
<ellipse cx="150" cy="120" rx="110" ry="80" fill="hsl(var(--secondary) / 0.55)"></ellipse>
<ellipse cx="120" cy="100" rx="70" ry="50" fill="hsl(var(--accent) / 0.45)"></ellipse>
<ellipse cx="180" cy="140" rx="60" ry="44" fill="hsl(var(--primary) / 0.40)"></ellipse>
</svg>
</div>{% endif %}
</div>
</section>

View File

@ -1,28 +0,0 @@
package main
import (
"bytes"
"context"
)
// PastelButtonBlock renders buttons as soft pills (9999px radius, tinted
// shadow, 2px hover lift, cubic-bezier(.22,1,.36,1) easing).
// Content shape: {text, href, variant}
func PastelButtonBlock(ctx context.Context, content map[string]any) string {
data := PastelButtonData{
Text: getStringOr(content, "text", "Continue"),
Href: safeLink(getString(content, "href")),
Variant: getStringOr(content, "variant", "primary"),
}
var buf bytes.Buffer
_ = pastelButtonComponent(data).Render(ctx, &buf)
return buf.String()
}
// PastelButtonData drives the button override.
type PastelButtonData struct {
Text string
Href string
Variant string
}

View File

@ -1,8 +0,0 @@
package main
// pastelButtonComponent renders the pill button override.
templ pastelButtonComponent(data PastelButtonData) {
<a href={ templ.SafeURL(data.Href) } class="pastel-pill" data-block-type="button" data-variant={ data.Variant }>
{ data.Text }
</a>
}

View File

@ -1,80 +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"
// pastelButtonComponent renders the pill button override.
func pastelButtonComponent(data PastelButtonData) 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, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.Href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 5, Col: 35}
}
_, 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, "\" class=\"pastel-pill\" data-block-type=\"button\" data-variant=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Variant)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 5, Col: 110}
}
_, 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, 3, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 6, Col: 13}
}
_, 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, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,29 +0,0 @@
package main
import (
"bytes"
"context"
)
// PastelCardBlock renders cards with the soft 20px radius, blush-tinted
// shadow, and optional watercolor border. The body field accepts pre-rendered
// HTML so other blocks can be composed inside.
// Content shape: {title, body, footer}
func PastelCardBlock(ctx context.Context, content map[string]any) string {
data := PastelCardData{
Title: getString(content, "title"),
Body: getString(content, "body"),
Footer: getString(content, "footer"),
}
var buf bytes.Buffer
_ = pastelCardComponent(data).Render(ctx, &buf)
return buf.String()
}
// PastelCardData drives the card override.
type PastelCardData struct {
Title string
Body string
Footer string
}

View File

@ -1,20 +0,0 @@
package main
// pastelCardComponent renders the soft pastel card.
templ pastelCardComponent(data PastelCardData) {
<div class="pastel-card p-6" data-block-type="card" style="border-radius: 20px;">
if data.Title != "" {
<h3 class="font-display text-2xl mb-3" style="color: hsl(var(--card-foreground));">{ data.Title }</h3>
}
if data.Body != "" {
<div class="font-body" style="color: hsl(var(--card-foreground) / 0.85); line-height: 1.75;">
@templ.Raw(data.Body)
</div>
}
if data.Footer != "" {
<div class="mt-6 pt-4 border-t font-body text-sm" style="border-color: hsl(var(--border)); color: hsl(var(--muted-foreground));">
@templ.Raw(data.Footer)
</div>
}
</div>
}

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"
// pastelCardComponent renders the soft pastel card.
func pastelCardComponent(data PastelCardData) 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=\"pastel-card p-6\" data-block-type=\"card\" style=\"border-radius: 20px;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<h3 class=\"font-display text-2xl mb-3\" style=\"color: hsl(var(--card-foreground));\">")
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: `card_override.templ`, Line: 7, Col: 98}
}
_, 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, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Body != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"font-body\" style=\"color: hsl(var(--card-foreground) / 0.85); line-height: 1.75;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Body).Render(ctx, templ_7745c5c3_Buffer)
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
}
}
if data.Footer != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"mt-6 pt-4 border-t font-body text-sm\" style=\"border-color: hsl(var(--border)); color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Footer).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
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,40 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// CozyFooterMeta defines the footer block.
var CozyFooterMeta = blocks.BlockMeta{
Key: "cozy-footer",
Title: "Cozy Footer",
Description: "Newsletter signup, closing affirmation, menu column, and social pills.",
Source: "pastel-dream",
Category: blocks.CategoryNavigation,
}
// CozyFooterBlock renders the footer.
// Content shape: {showSignup, affirmation, menuName, social[]}
func CozyFooterBlock(ctx context.Context, content map[string]any) string {
data := CozyFooterData{
ShowSignup: getBoolish(content, "showSignup", true),
Affirmation: getStringOr(content, "affirmation", "Be gentle with yourself today."),
MenuName: getString(content, "menuName"),
Social: getStringSlice(content, "social"),
}
var buf bytes.Buffer
_ = cozyFooterComponent(data).Render(ctx, &buf)
return buf.String()
}
// CozyFooterData is the rendered view of the cozy-footer block.
type CozyFooterData struct {
ShowSignup bool
Affirmation string
MenuName string
Social []string
}

View File

@ -1,42 +0,0 @@
package main
// cozyFooterComponent renders the cozy footer with newsletter signup,
// a closing affirmation, an optional menu column, and social pill links.
templ cozyFooterComponent(data CozyFooterData) {
<section class="relative py-16 md:py-20 overflow-hidden bg-watercolor-mint" data-block="pastel-dream:cozy-footer">
// Decorative watercolor blob, top right
<svg class="absolute -top-8 -right-8 w-64 h-64 -z-0" viewBox="0 0 300 300" aria-hidden="true">
<path d="M150,30 C220,30 270,90 270,150 C270,220 200,270 140,270 C80,270 30,210 30,150 C30,90 80,30 150,30 Z" fill="hsl(var(--primary) / 0.16)"></path>
</svg>
<div class="relative z-10 max-w-4xl mx-auto px-4 text-center">
<p class="font-display text-3xl mb-6" style="color: hsl(var(--foreground));">
{ data.Affirmation }
</p>
if data.ShowSignup {
<form class="flex flex-col sm:flex-row gap-3 max-w-md mx-auto mb-8" data-pastel-signup="true" onsubmit="event.preventDefault();">
<label class="sr-only" for="pastel-newsletter-email">Email</label>
<input
id="pastel-newsletter-email"
type="email"
placeholder="you@calm.day"
class="flex-1 px-5 py-3 font-body rounded-full"
style="background-color: hsl(var(--input)); color: hsl(var(--foreground)); border: 1px solid hsl(var(--border));"
/>
<button type="submit" class="pastel-pill">Stay in touch</button>
</form>
}
if len(data.Social) > 0 {
<div class="flex justify-center gap-3 flex-wrap mt-6">
for _, href := range data.Social {
<a href={ templ.SafeURL(href) } class="px-4 py-2 font-body text-xs uppercase tracking-[0.2em] rounded-full" style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));">
Visit
</a>
}
</div>
}
<p class="font-mono text-xs mt-10" style="color: hsl(var(--muted-foreground));">
Pastel Dream &middot; with care
</p>
</div>
</section>
}

View File

@ -1,94 +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"
// cozyFooterComponent renders the cozy footer with newsletter signup,
// a closing affirmation, an optional menu column, and social pill links.
func cozyFooterComponent(data CozyFooterData) 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=\"relative py-16 md:py-20 overflow-hidden bg-watercolor-mint\" data-block=\"pastel-dream:cozy-footer\"><svg class=\"absolute -top-8 -right-8 w-64 h-64 -z-0\" viewBox=\"0 0 300 300\" aria-hidden=\"true\"><path d=\"M150,30 C220,30 270,90 270,150 C270,220 200,270 140,270 C80,270 30,210 30,150 C30,90 80,30 150,30 Z\" fill=\"hsl(var(--primary) / 0.16)\"></path></svg><div class=\"relative z-10 max-w-4xl mx-auto px-4 text-center\"><p class=\"font-display text-3xl mb-6\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Affirmation)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cozy_footer.templ`, Line: 13, Col: 22}
}
_, 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, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.ShowSignup {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<form class=\"flex flex-col sm:flex-row gap-3 max-w-md mx-auto mb-8\" data-pastel-signup=\"true\" onsubmit=\"event.preventDefault();\"><label class=\"sr-only\" for=\"pastel-newsletter-email\">Email</label> <input id=\"pastel-newsletter-email\" type=\"email\" placeholder=\"you@calm.day\" class=\"flex-1 px-5 py-3 font-body rounded-full\" style=\"background-color: hsl(var(--input)); color: hsl(var(--foreground)); border: 1px solid hsl(var(--border));\"> <button type=\"submit\" class=\"pastel-pill\">Stay in touch</button></form>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Social) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"flex justify-center gap-3 flex-wrap mt-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, href := range data.Social {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cozy_footer.templ`, Line: 31, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" class=\"px-4 py-2 font-body text-xs uppercase tracking-[0.2em] rounded-full\" style=\"background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));\">Visit</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<p class=\"font-mono text-xs mt-10\" style=\"color: hsl(var(--muted-foreground));\">Pastel Dream &middot; with care</p></div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,187 +0,0 @@
package main
import (
"bytes"
"context"
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// PastelEmailWrapper wraps body content in the Pastel Dream branded email
// frame: cream paper background, watercolor blob watermark, 560px centered
// frame, Caveat Brush masthead, Nunito body, mint CTA pill, unsubscribe and a
// closing affirmation in the footer.
func PastelEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = pastelEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}
// emailColorOr falls back to the supplied default when the EmailContext has
// not been populated with the preset color (e.g. for raw previews).
// All defaults pull from the blush-morning palette, expressed as hex because
// email clients cannot resolve CSS custom properties.
func emailColorOr(value, fallback string) string {
if value == "" {
return fallback
}
return value
}
func pastelEmailBg(c templates.EmailColors) string {
// blush-morning background: hsl(25 60% 98%) ≈ #fdf7f1
return emailColorOr(c.Background, "#fdf7f1")
}
func pastelEmailCard(c templates.EmailColors) string {
return emailColorOr(c.Card, "#ffffff")
}
func pastelEmailForeground(c templates.EmailColors) string {
// hsl(340 20% 22%) ≈ #432e36
return emailColorOr(c.Foreground, "#432e36")
}
func pastelEmailMutedFg(c templates.EmailColors) string {
// hsl(340 12% 48%) ≈ #80707a
return emailColorOr(c.MutedForeground, "#80707a")
}
func pastelEmailPrimary(c templates.EmailColors) string {
// hsl(350 65% 72%) ≈ #e9a1ad
return emailColorOr(c.Primary, "#e9a1ad")
}
func pastelEmailPrimaryFg(c templates.EmailColors) string {
return emailColorOr(c.PrimaryForeground, "#432e36")
}
func pastelEmailBorder(c templates.EmailColors) string {
return emailColorOr(c.Border, "#f0d8de")
}
// pastelEmailTemplate is the Pastel Dream email body template.
templ pastelEmailTemplate(emailCtx templates.EmailContext, body string) {
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="x-apple-disable-message-reformatting"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>{ emailCtx.SiteSettings.SiteName }</title>
<style type="text/css">
body, table, td, p, a, li {
-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;
}
.pastel-email-frame {
max-width: 560px;
margin: 0 auto;
}
@media only screen and (max-width: 600px) {
.pastel-email-frame {
width: 100% !important;
max-width: 100% !important;
}
.pastel-pad {
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', Helvetica, Arial, sans-serif;", pastelEmailBg(emailCtx.Colors)) }>
if emailCtx.PreviewText != "" {
<div style="display: none; max-height: 0; overflow: hidden;">
{ emailCtx.PreviewText }
</div>
}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("background-color: %s;", pastelEmailBg(emailCtx.Colors)) }>
<tr>
<td align="center" style="padding: 40px 12px; position: relative;">
<!-- Watermark blob (top-right). Inline SVG so any client that supports it renders the wash. -->
<div style="position: absolute; top: 0; right: 0; width: 200px; height: 200px; opacity: 0.35; pointer-events: none;">
<svg viewBox="0 0 200 200" width="200" height="200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d={ "M100,20 C160,20 180,80 180,120 C180,170 130,180 90,180 C40,180 20,140 20,100 C20,60 50,20 100,20 Z" } fill={ pastelEmailPrimary(emailCtx.Colors) }></path>
</svg>
</div>
<table role="presentation" class="pastel-email-frame" width="560" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("max-width: 560px; background-color: %s; border-radius: 20px; border: 1px solid %s;", pastelEmailCard(emailCtx.Colors), pastelEmailBorder(emailCtx.Colors)) }>
<!-- Masthead -->
<tr>
<td align="center" class="pastel-pad" style={ fmt.Sprintf("padding: 36px 40px 8px; border-bottom: 1px solid %s;", pastelEmailBorder(emailCtx.Colors)) }>
if emailCtx.SiteSettings.LogoURL != "" {
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="max-height: 56px; width: auto; display: block;"/>
} else if emailCtx.SiteSettings.SiteName != "" {
<h1 style={ fmt.Sprintf("margin: 0; font-family: 'Caveat Brush', 'Sacramento', cursive; font-size: 36px; font-weight: 400; color: %s;", pastelEmailForeground(emailCtx.Colors)) }>
{ emailCtx.SiteSettings.SiteName }
</h1>
}
</td>
</tr>
<!-- Body -->
<tr>
<td class="pastel-pad" style={ fmt.Sprintf("padding: 32px 40px 24px; color: %s; font-size: 16px; line-height: 1.75;", pastelEmailForeground(emailCtx.Colors)) }>
@templ.Raw(body)
</td>
</tr>
<!-- Footer -->
<tr>
<td class="pastel-pad" align="center" style={ fmt.Sprintf("padding: 24px 40px 36px; border-top: 1px solid %s;", pastelEmailBorder(emailCtx.Colors)) }>
<p style={ fmt.Sprintf("margin: 0 0 12px; font-family: 'Caveat Brush', 'Sacramento', cursive; font-size: 18px; color: %s;", pastelEmailMutedFg(emailCtx.Colors)) }>
Be gentle with yourself today.
</p>
if emailCtx.SiteSettings.SiteURL != "" {
<p style={ fmt.Sprintf("margin: 0 0 12px; font-size: 13px; color: %s;", pastelEmailMutedFg(emailCtx.Colors)) }>
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style={ fmt.Sprintf("color: %s; text-decoration: none;", pastelEmailPrimary(emailCtx.Colors)) }>
{ emailCtx.SiteSettings.SiteURL }
</a>
</p>
}
if emailCtx.UnsubscribeURL != "" {
<p style={ fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", pastelEmailMutedFg(emailCtx.Colors)) }>
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", pastelEmailMutedFg(emailCtx.Colors)) }>
Unsubscribe
</a>
</p>
}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
}
// pastelEmailPillStyle returns the inline CSS for the mint-tinted CTA pill
// that templates may use inside the body slot. The wrapper exposes it as a
// reusable token because the body is opaque pre-rendered HTML.
func pastelEmailPillStyle(c templates.EmailColors) string {
primary := pastelEmailPrimary(c)
primaryFg := pastelEmailPrimaryFg(c)
return fmt.Sprintf(
"display: inline-block; padding: 12px 28px; border-radius: 9999px; background-color: %s; color: %s; font-family: 'Nunito', Helvetica, Arial, sans-serif; font-weight: 600; text-decoration: none;",
primary,
primaryFg,
)
}

View File

@ -1,450 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"bytes"
"context"
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// PastelEmailWrapper wraps body content in the Pastel Dream branded email
// frame: cream paper background, watercolor blob watermark, 560px centered
// frame, Caveat Brush masthead, Nunito body, mint CTA pill, unsubscribe and a
// closing affirmation in the footer.
func PastelEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = pastelEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}
// emailColorOr falls back to the supplied default when the EmailContext has
// not been populated with the preset color (e.g. for raw previews).
// All defaults pull from the blush-morning palette, expressed as hex because
// email clients cannot resolve CSS custom properties.
func emailColorOr(value, fallback string) string {
if value == "" {
return fallback
}
return value
}
func pastelEmailBg(c templates.EmailColors) string {
// blush-morning background: hsl(25 60% 98%) ≈ #fdf7f1
return emailColorOr(c.Background, "#fdf7f1")
}
func pastelEmailCard(c templates.EmailColors) string {
return emailColorOr(c.Card, "#ffffff")
}
func pastelEmailForeground(c templates.EmailColors) string {
// hsl(340 20% 22%) ≈ #432e36
return emailColorOr(c.Foreground, "#432e36")
}
func pastelEmailMutedFg(c templates.EmailColors) string {
// hsl(340 12% 48%) ≈ #80707a
return emailColorOr(c.MutedForeground, "#80707a")
}
func pastelEmailPrimary(c templates.EmailColors) string {
// hsl(350 65% 72%) ≈ #e9a1ad
return emailColorOr(c.Primary, "#e9a1ad")
}
func pastelEmailPrimaryFg(c templates.EmailColors) string {
return emailColorOr(c.PrimaryForeground, "#432e36")
}
func pastelEmailBorder(c templates.EmailColors) string {
return emailColorOr(c.Border, "#f0d8de")
}
// pastelEmailTemplate is the Pastel Dream email body template.
func pastelEmailTemplate(emailCtx templates.EmailContext, body string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"x-apple-disable-message-reformatting\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 73, Col: 41}
}
_, 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\tbody, table, td, p, a, li {\n\t\t\t\t-webkit-text-size-adjust: 100%;\n\t\t\t\t-ms-text-size-adjust: 100%;\n\t\t\t}\n\t\t\ttable, td {\n\t\t\t\tmso-table-lspace: 0pt;\n\t\t\t\tmso-table-rspace: 0pt;\n\t\t\t}\n\t\t\timg {\n\t\t\t\t-ms-interpolation-mode: bicubic;\n\t\t\t\tborder: 0;\n\t\t\t\theight: auto;\n\t\t\t\tline-height: 100%;\n\t\t\t\toutline: none;\n\t\t\t\ttext-decoration: none;\n\t\t\t}\n\t\t\tbody {\n\t\t\t\tmargin: 0 !important;\n\t\t\t\tpadding: 0 !important;\n\t\t\t\twidth: 100% !important;\n\t\t\t}\n\t\t\t.pastel-email-frame {\n\t\t\t\tmax-width: 560px;\n\t\t\t\tmargin: 0 auto;\n\t\t\t}\n\t\t\t@media only screen and (max-width: 600px) {\n\t\t\t\t.pastel-email-frame {\n\t\t\t\t\twidth: 100% !important;\n\t\t\t\t\tmax-width: 100% !important;\n\t\t\t\t}\n\t\t\t\t.pastel-pad {\n\t\t\t\t\tpadding-left: 24px !important;\n\t\t\t\t\tpadding-right: 24px !important;\n\t\t\t\t}\n\t\t\t}\n\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', Helvetica, Arial, sans-serif;", pastelEmailBg(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 112, Col: 172}
}
_, 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;\">")
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: 115, Col: 26}
}
_, 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\" 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("background-color: %s;", pastelEmailBg(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 118, Col: 161}
}
_, 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, "\"><tr><td align=\"center\" style=\"padding: 40px 12px; position: relative;\"><!-- Watermark blob (top-right). Inline SVG so any client that supports it renders the wash. --><div style=\"position: absolute; top: 0; right: 0; width: 200px; height: 200px; opacity: 0.35; pointer-events: none;\"><svg viewBox=\"0 0 200 200\" width=\"200\" height=\"200\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\"><path d=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue("M100,20 C160,20 180,80 180,120 C180,170 130,180 90,180 C40,180 20,140 20,100 C20,60 50,20 100,20 Z")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 124, Col: 117}
}
_, 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, 8, "\" fill=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(pastelEmailPrimary(emailCtx.Colors))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 124, Col: 162}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var7)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"></path></svg></div><table role=\"presentation\" class=\"pastel-email-frame\" width=\"560\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("max-width: 560px; background-color: %s; border-radius: 20px; border: 1px solid %s;", pastelEmailCard(emailCtx.Colors), pastelEmailBorder(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 127, Col: 289}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"><!-- Masthead --><tr><td align=\"center\" class=\"pastel-pad\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 36px 40px 8px; border-bottom: 1px solid %s;", pastelEmailBorder(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 130, Col: 156}
}
_, 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, 11, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.LogoURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, 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: 132, Col: 49}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var10)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, 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: 132, Col: 88}
}
_, 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, 14, "\" style=\"max-height: 56px; 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, 15, "<h1 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("margin: 0; font-family: 'Caveat Brush', 'Sacramento', cursive; font-size: 36px; font-weight: 400; color: %s;", pastelEmailForeground(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 134, Col: 184}
}
_, 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, 16, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, 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: 135, Col: 42}
}
_, 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, 17, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</td></tr><!-- Body --><tr><td class=\"pastel-pad\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 32px 40px 24px; color: %s; font-size: 16px; line-height: 1.75;", pastelEmailForeground(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 142, Col: 164}
}
_, 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, 19, "\">")
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, 20, "</td></tr><!-- Footer --><tr><td class=\"pastel-pad\" align=\"center\" 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("padding: 24px 40px 36px; border-top: 1px solid %s;", pastelEmailBorder(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 148, Col: 154}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\"><p style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0 0 12px; font-family: 'Caveat Brush', 'Sacramento', cursive; font-size: 18px; color: %s;", pastelEmailMutedFg(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 149, Col: 168}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">Be gentle with yourself today.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.SiteURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<p 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("margin: 0 0 12px; font-size: 13px; color: %s;", pastelEmailMutedFg(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 153, Col: 117}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 templ.SafeURL
templ_7745c5c3_Var18, 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: 154, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: none;", pastelEmailPrimary(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 154, Col: 160}
}
_, 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, 26, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 155, Col: 42}
}
_, 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, 27, "</a></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if emailCtx.UnsubscribeURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<p style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", pastelEmailMutedFg(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 160, Col: 110}
}
_, 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, 29, "\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 templ.SafeURL
templ_7745c5c3_Var22, 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: 161, Col: 58}
}
_, 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, 30, "\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: underline;", pastelEmailMutedFg(emailCtx.Colors)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 161, Col: 159}
}
_, 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, 31, "\">Unsubscribe</a></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</td></tr></table></td></tr></table></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// pastelEmailPillStyle returns the inline CSS for the mint-tinted CTA pill
// that templates may use inside the body slot. The wrapper exposes it as a
// reusable token because the body is opaque pre-rendered HTML.
func pastelEmailPillStyle(c templates.EmailColors) string {
primary := pastelEmailPrimary(c)
primaryFg := pastelEmailPrimaryFg(c)
return fmt.Sprintf(
"display: inline-block; padding: 12px 28px; border-radius: 9999px; background-color: %s; color: %s; font-family: 'Nunito', Helvetica, Arial, sans-serif; font-weight: 600; text-decoration: none;",
primary,
primaryFg,
)
}
var _ = templruntime.GeneratedTemplate

View File

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

View File

@ -1,53 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// FeatureGridSoftMeta defines the three-up soft feature grid.
var FeatureGridSoftMeta = blocks.BlockMeta{
Key: "feature-grid-soft",
Title: "Feature Trio",
Description: "Three-up card grid for services, offerings, or core values.",
Source: "pastel-dream",
Category: blocks.CategoryLayout,
}
// FeatureGridSoftBlock renders the feature grid.
// Content shape: {intro, items: [{icon, title, body}]}
func FeatureGridSoftBlock(ctx context.Context, content map[string]any) string {
rawItems := getSlice(content, "items")
items := make([]FeatureSoftItem, 0, len(rawItems))
for _, it := range rawItems {
items = append(items, FeatureSoftItem{
Icon: getString(it, "icon"),
Title: getString(it, "title"),
Body: getString(it, "body"),
})
}
data := FeatureGridSoftData{
Intro: getString(content, "intro"),
Items: items,
}
var buf bytes.Buffer
_ = featureGridSoftComponent(data).Render(ctx, &buf)
return buf.String()
}
// FeatureGridSoftData is the rendered view of the feature-grid-soft block.
type FeatureGridSoftData struct {
Intro string
Items []FeatureSoftItem
}
// FeatureSoftItem is a single card in the trio.
type FeatureSoftItem struct {
Icon string
Title string
Body string
}

View File

@ -1,40 +0,0 @@
package main
// featureGridSoftComponent renders the three-up grid. It reflows to 1 column
// at 360px and 2-3 columns at 768px+, satisfying UAT §7.
templ featureGridSoftComponent(data FeatureGridSoftData) {
<section class="py-16 md:py-20" data-block="pastel-dream:feature-grid-soft">
<div class="max-w-6xl mx-auto px-4">
if data.Intro != "" {
<div class="font-body text-center max-w-2xl mx-auto mb-12 text-lg" style="color: hsl(var(--foreground) / 0.78);">
@templ.Raw(data.Intro)
</div>
}
if len(data.Items) > 0 {
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
for _, item := range data.Items {
<article class="pastel-card p-8 transition-transform" style="transition-timing-function: cubic-bezier(.22,1,.36,1); transition-duration: 240ms;">
if item.Icon != "" {
<div class="mb-4">
<img src={ item.Icon } alt="" class="w-12 h-12 object-contain"/>
</div>
} else {
<div class="mb-4 w-12 h-12 rounded-full" style="background-color: hsl(var(--accent) / 0.5);" aria-hidden="true"></div>
}
if item.Title != "" {
<h3 class="font-display text-2xl mb-3" style="color: hsl(var(--foreground));">{ item.Title }</h3>
}
if item.Body != "" {
<p class="font-body text-base" style="color: hsl(var(--foreground) / 0.78); line-height: 1.75;">{ item.Body }</p>
}
</article>
}
</div>
} else {
<div class="text-center font-body text-base" style="color: hsl(var(--muted-foreground));">
<p>Add up to three cards to the trio.</p>
</div>
}
</div>
</section>
}

View File

@ -1,147 +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"
// featureGridSoftComponent renders the three-up grid. It reflows to 1 column
// at 360px and 2-3 columns at 768px+, satisfying UAT §7.
func featureGridSoftComponent(data FeatureGridSoftData) 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=\"py-16 md:py-20\" data-block=\"pastel-dream:feature-grid-soft\"><div class=\"max-w-6xl mx-auto px-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Intro != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"font-body text-center max-w-2xl mx-auto mb-12 text-lg\" style=\"color: hsl(var(--foreground) / 0.78);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Intro).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Items) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, item := range data.Items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<article class=\"pastel-card p-8 transition-transform\" style=\"transition-timing-function: cubic-bezier(.22,1,.36,1); transition-duration: 240ms;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.Icon != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"mb-4\"><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(item.Icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `feature_grid_soft.templ`, Line: 19, Col: 29}
}
_, 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, 7, "\" alt=\"\" class=\"w-12 h-12 object-contain\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"mb-4 w-12 h-12 rounded-full\" style=\"background-color: hsl(var(--accent) / 0.5);\" aria-hidden=\"true\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if item.Title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<h3 class=\"font-display text-2xl mb-3\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(item.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `feature_grid_soft.templ`, Line: 25, Col: 98}
}
_, 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, 10, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if item.Body != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<p class=\"font-body text-base\" style=\"color: hsl(var(--foreground) / 0.78); line-height: 1.75;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(item.Body)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `feature_grid_soft.templ`, Line: 28, Col: 115}
}
_, 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, 12, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"text-center font-body text-base\" style=\"color: hsl(var(--muted-foreground));\"><p>Add up to three cards to the trio.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

20
go.mod
View File

@ -1,20 +0,0 @@
module git.dev.alexdunmow.com/block/themes/pastel-dream
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,42 +0,0 @@
package main
import (
"bytes"
"context"
"strconv"
)
// PastelHeadingBlock renders headings with pastel-dream styling.
// Caveat Brush display at H1/H2; Nunito for H3+; soft brush-stroke underline.
// Content shape: {text, level: 1-6, textClass}
func PastelHeadingBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
textClass := getString(content, "textClass")
level := parseHeadingLevel(content)
var buf bytes.Buffer
_ = pastelHeadingComponent(level, text, textClass).Render(ctx, &buf)
return buf.String()
}
// parseHeadingLevel reads level from various JSON-compatible types and clamps
// the result to 1..6, 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
}

View File

@ -1,42 +0,0 @@
package main
// pastelHeadingBaseClass returns the Tailwind size/weight class for a level.
// H1 and H2 get the display face; H3+ uses the body face.
func pastelHeadingBaseClass(level int) string {
switch level {
case 1:
return "font-display text-5xl md:text-6xl leading-tight brush-underline"
case 2:
return "font-display text-4xl md:text-5xl leading-tight brush-underline"
case 3:
return "font-body text-3xl font-semibold tracking-tight"
case 4:
return "font-body text-2xl font-semibold"
case 5:
return "font-body text-xl font-medium"
case 6:
return "font-body text-lg font-medium"
default:
return "font-body text-3xl font-semibold tracking-tight"
}
}
// pastelHeadingComponent renders a heading with pastel-dream styling.
templ pastelHeadingComponent(level int, text, textClass string) {
switch level {
case 1:
<h1 class={ pastelHeadingBaseClass(1), textClass } style="color: hsl(var(--foreground));">{ text }</h1>
case 2:
<h2 class={ pastelHeadingBaseClass(2), textClass } style="color: hsl(var(--foreground));">{ text }</h2>
case 3:
<h3 class={ pastelHeadingBaseClass(3), textClass } style="color: hsl(var(--foreground));">{ text }</h3>
case 4:
<h4 class={ pastelHeadingBaseClass(4), textClass } style="color: hsl(var(--foreground));">{ text }</h4>
case 5:
<h5 class={ pastelHeadingBaseClass(5), textClass } style="color: hsl(var(--foreground));">{ text }</h5>
case 6:
<h6 class={ pastelHeadingBaseClass(6), textClass } style="color: hsl(var(--foreground));">{ text }</h6>
default:
<h2 class={ pastelHeadingBaseClass(2), textClass } style="color: hsl(var(--foreground));">{ text }</h2>
}
}

View File

@ -1,312 +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"
// pastelHeadingBaseClass returns the Tailwind size/weight class for a level.
// H1 and H2 get the display face; H3+ uses the body face.
func pastelHeadingBaseClass(level int) string {
switch level {
case 1:
return "font-display text-5xl md:text-6xl leading-tight brush-underline"
case 2:
return "font-display text-4xl md:text-5xl leading-tight brush-underline"
case 3:
return "font-body text-3xl font-semibold tracking-tight"
case 4:
return "font-body text-2xl font-semibold"
case 5:
return "font-body text-xl font-medium"
case 6:
return "font-body text-lg font-medium"
default:
return "font-body text-3xl font-semibold tracking-tight"
}
}
// pastelHeadingComponent renders a heading with pastel-dream styling.
func pastelHeadingComponent(level int, text, textClass string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch level {
case 1:
var templ_7745c5c3_Var2 = []any{pastelHeadingBaseClass(1), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 28, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 2:
var templ_7745c5c3_Var5 = []any{pastelHeadingBaseClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var5).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 30, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 3:
var templ_7745c5c3_Var8 = []any{pastelHeadingBaseClass(3), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h3 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var8).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 32, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 4:
var templ_7745c5c3_Var11 = []any{pastelHeadingBaseClass(4), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<h4 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var11).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 34, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</h4>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 5:
var templ_7745c5c3_Var14 = []any{pastelHeadingBaseClass(5), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h5 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var14).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 36, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h5>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 6:
var templ_7745c5c3_Var17 = []any{pastelHeadingBaseClass(6), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h6 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 38, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</h6>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
var templ_7745c5c3_Var20 = []any{pastelHeadingBaseClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var20).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 40, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,82 +0,0 @@
package main
// getString extracts a string value from a content map.
// Returns "" when the key is missing or holds a non-string value.
func getString(content map[string]any, key string) string {
if v, ok := content[key].(string); ok {
return v
}
return ""
}
// getStringOr extracts a string with a fallback for empty/missing values.
func getStringOr(content map[string]any, key, fallback string) string {
if v := getString(content, key); v != "" {
return v
}
return fallback
}
// getFloat extracts a float from content.
func getFloat(content map[string]any, key string, defaultVal float64) float64 {
if v, ok := content[key].(float64); ok {
return v
}
if v, ok := content[key].(int); ok {
return float64(v)
}
return defaultVal
}
// getBoolish reads "yes"/"no" select fields or actual booleans into a bool.
func getBoolish(content map[string]any, key string, defaultVal bool) bool {
if v, ok := content[key].(bool); ok {
return v
}
if v, ok := content[key].(string); ok {
switch v {
case "yes", "true", "1", "on":
return true
case "no", "false", "0", "off":
return false
}
}
return defaultVal
}
// getSlice extracts a slice of maps from a content map (collections/arrays
// of objects).
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
}
// getStringSlice extracts a slice of strings (e.g. social URLs).
func getStringSlice(content map[string]any, key string) []string {
if v, ok := content[key].([]any); ok {
result := make([]string, 0, len(v))
for _, item := range v {
if s, ok := item.(string); ok && s != "" {
result = append(result, s)
}
}
return result
}
return nil
}
// safeLink returns href if non-empty, otherwise "#".
func safeLink(href string) string {
if href == "" {
return "#"
}
return href
}

17
main.go
View File

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

231
manifest.yaml Normal file
View File

@ -0,0 +1,231 @@
# Pastel Dream theme — codeless .bnp manifest (WO-WZ-021 Phase B1).
# 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: pastel-dream
title: Pastel Dream
description: "Soft watercolor theme with blush, mint, and butter palettes for wellness, parenting, and spa brands."
page_templates:
- system: pastel-dream
key: default
title: Default
description: "Soft layered hero, masthead nav, footer with newsletter."
slots: [header, main, footer]
- system: pastel-dream
key: landing
title: Soft Landing
description: "Watercolor hero, affirmation strip, CTA, footer."
slots: [hero, main, cta, footer]
- system: pastel-dream
key: article
title: Reading Room
description: "Narrow, centered editorial column with side rail."
slots: [header, main, footer]
- system: pastel-dream
key: full-width
title: Open Air
description: "Edge-to-edge sections for galleries and seasonal looks."
slots: [header, main, footer]
template_overrides:
- { template: pastel-dream, block: heading }
- { template: pastel-dream, block: text }
- { template: pastel-dream, block: button }
- { template: pastel-dream, block: card }
email_wrappers:
- pastel-dream
css:
input_css_append: |
/* Pastel Dream — theme-specific CSS appended to host Tailwind input.
*
* Rules:
* - All colors must consume tokens via hsl(var(--token)). No hardcoded hex/rgb/named colors.
* - All font-family declarations must consume var(--font-*) with a graceful fallback stack.
* - Animations must be guarded by prefers-reduced-motion: reduce.
*/
:root {
--radius-soft: 20px;
}
/* --- Watercolor utility classes ---------------------------------------- */
/* Soft radial wash backgrounds, tinted from the active palette via primary
* and accent tokens. Used as decorative layers behind hero and footer.
*/
.bg-watercolor-blush {
background-image:
radial-gradient(60% 70% at 20% 30%, hsl(var(--primary) / 0.18) 0%, transparent 65%),
radial-gradient(50% 60% at 80% 70%, hsl(var(--accent) / 0.15) 0%, transparent 60%);
}
.bg-watercolor-mint {
background-image:
radial-gradient(60% 70% at 25% 35%, hsl(var(--secondary) / 0.45) 0%, transparent 65%),
radial-gradient(55% 65% at 75% 65%, hsl(var(--primary) / 0.12) 0%, transparent 60%);
}
/* --- Soft shadow utilities -------------------------------------------- */
/* Shadows are large, low-alpha, and tinted toward primary rather than grey. */
.shadow-soft {
box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18),
0 4px 12px -2px hsl(var(--primary) / 0.10);
}
.shadow-soft-hover {
box-shadow: 0 16px 48px -8px hsl(var(--primary) / 0.24),
0 6px 16px -2px hsl(var(--primary) / 0.14);
}
/* --- Deckle edge ------------------------------------------------------- */
/* Hand-torn paper effect for testimonial cards. Uses mask-image so it
* renders against whatever background lies beneath.
*/
.deckle-edge {
-webkit-mask-image:
radial-gradient(circle at 6px 6px, transparent 4px, hsl(var(--card)) 4.5px),
linear-gradient(to bottom, hsl(var(--card)) 0%, hsl(var(--card)) 100%);
mask-image:
radial-gradient(circle at 6px 6px, transparent 4px, hsl(var(--card)) 4.5px),
linear-gradient(to bottom, hsl(var(--card)) 0%, hsl(var(--card)) 100%);
-webkit-mask-composite: source-over;
mask-composite: add;
border: 1px dashed hsl(var(--border));
}
/* --- Pill button override --------------------------------------------- */
/* Spec §"Block overrides": every button is pill (9999px), tinted shadow,
* 2px hover lift, easing cubic-bezier(.22,1,.36,1).
*/
.pastel-pill {
border-radius: 9999px;
padding: 0.75rem 1.75rem;
font-family: var(--font-body, "Nunito", "Quicksand", system-ui, sans-serif);
font-weight: 600;
background-color: hsl(var(--primary));
color: hsl(var(--primary-foreground));
box-shadow: 0 8px 24px -6px hsl(var(--primary) / 0.40);
transition: transform 240ms cubic-bezier(.22,1,.36,1),
box-shadow 240ms cubic-bezier(.22,1,.36,1),
background-color 240ms cubic-bezier(.22,1,.36,1);
border: none;
cursor: pointer;
display: inline-block;
}
.pastel-pill:hover {
transform: translateY(-2px);
box-shadow: 0 12px 32px -6px hsl(var(--primary) / 0.50);
background-color: hsl(var(--primary) / 0.92);
}
.pastel-pill:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}
/* --- Soft card -------------------------------------------------------- */
.pastel-card {
border-radius: var(--radius-soft);
background-color: hsl(var(--card));
color: hsl(var(--card-foreground));
border: 1px solid hsl(var(--border));
box-shadow: 0 12px 40px -8px hsl(var(--primary) / 0.18),
0 4px 12px -2px hsl(var(--primary) / 0.10);
}
/* --- Brush-stroke underline for headings ------------------------------ */
.brush-underline {
position: relative;
display: inline-block;
}
.brush-underline::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: -0.2em;
height: 0.22em;
background-color: hsl(var(--accent) / 0.55);
border-radius: 9999px;
z-index: -1;
}
/* --- Typography fallbacks --------------------------------------------- */
/* Spec §"Visual language" pairs Caveat Brush + Nunito; admin can override
* via the font picker. Fallback stacks degrade gracefully.
*/
.font-display {
font-family: var(--font-heading, "Caveat Brush", "Sacramento", "Brush Script MT", cursive);
letter-spacing: 0.01em;
}
.font-body {
font-family: var(--font-body, "Nunito", "Quicksand", "Helvetica Neue", system-ui, sans-serif);
line-height: 1.75;
}
.font-mono {
font-family: var(--font-mono, "JetBrains Mono", "Fira Code", ui-monospace, monospace);
}
/* --- Animations ------------------------------------------------------- */
/* @keyframes pastel-shimmer — drift the accent overlay slowly across the
* affirmation strip. Spec §13.10: animation-duration >= 6s.
*/
@keyframes pastel-shimmer {
0% {
background-position: 0% 50%;
opacity: 0.85;
}
50% {
background-position: 100% 50%;
opacity: 1;
}
100% {
background-position: 0% 50%;
opacity: 0.85;
}
}
/* @keyframes breathe — gentle scale pulse, used by hero CTA and watercolor
* blobs for an in-and-out breath rhythm.
*/
@keyframes breathe {
0%, 100% {
transform: scale(1);
opacity: 0.85;
}
50% {
transform: scale(1.04);
opacity: 1;
}
}
.animate-pastel-shimmer {
background-size: 200% 200%;
animation: pastel-shimmer 8s ease-in-out infinite;
}
.animate-breathe {
animation: breathe 6s ease-in-out infinite;
}
/* --- Accessibility: respect prefers-reduced-motion -------------------- */
@media (prefers-reduced-motion: reduce) {
.animate-pastel-shimmer,
.animate-breathe,
.pastel-pill {
animation: none !important;
transition: none !important;
}
.pastel-pill:hover {
transform: none;
}
}

23
master_pages.json Normal file
View File

@ -0,0 +1,23 @@
[
{
"key": "pastel-dream:default-master",
"title": "Pastel Dream Default Master",
"page_templates": ["default", "article"],
"blocks": [
{ "block_key": "pastel-dream:soft-navbar", "title": "Soft Nav", "content": { "menuName": "main", "logo": "Pastel Dream" }, "slot": "header", "sort_order": 0 },
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main", "placeholder": "Page content" }, "slot": "main", "sort_order": 0 },
{ "block_key": "pastel-dream:cozy-footer", "title": "Cozy Footer", "content": { "showSignup": "yes", "affirmation": "Be gentle with yourself today." }, "slot": "footer", "sort_order": 0 }
]
},
{
"key": "pastel-dream:landing-master",
"title": "Pastel Dream Landing Master",
"page_templates": ["landing"],
"blocks": [
{ "block_key": "pastel-dream:watercolor-hero", "title": "Watercolor Hero", "content": { "eyebrow": "new", "headline": "Soft starts", "body": "A gentle place to begin.", "ctaText": "Begin" }, "slot": "hero", "sort_order": 0 },
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
{ "block_key": "pastel-dream:affirmation", "title": "Affirmation Strip", "content": { "quote": "You are doing enough.", "author": "Pastel Dream" }, "slot": "cta", "sort_order": 0 },
{ "block_key": "pastel-dream:cozy-footer", "title": "Cozy Footer", "content": { "showSignup": "yes" }, "slot": "footer", "sort_order": 0 }
]
}
]

View File

@ -2,11 +2,8 @@
name = "pastel-dream" name = "pastel-dream"
display_name = "Pastel Dream" display_name = "Pastel Dream"
scope = "@themes" scope = "@themes"
version = "0.1.2" version = "0.2.0"
description = "Soft watercolor theme with blush, mint, and butter palettes for wellness, parenting, and spa brands." description = "Soft watercolor theme with blush, mint, and butter palettes for wellness, parenting, and spa brands."
kind = "theme" kind = "theme"
categories = ["templates"] categories = ["templates"]
tags = ["pastel", "soft", "wellness", "parenting", "spa", "doula", "calm", "skincare", "mindfulness"] tags = ["pastel", "soft", "wellness", "parenting", "spa", "doula", "calm", "skincare", "mindfulness"]
[compatibility]
block_core = ">=0.15.0 <0.16.0"

View File

@ -1,187 +0,0 @@
package main
import (
"context"
"github.com/a-h/templ"
"git.dev.alexdunmow.com/block/core/blocks"
"git.dev.alexdunmow.com/block/core/plugin"
"git.dev.alexdunmow.com/block/core/templates"
)
// wrap adapts a templ-returning render function to templates.TemplateFunc.
// templ.Component already implements templates.HTMLComponent via 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 is the plugin entry point. It registers the system template, page
// templates, theme-owned blocks, built-in overrides, and the email wrapper.
//
// Order of operations matters: br.LoadSchemasFromFS MUST run before any
// br.Register call so schema binding sees the blocks as they register.
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
// System template registration.
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
Key: "pastel-dream",
Title: "Pastel Dream",
Description: "Soft watercolor theme with blush, mint, and butter palettes for wellness, parenting, and spa brands.",
})
// Page templates per spec §"Page templates".
if err := tr.RegisterPageTemplate("pastel-dream", templates.PageTemplateMeta{
Key: "default",
Title: "Default",
Description: "Soft layered hero, masthead nav, footer with newsletter.",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderPastelDefault)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("pastel-dream", templates.PageTemplateMeta{
Key: "landing",
Title: "Soft Landing",
Description: "Watercolor hero, affirmation strip, CTA, footer.",
Slots: []string{"hero", "main", "cta", "footer"},
}, wrap(RenderPastelLanding)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("pastel-dream", templates.PageTemplateMeta{
Key: "article",
Title: "Reading Room",
Description: "Narrow, centered editorial column with side rail.",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderPastelArticle)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("pastel-dream", templates.PageTemplateMeta{
Key: "full-width",
Title: "Open Air",
Description: "Edge-to-edge sections for galleries and seasonal looks.",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderPastelFullWidth)); err != nil {
return err
}
// Schemas MUST load before blocks register so editor metadata binds.
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
return err
}
// Theme-owned blocks.
br.Register(SoftNavbarMeta, SoftNavbarBlock)
br.Register(WatercolorHeroMeta, WatercolorHeroBlock)
br.Register(AffirmationMeta, AffirmationBlock)
br.Register(TestimonialSoftMeta, TestimonialSoftBlock)
br.Register(FeatureGridSoftMeta, FeatureGridSoftBlock)
br.Register(CozyFooterMeta, CozyFooterBlock)
// Overrides for built-ins, only when this theme is active.
br.RegisterTemplateOverride("pastel-dream", "heading", PastelHeadingBlock)
br.RegisterTemplateOverride("pastel-dream", "text", PastelTextBlock)
br.RegisterTemplateOverride("pastel-dream", "button", PastelButtonBlock)
br.RegisterTemplateOverride("pastel-dream", "card", PastelCardBlock)
// Email wrapper.
tr.RegisterEmailWrapper("pastel-dream", PastelEmailWrapper)
return nil
}
// DefaultMasterPages returns the master pages seeded on first plugin load.
// Two definitions per spec §"Master pages":
// - pastel-dream:default-master — used by default and article page templates
// - pastel-dream:landing-master — used by the landing page template
func DefaultMasterPages() []plugin.MasterPageDefinition {
return []plugin.MasterPageDefinition{
{
Key: "pastel-dream:default-master",
Title: "Pastel Dream Default Master",
PageTemplates: []string{"default", "article"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "pastel-dream:soft-navbar",
Title: "Soft Nav",
Content: map[string]any{
"menuName": "main",
"logo": "Pastel Dream",
},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{
"slotName": "main",
"placeholder": "Page content",
},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "pastel-dream:cozy-footer",
Title: "Cozy Footer",
Content: map[string]any{
"showSignup": "yes",
"affirmation": "Be gentle with yourself today.",
},
Slot: "footer",
SortOrder: 0,
},
},
},
{
Key: "pastel-dream:landing-master",
Title: "Pastel Dream Landing Master",
PageTemplates: []string{"landing"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "pastel-dream:watercolor-hero",
Title: "Watercolor Hero",
Content: map[string]any{
"eyebrow": "new",
"headline": "Soft starts",
"body": "A gentle place to begin.",
"ctaText": "Begin",
},
Slot: "hero",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{
"slotName": "main",
},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "pastel-dream:affirmation",
Title: "Affirmation Strip",
Content: map[string]any{
"quote": "You are doing enough.",
"author": "Pastel Dream",
},
Slot: "cta",
SortOrder: 0,
},
{
BlockKey: "pastel-dream:cozy-footer",
Title: "Cozy Footer",
Content: map[string]any{
"showSignup": "yes",
},
Slot: "footer",
SortOrder: 0,
},
},
},
}
}

View File

@ -1,38 +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 Pastel Dream theme.
var Registration = plugin.PluginRegistration{
Name: "pastel-dream",
Version: plugin.ParseModVersion(pluginModBytes),
Register: func(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
return Register(tr, br)
},
Assets: func() http.Handler { return AssetsHandler() },
Schemas: func() fs.FS { return Schemas() },
ThemePresets: func() []byte { return ThemePresets() },
BundledFonts: func() []byte { return BundledFonts() },
MasterPages: func() []plugin.MasterPageDefinition { return DefaultMasterPages() },
CSSManifest: func() *plugin.CSSManifest { return ThemeCSSManifest() },
}
// ThemeCSSManifest returns the Pastel Dream CSS to be appended to the host Tailwind input.
// This includes keyframes (pastel-shimmer, breathe), the --radius-soft variable,
// watercolor utility classes, font-family fallback stacks, and reduced-motion guards.
func ThemeCSSManifest() *plugin.CSSManifest {
css, err := assetsFS.ReadFile("assets/css/pastel-dream.css")
if err != nil {
return &plugin.CSSManifest{}
}
return &plugin.CSSManifest{
InputCSSAppend: string(css),
}
}

View File

@ -1,40 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// SoftNavbarMeta defines metadata for the rounded pill navbar.
var SoftNavbarMeta = blocks.BlockMeta{
Key: "soft-navbar",
Title: "Soft Navbar",
Description: "Rounded pill navigation with a watercolor blob behind the logo.",
Source: "pastel-dream",
Category: blocks.CategoryNavigation,
}
// SoftNavbarBlock renders the navbar.
// Content shape: {logo: string, menuName: string, ctaText: string, ctaHref: string}
func SoftNavbarBlock(ctx context.Context, content map[string]any) string {
data := SoftNavbarData{
Logo: getStringOr(content, "logo", "Pastel Dream"),
MenuName: getString(content, "menuName"),
CTAText: getString(content, "ctaText"),
CTAHref: safeLink(getString(content, "ctaHref")),
}
var buf bytes.Buffer
_ = softNavbarComponent(data).Render(ctx, &buf)
return buf.String()
}
// SoftNavbarData is the rendered view of the navbar block.
type SoftNavbarData struct {
Logo string
MenuName string
CTAText string
CTAHref string
}

View File

@ -1,31 +0,0 @@
package main
// softNavbarComponent renders a rounded pill navigation with watercolor wash
// behind the logo wordmark.
templ softNavbarComponent(data SoftNavbarData) {
<nav class="w-full px-4 py-4" data-block="pastel-dream:soft-navbar" data-menu={ data.MenuName }>
<div class="max-w-6xl mx-auto flex items-center justify-between gap-4 px-2 py-2 rounded-full bg-watercolor-blush" style="border-radius: 9999px;">
<a href="/" class="font-display text-2xl tracking-tight px-4 py-2 rounded-full" style="color: hsl(var(--primary-foreground)); background-color: hsl(var(--primary) / 0.15);">
{ data.Logo }
</a>
<div class="hidden md:flex items-center gap-6 font-body text-sm" data-pastel-menu={ data.MenuName }>
<a href="/" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">Home</a>
<a href="/about" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">About</a>
<a href="/journal" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">Journal</a>
<a href="/contact" style="color: hsl(var(--foreground));" class="hover:opacity-70 transition-opacity">Contact</a>
</div>
if data.CTAText != "" {
<a href={ templ.SafeURL(data.CTAHref) } class="pastel-pill text-sm">
{ data.CTAText }
</a>
}
<button class="md:hidden p-2 rounded-full" aria-label="Open menu" style="color: hsl(var(--foreground));">
<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">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
</div>
</nav>
}

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"
// softNavbarComponent renders a rounded pill navigation with watercolor wash
// behind the logo wordmark.
func softNavbarComponent(data SoftNavbarData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<nav class=\"w-full px-4 py-4\" data-block=\"pastel-dream:soft-navbar\" data-menu=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.MenuName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `soft_navbar.templ`, Line: 6, Col: 94}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var2)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><div class=\"max-w-6xl mx-auto flex items-center justify-between gap-4 px-2 py-2 rounded-full bg-watercolor-blush\" style=\"border-radius: 9999px;\"><a href=\"/\" class=\"font-display text-2xl tracking-tight px-4 py-2 rounded-full\" style=\"color: hsl(var(--primary-foreground)); background-color: hsl(var(--primary) / 0.15);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Logo)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `soft_navbar.templ`, Line: 9, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</a><div class=\"hidden md:flex items-center gap-6 font-body text-sm\" data-pastel-menu=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.MenuName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `soft_navbar.templ`, Line: 11, Col: 100}
}
_, 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, "\"><a href=\"/\" style=\"color: hsl(var(--foreground));\" class=\"hover:opacity-70 transition-opacity\">Home</a> <a href=\"/about\" style=\"color: hsl(var(--foreground));\" class=\"hover:opacity-70 transition-opacity\">About</a> <a href=\"/journal\" style=\"color: hsl(var(--foreground));\" class=\"hover:opacity-70 transition-opacity\">Journal</a> <a href=\"/contact\" style=\"color: hsl(var(--foreground));\" class=\"hover:opacity-70 transition-opacity\">Contact</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.CTAText != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<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(data.CTAHref))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `soft_navbar.templ`, Line: 18, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" class=\"pastel-pill text-sm\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.CTAText)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `soft_navbar.templ`, Line: 19, Col: 19}
}
_, 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, 7, "</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<button class=\"md:hidden p-2 rounded-full\" aria-label=\"Open menu\" style=\"color: hsl(var(--foreground));\"><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\"><line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\"></line> <line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\"></line> <line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\"></line></svg></button></div></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,252 +0,0 @@
package main
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PastelPageData carries the per-render page context.
type PastelPageData 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
}
// parsePastelPageData converts the wire-format doc map into a PastelPageData.
// It is lenient: missing fields fall back to sensible defaults so empty pages
// render without panicking.
func parsePastelPageData(doc map[string]any) PastelPageData {
title := "Untitled"
if t, ok := doc["title"].(string); ok && t != "" {
title = t
}
slots := make(map[string]string)
if s, ok := doc["slots"].(map[string]string); ok {
slots = s
}
themeCSS := ""
if tc, ok := doc["theme_css"].(string); ok {
themeCSS = tc
}
structuredData := ""
if sd, ok := doc["structured_data"].(string); ok {
structuredData = sd
}
cssHash := ""
if ch, ok := doc["css_hash"].(string); ok {
cssHash = ch
}
pageviewNonce := ""
if pn, ok := doc["pageview_nonce"].(string); ok {
pageviewNonce = pn
}
themeMode := "light"
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
themeMode = tm
}
return PastelPageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: bn.ParseSiteSettings(doc),
PageMeta: bn.ParsePageMeta(doc),
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: bn.ParseEngagementConfig(doc),
}
}
// PastelDefault is the standard pastel-dream page template — masthead, main
// column, and footer. Used by both `default` and (with a slightly wider main)
// the `full-width` template.
templ PastelDefault(data PastelPageData) {
<!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: []string{"/templates/pastel-dream/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full">
@templ.Raw(data.Slots["header"])
</header>
<main class="flex-grow w-full max-w-4xl mx-auto px-4 py-12">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="py-20 text-center font-body" style="color: hsl(var(--muted-foreground));">
<p>This page is still resting. Add a block to begin.</p>
</div>
}
</main>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// PastelLanding is the marketing landing layout — full-width hero, body,
// affirmation CTA, footer.
templ PastelLanding(data PastelPageData) {
<!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: []string{"/templates/pastel-dream/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<section class="w-full">
@templ.Raw(data.Slots["hero"])
</section>
<main class="flex-grow w-full">
if main, ok := data.Slots["main"]; ok && main != "" {
<div class="max-w-6xl mx-auto px-4 py-20">
@templ.Raw(main)
</div>
}
</main>
<section class="w-full">
@templ.Raw(data.Slots["cta"])
</section>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// PastelArticle is the narrow reading-room layout for editorial posts.
templ PastelArticle(data PastelPageData) {
<!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: []string{"/templates/pastel-dream/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full">
@templ.Raw(data.Slots["header"])
</header>
<main class="flex-grow w-full max-w-2xl mx-auto px-4 py-16">
if main, ok := data.Slots["main"]; ok && main != "" {
<article class="font-body prose max-w-none" style="line-height: 1.75; color: hsl(var(--foreground));">
@templ.Raw(main)
</article>
} else {
<div class="py-20 text-center" style="color: hsl(var(--muted-foreground));">
<p>This page is still resting. Add a block to begin.</p>
</div>
}
</main>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// PastelFullWidth is the edge-to-edge layout for galleries and seasonal looks.
templ PastelFullWidth(data PastelPageData) {
<!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: []string{"/templates/pastel-dream/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full">
@templ.Raw(data.Slots["header"])
</header>
<main class="flex-grow w-full">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="max-w-4xl mx-auto py-20 px-4 text-center" style="color: hsl(var(--muted-foreground));">
<p>This page is still resting. Add a block to begin.</p>
</div>
}
</main>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// RenderPastelDefault is the registered render entry for the default template.
func RenderPastelDefault(ctx context.Context, doc map[string]any) templ.Component {
return PastelDefault(parsePastelPageData(doc))
}
// RenderPastelLanding is the registered render entry for the landing template.
func RenderPastelLanding(ctx context.Context, doc map[string]any) templ.Component {
return PastelLanding(parsePastelPageData(doc))
}
// RenderPastelArticle is the registered render entry for the article template.
func RenderPastelArticle(ctx context.Context, doc map[string]any) templ.Component {
return PastelArticle(parsePastelPageData(doc))
}
// RenderPastelFullWidth is the registered render entry for the full-width template.
func RenderPastelFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return PastelFullWidth(parsePastelPageData(doc))
}

View File

@ -1,510 +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"
)
// PastelPageData carries the per-render page context.
type PastelPageData 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
}
// parsePastelPageData converts the wire-format doc map into a PastelPageData.
// It is lenient: missing fields fall back to sensible defaults so empty pages
// render without panicking.
func parsePastelPageData(doc map[string]any) PastelPageData {
title := "Untitled"
if t, ok := doc["title"].(string); ok && t != "" {
title = t
}
slots := make(map[string]string)
if s, ok := doc["slots"].(map[string]string); ok {
slots = s
}
themeCSS := ""
if tc, ok := doc["theme_css"].(string); ok {
themeCSS = tc
}
structuredData := ""
if sd, ok := doc["structured_data"].(string); ok {
structuredData = sd
}
cssHash := ""
if ch, ok := doc["css_hash"].(string); ok {
cssHash = ch
}
pageviewNonce := ""
if pn, ok := doc["pageview_nonce"].(string); ok {
pageviewNonce = pn
}
themeMode := "light"
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
themeMode = tm
}
return PastelPageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: bn.ParseSiteSettings(doc),
PageMeta: bn.ParsePageMeta(doc),
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: bn.ParseEngagementConfig(doc),
}
}
// PastelDefault is the standard pastel-dream page template — masthead, main
// column, and footer. Used by both `default` and (with a slightly wider main)
// the `full-width` template.
func PastelDefault(data PastelPageData) 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: []string{"/templates/pastel-dream/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<body class=\"font-body antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<header class=\"w-full\">")
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=\"flex-grow w-full max-w-4xl mx-auto px-4 py-12\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"py-20 text-center font-body\" style=\"color: hsl(var(--muted-foreground));\"><p>This page is still resting. Add a block to begin.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</main><footer class=\"w-full mt-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
})
}
// PastelLanding is the marketing landing layout — full-width hero, body,
// affirmation CTA, footer.
func PastelLanding(data PastelPageData) 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: []string{"/templates/pastel-dream/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<body class=\"font-body antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<section class=\"w-full\">")
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, 12, "</section><main class=\"flex-grow w-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"max-w-6xl mx-auto px-4 py-20\">")
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, 14, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</main><section class=\"w-full\">")
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, 16, "</section><footer class=\"w-full mt-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, 17, "</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, 18, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// PastelArticle is the narrow reading-room layout for editorial posts.
func PastelArticle(data PastelPageData) 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, 19, "<!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: []string{"/templates/pastel-dream/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<body class=\"font-body antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<header class=\"w-full\">")
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, 22, "</header><main class=\"flex-grow w-full max-w-2xl mx-auto px-4 py-16\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<article class=\"font-body prose max-w-none\" style=\"line-height: 1.75; color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"py-20 text-center\" style=\"color: hsl(var(--muted-foreground));\"><p>This page is still resting. Add a block to begin.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</main><footer class=\"w-full mt-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, 27, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// PastelFullWidth is the edge-to-edge layout for galleries and seasonal looks.
func PastelFullWidth(data PastelPageData) 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, 29, "<!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: []string{"/templates/pastel-dream/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<body class=\"font-body antialiased min-h-screen flex flex-col\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<header class=\"w-full\">")
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, 32, "</header><main class=\"flex-grow w-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div class=\"max-w-4xl mx-auto py-20 px-4 text-center\" style=\"color: hsl(var(--muted-foreground));\"><p>This page is still resting. Add a block to begin.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</main><footer class=\"w-full mt-auto\">")
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, 35, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RenderPastelDefault is the registered render entry for the default template.
func RenderPastelDefault(ctx context.Context, doc map[string]any) templ.Component {
return PastelDefault(parsePastelPageData(doc))
}
// RenderPastelLanding is the registered render entry for the landing template.
func RenderPastelLanding(ctx context.Context, doc map[string]any) templ.Component {
return PastelLanding(parsePastelPageData(doc))
}
// RenderPastelArticle is the registered render entry for the article template.
func RenderPastelArticle(ctx context.Context, doc map[string]any) templ.Component {
return PastelArticle(parsePastelPageData(doc))
}
// RenderPastelFullWidth is the registered render entry for the full-width template.
func RenderPastelFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return PastelFullWidth(parsePastelPageData(doc))
}
var _ = templruntime.GeneratedTemplate

View File

@ -0,0 +1,98 @@
<!doctype html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="x-apple-disable-message-reformatting">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{ site_name }}</title>
<style type="text/css">
body, table, td, p, a, li {
-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;
}
.pastel-email-frame {
max-width: 560px;
margin: 0 auto;
}
@media only screen and (max-width: 600px) {
.pastel-email-frame {
width: 100% !important;
max-width: 100% !important;
}
.pastel-pad {
padding-left: 24px !important;
padding-right: 24px !important;
}
}
</style>
</head>
<body style="background-color: {{ colors.background|default:"#fdf7f1" }}; margin: 0; padding: 0; font-family: &amp;#39;Nunito&amp;#39;, &amp;#39;Quicksand&amp;#39;, Helvetica, Arial, sans-serif;">
{% if preview_text %}<div style="display: none; max-height: 0; overflow: hidden;">
{{ preview_text }}
</div>{% endif %}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color: {{ colors.background|default:"#fdf7f1" }};">
<tr>
<td align="center" style="padding: 40px 12px; position: relative;">
<!-- Watermark blob (top-right). Inline SVG so any client that supports it renders the wash. -->
<div style="position: absolute; top: 0; right: 0; width: 200px; height: 200px; opacity: 0.35; pointer-events: none;">
<svg viewBox="0 0 200 200" width="200" height="200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M100,20 C160,20 180,80 180,120 C180,170 130,180 90,180 C40,180 20,140 20,100 C20,60 50,20 100,20 Z" fill="{{ colors.primary|default:"#e9a1ad" }}"></path>
</svg>
</div>
<table role="presentation" class="pastel-email-frame" width="560" cellspacing="0" cellpadding="0" border="0" style="max-width: 560px; background-color: {{ colors.card|default:"#ffffff" }}; border-radius: 20px; border: 1px solid {{ colors.border|default:"#f0d8de" }};">
<!-- Masthead -->
<tr>
<td align="center" class="pastel-pad" style="padding: 36px 40px 8px; border-bottom: 1px solid {{ colors.border|default:"#f0d8de" }};">
{% if logo_url %}<img src="{{ logo_url }}" alt="{{ site_name }}" style="max-height: 56px; width: auto; display: block;">{% elif site_name %}<h1 style="margin: 0; font-family: &amp;#39;Caveat Brush&amp;#39;, &amp;#39;Sacramento&amp;#39;, cursive; font-size: 36px; font-weight: 400; color: {{ colors.foreground|default:"#432e36" }};">
{{ site_name }}
</h1>{% endif %}
</td>
</tr>
<!-- Body -->
<tr>
<td class="pastel-pad" style="padding: 32px 40px 24px; color: {{ colors.foreground|default:"#432e36" }}; font-size: 16px; line-height: 1.75;">
{{ body|safe }}
</td>
</tr>
<!-- Footer -->
<tr>
<td class="pastel-pad" align="center" style="padding: 24px 40px 36px; border-top: 1px solid {{ colors.border|default:"#f0d8de" }};">
<p style="margin: 0 0 12px; font-family: &amp;#39;Caveat Brush&amp;#39;, &amp;#39;Sacramento&amp;#39;, cursive; font-size: 18px; color: {{ colors.mutedForeground|default:"#80707a" }};">
Be gentle with yourself today.
</p>
{% if site_url %}<p style="margin: 0 0 12px; font-size: 13px; color: {{ colors.mutedForeground|default:"#80707a" }};">
<a href="{{ site_url }}" style="color: {{ colors.primary|default:"#e9a1ad" }}; text-decoration: none;">
{{ site_url }}
</a>
</p>{% endif %}
{% if unsubscribe_url %}<p style="margin: 0; font-size: 11px; color: {{ colors.mutedForeground|default:"#80707a" }};">
<a href="{{ unsubscribe_url }}" style="color: {{ colors.mutedForeground|default:"#80707a" }}; text-decoration: underline;">
Unsubscribe
</a>
</p>{% endif %}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,3 @@
<a href="{{ href|default:"#" }}" class="pastel-pill" data-block-type="button" data-variant="{{ variant|default:"primary" }}">
{{ text|default:"Continue" }}
</a>

View File

@ -0,0 +1,9 @@
<div class="pastel-card p-6" data-block-type="card" style="border-radius: 20px;">
{% if title %}<h3 class="font-display text-2xl mb-3" style="color: hsl(var(--card-foreground));">{{ title }}</h3>{% endif %}
{% if body %}<div class="font-body" style="color: hsl(var(--card-foreground) / 0.85); line-height: 1.75;">
{{ body|safe }}
</div>{% endif %}
{% if footer %}<div class="mt-6 pt-4 border-t font-body text-sm" style="border-color: hsl(var(--border)); color: hsl(var(--muted-foreground));">
{{ footer|safe }}
</div>{% endif %}
</div>

View File

@ -0,0 +1 @@
{% if level|integer == 1 %}<h1 class="font-display text-5xl md:text-6xl leading-tight brush-underline {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h1>{% elif level|integer == 3 %}<h3 class="font-body text-3xl font-semibold tracking-tight {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h3>{% elif level|integer == 4 %}<h4 class="font-body text-2xl font-semibold {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h4>{% elif level|integer == 5 %}<h5 class="font-body text-xl font-medium {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h5>{% elif level|integer == 6 %}<h6 class="font-body text-lg font-medium {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h6>{% else %}<h2 class="font-display text-4xl md:text-5xl leading-tight brush-underline {{ textClass }}" style="color: hsl(var(--foreground));">{{ text }}</h2>{% endif %}

View File

@ -0,0 +1,3 @@
<div class="font-body prose max-w-none {{ class }}" style="color: hsl(var(--foreground)); line-height: 1.75;">
{{ text|safe }}
</div>

View File

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
{{ admin_banner_html|safe }}
<header class="w-full">
{{ slots.header|safe }}
</header>
<main class="flex-grow w-full max-w-2xl mx-auto px-4 py-16">
{% if slots.main %}<article class="font-body prose max-w-none" style="line-height: 1.75; color: hsl(var(--foreground));">
{{ slots.main|safe }}
</article>{% else %}<div class="py-20 text-center" style="color: hsl(var(--muted-foreground));">
<p>This page is still resting. Add a block to begin.</p>
</div>{% endif %}
</main>
<footer class="w-full mt-auto">
{{ slots.footer|safe }}
</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
{{ admin_banner_html|safe }}
<header class="w-full">
{{ slots.header|safe }}
</header>
<main class="flex-grow w-full max-w-4xl mx-auto px-4 py-12">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center font-body" style="color: hsl(var(--muted-foreground));">
<p>This page is still resting. Add a block to begin.</p>
</div>{% endif %}
</main>
<footer class="w-full mt-auto">
{{ slots.footer|safe }}
</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,19 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
{{ admin_banner_html|safe }}
<header class="w-full">
{{ slots.header|safe }}
</header>
<main class="flex-grow w-full">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="max-w-4xl mx-auto py-20 px-4 text-center" style="color: hsl(var(--muted-foreground));">
<p>This page is still resting. Add a block to begin.</p>
</div>{% endif %}
</main>
<footer class="w-full mt-auto">
{{ slots.footer|safe }}
</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="font-body antialiased min-h-screen flex flex-col" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
{{ admin_banner_html|safe }}
<section class="w-full">
{{ slots.hero|safe }}
</section>
<main class="flex-grow w-full">
{% if slots.main %}<div class="max-w-6xl mx-auto px-4 py-20">
{{ slots.main|safe }}
</div>{% endif %}
</main>
<section class="w-full">
{{ slots.cta|safe }}
</section>
<footer class="w-full mt-auto">
{{ slots.footer|safe }}
</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -1,50 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// TestimonialSoftMeta defines the soft testimonial card.
var TestimonialSoftMeta = blocks.BlockMeta{
Key: "testimonial-soft",
Title: "Soft Testimonial",
Description: "Quote card with a deckle paper edge, avatar, and rating.",
Source: "pastel-dream",
Category: blocks.CategoryTheme,
}
// TestimonialSoftBlock renders the testimonial card.
// Content shape: {quote, name, role, avatar, rating}
func TestimonialSoftBlock(ctx context.Context, content map[string]any) string {
rating := getFloat(content, "rating", 0)
if rating < 0 {
rating = 0
}
if rating > 5 {
rating = 5
}
data := TestimonialSoftData{
Quote: getString(content, "quote"),
Name: getString(content, "name"),
Role: getString(content, "role"),
Avatar: getString(content, "avatar"),
Rating: int(rating),
}
var buf bytes.Buffer
_ = testimonialSoftComponent(data).Render(ctx, &buf)
return buf.String()
}
// TestimonialSoftData is the rendered view of the testimonial-soft block.
type TestimonialSoftData struct {
Quote string
Name string
Role string
Avatar string
Rating int
}

View File

@ -1,65 +0,0 @@
package main
// initials returns up to two uppercase initials for a name. Used as an avatar
// fallback so the card renders cleanly when no portrait is provided.
func initials(name string) string {
if name == "" {
return ""
}
out := []rune{}
prevSpace := true
for _, r := range name {
if r == ' ' {
prevSpace = true
continue
}
if prevSpace {
if r >= 'a' && r <= 'z' {
r -= 32
}
out = append(out, r)
prevSpace = false
if len(out) >= 2 {
break
}
}
}
return string(out)
}
// testimonialSoftComponent renders the deckle-edged testimonial card.
templ testimonialSoftComponent(data TestimonialSoftData) {
<figure class="pastel-card deckle-edge p-8 max-w-xl mx-auto" data-block="pastel-dream:testimonial-soft">
if data.Rating > 0 {
<div class="flex gap-1 mb-4" aria-label="Rating">
for i := 0; i < data.Rating; i++ {
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color: hsl(var(--accent));">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>
}
</div>
}
<blockquote class="font-body text-lg leading-relaxed" style="color: hsl(var(--card-foreground));">
@templ.Raw(data.Quote)
</blockquote>
if data.Name != "" || data.Role != "" {
<figcaption class="flex items-center gap-4 mt-6 pt-6 border-t" style="border-color: hsl(var(--border));">
if data.Avatar != "" {
<img src={ data.Avatar } alt={ data.Name } class="w-12 h-12 rounded-full object-cover"/>
} else if data.Name != "" {
<span class="w-12 h-12 rounded-full flex items-center justify-center font-display text-lg" style="background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground));">
{ initials(data.Name) }
</span>
}
<div>
if data.Name != "" {
<p class="font-display text-xl leading-tight" style="color: hsl(var(--foreground));">{ data.Name }</p>
}
if data.Role != "" {
<p class="font-body text-sm" style="color: hsl(var(--muted-foreground));">{ data.Role }</p>
}
</div>
</figcaption>
}
</figure>
}

View File

@ -1,202 +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"
// initials returns up to two uppercase initials for a name. Used as an avatar
// fallback so the card renders cleanly when no portrait is provided.
func initials(name string) string {
if name == "" {
return ""
}
out := []rune{}
prevSpace := true
for _, r := range name {
if r == ' ' {
prevSpace = true
continue
}
if prevSpace {
if r >= 'a' && r <= 'z' {
r -= 32
}
out = append(out, r)
prevSpace = false
if len(out) >= 2 {
break
}
}
}
return string(out)
}
// testimonialSoftComponent renders the deckle-edged testimonial card.
func testimonialSoftComponent(data TestimonialSoftData) 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, "<figure class=\"pastel-card deckle-edge p-8 max-w-xl mx-auto\" data-block=\"pastel-dream:testimonial-soft\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Rating > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"flex gap-1 mb-4\" aria-label=\"Rating\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i := 0; i < data.Rating; i++ {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" style=\"color: hsl(var(--accent));\"><polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"></polygon></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<blockquote class=\"font-body text-lg leading-relaxed\" style=\"color: hsl(var(--card-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Quote).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</blockquote>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Name != "" || data.Role != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<figcaption class=\"flex items-center gap-4 mt-6 pt-6 border-t\" style=\"border-color: hsl(var(--border));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Avatar != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Avatar)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_soft.templ`, Line: 48, Col: 27}
}
_, 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, 9, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_soft.templ`, Line: 48, Col: 45}
}
_, 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, 10, "\" class=\"w-12 h-12 rounded-full object-cover\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if data.Name != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<span class=\"w-12 h-12 rounded-full flex items-center justify-center font-display text-lg\" style=\"background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(initials(data.Name))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_soft.templ`, Line: 51, 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, 12, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Name != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<p class=\"font-display text-xl leading-tight\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_soft.templ`, Line: 56, Col: 102}
}
_, 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, 15, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Role != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<p class=\"font-body text-sm\" style=\"color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Role)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `testimonial_soft.templ`, Line: 59, Col: 91}
}
_, 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, 17, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></figcaption>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</figure>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,17 +0,0 @@
package main
import (
"bytes"
"context"
)
// PastelTextBlock renders text with pastel-dream styling: Nunito body with
// generous line-height (1.75) and wider tracking on small caps.
func PastelTextBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
class := getString(content, "class")
var buf bytes.Buffer
_ = pastelTextComponent(text, class).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,8 +0,0 @@
package main
// pastelTextComponent renders body text with Nunito at line-height 1.75.
templ pastelTextComponent(text, class string) {
<div class={ "font-body prose max-w-none", class } style="color: hsl(var(--foreground)); line-height: 1.75;">
@templ.Raw(text)
</div>
}

View File

@ -1,67 +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"
// pastelTextComponent renders body text with Nunito at line-height 1.75.
func pastelTextComponent(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{"font-body prose max-w-none", 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, "\" style=\"color: hsl(var(--foreground)); line-height: 1.75;\">")
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

View File

@ -1,44 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// WatercolorHeroMeta defines metadata for the watercolor hero.
var WatercolorHeroMeta = blocks.BlockMeta{
Key: "watercolor-hero",
Title: "Watercolor Hero",
Description: "Hero with two-tone watercolor blobs layered behind soft display type.",
Source: "pastel-dream",
Category: blocks.CategoryTheme,
}
// WatercolorHeroBlock renders the hero section.
// Content shape: {eyebrow, headline, body, image, ctaText, ctaHref}
func WatercolorHeroBlock(ctx context.Context, content map[string]any) string {
data := WatercolorHeroData{
Eyebrow: getString(content, "eyebrow"),
Headline: getStringOr(content, "headline", "Soft starts"),
Body: getString(content, "body"),
Image: getString(content, "image"),
CTAText: getString(content, "ctaText"),
CTAHref: safeLink(getString(content, "ctaHref")),
}
var buf bytes.Buffer
_ = watercolorHeroComponent(data).Render(ctx, &buf)
return buf.String()
}
// WatercolorHeroData is the rendered view of the watercolor-hero block.
type WatercolorHeroData struct {
Eyebrow string
Headline string
Body string
Image string
CTAText string
CTAHref string
}

View File

@ -1,54 +0,0 @@
package main
// watercolorHeroComponent renders the watercolor hero with inline SVG blobs.
// Path fill colors come from hsl(var(--primary)) / hsl(var(--accent)) so they
// follow whatever preset is active. A scrim under the headline keeps text
// readable when an image is provided.
templ watercolorHeroComponent(data WatercolorHeroData) {
<section class="relative overflow-hidden py-20 md:py-28 bg-watercolor-blush" data-block="pastel-dream:watercolor-hero">
// Decorative watercolor blobs. Inline SVG so fills can reference theme tokens.
<svg class="absolute -top-12 -left-12 w-[28rem] h-[28rem] -z-0 animate-breathe" viewBox="0 0 400 400" aria-hidden="true">
<path d="M200,40 C290,40 360,120 360,200 C360,290 280,360 200,360 C110,360 40,280 40,200 C40,110 110,40 200,40 Z" fill="hsl(var(--primary) / 0.22)"></path>
</svg>
<svg class="absolute -bottom-16 -right-8 w-[24rem] h-[24rem] -z-0 animate-breathe" viewBox="0 0 400 400" aria-hidden="true">
<path d="M120,60 C200,40 320,80 360,180 C400,280 320,360 220,360 C120,360 40,300 40,220 C40,160 60,80 120,60 Z" fill="hsl(var(--accent) / 0.30)"></path>
</svg>
<div class="relative z-10 max-w-6xl mx-auto px-4 grid md:grid-cols-2 gap-12 items-center">
<div>
if data.Eyebrow != "" {
<p class="font-mono text-xs uppercase tracking-[0.2em] mb-4" style="color: hsl(var(--muted-foreground));">
{ data.Eyebrow }
</p>
}
<h1 class="font-display brush-underline text-5xl md:text-6xl leading-tight mb-6" style="color: hsl(var(--foreground));">
@templ.Raw(data.Headline)
</h1>
if data.Body != "" {
<div class="font-body text-lg max-w-prose mb-8" style="color: hsl(var(--foreground) / 0.78);">
@templ.Raw(data.Body)
</div>
}
if data.CTAText != "" {
<a href={ templ.SafeURL(data.CTAHref) } class="pastel-pill text-base animate-breathe">
{ data.CTAText }
</a>
}
</div>
if data.Image != "" {
<div class="relative">
<div class="pastel-card overflow-hidden">
<img src={ data.Image } alt="" class="w-full h-auto block"/>
</div>
</div>
} else {
<div class="relative h-64 md:h-80" aria-hidden="true">
<svg viewBox="0 0 300 240" class="w-full h-full">
<ellipse cx="150" cy="120" rx="110" ry="80" fill="hsl(var(--secondary) / 0.55)"></ellipse>
<ellipse cx="120" cy="100" rx="70" ry="50" fill="hsl(var(--accent) / 0.45)"></ellipse>
<ellipse cx="180" cy="140" rx="60" ry="44" fill="hsl(var(--primary) / 0.40)"></ellipse>
</svg>
</div>
}
</div>
</section>
}

View File

@ -1,153 +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"
// watercolorHeroComponent renders the watercolor hero with inline SVG blobs.
// Path fill colors come from hsl(var(--primary)) / hsl(var(--accent)) so they
// follow whatever preset is active. A scrim under the headline keeps text
// readable when an image is provided.
func watercolorHeroComponent(data WatercolorHeroData) 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=\"relative overflow-hidden py-20 md:py-28 bg-watercolor-blush\" data-block=\"pastel-dream:watercolor-hero\"><svg class=\"absolute -top-12 -left-12 w-[28rem] h-[28rem] -z-0 animate-breathe\" viewBox=\"0 0 400 400\" aria-hidden=\"true\"><path d=\"M200,40 C290,40 360,120 360,200 C360,290 280,360 200,360 C110,360 40,280 40,200 C40,110 110,40 200,40 Z\" fill=\"hsl(var(--primary) / 0.22)\"></path></svg> <svg class=\"absolute -bottom-16 -right-8 w-[24rem] h-[24rem] -z-0 animate-breathe\" viewBox=\"0 0 400 400\" aria-hidden=\"true\"><path d=\"M120,60 C200,40 320,80 360,180 C400,280 320,360 220,360 C120,360 40,300 40,220 C40,160 60,80 120,60 Z\" fill=\"hsl(var(--accent) / 0.30)\"></path></svg><div class=\"relative z-10 max-w-6xl mx-auto px-4 grid md:grid-cols-2 gap-12 items-center\"><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Eyebrow != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<p class=\"font-mono text-xs uppercase tracking-[0.2em] mb-4\" style=\"color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Eyebrow)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `watercolor_hero.templ`, Line: 20, Col: 20}
}
_, 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
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h1 class=\"font-display brush-underline text-5xl md:text-6xl leading-tight mb-6\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Headline).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Body != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"font-body text-lg max-w-prose mb-8\" style=\"color: hsl(var(--foreground) / 0.78);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Body).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.CTAText != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<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.CTAHref))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `watercolor_hero.templ`, Line: 32, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" class=\"pastel-pill text-base animate-breathe\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.CTAText)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `watercolor_hero.templ`, Line: 33, Col: 20}
}
_, 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, 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
}
if data.Image != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"relative\"><div class=\"pastel-card overflow-hidden\"><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Image)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `watercolor_hero.templ`, Line: 40, Col: 27}
}
_, 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, 13, "\" alt=\"\" class=\"w-full h-auto block\"></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"relative h-64 md:h-80\" aria-hidden=\"true\"><svg viewBox=\"0 0 300 240\" class=\"w-full h-full\"><ellipse cx=\"150\" cy=\"120\" rx=\"110\" ry=\"80\" fill=\"hsl(var(--secondary) / 0.55)\"></ellipse> <ellipse cx=\"120\" cy=\"100\" rx=\"70\" ry=\"50\" fill=\"hsl(var(--accent) / 0.45)\"></ellipse> <ellipse cx=\"180\" cy=\"140\" rx=\"60\" ry=\"44\" fill=\"hsl(var(--primary) / 0.40)\"></ellipse></svg></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate