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

Convert the Y2K theme from a Go+templ wasm plugin to a fully codeless .bnp:
blocks/blocks.yaml + .ninjatpl, page/system templates, template overrides,
email wrapper, master pages and CSS declared in manifest.yaml. All *.go, *.templ,
*_templ.go, go.mod and go.sum deleted; schemas moved schemas/ -> blocks/.

Static parity proven 48/48 identical against the templ baseline (10 blocks x
full+empty+variant branches, 4 overrides incl. heading L1-6, email full+empty).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-07-04 15:10:52 +08:00
parent 93001afe44
commit a86a54639a
89 changed files with 619 additions and 4729 deletions

View File

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

66
blocks/blocks.yaml Normal file
View File

@ -0,0 +1,66 @@
# Y2K theme block definitions (codeless .bnp, WO-WZ-021).
# Keys are fully qualified (y2k:<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-key attributes and the master_pages.json references.
blocks:
- key: y2k:chrome_navbar
title: Chrome Navbar
description: Beveled chrome navigation bar with hover sparkle.
category: navigation
schema: chrome_navbar.schema.json
template: chrome_navbar.ninjatpl
- key: y2k:metaball_hero
title: Metaball Hero
description: SVG metaballs on a gradient mesh canvas with headline + CTA.
category: layout
schema: metaball_hero.schema.json
template: metaball_hero.ninjatpl
- key: y2k:waveform_player
title: Waveform Player
description: Soundcloud-style audio player with a canvas waveform.
category: content
schema: waveform_player.schema.json
template: waveform_player.ninjatpl
- key: y2k:marquee
title: Marquee Ticker
description: Scrolling text marquee with chrome edges; pauses on hover.
category: layout
schema: marquee.schema.json
template: marquee.ninjatpl
- key: y2k:tracklist
title: Tracklist
description: Tabular tracklist styled like a CD jewel case insert.
category: content
schema: tracklist.schema.json
template: tracklist.ninjatpl
- key: y2k:merch_card
title: Merch Card
description: Plastic-bevel product card with a corner sticker badge.
category: content
schema: merch_card.schema.json
template: merch_card.ninjatpl
- key: y2k:webring_badge
title: Webring Badge
description: Canonical 88x31 webring button row with prev/next/ring links.
category: navigation
schema: webring_badge.schema.json
template: webring_badge.ninjatpl
- key: y2k:glitter_divider
title: Glitter Divider
description: Animated sparkle horizontal rule.
category: layout
schema: glitter_divider.schema.json
template: glitter_divider.ninjatpl
- key: y2k:footer_chrome
title: Chrome Footer
description: Beveled chrome footer strip with optional webring and copyright.
category: layout
schema: footer_chrome.schema.json
template: footer_chrome.ninjatpl
- key: y2k:nft_gallery
title: NFT Gallery
description: Holographic-foil hover gallery for NFT items (presentational only).
category: content
schema: nft_gallery.schema.json
template: nft_gallery.ninjatpl

View File

@ -0,0 +1,12 @@
<nav class="y2k-chrome-bg y2k-bevel relative w-full" data-block-key="y2k:chrome_navbar" data-menu="{{ menuName|default:"main" }}">
<div class="max-w-6xl mx-auto flex items-center justify-between gap-4 px-4 py-3">
<a href="/" class="flex items-center gap-2 text-foreground no-underline">
{% if logoImage %}<img src="{{ logoImage }}" alt="{{ logoText }}" class="h-8 w-auto y2k-image-frame">{% else %}<span class="y2k-heading text-xl uppercase">{{ logoText|default:"y2k.fm" }}</span>{% endif %}
</a>
<button type="button" class="md:hidden y2k-button text-sm" aria-controls="y2k-nav-{{ menuName|default:"main" }}" aria-expanded="false" data-y2k-nav-toggle>Menu</button>
<div id="y2k-nav-{{ menuName|default:"main" }}" class="hidden md:flex items-center gap-4" data-menu-target="{{ menuName|default:"main" }}">
<span class="text-muted-foreground text-sm uppercase tracking-wider">menu:{{ menuName|default:"main" }}</span>
</div>
</div>
<span class="y2k-sparkle absolute right-3 top-1 text-accent">*</span>
</nav>

View File

@ -0,0 +1,7 @@
<div class="y2k-chrome-bg y2k-bevel mt-8" data-block-key="y2k:footer_chrome">
<div class="max-w-6xl mx-auto px-4 py-4 flex flex-wrap items-center justify-between gap-3">
{% if menuName %}<nav class="flex gap-3 text-sm uppercase tracking-wider" data-menu-target="{{ menuName }}"><span class="text-muted-foreground">menu:{{ menuName }}</span></nav>{% endif %}
{% if showWebring != "no" and showWebring != false %}<div class="flex items-center gap-2"><a href="#" class="y2k-webring-badge">prev</a><a href="#" class="y2k-webring-badge">ring</a><a href="#" class="y2k-webring-badge">next</a></div>{% endif %}
{% if copyright %}<span class="text-xs text-muted-foreground">{{ copyright }}</span>{% endif %}
</div>
</div>

View File

@ -0,0 +1,6 @@
<hr class="border-0 my-6" role="separator" data-block-key="y2k:glitter_divider" data-variant="{{ variant|default:"sparkle" }}">
<div class="flex items-center justify-center gap-2 -mt-4 mb-4 text-accent" aria-hidden="true">
<span class="y2k-sparkle">{% if variant == "hearts" %}&lt;3{% elif variant == "chrome" %}~{% else %}*{% endif %}</span>
<span class="y2k-sparkle" style="animation-delay: 200ms">{% if variant == "hearts" %}&lt;3{% elif variant == "chrome" %}~{% else %}*{% endif %}</span>
<span class="y2k-sparkle" style="animation-delay: 400ms">{% if variant == "hearts" %}&lt;3{% elif variant == "chrome" %}~{% else %}*{% endif %}</span>
</div>

5
blocks/marquee.ninjatpl Normal file
View File

@ -0,0 +1,5 @@
<div class="y2k-marquee y2k-chrome-bg" data-block-key="y2k:marquee" data-direction="{{ direction|default:"left" }}" tabindex="0" aria-label="ticker">
<div class="y2k-marquee-track py-2 px-4 text-sm uppercase tracking-wider text-foreground" data-speed="{{ speed|default:"medium" }}">
{% if items %}{% for item in items %}<span class="inline-flex items-center gap-2"><span class="text-accent">*</span>{{ item }}</span>{% endfor %}{% for item in items %}<span class="inline-flex items-center gap-2" aria-hidden="true"><span class="text-accent">*</span>{{ item }}</span>{% endfor %}{% else %}<span class="inline-flex items-center gap-2"><span class="text-accent">*</span>now playing</span><span class="inline-flex items-center gap-2"><span class="text-accent">*</span>new drop friday</span><span class="inline-flex items-center gap-2"><span class="text-accent">*</span>subscribe to the zine</span><span class="inline-flex items-center gap-2" aria-hidden="true"><span class="text-accent">*</span>now playing</span><span class="inline-flex items-center gap-2" aria-hidden="true"><span class="text-accent">*</span>new drop friday</span><span class="inline-flex items-center gap-2" aria-hidden="true"><span class="text-accent">*</span>subscribe to the zine</span>{% endif %}
</div>
</div>

View File

@ -0,0 +1,13 @@
<article class="relative y2k-bevel rounded-2xl overflow-hidden bg-card flex flex-col" data-block-key="y2k:merch_card">
{% if sticker %}<span class="absolute top-3 right-3 z-10 y2k-button text-xs uppercase tracking-widest">{% if sticker == "sold-out" %}sold out{% else %}{{ sticker }}{% endif %}</span>{% endif %}
<div class="aspect-square bg-muted overflow-hidden y2k-image-frame border-0 rounded-none">
{% if image %}<img src="{{ image }}" alt="{{ title }}" class="w-full h-full object-cover">{% else %}<div class="w-full h-full flex items-center justify-center text-muted-foreground text-xs uppercase tracking-widest">no image</div>{% endif %}
</div>
<div class="p-4 flex flex-col gap-2 flex-1">
<h3 class="y2k-heading text-foreground text-lg">{{ title|default:"untitled" }}</h3>
<div class="flex items-center justify-between gap-3 mt-auto">
<span class="text-foreground font-bold">{{ price|default:"$--" }}</span>
{% if buyHref %}<a href="{{ buyHref }}" class="y2k-button text-sm">buy</a>{% endif %}
</div>
</div>
</article>

View File

@ -0,0 +1,32 @@
<section class="relative overflow-hidden y2k-mesh-bg py-24 md:py-32" data-block-key="y2k:metaball_hero" data-bg-preset="{{ bgPreset|default:"magenta-teal" }}">
<svg class="absolute inset-0 w-full h-full opacity-80" viewBox="0 0 600 400" preserveAspectRatio="none" aria-hidden="true">
<defs>
<filter id="y2k-metaball" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur in="SourceGraphic" stdDeviation="18" result="b1"></feGaussianBlur>
<feGaussianBlur in="b1" stdDeviation="8" result="b2"></feGaussianBlur>
<feColorMatrix in="b2" type="matrix" values="
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 22 -10" result="meta"></feColorMatrix>
<feComposite in="meta" in2="SourceGraphic" operator="atop"></feComposite>
</filter>
<linearGradient id="y2k-mb-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="currentColor"></stop>
<stop offset="100%" stop-color="currentColor" stop-opacity="0.7"></stop>
</linearGradient>
</defs>
<g filter="url(#y2k-metaball)" fill="url(#y2k-mb-grad)" class="text-primary">
<circle class="y2k-metaball" cx="180" cy="200" r="80"></circle>
<circle class="y2k-metaball" cx="320" cy="160" r="70"></circle>
<circle class="y2k-metaball" cx="430" cy="240" r="90"></circle>
</g>
</svg>
<div class="relative z-10 max-w-4xl mx-auto px-4 text-center">
<div class="inline-block px-6 py-4 rounded-2xl bg-card/70 backdrop-blur-md y2k-bevel">
<h1 class="y2k-heading text-[4rem] md:text-[5rem] leading-tight font-bold text-foreground">{{ headline|default:"new EP out now" }}</h1>
{% if sub %}<p class="text-lg md:text-xl text-foreground/80 mt-3 mb-6">{{ sub }}</p>{% endif %}
{% if ctaLabel %}<a href="{{ ctaHref|default:"#" }}" class="y2k-button text-lg">{{ ctaLabel }}</a>{% endif %}
</div>
</div>
</section>

View File

@ -0,0 +1,9 @@
<section class="my-6" data-block-key="y2k:nft_gallery">
{% if not items %}<p class="text-center text-muted-foreground py-12">No items yet.</p>{% else %}<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">{% for item in items %}<a href="{{ item.href|default:"#" }}" class="y2k-foil y2k-bevel block bg-card rounded-xl overflow-hidden no-underline text-foreground">
<div class="aspect-square bg-muted overflow-hidden">{% if item.image %}<img src="{{ item.image }}" alt="{{ item.title }}" class="w-full h-full object-cover">{% else %}<div class="w-full h-full flex items-center justify-center text-muted-foreground text-xs uppercase tracking-widest">no image</div>{% endif %}</div>
<div class="p-3 flex items-center justify-between gap-2">
<span class="font-semibold truncate">{{ item.title|default:"untitled" }}</span>
{% if item.chain %}<span class="text-xs uppercase tracking-widest text-muted-foreground">{{ item.chain }}</span>{% endif %}
</div>
</a>{% endfor %}</div>{% endif %}
</section>

View File

@ -0,0 +1,8 @@
<section class="y2k-bevel rounded-xl overflow-hidden bg-card my-6" data-block-key="y2k:tracklist">
<header class="y2k-chrome-bg px-4 py-2 text-foreground uppercase tracking-wider text-xs">tracklist</header>
{% if not tracks %}<p class="px-4 py-6 text-center text-muted-foreground">No tracks yet.</p>{% else %}<ol class="divide-y divide-border">{% for track in tracks %}<li class="flex flex-wrap items-center gap-3 px-4 py-3">
<span class="w-6 shrink-0 text-muted-foreground text-sm tabular-nums">{{ forloop.Counter }}</span>
<span class="flex-1 min-w-0 truncate text-foreground">{% if track.url %}<a href="{{ track.url }}" class="hover:underline">{{ track.title|default:"untitled track" }}</a>{% else %}{{ track.title|default:"untitled track" }}{% endif %}</span>
<span class="text-muted-foreground text-sm tabular-nums whitespace-nowrap">{{ track.duration|default:"--:--" }}</span>
</li>{% endfor %}</ol>{% endif %}
</section>

View File

@ -0,0 +1,13 @@
{% if audioUrl or trackTitle %}<figure class="y2k-bevel y2k-chrome-bg rounded-xl overflow-hidden flex items-stretch gap-4 p-3 my-4" data-block-key="y2k:waveform_player">
<div class="w-20 h-20 shrink-0 y2k-image-frame overflow-hidden bg-muted">
{% if coverImage %}<img src="{{ coverImage }}" alt="{{ trackTitle }}" class="w-full h-full object-cover">{% else %}<svg viewBox="0 0 24 24" class="w-full h-full text-muted-foreground p-4" fill="currentColor" aria-hidden="true"><path d="M9 19V6l11-3v13"></path></svg>{% endif %}
</div>
<div class="flex-1 min-w-0 flex flex-col justify-between gap-2">
<figcaption class="flex items-baseline justify-between gap-3 min-w-0">
<span class="y2k-heading text-base truncate text-foreground">{{ trackTitle|default:"untitled track" }}</span>
{% if artist %}<span class="text-xs uppercase tracking-wider text-muted-foreground shrink-0">{{ artist }}</span>{% endif %}
</figcaption>
<canvas class="w-full h-12 bg-muted/40 rounded" data-y2k-waveform data-audio-url="{{ audioUrl }}" aria-label="audio waveform"></canvas>
{% if audioUrl %}<audio controls preload="none" class="w-full" src="{{ audioUrl }}">Your browser does not support the audio element.</audio>{% else %}<p class="text-xs text-muted-foreground">No audio source configured.</p>{% endif %}
</div>
</figure>{% endif %}

View File

@ -0,0 +1,5 @@
<nav class="flex items-center gap-2 my-4" data-block-key="y2k:webring_badge" aria-label="webring">
<a href="{{ prevHref|default:"#" }}" class="y2k-webring-badge">prev</a>
<span class="y2k-webring-badge">{{ ringName|default:"the y2k.fm ring" }}</span>
<a href="{{ nextHref|default:"#" }}" class="y2k-webring-badge">next</a>
</nav>

View File

@ -1,28 +0,0 @@
package main
import (
"bytes"
"context"
)
// Y2KButtonBlock applies the plastic-bevel styling to the built-in button.
// Content: {text, url, variant, class}
func Y2KButtonBlock(ctx context.Context, content map[string]any) string {
data := Y2KButtonData{
Text: getStringOr(content, "text", "button"),
URL: getStringOr(content, "url", "#"),
Variant: getStringOr(content, "variant", "primary"),
Class: getString(content, "class"),
}
var buf bytes.Buffer
_ = y2kButtonComponent(data).Render(ctx, &buf)
return buf.String()
}
// Y2KButtonData is the typed shape for the templ component.
type Y2KButtonData struct {
Text string
URL string
Variant string
Class string
}

View File

@ -1,9 +0,0 @@
package main
// y2kButtonComponent renders the plastic-bevel button. The 3D bevel + inset
// active state are owned by .y2k-button in css.go (UAT 13.9).
templ y2kButtonComponent(data Y2KButtonData) {
<a href={ templ.SafeURL(data.URL) } class={ "y2k-button", data.Class } data-variant={ data.Variant }>
{ data.Text }
</a>
}

View File

@ -1,99 +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"
// y2kButtonComponent renders the plastic-bevel button. The 3D bevel + inset
// active state are owned by .y2k-button in css.go (UAT 13.9).
func y2kButtonComponent(data Y2KButtonData) 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{"y2k-button", data.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, "<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.URL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 6, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" data-variant=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Variant)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 6, Col: 99}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 7, Col: 13}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,37 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// ChromeNavbarBlockMeta declares the y2k chrome navbar.
var ChromeNavbarBlockMeta = blocks.BlockMeta{
Key: "chrome_navbar",
Title: "Chrome Navbar",
Description: "Beveled chrome navigation bar with hover sparkle.",
Source: "y2k",
Category: blocks.CategoryNavigation,
}
// ChromeNavbarBlock renders the navbar.
// Content: {menuName, logoText, logoImage}
func ChromeNavbarBlock(ctx context.Context, content map[string]any) string {
data := ChromeNavbarData{
MenuName: getStringOr(content, "menuName", "main"),
LogoText: getString(content, "logoText"),
LogoImage: getString(content, "logoImage"),
}
var buf bytes.Buffer
_ = chromeNavbarComponent(data).Render(ctx, &buf)
return buf.String()
}
// ChromeNavbarData is the typed shape for the templ component.
type ChromeNavbarData struct {
MenuName string
LogoText string
LogoImage string
}

View File

@ -1,38 +0,0 @@
package main
// chromeNavbarComponent renders the beveled chrome navbar. The menu itself is
// hydrated client-side by the CMS; this block only owns the chrome shell and
// brand area.
templ chromeNavbarComponent(data ChromeNavbarData) {
<nav class="y2k-chrome-bg y2k-bevel relative w-full" data-block-key="y2k:chrome_navbar" data-menu={ data.MenuName }>
<div class="max-w-6xl mx-auto flex items-center justify-between gap-4 px-4 py-3">
<a href="/" class="flex items-center gap-2 text-foreground no-underline">
if data.LogoImage != "" {
<img src={ data.LogoImage } alt={ data.LogoText } class="h-8 w-auto y2k-image-frame"/>
} else {
<span class="y2k-heading text-xl uppercase">{ defaultLogoText(data.LogoText) }</span>
}
</a>
<button
type="button"
class="md:hidden y2k-button text-sm"
aria-controls={ "y2k-nav-" + data.MenuName }
aria-expanded="false"
data-y2k-nav-toggle
>
Menu
</button>
<div id={ "y2k-nav-" + data.MenuName } class="hidden md:flex items-center gap-4" data-menu-target={ data.MenuName }>
<span class="text-muted-foreground text-sm uppercase tracking-wider">{ "menu:" + data.MenuName }</span>
</div>
</div>
<span class="y2k-sparkle absolute right-3 top-1 text-accent">*</span>
</nav>
}
func defaultLogoText(s string) string {
if s == "" {
return "y2k.fm"
}
return s
}

View File

@ -1,169 +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"
// chromeNavbarComponent renders the beveled chrome navbar. The menu itself is
// hydrated client-side by the CMS; this block only owns the chrome shell and
// brand area.
func chromeNavbarComponent(data ChromeNavbarData) 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=\"y2k-chrome-bg y2k-bevel relative w-full\" data-block-key=\"y2k:chrome_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: `chrome_navbar.templ`, Line: 7, Col: 114}
}
_, 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-4 py-3\"><a href=\"/\" class=\"flex items-center gap-2 text-foreground no-underline\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.LogoImage != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.LogoImage)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `chrome_navbar.templ`, Line: 11, Col: 30}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.LogoText)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `chrome_navbar.templ`, Line: 11, Col: 52}
}
_, 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, 5, "\" class=\"h-8 w-auto y2k-image-frame\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"y2k-heading text-xl uppercase\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(defaultLogoText(data.LogoText))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `chrome_navbar.templ`, Line: 13, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</a> <button type=\"button\" class=\"md:hidden y2k-button text-sm\" aria-controls=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue("y2k-nav-" + data.MenuName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `chrome_navbar.templ`, Line: 19, Col: 46}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" aria-expanded=\"false\" data-y2k-nav-toggle>Menu</button><div id=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue("y2k-nav-" + data.MenuName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `chrome_navbar.templ`, Line: 25, Col: 39}
}
_, 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, 10, "\" class=\"hidden md:flex items-center gap-4\" data-menu-target=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.MenuName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `chrome_navbar.templ`, Line: 25, Col: 116}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\"><span class=\"text-muted-foreground text-sm uppercase tracking-wider\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs("menu:" + data.MenuName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `chrome_navbar.templ`, Line: 26, Col: 98}
}
_, 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, 12, "</span></div></div><span class=\"y2k-sparkle absolute right-3 top-1 text-accent\">*</span></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func defaultLogoText(s string) string {
if s == "" {
return "y2k.fm"
}
return s
}
var _ = templruntime.GeneratedTemplate

228
css.go
View File

@ -1,228 +0,0 @@
package main
// y2kInputCSS is appended to the host Tailwind input. It declares the chrome
// gradient stops, mesh anchors, bevel shadows, marquee/sparkle/metaball
// keyframes, and the utility classes those keyframes drive. Every colour is
// expressed through theme HSL custom properties (no hardcoded hex/rgb).
const y2kInputCSS = `
/* === y2k theme — chrome/bevel/mesh tokens === */
:root {
--chrome-1: 280 30% 95%;
--chrome-2: 200 40% 75%;
--chrome-3: 320 60% 60%;
--chrome-4: 280 40% 25%;
--mesh-a: 320 95% 58%;
--mesh-b: 185 90% 60%;
--mesh-c: 60 95% 70%;
--bevel-light: 0 0% 100%;
--bevel-dark: 280 60% 12%;
}
.dark {
--chrome-1: 280 30% 30%;
--chrome-2: 200 40% 45%;
--chrome-3: 320 60% 50%;
--chrome-4: 280 40% 10%;
--mesh-a: 320 100% 65%;
--mesh-b: 185 95% 55%;
--mesh-c: 60 100% 65%;
--bevel-light: 0 0% 100%;
--bevel-dark: 280 80% 4%;
}
/* === keyframes === */
@keyframes marquee-x {
from { transform: translate3d(0, 0, 0); }
to { transform: translate3d(-50%, 0, 0); }
}
@keyframes sparkle {
0%, 100% { opacity: 0; transform: scale(0.5) rotate(0deg); }
50% { opacity: 1; transform: scale(1) rotate(180deg); }
}
@keyframes metaball-morph {
0%, 100% { d: path("M 50 100 a 50 50 0 1 1 100 0 a 50 50 0 1 1 -100 0"); }
50% { d: path("M 40 95 a 60 55 0 1 1 120 5 a 55 60 0 1 1 -120 -5"); }
}
/* === chrome surface utilities === */
.y2k-chrome-bg {
background-image:
linear-gradient(180deg,
hsl(var(--chrome-1)) 0%,
hsl(var(--chrome-2)) 35%,
hsl(var(--chrome-3)) 70%,
hsl(var(--chrome-4)) 100%);
}
.y2k-mesh-bg {
background-image:
radial-gradient(at 20% 20%, hsl(var(--mesh-a) / 0.85) 0%, transparent 55%),
radial-gradient(at 80% 30%, hsl(var(--mesh-b) / 0.80) 0%, transparent 55%),
radial-gradient(at 50% 80%, hsl(var(--mesh-c) / 0.70) 0%, transparent 55%),
linear-gradient(135deg, hsl(var(--background)) 0%, hsl(var(--card)) 100%);
}
.y2k-bevel {
border: 2px solid hsl(var(--border));
box-shadow:
inset 0 1px 0 hsl(var(--bevel-light) / 0.85),
inset 0 -2px 0 hsl(var(--bevel-dark) / 0.35),
0 2px 6px hsl(var(--bevel-dark) / 0.30);
}
.y2k-bevel:active {
box-shadow:
inset 0 2px 4px hsl(var(--bevel-dark) / 0.45),
inset 0 -1px 0 hsl(var(--bevel-light) / 0.40);
}
.y2k-glow {
box-shadow: 0 0 24px hsl(var(--primary) / 0.45);
}
/* === marquee === */
.y2k-marquee {
overflow: hidden;
position: relative;
border-top: 2px solid hsl(var(--border));
border-bottom: 2px solid hsl(var(--border));
}
.y2k-marquee-track {
display: inline-flex;
gap: 3rem;
white-space: nowrap;
animation-name: marquee-x;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.y2k-marquee-track[data-speed="slow"] { animation-duration: 60s; }
.y2k-marquee-track[data-speed="medium"] { animation-duration: 30s; }
.y2k-marquee-track[data-speed="fast"] { animation-duration: 12s; }
.y2k-marquee:hover .y2k-marquee-track,
.y2k-marquee:focus-within .y2k-marquee-track {
animation-play-state: paused;
}
/* === sparkle decoration === */
.y2k-sparkle {
pointer-events: none;
animation: sparkle 1.6s ease-in-out infinite;
}
/* === metaball morph === */
.y2k-metaball {
animation: metaball-morph 6s ease-in-out infinite;
}
/* === webring 88x31 badge === */
.y2k-webring-badge {
display: inline-block;
width: 88px;
height: 31px;
line-height: 31px;
text-align: center;
font-size: 11px;
letter-spacing: 0.05em;
text-transform: uppercase;
border: 1px solid hsl(var(--border));
background-image: linear-gradient(180deg, hsl(var(--chrome-1)) 0%, hsl(var(--chrome-3)) 100%);
color: hsl(var(--primaryForeground));
text-decoration: none;
box-shadow: inset 0 1px 0 hsl(var(--bevel-light) / 0.6);
}
/* === holographic foil for NFT gallery === */
.y2k-foil {
position: relative;
isolation: isolate;
}
.y2k-foil::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0;
transition: opacity 220ms ease;
background-image: conic-gradient(
from 0deg,
hsl(var(--mesh-a) / 0.45),
hsl(var(--mesh-b) / 0.45),
hsl(var(--mesh-c) / 0.45),
hsl(var(--mesh-a) / 0.45)
);
mix-blend-mode: color-dodge;
filter: hue-rotate(0deg);
}
.y2k-foil:hover::after,
.y2k-foil:focus-within::after {
opacity: 1;
filter: hue-rotate(45deg);
}
/* === plastic button override === */
.y2k-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.65rem 1.25rem;
font-weight: 600;
border: 2px solid hsl(var(--border));
border-radius: 0.625rem;
background-image: linear-gradient(180deg,
hsl(var(--primary) / 0.95) 0%,
hsl(var(--primary)) 50%,
hsl(var(--primary) / 0.85) 100%);
color: hsl(var(--primaryForeground));
box-shadow:
inset 0 1px 0 hsl(var(--bevel-light) / 0.7),
inset 0 -2px 0 hsl(var(--bevel-dark) / 0.3),
0 2px 6px hsl(var(--bevel-dark) / 0.25);
transition: transform 90ms ease, box-shadow 90ms ease;
}
.y2k-button:hover { transform: translateY(-1px); }
.y2k-button:active {
transform: translateY(1px);
box-shadow:
inset 0 2px 4px hsl(var(--bevel-dark) / 0.45),
inset 0 -1px 0 hsl(var(--bevel-light) / 0.30);
}
.y2k-button:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}
/* === image chrome frame === */
.y2k-image-frame {
border-radius: 0.625rem;
border: 2px solid hsl(var(--border));
box-shadow:
inset 0 1px 0 hsl(var(--bevel-light) / 0.5),
0 4px 14px hsl(var(--bevel-dark) / 0.35);
}
/* === heading chrome stroke === */
.y2k-heading {
font-family: var(--font-heading, "Stretch Pro", "VT323", "Courier New", monospace);
letter-spacing: 0.04em;
text-shadow:
0 1px 0 hsl(var(--bevel-light) / 0.65),
0 2px 0 hsl(var(--bevel-dark) / 0.35),
0 6px 22px hsl(var(--primary) / 0.45);
}
/* === text holographic link underline === */
.y2k-text a {
background-image: linear-gradient(90deg,
hsl(var(--mesh-a)),
hsl(var(--mesh-b)),
hsl(var(--mesh-c)));
background-size: 100% 2px;
background-repeat: no-repeat;
background-position: 0 100%;
text-decoration: none;
}
/* === reduced motion overrides === */
@media (prefers-reduced-motion: reduce) {
.y2k-marquee-track,
.y2k-sparkle,
.y2k-metaball {
animation: none !important;
}
}
`

View File

@ -1,83 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/templates"
)
// Y2KEmailWrapper wraps body content in a Y2K-branded email shell:
//
// - Centred 600px chrome-bordered card on a near-black background.
// - Magenta-to-teal gradient header bar.
// - Inter Tight body (with system fallback).
// - Plain marquee pill footer.
//
// All colours are sourced from emailCtx.Colors when present, falling back to
// HSL token-equivalent hex values that match the chrome-dream dark preset.
func Y2KEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = y2kEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}
// Resolved palette per render — these helpers translate the preset HSL triples
// to email-safe hex via theme tokens, with fallbacks aligned to the
// chrome-dream dark preset.
func y2kEmailBg(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Background != "" {
return emailCtx.Colors.Background
}
return "#0c0820" // chrome-dream dark background equivalent
}
func y2kEmailCard(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Card != "" {
return emailCtx.Colors.Card
}
return "#150f2a"
}
func y2kEmailFg(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Foreground != "" {
return emailCtx.Colors.Foreground
}
return "#f4e8fa"
}
func y2kEmailMuted(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Muted != "" {
return emailCtx.Colors.Muted
}
return "#1f1740"
}
func y2kEmailMutedFg(emailCtx templates.EmailContext) string {
if emailCtx.Colors.MutedForeground != "" {
return emailCtx.Colors.MutedForeground
}
return "#b9a8d3"
}
func y2kEmailBorder(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Border != "" {
return emailCtx.Colors.Border
}
return "#4d2b6f"
}
func y2kEmailPrimary(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Primary != "" {
return emailCtx.Colors.Primary
}
return "#ff4dd2"
}
func y2kEmailSecondary(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Secondary != "" {
return emailCtx.Colors.Secondary
}
return "#4ddff0"
}

View File

@ -1,88 +0,0 @@
package main
import (
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// y2kEmailTemplate is the Y2K-branded email shell.
templ y2kEmailTemplate(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; }
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
@media only screen and (max-width: 620px) {
.y2k-email-container { width: 100% !important; max-width: 100% !important; }
.y2k-email-content { padding-left: 20px !important; padding-right: 20px !important; }
}
</style>
</head>
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Inter Tight','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;", y2kEmailBg(emailCtx)) }>
if emailCtx.PreviewText != "" {
<div style="display:none;max-height:0;overflow:hidden;mso-hide:all;">{ emailCtx.PreviewText }</div>
}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center" style={ fmt.Sprintf("padding: 36px 12px; background-color: %s;", y2kEmailBg(emailCtx)) }>
<table
role="presentation"
class="y2k-email-container"
width="600"
cellspacing="0"
cellpadding="0"
border="0"
style={ fmt.Sprintf("max-width: 600px; background-color: %s; border: 2px solid %s; border-radius: 12px; overflow: hidden;", y2kEmailCard(emailCtx), y2kEmailBorder(emailCtx)) }
>
<!-- gradient header bar -->
<tr>
<td style={ fmt.Sprintf("padding: 18px 28px; background-image: linear-gradient(90deg, %s 0%%, %s 100%%); color: %s; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;", y2kEmailPrimary(emailCtx), y2kEmailSecondary(emailCtx), y2kEmailFg(emailCtx)) }>
if emailCtx.SiteSettings.SiteName != "" {
{ emailCtx.SiteSettings.SiteName }
} else {
y2k.fm
}
</td>
</tr>
<!-- body content -->
<tr>
<td class="y2k-email-content" style={ fmt.Sprintf("padding: 32px 40px; color: %s; font-size: 16px; line-height: 1.7;", y2kEmailFg(emailCtx)) }>
@templ.Raw(body)
</td>
</tr>
<!-- marquee pill footer -->
<tr>
<td align="center" style={ fmt.Sprintf("padding: 18px 28px; background-color: %s;", y2kEmailMuted(emailCtx)) }>
<table role="presentation" cellspacing="0" cellpadding="0" border="0">
<tr>
<td style={ fmt.Sprintf("padding: 8px 18px; border-radius: 999px; background-image: linear-gradient(90deg, %s 0%%, %s 100%%); color: %s; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em;", y2kEmailPrimary(emailCtx), y2kEmailSecondary(emailCtx), y2kEmailFg(emailCtx)) }>
* now playing * new drop friday * subscribe to the zine *
</td>
</tr>
</table>
if emailCtx.UnsubscribeURL != "" {
<p style={ fmt.Sprintf("margin: 12px 0 0; font-size: 11px; color: %s;", y2kEmailMutedFg(emailCtx)) }>
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", y2kEmailMutedFg(emailCtx)) }>
unsubscribe
</a>
</p>
}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
}

View File

@ -1,251 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// y2kEmailTemplate is the Y2K-branded email shell.
func y2kEmailTemplate(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: 18, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</title><style type=\"text/css\">\n\t\t\t\tbody, table, td, p, a, li { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }\n\t\t\t\ttable, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }\n\t\t\t\timg { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }\n\t\t\t\tbody { margin: 0 !important; padding: 0 !important; width: 100% !important; }\n\t\t\t\ta[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }\n\t\t\t\t@media only screen and (max-width: 620px) {\n\t\t\t\t\t.y2k-email-container { width: 100% !important; max-width: 100% !important; }\n\t\t\t\t\t.y2k-email-content { padding-left: 20px !important; padding-right: 20px !important; }\n\t\t\t\t}\n\t\t\t</style></head><body style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Inter Tight','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;", y2kEmailBg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 31, Col: 195}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.PreviewText != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div style=\"display:none;max-height:0;overflow:hidden;mso-hide:all;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 33, Col: 95}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<table role=\"presentation\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"center\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 36px 12px; background-color: %s;", y2kEmailBg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 37, Col: 110}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\"><table role=\"presentation\" class=\"y2k-email-container\" width=\"600\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("max-width: 600px; background-color: %s; border: 2px solid %s; border-radius: 12px; overflow: hidden;", y2kEmailCard(emailCtx), y2kEmailBorder(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 45, Col: 180}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><!-- gradient header bar --><tr><td style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 18px 28px; background-image: linear-gradient(90deg, %s 0%%, %s 100%%); color: %s; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;", y2kEmailPrimary(emailCtx), y2kEmailSecondary(emailCtx), y2kEmailFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 49, Col: 271}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.SiteName != "" {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, 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: 51, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "y2k.fm")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</td></tr><!-- body content --><tr><td class=\"y2k-email-content\" 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: 32px 40px; color: %s; font-size: 16px; line-height: 1.7;", y2kEmailFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 59, Col: 148}
}
_, 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, 12, "\">")
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, 13, "</td></tr><!-- marquee pill footer --><tr><td align=\"center\" style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 18px 28px; background-color: %s;", y2kEmailMuted(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 65, Col: 116}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\"><table role=\"presentation\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td style=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 8px 18px; border-radius: 999px; background-image: linear-gradient(90deg, %s 0%%, %s 100%%); color: %s; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em;", y2kEmailPrimary(emailCtx), y2kEmailSecondary(emailCtx), y2kEmailFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 68, Col: 313}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\">* now playing * new drop friday * subscribe to the zine *</td></tr></table>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.UnsubscribeURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<p 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: 12px 0 0; font-size: 11px; color: %s;", y2kEmailMutedFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 74, Col: 108}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 templ.SafeURL
templ_7745c5c3_Var13, 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: 75, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" 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("color: %s; text-decoration: underline;", y2kEmailMutedFg(emailCtx)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 75, Col: 150}
}
_, 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, "\">unsubscribe</a></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</td></tr></table></td></tr></table></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,57 +0,0 @@
package main
import (
"embed"
"io/fs"
"net/http"
"git.dev.alexdunmow.com/block/core/plugin"
)
//go:embed assets/*
var assetsFS embed.FS
//go:embed schemas/*
var schemasFS embed.FS
//go:embed presets.json
var presetsData []byte
//go:embed fonts.json
var fontsData []byte
//go:embed plugin.mod
var pluginModBytes []byte
// Assets returns the embedded assets filesystem rooted at assets/.
func Assets() fs.FS {
sub, _ := fs.Sub(assetsFS, "assets")
return sub
}
// Schemas returns the embedded schemas filesystem rooted at schemas/.
func Schemas() fs.FS {
sub, _ := fs.Sub(schemasFS, "schemas")
return sub
}
// AssetsHandler serves embedded asset files at /templates/y2k/...
func AssetsHandler() http.Handler {
return http.FileServer(http.FS(Assets()))
}
// ThemePresets returns the raw presets.json bytes.
func ThemePresets() []byte { return presetsData }
// BundledFonts returns the raw fonts.json bytes.
func BundledFonts() []byte { return fontsData }
// ThemeCSSManifest returns the theme's CSS manifest. Y2K injects keyframes
// (marquee-x, sparkle, metaball-morph) and chrome/bevel utility classes that
// Tailwind alone cannot express, plus CSS custom properties (--chrome-1..4,
// --mesh-a/b/c, --bevel-light/dark) into the host Tailwind input.
func ThemeCSSManifest() *plugin.CSSManifest {
return &plugin.CSSManifest{
InputCSSAppend: y2kInputCSS,
}
}

View File

@ -1,46 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// FooterChromeBlockMeta declares the chrome footer block.
var FooterChromeBlockMeta = blocks.BlockMeta{
Key: "footer_chrome",
Title: "Chrome Footer",
Description: "Beveled chrome footer strip with optional webring and copyright.",
Source: "y2k",
Category: blocks.CategoryLayout,
}
// FooterChromeBlock renders the footer.
// Content: {menuName, showWebring, copyright}
func FooterChromeBlock(ctx context.Context, content map[string]any) string {
// showWebring is stored as either bool (legacy/master-page seed) or string
// ("yes" | "no") from the schema's select editor.
show := true
switch v := content["showWebring"].(type) {
case bool:
show = v
case string:
show = v != "no"
}
data := FooterChromeData{
MenuName: getString(content, "menuName"),
ShowWebring: show,
Copyright: getString(content, "copyright"),
}
var buf bytes.Buffer
_ = footerChromeComponent(data).Render(ctx, &buf)
return buf.String()
}
// FooterChromeData is the typed shape for the templ component.
type FooterChromeData struct {
MenuName string
ShowWebring bool
Copyright string
}

View File

@ -1,24 +0,0 @@
package main
// footerChromeComponent renders the bottom chrome strip with optional webring.
templ footerChromeComponent(data FooterChromeData) {
<div class="y2k-chrome-bg y2k-bevel mt-8" data-block-key="y2k:footer_chrome">
<div class="max-w-6xl mx-auto px-4 py-4 flex flex-wrap items-center justify-between gap-3">
if data.MenuName != "" {
<nav class="flex gap-3 text-sm uppercase tracking-wider" data-menu-target={ data.MenuName }>
<span class="text-muted-foreground">{ "menu:" + data.MenuName }</span>
</nav>
}
if data.ShowWebring {
<div class="flex items-center gap-2">
<a href="#" class="y2k-webring-badge">prev</a>
<a href="#" class="y2k-webring-badge">ring</a>
<a href="#" class="y2k-webring-badge">next</a>
</div>
}
if data.Copyright != "" {
<span class="text-xs text-muted-foreground">{ data.Copyright }</span>
}
</div>
</div>
}

View File

@ -1,102 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// footerChromeComponent renders the bottom chrome strip with optional webring.
func footerChromeComponent(data FooterChromeData) 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=\"y2k-chrome-bg y2k-bevel mt-8\" data-block-key=\"y2k:footer_chrome\"><div class=\"max-w-6xl mx-auto px-4 py-4 flex flex-wrap items-center justify-between gap-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.MenuName != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav class=\"flex gap-3 text-sm uppercase tracking-wider\" data-menu-target=\"")
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: `footer_chrome.templ`, Line: 8, Col: 93}
}
_, 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, 3, "\"><span class=\"text-muted-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("menu:" + data.MenuName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer_chrome.templ`, Line: 9, Col: 66}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</span></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.ShowWebring {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"flex items-center gap-2\"><a href=\"#\" class=\"y2k-webring-badge\">prev</a> <a href=\"#\" class=\"y2k-webring-badge\">ring</a> <a href=\"#\" class=\"y2k-webring-badge\">next</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.Copyright != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"text-xs text-muted-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Copyright)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer_chrome.templ`, Line: 20, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,26 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// GlitterDividerBlockMeta declares the animated sparkle divider.
var GlitterDividerBlockMeta = blocks.BlockMeta{
Key: "glitter_divider",
Title: "Glitter Divider",
Description: "Animated sparkle horizontal rule.",
Source: "y2k",
Category: blocks.CategoryLayout,
}
// GlitterDividerBlock renders the divider.
// Content: {variant}
func GlitterDividerBlock(ctx context.Context, content map[string]any) string {
variant := getStringOr(content, "variant", "sparkle")
var buf bytes.Buffer
_ = glitterDividerComponent(variant).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,25 +0,0 @@
package main
// glitterDividerComponent renders a sparkle hr. The glyph used per variant is
// inert (decorative) — the divider itself carries role="separator".
templ glitterDividerComponent(variant string) {
<hr class="border-0 my-6" role="separator" data-block-key="y2k:glitter_divider" data-variant={ variant }/>
<div class="flex items-center justify-center gap-2 -mt-4 mb-4 text-accent" aria-hidden="true">
<span class="y2k-sparkle">{ glitterGlyph(variant) }</span>
<span class="y2k-sparkle" style="animation-delay: 200ms">{ glitterGlyph(variant) }</span>
<span class="y2k-sparkle" style="animation-delay: 400ms">{ glitterGlyph(variant) }</span>
</div>
}
func glitterGlyph(v string) string {
switch v {
case "stars":
return "*"
case "hearts":
return "<3"
case "chrome":
return "~"
default:
return "*"
}
}

View File

@ -1,107 +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"
// glitterDividerComponent renders a sparkle hr. The glyph used per variant is
// inert (decorative) — the divider itself carries role="separator".
func glitterDividerComponent(variant string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<hr class=\"border-0 my-6\" role=\"separator\" data-block-key=\"y2k:glitter_divider\" data-variant=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(variant)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `glitter_divider.templ`, Line: 6, Col: 103}
}
_, 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=\"flex items-center justify-center gap-2 -mt-4 mb-4 text-accent\" aria-hidden=\"true\"><span class=\"y2k-sparkle\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(glitterGlyph(variant))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `glitter_divider.templ`, Line: 8, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</span> <span class=\"y2k-sparkle\" style=\"animation-delay: 200ms\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(glitterGlyph(variant))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `glitter_divider.templ`, Line: 9, Col: 82}
}
_, 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, "</span> <span class=\"y2k-sparkle\" style=\"animation-delay: 400ms\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(glitterGlyph(variant))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `glitter_divider.templ`, Line: 10, Col: 82}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func glitterGlyph(v string) string {
switch v {
case "stars":
return "*"
case "hearts":
return "<3"
case "chrome":
return "~"
default:
return "*"
}
}
var _ = templruntime.GeneratedTemplate

20
go.mod
View File

@ -1,20 +0,0 @@
module git.dev.alexdunmow.com/block/themes/y2k
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,39 +0,0 @@
package main
import (
"bytes"
"context"
"strconv"
)
// Y2KHeadingBlock applies the chrome-stroke heading styling to the built-in
// heading block when the y2k theme is active.
// Content: {text, level, textClass}
func Y2KHeadingBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
textClass := getString(content, "textClass")
level := parseHeadingLevel(content)
var buf bytes.Buffer
_ = y2kHeadingComponent(level, text, textClass).Render(ctx, &buf)
return buf.String()
}
// parseHeadingLevel reads "level" from the content map, accepting float64
// (JSON), int (Go), or numeric strings. Returns 2 as a safe default.
func parseHeadingLevel(content map[string]any) int {
if v, ok := content["level"].(float64); ok {
l := int(v)
if l >= 1 && l <= 6 {
return l
}
}
if v, ok := content["level"].(int); ok && v >= 1 && v <= 6 {
return v
}
if v, ok := content["level"].(string); ok {
if l, err := strconv.Atoi(v); err == nil && l >= 1 && l <= 6 {
return l
}
}
return 2
}

View File

@ -1,41 +0,0 @@
package main
// y2kHeadingBaseClass returns the per-level Tailwind sizing classes.
func y2kHeadingBaseClass(level int) string {
switch level {
case 1:
return "text-5xl md:text-6xl"
case 2:
return "text-3xl md:text-4xl"
case 3:
return "text-2xl"
case 4:
return "text-xl"
case 5:
return "text-lg"
case 6:
return "text-base"
default:
return "text-3xl md:text-4xl"
}
}
// y2kHeadingComponent renders a heading with chrome stroke + drop shadow.
templ y2kHeadingComponent(level int, text, textClass string) {
switch level {
case 1:
<h1 class={ "y2k-heading font-bold text-foreground", y2kHeadingBaseClass(1), textClass }>{ text }</h1>
case 2:
<h2 class={ "y2k-heading font-bold text-foreground", y2kHeadingBaseClass(2), textClass }>{ text }</h2>
case 3:
<h3 class={ "y2k-heading font-semibold text-foreground", y2kHeadingBaseClass(3), textClass }>{ text }</h3>
case 4:
<h4 class={ "y2k-heading font-semibold text-foreground", y2kHeadingBaseClass(4), textClass }>{ text }</h4>
case 5:
<h5 class={ "y2k-heading font-semibold text-foreground", y2kHeadingBaseClass(5), textClass }>{ text }</h5>
case 6:
<h6 class={ "y2k-heading font-semibold text-foreground", y2kHeadingBaseClass(6), textClass }>{ text }</h6>
default:
<h2 class={ "y2k-heading font-bold text-foreground", y2kHeadingBaseClass(2), textClass }>{ text }</h2>
}
}

View File

@ -1,311 +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"
// y2kHeadingBaseClass returns the per-level Tailwind sizing classes.
func y2kHeadingBaseClass(level int) string {
switch level {
case 1:
return "text-5xl md:text-6xl"
case 2:
return "text-3xl md:text-4xl"
case 3:
return "text-2xl"
case 4:
return "text-xl"
case 5:
return "text-lg"
case 6:
return "text-base"
default:
return "text-3xl md:text-4xl"
}
}
// y2kHeadingComponent renders a heading with chrome stroke + drop shadow.
func y2kHeadingComponent(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{"y2k-heading font-bold text-foreground", y2kHeadingBaseClass(1), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 27, Col: 98}
}
_, 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{"y2k-heading font-bold text-foreground", y2kHeadingBaseClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var5).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var6)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 29, Col: 98}
}
_, 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{"y2k-heading font-semibold text-foreground", y2kHeadingBaseClass(3), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h3 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var8).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var9)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 31, Col: 102}
}
_, 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{"y2k-heading font-semibold text-foreground", y2kHeadingBaseClass(4), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var11...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<h4 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var11).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var12)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 33, Col: 102}
}
_, 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{"y2k-heading font-semibold text-foreground", y2kHeadingBaseClass(5), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var14...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h5 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var14).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var15)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 35, Col: 102}
}
_, 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{"y2k-heading font-semibold text-foreground", y2kHeadingBaseClass(6), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var17...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h6 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var17).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 37, Col: 102}
}
_, 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{"y2k-heading font-bold text-foreground", y2kHeadingBaseClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var20...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var20).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 39, Col: 98}
}
_, 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,48 +0,0 @@
package main
// getString safely extracts a string value from a content map.
func getString(content map[string]any, key string) string {
if v, ok := content[key].(string); ok {
return v
}
return ""
}
// getStringOr returns the string value for key or the default when missing/empty.
func getStringOr(content map[string]any, key, dflt string) string {
if v := getString(content, key); v != "" {
return v
}
return dflt
}
// getStringSlice extracts a []string from a JSON array of strings.
func getStringSlice(content map[string]any, key string) []string {
raw, ok := content[key].([]any)
if !ok {
return nil
}
out := make([]string, 0, len(raw))
for _, item := range raw {
if s, ok := item.(string); ok {
out = append(out, s)
}
}
return out
}
// getSlice extracts a []map[string]any from a JSON array of objects.
func getSlice(content map[string]any, key string) []map[string]any {
raw, ok := content[key].([]any)
if !ok {
return nil
}
out := make([]map[string]any, 0, len(raw))
for _, item := range raw {
if m, ok := item.(map[string]any); ok {
out = append(out, m)
}
}
return out
}

View File

@ -1,32 +0,0 @@
package main
import (
"bytes"
"context"
)
// Y2KImageBlock applies the chrome-bordered image frame to the built-in
// image block.
// Content: {url, alt, caption, class}
func Y2KImageBlock(ctx context.Context, content map[string]any) string {
data := Y2KImageData{
URL: getString(content, "url"),
Alt: getString(content, "alt"),
Caption: getString(content, "caption"),
Class: getString(content, "class"),
}
if data.URL == "" {
return ""
}
var buf bytes.Buffer
_ = y2kImageComponent(data).Render(ctx, &buf)
return buf.String()
}
// Y2KImageData is the typed shape for the templ component.
type Y2KImageData struct {
URL string
Alt string
Caption string
Class string
}

View File

@ -1,11 +0,0 @@
package main
// y2kImageComponent wraps an image in the chrome y2k-image-frame.
templ y2kImageComponent(data Y2KImageData) {
<figure class={ "my-4", data.Class }>
<img src={ data.URL } alt={ data.Alt } class="y2k-image-frame w-full h-auto"/>
if data.Caption != "" {
<figcaption class="mt-2 text-sm text-muted-foreground text-center">{ data.Caption }</figcaption>
}
</figure>
}

View File

@ -1,108 +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"
// y2kImageComponent wraps an image in the chrome y2k-image-frame.
func y2kImageComponent(data Y2KImageData) 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{"my-4", data.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, "<figure class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.URL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 6, Col: 21}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Alt)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 6, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" class=\"y2k-image-frame w-full h-auto\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Caption != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<figcaption class=\"mt-2 text-sm text-muted-foreground text-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Caption)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `image_override.templ`, Line: 8, Col: 84}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</figcaption>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</figure>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

17
main.go
View File

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

264
manifest.yaml Normal file
View File

@ -0,0 +1,264 @@
# Y2K theme — codeless .bnp manifest (WO-WZ-021 Phase B).
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
theme_presets: presets.json
bundled_fonts: fonts.json
master_pages: master_pages.json
system_templates:
- key: y2k
title: Y2K
description: "Liquid-chrome Y2K revival theme for indie musicians, zines, and merch shops."
page_templates:
- system: y2k
key: default
title: Default
description: "Chrome navbar + content + footer."
slots: [header, main, footer]
- system: y2k
key: landing
title: Landing
description: "Hero metaball + marquee + CTAs."
slots: [hero, marquee, main, cta, footer]
- system: y2k
key: article
title: Article / Zine Page
description: "Narrow column with sidebar tracklist."
slots: [header, main, aside, footer]
- system: y2k
key: full-width
title: Full Width
description: "Edge-to-edge gradient mesh canvas."
slots: [header, main, footer]
template_overrides:
- { template: y2k, block: heading }
- { template: y2k, block: text }
- { template: y2k, block: button }
- { template: y2k, block: image }
email_wrappers:
- y2k
css:
input_css_append: |
/* === y2k theme — chrome/bevel/mesh tokens === */
:root {
--chrome-1: 280 30% 95%;
--chrome-2: 200 40% 75%;
--chrome-3: 320 60% 60%;
--chrome-4: 280 40% 25%;
--mesh-a: 320 95% 58%;
--mesh-b: 185 90% 60%;
--mesh-c: 60 95% 70%;
--bevel-light: 0 0% 100%;
--bevel-dark: 280 60% 12%;
}
.dark {
--chrome-1: 280 30% 30%;
--chrome-2: 200 40% 45%;
--chrome-3: 320 60% 50%;
--chrome-4: 280 40% 10%;
--mesh-a: 320 100% 65%;
--mesh-b: 185 95% 55%;
--mesh-c: 60 100% 65%;
--bevel-light: 0 0% 100%;
--bevel-dark: 280 80% 4%;
}
/* === keyframes === */
@keyframes marquee-x {
from { transform: translate3d(0, 0, 0); }
to { transform: translate3d(-50%, 0, 0); }
}
@keyframes sparkle {
0%, 100% { opacity: 0; transform: scale(0.5) rotate(0deg); }
50% { opacity: 1; transform: scale(1) rotate(180deg); }
}
@keyframes metaball-morph {
0%, 100% { d: path("M 50 100 a 50 50 0 1 1 100 0 a 50 50 0 1 1 -100 0"); }
50% { d: path("M 40 95 a 60 55 0 1 1 120 5 a 55 60 0 1 1 -120 -5"); }
}
/* === chrome surface utilities === */
.y2k-chrome-bg {
background-image:
linear-gradient(180deg,
hsl(var(--chrome-1)) 0%,
hsl(var(--chrome-2)) 35%,
hsl(var(--chrome-3)) 70%,
hsl(var(--chrome-4)) 100%);
}
.y2k-mesh-bg {
background-image:
radial-gradient(at 20% 20%, hsl(var(--mesh-a) / 0.85) 0%, transparent 55%),
radial-gradient(at 80% 30%, hsl(var(--mesh-b) / 0.80) 0%, transparent 55%),
radial-gradient(at 50% 80%, hsl(var(--mesh-c) / 0.70) 0%, transparent 55%),
linear-gradient(135deg, hsl(var(--background)) 0%, hsl(var(--card)) 100%);
}
.y2k-bevel {
border: 2px solid hsl(var(--border));
box-shadow:
inset 0 1px 0 hsl(var(--bevel-light) / 0.85),
inset 0 -2px 0 hsl(var(--bevel-dark) / 0.35),
0 2px 6px hsl(var(--bevel-dark) / 0.30);
}
.y2k-bevel:active {
box-shadow:
inset 0 2px 4px hsl(var(--bevel-dark) / 0.45),
inset 0 -1px 0 hsl(var(--bevel-light) / 0.40);
}
.y2k-glow {
box-shadow: 0 0 24px hsl(var(--primary) / 0.45);
}
/* === marquee === */
.y2k-marquee {
overflow: hidden;
position: relative;
border-top: 2px solid hsl(var(--border));
border-bottom: 2px solid hsl(var(--border));
}
.y2k-marquee-track {
display: inline-flex;
gap: 3rem;
white-space: nowrap;
animation-name: marquee-x;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.y2k-marquee-track[data-speed="slow"] { animation-duration: 60s; }
.y2k-marquee-track[data-speed="medium"] { animation-duration: 30s; }
.y2k-marquee-track[data-speed="fast"] { animation-duration: 12s; }
.y2k-marquee:hover .y2k-marquee-track,
.y2k-marquee:focus-within .y2k-marquee-track {
animation-play-state: paused;
}
/* === sparkle decoration === */
.y2k-sparkle {
pointer-events: none;
animation: sparkle 1.6s ease-in-out infinite;
}
/* === metaball morph === */
.y2k-metaball {
animation: metaball-morph 6s ease-in-out infinite;
}
/* === webring 88x31 badge === */
.y2k-webring-badge {
display: inline-block;
width: 88px;
height: 31px;
line-height: 31px;
text-align: center;
font-size: 11px;
letter-spacing: 0.05em;
text-transform: uppercase;
border: 1px solid hsl(var(--border));
background-image: linear-gradient(180deg, hsl(var(--chrome-1)) 0%, hsl(var(--chrome-3)) 100%);
color: hsl(var(--primaryForeground));
text-decoration: none;
box-shadow: inset 0 1px 0 hsl(var(--bevel-light) / 0.6);
}
/* === holographic foil for NFT gallery === */
.y2k-foil {
position: relative;
isolation: isolate;
}
.y2k-foil::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0;
transition: opacity 220ms ease;
background-image: conic-gradient(
from 0deg,
hsl(var(--mesh-a) / 0.45),
hsl(var(--mesh-b) / 0.45),
hsl(var(--mesh-c) / 0.45),
hsl(var(--mesh-a) / 0.45)
);
mix-blend-mode: color-dodge;
filter: hue-rotate(0deg);
}
.y2k-foil:hover::after,
.y2k-foil:focus-within::after {
opacity: 1;
filter: hue-rotate(45deg);
}
/* === plastic button override === */
.y2k-button {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.65rem 1.25rem;
font-weight: 600;
border: 2px solid hsl(var(--border));
border-radius: 0.625rem;
background-image: linear-gradient(180deg,
hsl(var(--primary) / 0.95) 0%,
hsl(var(--primary)) 50%,
hsl(var(--primary) / 0.85) 100%);
color: hsl(var(--primaryForeground));
box-shadow:
inset 0 1px 0 hsl(var(--bevel-light) / 0.7),
inset 0 -2px 0 hsl(var(--bevel-dark) / 0.3),
0 2px 6px hsl(var(--bevel-dark) / 0.25);
transition: transform 90ms ease, box-shadow 90ms ease;
}
.y2k-button:hover { transform: translateY(-1px); }
.y2k-button:active {
transform: translateY(1px);
box-shadow:
inset 0 2px 4px hsl(var(--bevel-dark) / 0.45),
inset 0 -1px 0 hsl(var(--bevel-light) / 0.30);
}
.y2k-button:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}
/* === image chrome frame === */
.y2k-image-frame {
border-radius: 0.625rem;
border: 2px solid hsl(var(--border));
box-shadow:
inset 0 1px 0 hsl(var(--bevel-light) / 0.5),
0 4px 14px hsl(var(--bevel-dark) / 0.35);
}
/* === heading chrome stroke === */
.y2k-heading {
font-family: var(--font-heading, "Stretch Pro", "VT323", "Courier New", monospace);
letter-spacing: 0.04em;
text-shadow:
0 1px 0 hsl(var(--bevel-light) / 0.65),
0 2px 0 hsl(var(--bevel-dark) / 0.35),
0 6px 22px hsl(var(--primary) / 0.45);
}
/* === text holographic link underline === */
.y2k-text a {
background-image: linear-gradient(90deg,
hsl(var(--mesh-a)),
hsl(var(--mesh-b)),
hsl(var(--mesh-c)));
background-size: 100% 2px;
background-repeat: no-repeat;
background-position: 0 100%;
text-decoration: none;
}
/* === reduced motion overrides === */
@media (prefers-reduced-motion: reduce) {
.y2k-marquee-track,
.y2k-sparkle,
.y2k-metaball {
animation: none !important;
}
}

View File

@ -1,40 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// MarqueeBlockMeta declares the marquee ticker block.
var MarqueeBlockMeta = blocks.BlockMeta{
Key: "marquee",
Title: "Marquee Ticker",
Description: "Scrolling text marquee with chrome edges; pauses on hover.",
Source: "y2k",
Category: blocks.CategoryLayout,
}
// MarqueeBlock renders the marquee.
// Content: {items[], speed, direction}
func MarqueeBlock(ctx context.Context, content map[string]any) string {
data := MarqueeData{
Items: getStringSlice(content, "items"),
Speed: getStringOr(content, "speed", "medium"),
Direction: getStringOr(content, "direction", "left"),
}
if len(data.Items) == 0 {
data.Items = []string{"now playing", "new drop friday", "subscribe to the zine"}
}
var buf bytes.Buffer
_ = marqueeComponent(data).Render(ctx, &buf)
return buf.String()
}
// MarqueeData is the typed shape for the templ component.
type MarqueeData struct {
Items []string
Speed string
Direction string
}

View File

@ -1,29 +0,0 @@
package main
// marqueeComponent renders a horizontally scrolling marquee. The track is
// duplicated so the CSS keyframes can loop seamlessly. Speed is keyed off the
// data-speed attribute which the y2k stylesheet maps to a duration.
templ marqueeComponent(data MarqueeData) {
<div
class="y2k-marquee y2k-chrome-bg"
data-block-key="y2k:marquee"
data-direction={ data.Direction }
tabindex="0"
aria-label="ticker"
>
<div class="y2k-marquee-track py-2 px-4 text-sm uppercase tracking-wider text-foreground" data-speed={ data.Speed }>
for _, item := range data.Items {
<span class="inline-flex items-center gap-2">
<span class="text-accent">*</span>
{ item }
</span>
}
for _, item := range data.Items {
<span class="inline-flex items-center gap-2" aria-hidden="true">
<span class="text-accent">*</span>
{ item }
</span>
}
</div>
</div>
}

View File

@ -1,111 +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"
// marqueeComponent renders a horizontally scrolling marquee. The track is
// duplicated so the CSS keyframes can loop seamlessly. Speed is keyed off the
// data-speed attribute which the y2k stylesheet maps to a duration.
func marqueeComponent(data MarqueeData) 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=\"y2k-marquee y2k-chrome-bg\" data-block-key=\"y2k:marquee\" data-direction=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Direction)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `marquee.templ`, Line: 10, Col: 33}
}
_, 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, "\" tabindex=\"0\" aria-label=\"ticker\"><div class=\"y2k-marquee-track py-2 px-4 text-sm uppercase tracking-wider text-foreground\" data-speed=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Speed)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `marquee.templ`, Line: 14, Col: 115}
}
_, 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
}
for _, item := range data.Items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<span class=\"inline-flex items-center gap-2\"><span class=\"text-accent\">*</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(item)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `marquee.templ`, Line: 18, Col: 11}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
for _, item := range data.Items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"inline-flex items-center gap-2\" aria-hidden=\"true\"><span class=\"text-accent\">*</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(item)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `marquee.templ`, Line: 24, Col: 11}
}
_, 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, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

24
master_pages.json Normal file
View File

@ -0,0 +1,24 @@
[
{
"key": "y2k:default-master",
"title": "Y2K Default Master",
"page_templates": ["default", "article"],
"blocks": [
{ "block_key": "y2k:chrome_navbar", "title": "Chrome Navbar", "content": { "menuName": "main", "logoText": "y2k.fm" }, "slot": "header", "sort_order": 0 },
{ "block_key": "y2k:marquee", "title": "Top Marquee", "content": { "items": ["new drop friday", "listen on bandcamp", "subscribe to the zine"], "speed": "medium" }, "slot": "header", "sort_order": 1 },
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
{ "block_key": "y2k:footer_chrome", "title": "Chrome Footer", "content": { "showWebring": true, "copyright": "(c) y2k.fm" }, "slot": "footer", "sort_order": 0 }
]
},
{
"key": "y2k:landing-master",
"title": "Y2K Landing Master",
"page_templates": ["landing", "full-width"],
"blocks": [
{ "block_key": "y2k:metaball_hero", "title": "Metaball Hero", "content": { "headline": "new EP out now", "sub": "liquid chrome dreams", "ctaLabel": "listen" }, "slot": "hero", "sort_order": 0 },
{ "block_key": "y2k:marquee", "title": "Marquee", "content": { "items": ["* new * new * new"], "speed": "fast" }, "slot": "marquee", "sort_order": 0 },
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
{ "block_key": "y2k:footer_chrome", "title": "Chrome Footer", "content": { "showWebring": true }, "slot": "footer", "sort_order": 0 }
]
}
]

View File

@ -1,41 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// MerchCardBlockMeta declares the merch product card block.
var MerchCardBlockMeta = blocks.BlockMeta{
Key: "merch_card",
Title: "Merch Card",
Description: "Plastic-bevel product card with a corner sticker badge.",
Source: "y2k",
Category: blocks.CategoryContent,
}
// MerchCardBlock renders the merch card.
// Content: {title, price, image, buyHref, sticker}
func MerchCardBlock(ctx context.Context, content map[string]any) string {
data := MerchCardData{
Title: getString(content, "title"),
Price: getString(content, "price"),
Image: getString(content, "image"),
BuyHref: getString(content, "buyHref"),
Sticker: getString(content, "sticker"),
}
var buf bytes.Buffer
_ = merchCardComponent(data).Render(ctx, &buf)
return buf.String()
}
// MerchCardData is the typed shape for the templ component.
type MerchCardData struct {
Title string
Price string
Image string
BuyHref string
Sticker string
}

View File

@ -1,52 +0,0 @@
package main
// merchCardComponent renders a single merch card. The card class is applied
// to a grid container managed by the page; this block stays self-contained.
templ merchCardComponent(data MerchCardData) {
<article class="relative y2k-bevel rounded-2xl overflow-hidden bg-card flex flex-col" data-block-key="y2k:merch_card">
if data.Sticker != "" {
<span class="absolute top-3 right-3 z-10 y2k-button text-xs uppercase tracking-widest">{ stickerLabel(data.Sticker) }</span>
}
<div class="aspect-square bg-muted overflow-hidden y2k-image-frame border-0 rounded-none">
if data.Image != "" {
<img src={ data.Image } alt={ data.Title } class="w-full h-full object-cover"/>
} else {
<div class="w-full h-full flex items-center justify-center text-muted-foreground text-xs uppercase tracking-widest">
no image
</div>
}
</div>
<div class="p-4 flex flex-col gap-2 flex-1">
<h3 class="y2k-heading text-foreground text-lg">{ titleOr(data.Title) }</h3>
<div class="flex items-center justify-between gap-3 mt-auto">
<span class="text-foreground font-bold">{ priceOr(data.Price) }</span>
if data.BuyHref != "" {
<a href={ templ.SafeURL(data.BuyHref) } class="y2k-button text-sm">buy</a>
}
</div>
</div>
</article>
}
func stickerLabel(s string) string {
switch s {
case "sold-out":
return "sold out"
default:
return s
}
}
func titleOr(s string) string {
if s == "" {
return "untitled"
}
return s
}
func priceOr(s string) string {
if s == "" {
return "$--"
}
return s
}

View File

@ -1,178 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// merchCardComponent renders a single merch card. The card class is applied
// to a grid container managed by the page; this block stays self-contained.
func merchCardComponent(data MerchCardData) 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, "<article class=\"relative y2k-bevel rounded-2xl overflow-hidden bg-card flex flex-col\" data-block-key=\"y2k:merch_card\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Sticker != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<span class=\"absolute top-3 right-3 z-10 y2k-button text-xs uppercase tracking-widest\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(stickerLabel(data.Sticker))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `merch_card.templ`, Line: 8, Col: 118}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"aspect-square bg-muted overflow-hidden y2k-image-frame border-0 rounded-none\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Image != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Image)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `merch_card.templ`, Line: 12, Col: 25}
}
_, 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, 6, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `merch_card.templ`, Line: 12, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" class=\"w-full h-full object-cover\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"w-full h-full flex items-center justify-center text-muted-foreground text-xs uppercase tracking-widest\">no image</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div><div class=\"p-4 flex flex-col gap-2 flex-1\"><h3 class=\"y2k-heading text-foreground text-lg\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(titleOr(data.Title))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `merch_card.templ`, Line: 20, Col: 72}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</h3><div class=\"flex items-center justify-between gap-3 mt-auto\"><span class=\"text-foreground font-bold\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(priceOr(data.Price))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `merch_card.templ`, Line: 22, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.BuyHref != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 templ.SafeURL
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.BuyHref))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `merch_card.templ`, Line: 24, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" class=\"y2k-button text-sm\">buy</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func stickerLabel(s string) string {
switch s {
case "sold-out":
return "sold out"
default:
return s
}
}
func titleOr(s string) string {
if s == "" {
return "untitled"
}
return s
}
func priceOr(s string) string {
if s == "" {
return "$--"
}
return s
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,41 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// MetaballHeroBlockMeta declares the metaball hero block.
var MetaballHeroBlockMeta = blocks.BlockMeta{
Key: "metaball_hero",
Title: "Metaball Hero",
Description: "SVG metaballs on a gradient mesh canvas with headline + CTA.",
Source: "y2k",
Category: blocks.CategoryLayout,
}
// MetaballHeroBlock renders the metaball hero.
// Content: {headline, sub, ctaLabel, ctaHref, bgPreset}
func MetaballHeroBlock(ctx context.Context, content map[string]any) string {
data := MetaballHeroData{
Headline: getString(content, "headline"),
Sub: getString(content, "sub"),
CTALabel: getString(content, "ctaLabel"),
CTAHref: getString(content, "ctaHref"),
BgPreset: getStringOr(content, "bgPreset", "magenta-teal"),
}
var buf bytes.Buffer
_ = metaballHeroComponent(data).Render(ctx, &buf)
return buf.String()
}
// MetaballHeroData is the typed shape for the templ component.
type MetaballHeroData struct {
Headline string
Sub string
CTALabel string
CTAHref string
BgPreset string
}

View File

@ -1,64 +0,0 @@
package main
// metaballHeroComponent renders the headline + CTA on a gradient mesh with
// SVG metaballs (Gaussian-blur + threshold filter technique).
templ metaballHeroComponent(data MetaballHeroData) {
<section
class="relative overflow-hidden y2k-mesh-bg py-24 md:py-32"
data-block-key="y2k:metaball_hero"
data-bg-preset={ data.BgPreset }
>
<svg class="absolute inset-0 w-full h-full opacity-80" viewBox="0 0 600 400" preserveAspectRatio="none" aria-hidden="true">
<defs>
<filter id="y2k-metaball" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur in="SourceGraphic" stdDeviation="18" result="b1"/>
<feGaussianBlur in="b1" stdDeviation="8" result="b2"/>
<feColorMatrix in="b2" type="matrix" values="
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 22 -10" result="meta"/>
<feComposite in="meta" in2="SourceGraphic" operator="atop"/>
</filter>
<linearGradient id="y2k-mb-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="currentColor"/>
<stop offset="100%" stop-color="currentColor" stop-opacity="0.7"/>
</linearGradient>
</defs>
<g filter="url(#y2k-metaball)" fill="url(#y2k-mb-grad)" class="text-primary">
<circle class="y2k-metaball" cx="180" cy="200" r="80"/>
<circle class="y2k-metaball" cx="320" cy="160" r="70"/>
<circle class="y2k-metaball" cx="430" cy="240" r="90"/>
</g>
</svg>
<div class="relative z-10 max-w-4xl mx-auto px-4 text-center">
<div class="inline-block px-6 py-4 rounded-2xl bg-card/70 backdrop-blur-md y2k-bevel">
<h1 class="y2k-heading text-[4rem] md:text-[5rem] leading-tight font-bold text-foreground">
{ headlineOrDefault(data.Headline) }
</h1>
if data.Sub != "" {
<p class="text-lg md:text-xl text-foreground/80 mt-3 mb-6">{ data.Sub }</p>
}
if data.CTALabel != "" {
<a href={ templ.SafeURL(ctaHrefOr(data.CTAHref)) } class="y2k-button text-lg">
{ data.CTALabel }
</a>
}
</div>
</div>
</section>
}
func headlineOrDefault(s string) string {
if s == "" {
return "new EP out now"
}
return s
}
func ctaHrefOr(s string) string {
if s == "" {
return "#"
}
return s
}

View File

@ -1,137 +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"
// metaballHeroComponent renders the headline + CTA on a gradient mesh with
// SVG metaballs (Gaussian-blur + threshold filter technique).
func metaballHeroComponent(data MetaballHeroData) 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 y2k-mesh-bg py-24 md:py-32\" data-block-key=\"y2k:metaball_hero\" data-bg-preset=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.BgPreset)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metaball_hero.templ`, Line: 9, Col: 32}
}
_, 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, "\"><svg class=\"absolute inset-0 w-full h-full opacity-80\" viewBox=\"0 0 600 400\" preserveAspectRatio=\"none\" aria-hidden=\"true\"><defs><filter id=\"y2k-metaball\" x=\"-20%\" y=\"-20%\" width=\"140%\" height=\"140%\"><feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"18\" result=\"b1\"></feGaussianBlur> <feGaussianBlur in=\"b1\" stdDeviation=\"8\" result=\"b2\"></feGaussianBlur> <feColorMatrix in=\"b2\" type=\"matrix\" values=\"\n\t\t\t\t\t\t1 0 0 0 0\n\t\t\t\t\t\t0 1 0 0 0\n\t\t\t\t\t\t0 0 1 0 0\n\t\t\t\t\t\t0 0 0 22 -10\" result=\"meta\"></feColorMatrix> <feComposite in=\"meta\" in2=\"SourceGraphic\" operator=\"atop\"></feComposite></filter> <linearGradient id=\"y2k-mb-grad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"100%\"><stop offset=\"0%\" stop-color=\"currentColor\"></stop> <stop offset=\"100%\" stop-color=\"currentColor\" stop-opacity=\"0.7\"></stop></linearGradient></defs> <g filter=\"url(#y2k-metaball)\" fill=\"url(#y2k-mb-grad)\" class=\"text-primary\"><circle class=\"y2k-metaball\" cx=\"180\" cy=\"200\" r=\"80\"></circle> <circle class=\"y2k-metaball\" cx=\"320\" cy=\"160\" r=\"70\"></circle> <circle class=\"y2k-metaball\" cx=\"430\" cy=\"240\" r=\"90\"></circle></g></svg><div class=\"relative z-10 max-w-4xl mx-auto px-4 text-center\"><div class=\"inline-block px-6 py-4 rounded-2xl bg-card/70 backdrop-blur-md y2k-bevel\"><h1 class=\"y2k-heading text-[4rem] md:text-[5rem] leading-tight font-bold text-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(headlineOrDefault(data.Headline))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metaball_hero.templ`, Line: 37, Col: 39}
}
_, 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, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Sub != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<p class=\"text-lg md:text-xl text-foreground/80 mt-3 mb-6\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Sub)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metaball_hero.templ`, Line: 40, Col: 74}
}
_, 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, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.CTALabel != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<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(ctaHrefOr(data.CTAHref)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metaball_hero.templ`, Line: 43, Col: 53}
}
_, 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, "\" class=\"y2k-button text-lg\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.CTALabel)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `metaball_hero.templ`, Line: 44, Col: 21}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func headlineOrDefault(s string) string {
if s == "" {
return "new EP out now"
}
return s
}
func ctaHrefOr(s string) string {
if s == "" {
return "#"
}
return s
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,49 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// NFTGalleryBlockMeta declares the holographic-foil NFT gallery block.
// Presentational only - no signing UX in v0.1 per spec open question.
var NFTGalleryBlockMeta = blocks.BlockMeta{
Key: "nft_gallery",
Title: "NFT Gallery",
Description: "Holographic-foil hover gallery for NFT items (presentational only).",
Source: "y2k",
Category: blocks.CategoryContent,
}
// NFTGalleryBlock renders the gallery.
// Content: {items[{title, image, chain, href}]}
func NFTGalleryBlock(ctx context.Context, content map[string]any) string {
raw := getSlice(content, "items")
items := make([]NFTItem, 0, len(raw))
for _, r := range raw {
items = append(items, NFTItem{
Title: getString(r, "title"),
Image: getString(r, "image"),
Chain: getString(r, "chain"),
Href: getString(r, "href"),
})
}
var buf bytes.Buffer
_ = nftGalleryComponent(NFTGalleryData{Items: items}).Render(ctx, &buf)
return buf.String()
}
// NFTGalleryData is the typed shape for the templ component.
type NFTGalleryData struct {
Items []NFTItem
}
// NFTItem is a single gallery card.
type NFTItem struct {
Title string
Image string
Chain string
Href string
}

View File

@ -1,33 +0,0 @@
package main
// nftGalleryComponent renders a grid of foil-on-hover NFT cards.
templ nftGalleryComponent(data NFTGalleryData) {
<section class="my-6" data-block-key="y2k:nft_gallery">
if len(data.Items) == 0 {
<p class="text-center text-muted-foreground py-12">No items yet.</p>
} else {
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
for _, item := range data.Items {
<a
href={ templ.SafeURL(orHash(item.Href)) }
class="y2k-foil y2k-bevel block bg-card rounded-xl overflow-hidden no-underline text-foreground"
>
<div class="aspect-square bg-muted overflow-hidden">
if item.Image != "" {
<img src={ item.Image } alt={ item.Title } class="w-full h-full object-cover"/>
} else {
<div class="w-full h-full flex items-center justify-center text-muted-foreground text-xs uppercase tracking-widest">no image</div>
}
</div>
<div class="p-3 flex items-center justify-between gap-2">
<span class="font-semibold truncate">{ titleOr(item.Title) }</span>
if item.Chain != "" {
<span class="text-xs uppercase tracking-widest text-muted-foreground">{ item.Chain }</span>
}
</div>
</a>
}
</div>
}
</section>
}

View File

@ -1,156 +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"
// nftGalleryComponent renders a grid of foil-on-hover NFT cards.
func nftGalleryComponent(data NFTGalleryData) 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=\"my-6\" data-block-key=\"y2k:nft_gallery\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(data.Items) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<p class=\"text-center text-muted-foreground py-12\">No items yet.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, item := range data.Items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<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(orHash(item.Href)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `nft_gallery.templ`, Line: 12, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" class=\"y2k-foil y2k-bevel block bg-card rounded-xl overflow-hidden no-underline text-foreground\"><div class=\"aspect-square bg-muted overflow-hidden\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.Image != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(item.Image)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `nft_gallery.templ`, Line: 17, 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, 7, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(item.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `nft_gallery.templ`, Line: 17, Col: 48}
}
_, 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, 8, "\" class=\"w-full h-full object-cover\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"w-full h-full flex items-center justify-center text-muted-foreground text-xs uppercase tracking-widest\">no image</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div><div class=\"p-3 flex items-center justify-between gap-2\"><span class=\"font-semibold truncate\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(titleOr(item.Title))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `nft_gallery.templ`, Line: 23, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if item.Chain != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<span class=\"text-xs uppercase tracking-widest text-muted-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(item.Chain)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `nft_gallery.templ`, Line: 25, Col: 90}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -2,11 +2,8 @@
name = "y2k"
display_name = "Y2K"
scope = "@themes"
version = "0.1.2"
version = "0.2.0"
description = "Liquid-chrome Y2K revival theme for indie musicians, zines, and merch shops. Gradient mesh backgrounds, plastic-bevel buttons, marquee text, waveform blocks."
kind = "theme"
categories = ["templates"]
tags = ["retro", "chrome", "vaporwave", "pastel", "music", "creator", "nostalgia", "web3"]
[compatibility]
block_core = ">=0.15.0 <0.16.0"

View File

@ -1,173 +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 registers the y2k system template, its four page templates, all
// theme-specific blocks, the four built-in overrides, and the email wrapper.
//
// Call order: page templates can be registered any time, but schemas MUST load
// before any block Register call.
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
// 1. System template
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
Key: "y2k",
Title: "Y2K",
Description: "Liquid-chrome Y2K revival theme for indie musicians, zines, and merch shops.",
})
// 2. Page templates (slots match the spec verbatim)
if err := tr.RegisterPageTemplate("y2k", templates.PageTemplateMeta{
Key: "default",
Title: "Default",
Description: "Chrome navbar + content + footer.",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderY2K)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("y2k", templates.PageTemplateMeta{
Key: "landing",
Title: "Landing",
Description: "Hero metaball + marquee + CTAs.",
Slots: []string{"hero", "marquee", "main", "cta", "footer"},
}, wrap(RenderY2KLanding)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("y2k", templates.PageTemplateMeta{
Key: "article",
Title: "Article / Zine Page",
Description: "Narrow column with sidebar tracklist.",
Slots: []string{"header", "main", "aside", "footer"},
}, wrap(RenderY2KArticle)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("y2k", templates.PageTemplateMeta{
Key: "full-width",
Title: "Full Width",
Description: "Edge-to-edge gradient mesh canvas.",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderY2KFullWidth)); err != nil {
return err
}
// 3. Schemas first — must precede br.Register so they bind.
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
return err
}
// 4. Theme-specific blocks (10 total per spec §11).
br.Register(ChromeNavbarBlockMeta, ChromeNavbarBlock)
br.Register(MetaballHeroBlockMeta, MetaballHeroBlock)
br.Register(WaveformPlayerBlockMeta, WaveformPlayerBlock)
br.Register(MarqueeBlockMeta, MarqueeBlock)
br.Register(TracklistBlockMeta, TracklistBlock)
br.Register(MerchCardBlockMeta, MerchCardBlock)
br.Register(WebringBadgeBlockMeta, WebringBadgeBlock)
br.Register(GlitterDividerBlockMeta, GlitterDividerBlock)
br.Register(FooterChromeBlockMeta, FooterChromeBlock)
br.Register(NFTGalleryBlockMeta, NFTGalleryBlock)
// 5. Built-in overrides (only apply when y2k is the active theme).
br.RegisterTemplateOverride("y2k", "heading", Y2KHeadingBlock)
br.RegisterTemplateOverride("y2k", "text", Y2KTextBlock)
br.RegisterTemplateOverride("y2k", "button", Y2KButtonBlock)
br.RegisterTemplateOverride("y2k", "image", Y2KImageBlock)
// 6. Email wrapper for branded transactional mail.
tr.RegisterEmailWrapper("y2k", Y2KEmailWrapper)
return nil
}
// DefaultMasterPages returns the master pages that y2k seeds on first load.
// Block keys reference theme-owned blocks as "y2k:<key>"; the built-in slot
// block uses {"slotName":"main"} per CLAUDE.md.
func DefaultMasterPages() []plugin.MasterPageDefinition {
return []plugin.MasterPageDefinition{
{
Key: "y2k:default-master",
Title: "Y2K Default Master",
PageTemplates: []string{"default", "article"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "y2k:chrome_navbar",
Title: "Chrome Navbar",
Content: map[string]any{"menuName": "main", "logoText": "y2k.fm"},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "y2k:marquee",
Title: "Top Marquee",
Content: map[string]any{"items": []any{"new drop friday", "listen on bandcamp", "subscribe to the zine"}, "speed": "medium"},
Slot: "header",
SortOrder: 1,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{"slotName": "main"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "y2k:footer_chrome",
Title: "Chrome Footer",
Content: map[string]any{"showWebring": true, "copyright": "(c) y2k.fm"},
Slot: "footer",
SortOrder: 0,
},
},
},
{
Key: "y2k:landing-master",
Title: "Y2K Landing Master",
PageTemplates: []string{"landing", "full-width"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "y2k:metaball_hero",
Title: "Metaball Hero",
Content: map[string]any{"headline": "new EP out now", "sub": "liquid chrome dreams", "ctaLabel": "listen"},
Slot: "hero",
SortOrder: 0,
},
{
BlockKey: "y2k:marquee",
Title: "Marquee",
Content: map[string]any{"items": []any{"* new * new * new"}, "speed": "fast"},
Slot: "marquee",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{"slotName": "main"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "y2k:footer_chrome",
Title: "Chrome Footer",
Content: map[string]any{"showWebring": true},
Slot: "footer",
SortOrder: 0,
},
},
},
}
}

View File

@ -1,26 +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 Y2K theme.
// The CMS plugin loader looks up this exported symbol after dlopen.
var Registration = plugin.PluginRegistration{
Name: "y2k",
Version: plugin.ParseModVersion(pluginModBytes),
Register: func(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
return Register(tr, br)
},
Assets: func() http.Handler { return AssetsHandler() },
Schemas: func() fs.FS { return Schemas() },
ThemePresets: func() []byte { return ThemePresets() },
BundledFonts: func() []byte { return BundledFonts() },
MasterPages: func() []plugin.MasterPageDefinition { return DefaultMasterPages() },
CSSManifest: func() *plugin.CSSManifest { return ThemeCSSManifest() },
}

View File

@ -1,244 +0,0 @@
package main
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PageData is the per-render shape used by every y2k page template.
// It mirrors gotham's shape so the bn.Head/bn.BodyEnd helpers can be reused.
type PageData struct {
Title string
Slots map[string]string
ThemeMode string
ThemeCSS string
SiteSettings bn.SiteSettingsData
PageMeta bn.PageMeta
StructuredData string
CSSHash string
PageviewNonce string
EngagementConfig bn.EngagementConfig
}
func parseY2KPageData(doc map[string]any) PageData {
title := "Untitled"
if t, ok := doc["title"].(string); ok {
title = t
}
slots := map[string]string{}
if s, ok := doc["slots"].(map[string]string); ok {
slots = s
}
themeMode := "light"
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
themeMode = tm
}
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
}
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: bn.ParseSiteSettings(doc),
PageMeta: bn.ParsePageMeta(doc),
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: bn.ParseEngagementConfig(doc),
}
}
// === Default page template ===
templ Y2KDefault(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/y2k/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full">
@templ.Raw(data.Slots["header"])
</header>
<main class="flex-grow max-w-4xl mx-auto w-full px-4 py-8">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="py-20 text-center">
<p class="text-muted-foreground">No content blocks assigned to this page.</p>
</div>
}
</main>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// === Landing page template ===
templ Y2KLanding(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/y2k/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
@bn.AdminBypassBanner(data.SiteSettings)
<section class="w-full">
@templ.Raw(data.Slots["hero"])
</section>
<section class="w-full">
@templ.Raw(data.Slots["marquee"])
</section>
<main class="flex-grow">
if main, ok := data.Slots["main"]; ok && main != "" {
<div class="max-w-6xl mx-auto px-4 py-16">
@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>
}
// === Article / Zine page template ===
templ Y2KArticle(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/y2k/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
@bn.AdminBypassBanner(data.SiteSettings)
<header class="w-full">
@templ.Raw(data.Slots["header"])
</header>
<div class="max-w-6xl mx-auto w-full px-4 py-10 flex-grow grid gap-8 md:grid-cols-[1fr_280px]">
<main class="min-w-0">
if main, ok := data.Slots["main"]; ok && main != "" {
<article class="y2k-text prose max-w-none">
@templ.Raw(main)
</article>
} else {
<div class="py-20 text-center">
<p class="text-muted-foreground">No content blocks assigned to this page.</p>
</div>
}
</main>
<aside class="min-w-0">
@templ.Raw(data.Slots["aside"])
</aside>
</div>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// === Full-width page template ===
templ Y2KFullWidth(data PageData) {
<!DOCTYPE html>
<html lang="en">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/y2k/style.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col y2k-mesh-bg">
@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">
<p class="text-muted-foreground">No content blocks assigned to this page.</p>
</div>
}
</main>
<footer class="w-full mt-auto">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
func RenderY2K(ctx context.Context, doc map[string]any) templ.Component {
return Y2KDefault(parseY2KPageData(doc))
}
func RenderY2KLanding(ctx context.Context, doc map[string]any) templ.Component {
return Y2KLanding(parseY2KPageData(doc))
}
func RenderY2KArticle(ctx context.Context, doc map[string]any) templ.Component {
return Y2KArticle(parseY2KPageData(doc))
}
func RenderY2KFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return Y2KFullWidth(parseY2KPageData(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"
)
// PageData is the per-render shape used by every y2k page template.
// It mirrors gotham's shape so the bn.Head/bn.BodyEnd helpers can be reused.
type PageData struct {
Title string
Slots map[string]string
ThemeMode string
ThemeCSS string
SiteSettings bn.SiteSettingsData
PageMeta bn.PageMeta
StructuredData string
CSSHash string
PageviewNonce string
EngagementConfig bn.EngagementConfig
}
func parseY2KPageData(doc map[string]any) PageData {
title := "Untitled"
if t, ok := doc["title"].(string); ok {
title = t
}
slots := map[string]string{}
if s, ok := doc["slots"].(map[string]string); ok {
slots = s
}
themeMode := "light"
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
themeMode = tm
}
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
}
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: bn.ParseSiteSettings(doc),
PageMeta: bn.ParsePageMeta(doc),
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: bn.ParseEngagementConfig(doc),
}
}
// === Default page template ===
func Y2KDefault(data PageData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/y2k/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=\"bg-background text-foreground antialiased min-h-screen flex flex-col\">")
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 max-w-4xl mx-auto w-full px-4 py-8\">")
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\"><p class=\"text-muted-foreground\">No content blocks assigned to this page.</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
})
}
// === Landing page template ===
func Y2KLanding(data PageData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/y2k/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=\"bg-background text-foreground antialiased min-h-screen flex flex-col\">")
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><section class=\"w-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["marquee"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</section><main class=\"flex-grow\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"max-w-6xl mx-auto px-4 py-16\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</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, 17, "</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, 18, "</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, 19, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// === Article / Zine page template ===
func Y2KArticle(data PageData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<!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/y2k/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, 21, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<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, 23, "</header><div class=\"max-w-6xl mx-auto w-full px-4 py-10 flex-grow grid gap-8 md:grid-cols-[1fr_280px]\"><main class=\"min-w-0\">")
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, 24, "<article class=\"y2k-text prose max-w-none\">")
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, 25, "</article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<div class=\"py-20 text-center\"><p class=\"text-muted-foreground\">No content blocks assigned to this page.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</main><aside class=\"min-w-0\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["aside"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</aside></div><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, 29, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// === Full-width page template ===
func Y2KFullWidth(data PageData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<!doctype html><html lang=\"en\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/y2k/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, 32, "<body class=\"bg-background text-foreground antialiased min-h-screen flex flex-col y2k-mesh-bg\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<header class=\"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, 34, "</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, 35, "<div class=\"max-w-4xl mx-auto py-20 px-4 text-center\"><p class=\"text-muted-foreground\">No content blocks assigned to this page.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</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, 37, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func RenderY2K(ctx context.Context, doc map[string]any) templ.Component {
return Y2KDefault(parseY2KPageData(doc))
}
func RenderY2KLanding(ctx context.Context, doc map[string]any) templ.Component {
return Y2KLanding(parseY2KPageData(doc))
}
func RenderY2KArticle(ctx context.Context, doc map[string]any) templ.Component {
return Y2KArticle(parseY2KPageData(doc))
}
func RenderY2KFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return Y2KFullWidth(parseY2KPageData(doc))
}
var _ = templruntime.GeneratedTemplate

View File

@ -0,0 +1,53 @@
<!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; }
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
@media only screen and (max-width: 620px) {
.y2k-email-container { width: 100% !important; max-width: 100% !important; }
.y2k-email-content { padding-left: 20px !important; padding-right: 20px !important; }
}
</style>
</head>
<body style="background-color: {{ colors.background|default:"#0c0820" }}; margin: 0; padding: 0; font-family: &amp;#39;Inter Tight&amp;#39;,&amp;#39;Inter&amp;#39;,-apple-system,BlinkMacSystemFont,&amp;#39;Segoe UI&amp;#39;,Roboto,sans-serif;">
{% if preview_text %}<div style="display:none;max-height:0;overflow:hidden;mso-hide:all;">{{ preview_text }}</div>{% endif %}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center" style="padding: 36px 12px; background-color: {{ colors.background|default:"#0c0820" }};">
<table role="presentation" class="y2k-email-container" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px; background-color: {{ colors.card|default:"#150f2a" }}; border: 2px solid {{ colors.border|default:"#4d2b6f" }}; border-radius: 12px; overflow: hidden;">
<!-- gradient header bar -->
<tr>
<td style="padding: 18px 28px; background-image: linear-gradient(90deg, {{ colors.primary|default:"#ff4dd2" }} 0%, {{ colors.secondary|default:"#4ddff0" }} 100%); color: {{ colors.foreground|default:"#f4e8fa" }}; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;">
{% if site_name %}{{ site_name }}{% else %}y2k.fm{% endif %}
</td>
</tr>
<!-- body content -->
<tr>
<td class="y2k-email-content" style="padding: 32px 40px; color: {{ colors.foreground|default:"#f4e8fa" }}; font-size: 16px; line-height: 1.7;">{{ body|safe }}</td>
</tr>
<!-- marquee pill footer -->
<tr>
<td align="center" style="padding: 18px 28px; background-color: {{ colors.muted|default:"#1f1740" }};">
<table role="presentation" cellspacing="0" cellpadding="0" border="0">
<tr>
<td style="padding: 8px 18px; border-radius: 999px; background-image: linear-gradient(90deg, {{ colors.primary|default:"#ff4dd2" }} 0%, {{ colors.secondary|default:"#4ddff0" }} 100%); color: {{ colors.foreground|default:"#f4e8fa" }}; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em;">* now playing * new drop friday * subscribe to the zine *</td>
</tr>
</table>
{% if unsubscribe_url %}<p style="margin: 12px 0 0; font-size: 11px; color: {{ colors.mutedForeground|default:"#b9a8d3" }};"><a href="{{ unsubscribe_url }}" style="color: {{ colors.mutedForeground|default:"#b9a8d3" }}; text-decoration: underline;">unsubscribe</a></p>{% endif %}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1 @@
<a href="{{ url|default:"#" }}" class="y2k-button {{ class }}" data-variant="{{ variant|default:"primary" }}">{{ text|default:"button" }}</a>

View File

@ -0,0 +1 @@
{% if level|integer == 1 %}<h1 class="y2k-heading font-bold text-foreground text-5xl md:text-6xl {{ textClass }}">{{ text }}</h1>{% elif level|integer == 3 %}<h3 class="y2k-heading font-semibold text-foreground text-2xl {{ textClass }}">{{ text }}</h3>{% elif level|integer == 4 %}<h4 class="y2k-heading font-semibold text-foreground text-xl {{ textClass }}">{{ text }}</h4>{% elif level|integer == 5 %}<h5 class="y2k-heading font-semibold text-foreground text-lg {{ textClass }}">{{ text }}</h5>{% elif level|integer == 6 %}<h6 class="y2k-heading font-semibold text-foreground text-base {{ textClass }}">{{ text }}</h6>{% else %}<h2 class="y2k-heading font-bold text-foreground text-3xl md:text-4xl {{ textClass }}">{{ text }}</h2>{% endif %}

View File

@ -0,0 +1 @@
{% if url %}<figure class="my-4 {{ class }}"><img src="{{ url }}" alt="{{ alt }}" class="y2k-image-frame w-full h-auto">{% if caption %}<figcaption class="mt-2 text-sm text-muted-foreground text-center">{{ caption }}</figcaption>{% endif %}</figure>{% endif %}

View File

@ -0,0 +1 @@
<div class="y2k-text prose max-w-none {{ class }}">{{ text|safe }}</div>

View File

@ -0,0 +1,22 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<header class="w-full">
{{ slots.header|safe }}
</header>
<div class="max-w-6xl mx-auto w-full px-4 py-10 flex-grow grid gap-8 md:grid-cols-[1fr_280px]">
<main class="min-w-0">
{% if slots.main %}<article class="y2k-text prose max-w-none">{{ slots.main|safe }}</article>{% else %}<div class="py-20 text-center"><p class="text-muted-foreground">No content blocks assigned to this page.</p></div>{% endif %}
</main>
<aside class="min-w-0">
{{ slots.aside|safe }}
</aside>
</div>
<footer class="w-full mt-auto">
{{ slots.footer|safe }}
</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<header class="w-full">
{{ slots.header|safe }}
</header>
<main class="flex-grow max-w-4xl mx-auto w-full px-4 py-8">
{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center"><p class="text-muted-foreground">No content blocks assigned to this page.</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,17 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col y2k-mesh-bg">
{{ 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"><p class="text-muted-foreground">No content blocks assigned to this page.</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,23 @@
<!doctype html>
<html lang="en">
{{ head_html|safe }}
<body class="bg-background text-foreground antialiased min-h-screen flex flex-col">
{{ admin_banner_html|safe }}
<section class="w-full">
{{ slots.hero|safe }}
</section>
<section class="w-full">
{{ slots.marquee|safe }}
</section>
<main class="flex-grow">
{% if slots.main %}<div class="max-w-6xl mx-auto px-4 py-16">{{ 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,17 +0,0 @@
package main
import (
"bytes"
"context"
)
// Y2KTextBlock applies the holographic link underline to the built-in text
// block when the y2k theme is active.
// Content: {text, class}
func Y2KTextBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
class := getString(content, "class")
var buf bytes.Buffer
_ = y2kTextComponent(text, class).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,9 +0,0 @@
package main
// y2kTextComponent wraps prose in the y2k-text container so links pick up the
// holographic underline gradient.
templ y2kTextComponent(text, class string) {
<div class={ "y2k-text prose max-w-none", class }>
@templ.Raw(text)
</div>
}

View File

@ -1,68 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// y2kTextComponent wraps prose in the y2k-text container so links pick up the
// holographic underline gradient.
func y2kTextComponent(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{"y2k-text 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, "\">")
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,46 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// TracklistBlockMeta declares the CD-jewel-case tracklist block.
var TracklistBlockMeta = blocks.BlockMeta{
Key: "tracklist",
Title: "Tracklist",
Description: "Tabular tracklist styled like a CD jewel case insert.",
Source: "y2k",
Category: blocks.CategoryContent,
}
// TracklistBlock renders the tracklist.
// Content: {tracks[{title, duration, url}]}
func TracklistBlock(ctx context.Context, content map[string]any) string {
raw := getSlice(content, "tracks")
tracks := make([]TrackRow, 0, len(raw))
for _, r := range raw {
tracks = append(tracks, TrackRow{
Title: getString(r, "title"),
Duration: getString(r, "duration"),
URL: getString(r, "url"),
})
}
var buf bytes.Buffer
_ = tracklistComponent(TracklistData{Tracks: tracks}).Render(ctx, &buf)
return buf.String()
}
// TracklistData is the typed shape for the templ component.
type TracklistData struct {
Tracks []TrackRow
}
// TrackRow is a single tracklist row.
type TrackRow struct {
Title string
Duration string
URL string
}

View File

@ -1,39 +0,0 @@
package main
import "strconv"
// tracklistComponent renders the track table. Rows wrap on small screens so
// duration is never dropped (UAT 7.6).
templ tracklistComponent(data TracklistData) {
<section class="y2k-bevel rounded-xl overflow-hidden bg-card my-6" data-block-key="y2k:tracklist">
<header class="y2k-chrome-bg px-4 py-2 text-foreground uppercase tracking-wider text-xs">
tracklist
</header>
if len(data.Tracks) == 0 {
<p class="px-4 py-6 text-center text-muted-foreground">No tracks yet.</p>
} else {
<ol class="divide-y divide-border">
for i, t := range data.Tracks {
<li class="flex flex-wrap items-center gap-3 px-4 py-3">
<span class="w-6 shrink-0 text-muted-foreground text-sm tabular-nums">{ strconv.Itoa(i + 1) }</span>
<span class="flex-1 min-w-0 truncate text-foreground">
if t.URL != "" {
<a href={ templ.SafeURL(t.URL) } class="hover:underline">{ trackTitleOr(t.Title) }</a>
} else {
{ trackTitleOr(t.Title) }
}
</span>
<span class="text-muted-foreground text-sm tabular-nums whitespace-nowrap">{ durationOrDash(t.Duration) }</span>
</li>
}
</ol>
}
</section>
}
func durationOrDash(s string) string {
if s == "" {
return "--:--"
}
return s
}

View File

@ -1,148 +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 "strconv"
// tracklistComponent renders the track table. Rows wrap on small screens so
// duration is never dropped (UAT 7.6).
func tracklistComponent(data TracklistData) 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=\"y2k-bevel rounded-xl overflow-hidden bg-card my-6\" data-block-key=\"y2k:tracklist\"><header class=\"y2k-chrome-bg px-4 py-2 text-foreground uppercase tracking-wider text-xs\">tracklist</header>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(data.Tracks) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<p class=\"px-4 py-6 text-center text-muted-foreground\">No tracks yet.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<ol class=\"divide-y divide-border\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i, t := range data.Tracks {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<li class=\"flex flex-wrap items-center gap-3 px-4 py-3\"><span class=\"w-6 shrink-0 text-muted-foreground text-sm tabular-nums\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(i + 1))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tracklist.templ`, Line: 18, Col: 97}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</span> <span class=\"flex-1 min-w-0 truncate text-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if t.URL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<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(t.URL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tracklist.templ`, Line: 21, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" class=\"hover:underline\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(trackTitleOr(t.Title))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tracklist.templ`, Line: 21, Col: 88}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(trackTitleOr(t.Title))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tracklist.templ`, Line: 23, Col: 31}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</span> <span class=\"text-muted-foreground text-sm tabular-nums whitespace-nowrap\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(durationOrDash(t.Duration))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tracklist.templ`, Line: 26, Col: 109}
}
_, 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, 10, "</span></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</ol>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func durationOrDash(s string) string {
if s == "" {
return "--:--"
}
return s
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,42 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// WaveformPlayerBlockMeta declares the waveform audio player block.
var WaveformPlayerBlockMeta = blocks.BlockMeta{
Key: "waveform_player",
Title: "Waveform Player",
Description: "Soundcloud-style audio player with a canvas waveform.",
Source: "y2k",
Category: blocks.CategoryContent,
}
// WaveformPlayerBlock renders the audio block.
// Content: {trackTitle, audioUrl, artist, coverImage}
func WaveformPlayerBlock(ctx context.Context, content map[string]any) string {
data := WaveformPlayerData{
TrackTitle: getString(content, "trackTitle"),
AudioURL: getString(content, "audioUrl"),
Artist: getString(content, "artist"),
CoverImage: getString(content, "coverImage"),
}
if data.AudioURL == "" && data.TrackTitle == "" {
return ""
}
var buf bytes.Buffer
_ = waveformPlayerComponent(data).Render(ctx, &buf)
return buf.String()
}
// WaveformPlayerData is the typed shape for the templ component.
type WaveformPlayerData struct {
TrackTitle string
AudioURL string
Artist string
CoverImage string
}

View File

@ -1,49 +0,0 @@
package main
// waveformPlayerComponent renders a Soundcloud-style horizontal player. The
// canvas waveform is filled client-side from the audio source using WebAudio
// peak decode (v0.1 strategy noted in the spec's open questions).
templ waveformPlayerComponent(data WaveformPlayerData) {
<figure
class="y2k-bevel y2k-chrome-bg rounded-xl overflow-hidden flex items-stretch gap-4 p-3 my-4"
data-block-key="y2k:waveform_player"
>
<div class="w-20 h-20 shrink-0 y2k-image-frame overflow-hidden bg-muted">
if data.CoverImage != "" {
<img src={ data.CoverImage } alt={ data.TrackTitle } class="w-full h-full object-cover"/>
} else {
<svg viewBox="0 0 24 24" class="w-full h-full text-muted-foreground p-4" fill="currentColor" aria-hidden="true">
<path d="M9 19V6l11-3v13"/>
</svg>
}
</div>
<div class="flex-1 min-w-0 flex flex-col justify-between gap-2">
<figcaption class="flex items-baseline justify-between gap-3 min-w-0">
<span class="y2k-heading text-base truncate text-foreground">{ trackTitleOr(data.TrackTitle) }</span>
if data.Artist != "" {
<span class="text-xs uppercase tracking-wider text-muted-foreground shrink-0">{ data.Artist }</span>
}
</figcaption>
<canvas
class="w-full h-12 bg-muted/40 rounded"
data-y2k-waveform
data-audio-url={ data.AudioURL }
aria-label="audio waveform"
></canvas>
if data.AudioURL != "" {
<audio controls preload="none" class="w-full" src={ data.AudioURL }>
Your browser does not support the audio element.
</audio>
} else {
<p class="text-xs text-muted-foreground">No audio source configured.</p>
}
</div>
</figure>
}
func trackTitleOr(s string) string {
if s == "" {
return "untitled track"
}
return s
}

View File

@ -1,168 +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"
// waveformPlayerComponent renders a Soundcloud-style horizontal player. The
// canvas waveform is filled client-side from the audio source using WebAudio
// peak decode (v0.1 strategy noted in the spec's open questions).
func waveformPlayerComponent(data WaveformPlayerData) 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=\"y2k-bevel y2k-chrome-bg rounded-xl overflow-hidden flex items-stretch gap-4 p-3 my-4\" data-block-key=\"y2k:waveform_player\"><div class=\"w-20 h-20 shrink-0 y2k-image-frame overflow-hidden bg-muted\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.CoverImage != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<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.CoverImage)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `waveform_player.templ`, Line: 13, Col: 30}
}
_, 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, 3, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.TrackTitle)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `waveform_player.templ`, Line: 13, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" class=\"w-full h-full object-cover\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<svg viewBox=\"0 0 24 24\" class=\"w-full h-full text-muted-foreground p-4\" fill=\"currentColor\" aria-hidden=\"true\"><path d=\"M9 19V6l11-3v13\"></path></svg>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><div class=\"flex-1 min-w-0 flex flex-col justify-between gap-2\"><figcaption class=\"flex items-baseline justify-between gap-3 min-w-0\"><span class=\"y2k-heading text-base truncate text-foreground\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(trackTitleOr(data.TrackTitle))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `waveform_player.templ`, Line: 22, Col: 96}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Artist != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<span class=\"text-xs uppercase tracking-wider text-muted-foreground shrink-0\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Artist)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `waveform_player.templ`, Line: 24, Col: 96}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</figcaption><canvas class=\"w-full h-12 bg-muted/40 rounded\" data-y2k-waveform data-audio-url=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.AudioURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `waveform_player.templ`, Line: 30, Col: 34}
}
_, 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, 11, "\" aria-label=\"audio waveform\"></canvas>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.AudioURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<audio controls preload=\"none\" class=\"w-full\" src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.AudioURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `waveform_player.templ`, Line: 34, Col: 69}
}
_, 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, 13, "\">Your browser does not support the audio element.</audio>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<p class=\"text-xs text-muted-foreground\">No audio source configured.</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></figure>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func trackTitleOr(s string) string {
if s == "" {
return "untitled track"
}
return s
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,37 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// WebringBadgeBlockMeta declares the canonical 88x31 webring badge row.
var WebringBadgeBlockMeta = blocks.BlockMeta{
Key: "webring_badge",
Title: "Webring Badge",
Description: "Canonical 88x31 webring button row with prev/next/ring links.",
Source: "y2k",
Category: blocks.CategoryNavigation,
}
// WebringBadgeBlock renders the badge row.
// Content: {prevHref, nextHref, ringName}
func WebringBadgeBlock(ctx context.Context, content map[string]any) string {
data := WebringBadgeData{
PrevHref: getString(content, "prevHref"),
NextHref: getString(content, "nextHref"),
RingName: getStringOr(content, "ringName", "the y2k.fm ring"),
}
var buf bytes.Buffer
_ = webringBadgeComponent(data).Render(ctx, &buf)
return buf.String()
}
// WebringBadgeData is the typed shape for the templ component.
type WebringBadgeData struct {
PrevHref string
NextHref string
RingName string
}

View File

@ -1,18 +0,0 @@
package main
// webringBadgeComponent renders the prev/ring/next badge triplet at the
// canonical 88x31 button size.
templ webringBadgeComponent(data WebringBadgeData) {
<nav class="flex items-center gap-2 my-4" data-block-key="y2k:webring_badge" aria-label="webring">
<a href={ templ.SafeURL(orHash(data.PrevHref)) } class="y2k-webring-badge">prev</a>
<span class="y2k-webring-badge">{ data.RingName }</span>
<a href={ templ.SafeURL(orHash(data.NextHref)) } class="y2k-webring-badge">next</a>
</nav>
}
func orHash(s string) string {
if s == "" {
return "#"
}
return s
}

View File

@ -1,88 +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"
// webringBadgeComponent renders the prev/ring/next badge triplet at the
// canonical 88x31 button size.
func webringBadgeComponent(data WebringBadgeData) 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=\"flex items-center gap-2 my-4\" data-block-key=\"y2k:webring_badge\" aria-label=\"webring\"><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(orHash(data.PrevHref)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `webring_badge.templ`, Line: 7, Col: 48}
}
_, 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=\"y2k-webring-badge\">prev</a> <span class=\"y2k-webring-badge\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.RingName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `webring_badge.templ`, Line: 8, Col: 49}
}
_, 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, "</span> <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(orHash(data.NextHref)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `webring_badge.templ`, Line: 9, Col: 48}
}
_, 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, "\" class=\"y2k-webring-badge\">next</a></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func orHash(s string) string {
if s == "" {
return "#"
}
return s
}
var _ = templruntime.GeneratedTemplate