initial: theme plugin y2k
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously an unversioned directory inside ~/src/blockninja-themes/y2k. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
commit
49f9c90589
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
*.so
|
||||||
|
*.test
|
||||||
|
tmp/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
237
BUILD_REPORT.md
Normal file
237
BUILD_REPORT.md
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
# Y2K theme — build report
|
||||||
|
|
||||||
|
Implementation pass: 2026-06-06.
|
||||||
|
Reference style: templ (gotham-shaped, per spec §11 "Tech choice").
|
||||||
|
|
||||||
|
## What landed
|
||||||
|
|
||||||
|
### Registration (`registration.go`, `register.go`)
|
||||||
|
|
||||||
|
- `Registration` exported, `Name: "y2k"`, `Version: plugin.ParseModVersion(pluginModBytes)`.
|
||||||
|
- `tr.RegisterSystemTemplate(Key: "y2k", ...)` — exactly one call.
|
||||||
|
- `tr.RegisterPageTemplate("y2k", ...)` called four times with slots verbatim:
|
||||||
|
- `default` → `["header", "main", "footer"]`
|
||||||
|
- `landing` → `["hero", "marquee", "main", "cta", "footer"]`
|
||||||
|
- `article` → `["header", "main", "aside", "footer"]`
|
||||||
|
- `full-width` → `["header", "main", "footer"]`
|
||||||
|
- `br.LoadSchemasFromFS(Schemas())` invoked once, before any `br.Register(...)`.
|
||||||
|
- 10 theme blocks registered (unqualified keys, `Source: "y2k"`):
|
||||||
|
`chrome_navbar`, `metaball_hero`, `waveform_player`, `marquee`,
|
||||||
|
`tracklist`, `merch_card`, `webring_badge`, `glitter_divider`,
|
||||||
|
`footer_chrome`, `nft_gallery`.
|
||||||
|
- 4 overrides registered: `heading`, `text`, `button`, `image` via
|
||||||
|
`br.RegisterTemplateOverride("y2k", ...)`.
|
||||||
|
- `tr.RegisterEmailWrapper("y2k", Y2KEmailWrapper)` wired.
|
||||||
|
- `DefaultMasterPages()` seeds `y2k:default-master` (applies to `default`,
|
||||||
|
`article`) and `y2k:landing-master` (applies to `landing`, `full-width`)
|
||||||
|
with the exact block order from spec §"Master pages".
|
||||||
|
|
||||||
|
### Plugin metadata (`plugin.mod`)
|
||||||
|
|
||||||
|
- `name = "y2k"`, `display_name = "Y2K"` (3 chars, ≤40), `scope = "@themes"`.
|
||||||
|
- `kind = "theme"` (per global rule, not "plugin").
|
||||||
|
- `categories = ["templates", "media"]` — both whitelisted.
|
||||||
|
- `tags` — 8 entries (within the 5–9 UAT range).
|
||||||
|
- `version = "0.1.0"`.
|
||||||
|
- `[compatibility] block_core = ">=0.11.0 <0.12.0"` verbatim.
|
||||||
|
- Description 157 chars (≤240).
|
||||||
|
|
||||||
|
### Schemas (`schemas/*.schema.json`)
|
||||||
|
|
||||||
|
- 10 JSON Schema files, all draft-07.
|
||||||
|
- Property names match `content["…"]` reads in the Go files one-to-one (no
|
||||||
|
orphans either direction; verified by script during build).
|
||||||
|
- All `x-editor` values are members of the whitelist
|
||||||
|
`{text, richtext, media, color, select, number, slug, textarea, array,
|
||||||
|
collection, bucket-picker, menu-select, template-select, link}`.
|
||||||
|
|
||||||
|
### Presets (`presets.json`)
|
||||||
|
|
||||||
|
- Exactly 3 entries with `id` values `chrome-dream`, `cd-rom-after-hours`,
|
||||||
|
`bubblegum-trapper`.
|
||||||
|
- `chrome-dream` declares `mode: "both"` with both `lightColors` and
|
||||||
|
`darkColors` blocks (38 tokens across both).
|
||||||
|
- `cd-rom-after-hours` declares `mode: "dark"` with `darkColors` only.
|
||||||
|
- `bubblegum-trapper` declares `mode: "light"` with `lightColors` only.
|
||||||
|
- All 19 tokens present per colour block.
|
||||||
|
- Every value matches the regex `^\d+ \d+% \d+%$` (HSL triples, no `hsl()`
|
||||||
|
wrapper). Verified via Python regex check.
|
||||||
|
- Values copied verbatim from spec §4 tables.
|
||||||
|
|
||||||
|
### Fonts (`fonts.json`, `RECOMMENDED_FONTS.md`)
|
||||||
|
|
||||||
|
- `fonts.json` is `[]` per the wave-1 policy in `themes/docs/FONTS.md`.
|
||||||
|
- `RECOMMENDED_FONTS.md` lists the spec §5 fonts as Google Fonts picker
|
||||||
|
recommendations (Inter Tight via Google; VT323 via Google as the
|
||||||
|
open-licensed fallback for Stretch Pro; Departure Mono and Stretch Pro as
|
||||||
|
admin uploads).
|
||||||
|
- All `font-family` references in templates and CSS go through
|
||||||
|
`var(--font-heading|body|mono, <fallback-stack>)`. The headline heavy
|
||||||
|
fallback stack is `"Stretch Pro", "VT323", "Courier New", monospace`.
|
||||||
|
|
||||||
|
### CSS (`css.go` via `CSSManifest.InputCSSAppend`)
|
||||||
|
|
||||||
|
- Declares `--chrome-1..4`, `--mesh-a/b/c`, `--bevel-light/dark` custom
|
||||||
|
properties on `:root` and `.dark` (UAT 13.2, 13.3, 13.4).
|
||||||
|
- Defines exactly one `@keyframes marquee-x`, one `@keyframes sparkle`, one
|
||||||
|
`@keyframes metaball-morph` block (UAT 13.5).
|
||||||
|
- Provides utility classes:
|
||||||
|
- `.y2k-chrome-bg` (layered linear gradient through `--chrome-1..4`).
|
||||||
|
- `.y2k-mesh-bg` (radial-gradient mesh through `--mesh-a/b/c`).
|
||||||
|
- `.y2k-bevel` (`border: 2px solid hsl(var(--border))` + inset shadows
|
||||||
|
using `--bevel-light/dark`; UAT 13.4).
|
||||||
|
- `.y2k-button` (3D plastic bevel with `border-style: solid`,
|
||||||
|
`border-width: 2px`, `box-shadow` containing `inset` — UAT 13.9).
|
||||||
|
- `.y2k-marquee` + `.y2k-marquee-track` with `overflow: hidden` and the
|
||||||
|
`marquee-x` animation; `animation-play-state: paused` on hover/focus
|
||||||
|
(UAT 13.10, 6.7).
|
||||||
|
- `.y2k-webring-badge` at the canonical `width: 88px; height: 31px`
|
||||||
|
(UAT 13.11).
|
||||||
|
- `.y2k-foil::after` with `conic-gradient(` and `filter: hue-rotate(`
|
||||||
|
that activates on `:hover` (UAT 13.12).
|
||||||
|
- `.y2k-heading`, `.y2k-text`, `.y2k-image-frame`, `.y2k-sparkle`,
|
||||||
|
`.y2k-metaball`, `.y2k-glow`.
|
||||||
|
- Honors `prefers-reduced-motion: reduce` by disabling marquee, sparkle,
|
||||||
|
and metaball-morph animations (UAT 6.6).
|
||||||
|
- All colour references go through `hsl(var(--token))`; no hardcoded
|
||||||
|
hex/rgb appears in any `.go` or `.templ` file outside the email wrapper
|
||||||
|
(which uses hex fallbacks that translate the chrome-dream dark preset for
|
||||||
|
Gmail/Outlook compatibility, exactly as gotham does).
|
||||||
|
|
||||||
|
### Email wrapper
|
||||||
|
|
||||||
|
- `tr.RegisterEmailWrapper("y2k", Y2KEmailWrapper)` registered exactly once.
|
||||||
|
- 600px centred chrome-bordered card on a near-black background
|
||||||
|
(`#0c0820`-equivalent sourced from `emailCtx.Colors.Background` when set,
|
||||||
|
otherwise the chrome-dream dark token value).
|
||||||
|
- Gradient header bar uses `linear-gradient(90deg, primary, secondary)`.
|
||||||
|
- Inter Tight body with system fallback stack.
|
||||||
|
- Marquee footer pill renders the same items as the in-page marquee.
|
||||||
|
- Plain-text fallback is deferred to the CMS-default stripping (the wrapper
|
||||||
|
receives `body` already rendered; the marquee items are duplicated into a
|
||||||
|
visible string so a plain-text view still surfaces them).
|
||||||
|
|
||||||
|
## Build output
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cd /home/alex/src/blockninja/themes/y2k && go mod tidy
|
||||||
|
(exit 0)
|
||||||
|
|
||||||
|
$ cd /home/alex/src/blockninja/themes/y2k && /home/alex/go/bin/templ generate
|
||||||
|
(✓) Complete [ updates=16 duration=10ms ]
|
||||||
|
|
||||||
|
$ cd /home/alex/src/blockninja/themes/y2k && make
|
||||||
|
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o y2k.so .
|
||||||
|
(exit 0, no warnings)
|
||||||
|
|
||||||
|
$ ls -la y2k.so
|
||||||
|
21,543,872 bytes (≈21 MB) ELF 64-bit LSB shared object, stripped
|
||||||
|
```
|
||||||
|
|
||||||
|
`make` exits 0 with zero `warning` lines in stdout/stderr (UAT 2.1).
|
||||||
|
|
||||||
|
## Safety check
|
||||||
|
|
||||||
|
```
|
||||||
|
$ /tmp/check-safety . --plugin-dir /home/alex/src/blockninja/themes/y2k
|
||||||
|
(... 22 checks, all OK or SKIP for "no frontend sources" ...)
|
||||||
|
exit=0
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: the task brief references `cd ~/src/blockninja/backend && go run
|
||||||
|
./cmd/check-safety …`. The actual command path on this host is
|
||||||
|
`~/src/blockninja/check-safety/` (no `/backend/cmd/` subpath). The
|
||||||
|
invocation above is the host-local equivalent and exits 0.
|
||||||
|
|
||||||
|
Specifically verified:
|
||||||
|
- Check 21 (presets.json validation): OK — single preset file validated.
|
||||||
|
- Check 22 (no hand-rolled HTML sanitization): OK.
|
||||||
|
- No `git.dev.alexdunmow.com/block/cms/...` import boundary violations.
|
||||||
|
- No `^replace ` directives in `go.mod`.
|
||||||
|
- `block/core v0.11.1` pinned to match `cms/backend/go.mod`.
|
||||||
|
|
||||||
|
## Open items / deferred
|
||||||
|
|
||||||
|
The following spec/UAT requirements are intentionally deferred to a later
|
||||||
|
wave and would block sign-off on the running container but do not block
|
||||||
|
this build pass:
|
||||||
|
|
||||||
|
- **Bundled woff2 files**. Per `themes/docs/FONTS.md` wave-1 policy this
|
||||||
|
pass ships `fonts.json = []`. UAT §11 file-existence checks pass
|
||||||
|
trivially. Wave-2 will commission/licence Stretch Pro and Departure Mono
|
||||||
|
and bundle them.
|
||||||
|
- **`LICENSES.md`** — explicitly skipped per FONTS.md wave-1 policy
|
||||||
|
("No `LICENSES.md` needed in this pass").
|
||||||
|
- **Marketplace screenshots** (`marketplace/screenshots/*.png`, 1440×900).
|
||||||
|
Not produced in this pass — gated by a running container with deployed
|
||||||
|
CSS.
|
||||||
|
- **Demo content seed** ("Static Lagoon" fictional artist with 6 tracks,
|
||||||
|
4 merch items, 2 zine articles, 5 webring entries). Not produced — the
|
||||||
|
data plane is out of scope for the .so build pass.
|
||||||
|
- **Container-level UAT items**: §2.7–2.8 (`make rebuild`, log scrape),
|
||||||
|
§5.7 (no console errors at the rendered URL), §6.1–6.7 (Lighthouse /
|
||||||
|
computed-style assertions on a running site), §7.1–7.6 (responsive
|
||||||
|
viewport checks), §8.* (rendering each block in three states in the
|
||||||
|
browser), §9.7 (admin-replace round-trip), §10.2–10.4 (Litmus / Gmail /
|
||||||
|
Apple Mail / Outlook 365 previews), §11.4–11.5 (network-tab font 200
|
||||||
|
checks, FOUT trace), §12.* (marketplace assets, demo seed), §13.1, 13.6
|
||||||
|
(computed-style assertions on rendered pages), §14.* (three-theme
|
||||||
|
install regression), §15.* (three named reviewer sign-offs).
|
||||||
|
- **Waveform peak-data source**. The block ships with a placeholder
|
||||||
|
`<canvas data-y2k-waveform>` element that the CMS' client runtime can
|
||||||
|
populate via WebAudio decode (deferred to v0.2 per spec open question).
|
||||||
|
- **Cursor-trail sparkle script**. Only the CSS classes are shipped; the
|
||||||
|
document-level cursor-trail script is deferred and gated by
|
||||||
|
`data-editor-mode` on `<html>` per the spec's open question.
|
||||||
|
- **Mobile menu drawer JS**. The chrome navbar exposes the
|
||||||
|
`[aria-controls]` toggle button required by UAT §7.4 but the
|
||||||
|
drawer-open behaviour is owned by the host menu script.
|
||||||
|
|
||||||
|
## File inventory
|
||||||
|
|
||||||
|
```
|
||||||
|
y2k/
|
||||||
|
├── BUILD_REPORT.md (this file)
|
||||||
|
├── Makefile
|
||||||
|
├── RECOMMENDED_FONTS.md
|
||||||
|
├── assets/
|
||||||
|
│ └── placeholder.txt
|
||||||
|
├── button_override.{go,templ,_templ.go}
|
||||||
|
├── chrome_navbar.{go,templ,_templ.go}
|
||||||
|
├── css.go
|
||||||
|
├── email_wrapper.{go,templ,_templ.go}
|
||||||
|
├── embed.go
|
||||||
|
├── fonts.json
|
||||||
|
├── footer_chrome.{go,templ,_templ.go}
|
||||||
|
├── glitter_divider.{go,templ,_templ.go}
|
||||||
|
├── go.mod
|
||||||
|
├── go.sum
|
||||||
|
├── heading_override.{go,templ,_templ.go}
|
||||||
|
├── helpers.go
|
||||||
|
├── image_override.{go,templ,_templ.go}
|
||||||
|
├── marquee.{go,templ,_templ.go}
|
||||||
|
├── merch_card.{go,templ,_templ.go}
|
||||||
|
├── metaball_hero.{go,templ,_templ.go}
|
||||||
|
├── nft_gallery.{go,templ,_templ.go}
|
||||||
|
├── plugin.mod
|
||||||
|
├── presets.json
|
||||||
|
├── register.go
|
||||||
|
├── registration.go
|
||||||
|
├── schemas/
|
||||||
|
│ ├── chrome_navbar.schema.json
|
||||||
|
│ ├── footer_chrome.schema.json
|
||||||
|
│ ├── glitter_divider.schema.json
|
||||||
|
│ ├── marquee.schema.json
|
||||||
|
│ ├── merch_card.schema.json
|
||||||
|
│ ├── metaball_hero.schema.json
|
||||||
|
│ ├── nft_gallery.schema.json
|
||||||
|
│ ├── tracklist.schema.json
|
||||||
|
│ ├── waveform_player.schema.json
|
||||||
|
│ └── webring_badge.schema.json
|
||||||
|
├── template.{templ,_templ.go}
|
||||||
|
├── text_override.{go,templ,_templ.go}
|
||||||
|
├── tracklist.{go,templ,_templ.go}
|
||||||
|
├── waveform_player.{go,templ,_templ.go}
|
||||||
|
├── webring_badge.{go,templ,_templ.go}
|
||||||
|
└── y2k.so (21 MB)
|
||||||
|
```
|
||||||
27
Makefile
Normal file
27
Makefile
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Y2K — local build helpers (.so plugin workflow)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# make # build y2k.so locally (CGO + buildmode=plugin)
|
||||||
|
# make clean # remove y2k.so
|
||||||
|
# make templ # regenerate *_templ.go via templ generate
|
||||||
|
|
||||||
|
.PHONY: all clean templ help
|
||||||
|
|
||||||
|
PLUGIN_NAME := y2k
|
||||||
|
|
||||||
|
all: $(PLUGIN_NAME).so
|
||||||
|
|
||||||
|
$(PLUGIN_NAME).so: $(wildcard *.go) plugin.mod go.mod
|
||||||
|
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o $(PLUGIN_NAME).so .
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(PLUGIN_NAME).so
|
||||||
|
|
||||||
|
templ:
|
||||||
|
templ generate
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Targets:"
|
||||||
|
@echo " all Build $(PLUGIN_NAME).so locally (default)"
|
||||||
|
@echo " clean Remove $(PLUGIN_NAME).so"
|
||||||
|
@echo " templ Regenerate *_templ.go files"
|
||||||
50
RECOMMENDED_FONTS.md
Normal file
50
RECOMMENDED_FONTS.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Recommended fonts for the Y2K theme
|
||||||
|
|
||||||
|
The Y2K theme ships `fonts.json = []` per the wave-1 fonts policy
|
||||||
|
(`~/src/blockninja/themes/docs/FONTS.md`). The theme CSS consumes fonts via
|
||||||
|
the host CSS custom properties `--font-heading`, `--font-body`, `--font-mono`
|
||||||
|
with fallback stacks tuned to match the spec's intended aesthetic.
|
||||||
|
|
||||||
|
To get the intended Y2K look, an admin should open the typography panel and
|
||||||
|
assign the following Google Fonts (or upload the licensed picks where noted):
|
||||||
|
|
||||||
|
## Heading (display)
|
||||||
|
|
||||||
|
- **Stretch Pro** — commercial, not in the Google Fonts curated list.
|
||||||
|
- Source: `upload:Stretch Pro` (admin must source the woff2 separately and
|
||||||
|
upload via the typography panel's Upload tab).
|
||||||
|
- Why: matches the spec's primary display face for chrome-stretched
|
||||||
|
headlines.
|
||||||
|
- **VT323** — open license, available in Google Fonts.
|
||||||
|
- Source: `google:VT323`.
|
||||||
|
- Instruction: Open the typography panel, switch to the Google Fonts tab,
|
||||||
|
search "VT323", click Add, then assign it to the Heading slot. Use this
|
||||||
|
when Stretch Pro is not licensed for the site.
|
||||||
|
|
||||||
|
## Body
|
||||||
|
|
||||||
|
- **Inter Tight** — open license, available in Google Fonts.
|
||||||
|
- Source: `google:Inter Tight`.
|
||||||
|
- Instruction: Open the typography panel, switch to the Google Fonts tab,
|
||||||
|
search "Inter Tight", click Add, then assign it to the Body slot.
|
||||||
|
|
||||||
|
## Mono
|
||||||
|
|
||||||
|
- **Departure Mono** — open license, available as a single-foundry download.
|
||||||
|
- Source: `upload:Departure Mono` (sourced from
|
||||||
|
https://departuremono.com — admin uploads the woff2 via the Upload tab).
|
||||||
|
- Instruction: After uploading the woff2, assign it to the Mono slot in the
|
||||||
|
typography panel.
|
||||||
|
|
||||||
|
## What works out of the box
|
||||||
|
|
||||||
|
Until an admin assigns fonts, the theme renders with the following fallback
|
||||||
|
stacks (declared in `css.go` via the host Tailwind input):
|
||||||
|
|
||||||
|
- Headings: `var(--font-heading, "Stretch Pro", "VT323", "Courier New", monospace)`
|
||||||
|
- Body: inherited from the host `--font-body` with default
|
||||||
|
`system-ui, -apple-system, Segoe UI, Roboto, sans-serif`.
|
||||||
|
- Mono: inherited from the host `--font-mono` with default
|
||||||
|
`ui-monospace, "Departure Mono", "JetBrains Mono", monospace`.
|
||||||
|
|
||||||
|
These keep the theme readable and on-brand even before any picker action.
|
||||||
0
assets/.gitkeep
Normal file
0
assets/.gitkeep
Normal file
5
assets/placeholder.txt
Normal file
5
assets/placeholder.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Y2K theme assets directory.
|
||||||
|
|
||||||
|
This pass ships no bundled fonts or images (fonts.json = []).
|
||||||
|
Add woff2 files under fonts/ and reference them from fonts.json once font
|
||||||
|
licensing has been resolved (see RECOMMENDED_FONTS.md).
|
||||||
28
button_override.go
Normal file
28
button_override.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
9
button_override.templ
Normal file
9
button_override.templ
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
99
button_override_templ.go
Normal file
99
button_override_templ.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// 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
|
||||||
37
chrome_navbar.go
Normal file
37
chrome_navbar.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
38
chrome_navbar.templ
Normal file
38
chrome_navbar.templ
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
169
chrome_navbar_templ.go
Normal file
169
chrome_navbar_templ.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// 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
Normal file
228
css.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
83
email_wrapper.go
Normal file
83
email_wrapper.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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"
|
||||||
|
}
|
||||||
88
email_wrapper.templ
Normal file
88
email_wrapper.templ
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
251
email_wrapper_templ.go
Normal file
251
email_wrapper_templ.go
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
// 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
|
||||||
57
embed.go
Normal file
57
embed.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
1
fonts.json
Normal file
1
fonts.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
46
footer_chrome.go
Normal file
46
footer_chrome.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
24
footer_chrome.templ
Normal file
24
footer_chrome.templ
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
102
footer_chrome_templ.go
Normal file
102
footer_chrome_templ.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// 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
|
||||||
26
glitter_divider.go
Normal file
26
glitter_divider.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
25
glitter_divider.templ
Normal file
25
glitter_divider.templ
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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 "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
107
glitter_divider_templ.go
Normal file
107
glitter_divider_templ.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// 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
Normal file
20
go.mod
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module git.dev.alexdunmow.com/block/themes/y2k
|
||||||
|
|
||||||
|
go 1.26.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
git.dev.alexdunmow.com/block/core v0.11.1
|
||||||
|
github.com/a-h/templ v0.3.1020
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
connectrpc.com/connect v1.20.0 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
||||||
|
golang.org/x/mod v0.34.0 // indirect
|
||||||
|
golang.org/x/text v0.36.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
|
)
|
||||||
42
go.sum
Normal file
42
go.sum
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
|
||||||
|
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
|
||||||
|
git.dev.alexdunmow.com/block/core v0.11.1 h1:5b3Ps9CLor2FGyxw/Qovt27AGZKR5Xi1JZGi/TfliTA=
|
||||||
|
git.dev.alexdunmow.com/block/core v0.11.1/go.mod h1:ZwzEOxRDLDfrhQGqo6hLw01/C1z/aS4Dm9ljQMl0Bg4=
|
||||||
|
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||||
|
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
|
||||||
|
github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||||
|
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
|
||||||
|
golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
|
||||||
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
39
heading_override.go
Normal file
39
heading_override.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
41
heading_override.templ
Normal file
41
heading_override.templ
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
|
}
|
||||||
311
heading_override_templ.go
Normal file
311
heading_override_templ.go
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
// 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
|
||||||
55
helpers.go
Normal file
55
helpers.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBool extracts a boolean value, defaulting to dflt when absent.
|
||||||
|
func getBool(content map[string]any, key string, dflt bool) bool {
|
||||||
|
if v, ok := content[key].(bool); ok {
|
||||||
|
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
|
||||||
|
}
|
||||||
32
image_override.go
Normal file
32
image_override.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
11
image_override.templ
Normal file
11
image_override.templ
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
108
image_override_templ.go
Normal file
108
image_override_templ.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// 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
|
||||||
40
marquee.go
Normal file
40
marquee.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
29
marquee.templ
Normal file
29
marquee.templ
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
111
marquee_templ.go
Normal file
111
marquee_templ.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Code generated by templ - DO NOT EDIT.
|
||||||
|
|
||||||
|
// templ: version: v0.3.1020
|
||||||
|
package main
|
||||||
|
|
||||||
|
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||||
|
|
||||||
|
import "github.com/a-h/templ"
|
||||||
|
import templruntime "github.com/a-h/templ/runtime"
|
||||||
|
|
||||||
|
// 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
|
||||||
41
merch_card.go
Normal file
41
merch_card.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
52
merch_card.templ
Normal file
52
merch_card.templ
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
178
merch_card_templ.go
Normal file
178
merch_card_templ.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// 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
|
||||||
41
metaball_hero.go
Normal file
41
metaball_hero.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
64
metaball_hero.templ
Normal file
64
metaball_hero.templ
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
137
metaball_hero_templ.go
Normal file
137
metaball_hero_templ.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// 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
|
||||||
49
nft_gallery.go
Normal file
49
nft_gallery.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
33
nft_gallery.templ
Normal file
33
nft_gallery.templ
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
156
nft_gallery_templ.go
Normal file
156
nft_gallery_templ.go
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// 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
|
||||||
12
plugin.mod
Normal file
12
plugin.mod
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[plugin]
|
||||||
|
name = "y2k"
|
||||||
|
display_name = "Y2K"
|
||||||
|
scope = "@themes"
|
||||||
|
version = "0.1.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", "media"]
|
||||||
|
tags = ["retro", "chrome", "vaporwave", "pastel", "music", "creator", "nostalgia", "web3"]
|
||||||
|
|
||||||
|
[compatibility]
|
||||||
|
block_core = ">=0.11.0 <0.12.0"
|
||||||
110
presets.json
Normal file
110
presets.json
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "chrome-dream",
|
||||||
|
"name": "Liquid Chrome Dream",
|
||||||
|
"description": "Magenta-to-teal mesh on tinted glass cards. Works in both light and dark modes.",
|
||||||
|
"theme": {
|
||||||
|
"mode": "both",
|
||||||
|
"lightColors": {
|
||||||
|
"background": "300 40% 97%",
|
||||||
|
"foreground": "280 60% 12%",
|
||||||
|
"card": "300 30% 99%",
|
||||||
|
"cardForeground": "280 60% 12%",
|
||||||
|
"popover": "300 30% 99%",
|
||||||
|
"popoverForeground": "280 60% 12%",
|
||||||
|
"primary": "320 95% 58%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "185 90% 60%",
|
||||||
|
"secondaryForeground": "200 80% 10%",
|
||||||
|
"muted": "300 25% 94%",
|
||||||
|
"mutedForeground": "280 25% 40%",
|
||||||
|
"accent": "60 95% 70%",
|
||||||
|
"accentForeground": "280 60% 12%",
|
||||||
|
"destructive": "0 90% 60%",
|
||||||
|
"destructiveForeground": "0 0% 100%",
|
||||||
|
"border": "300 40% 88%",
|
||||||
|
"input": "300 30% 92%",
|
||||||
|
"ring": "320 95% 58%"
|
||||||
|
},
|
||||||
|
"darkColors": {
|
||||||
|
"background": "280 35% 6%",
|
||||||
|
"foreground": "300 30% 96%",
|
||||||
|
"card": "280 40% 10%",
|
||||||
|
"cardForeground": "300 30% 96%",
|
||||||
|
"popover": "280 40% 10%",
|
||||||
|
"popoverForeground": "300 30% 96%",
|
||||||
|
"primary": "320 100% 65%",
|
||||||
|
"primaryForeground": "280 40% 6%",
|
||||||
|
"secondary": "185 95% 55%",
|
||||||
|
"secondaryForeground": "200 80% 8%",
|
||||||
|
"muted": "280 30% 14%",
|
||||||
|
"mutedForeground": "300 20% 65%",
|
||||||
|
"accent": "60 100% 65%",
|
||||||
|
"accentForeground": "280 60% 8%",
|
||||||
|
"destructive": "0 95% 60%",
|
||||||
|
"destructiveForeground": "0 0% 100%",
|
||||||
|
"border": "300 40% 22%",
|
||||||
|
"input": "280 30% 16%",
|
||||||
|
"ring": "320 100% 65%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "cd-rom-after-hours",
|
||||||
|
"name": "CD-ROM After Hours",
|
||||||
|
"description": "After-hours CD-ROM glow — near-black with neon pink and cyan edge leaks.",
|
||||||
|
"theme": {
|
||||||
|
"mode": "dark",
|
||||||
|
"darkColors": {
|
||||||
|
"background": "260 50% 5%",
|
||||||
|
"foreground": "180 100% 88%",
|
||||||
|
"card": "260 50% 9%",
|
||||||
|
"cardForeground": "180 100% 88%",
|
||||||
|
"popover": "260 50% 9%",
|
||||||
|
"popoverForeground": "180 100% 88%",
|
||||||
|
"primary": "285 100% 62%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "180 100% 50%",
|
||||||
|
"secondaryForeground": "260 50% 5%",
|
||||||
|
"muted": "260 30% 14%",
|
||||||
|
"mutedForeground": "260 20% 60%",
|
||||||
|
"accent": "45 100% 60%",
|
||||||
|
"accentForeground": "260 50% 5%",
|
||||||
|
"destructive": "0 90% 55%",
|
||||||
|
"destructiveForeground": "0 0% 100%",
|
||||||
|
"border": "285 60% 25%",
|
||||||
|
"input": "260 30% 16%",
|
||||||
|
"ring": "285 100% 62%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bubblegum-trapper",
|
||||||
|
"name": "Bubblegum Trapper",
|
||||||
|
"description": "Lisa Frank Trapper-Keeper energy — pastel candy and sour apple accents.",
|
||||||
|
"theme": {
|
||||||
|
"mode": "light",
|
||||||
|
"lightColors": {
|
||||||
|
"background": "45 100% 97%",
|
||||||
|
"foreground": "300 50% 15%",
|
||||||
|
"card": "0 0% 100%",
|
||||||
|
"cardForeground": "300 50% 15%",
|
||||||
|
"popover": "0 0% 100%",
|
||||||
|
"popoverForeground": "300 50% 15%",
|
||||||
|
"primary": "330 95% 60%",
|
||||||
|
"primaryForeground": "0 0% 100%",
|
||||||
|
"secondary": "200 90% 70%",
|
||||||
|
"secondaryForeground": "200 80% 10%",
|
||||||
|
"muted": "45 60% 92%",
|
||||||
|
"mutedForeground": "300 25% 45%",
|
||||||
|
"accent": "90 75% 60%",
|
||||||
|
"accentForeground": "90 80% 10%",
|
||||||
|
"destructive": "0 85% 55%",
|
||||||
|
"destructiveForeground": "0 0% 100%",
|
||||||
|
"border": "330 50% 85%",
|
||||||
|
"input": "330 40% 90%",
|
||||||
|
"ring": "330 95% 60%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
173
register.go
Normal file
173
register.go
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
26
registration.go
Normal file
26
registration.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
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() },
|
||||||
|
}
|
||||||
29
schemas/chrome_navbar.schema.json
Normal file
29
schemas/chrome_navbar.schema.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Chrome Navbar",
|
||||||
|
"description": "Beveled chrome navigation bar with hover sparkle and brand mark.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"menuName": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Menu",
|
||||||
|
"description": "Menu to render as the primary nav.",
|
||||||
|
"x-editor": "menu-select",
|
||||||
|
"default": "main"
|
||||||
|
},
|
||||||
|
"logoText": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Logo Text",
|
||||||
|
"description": "Brand wordmark (used when no logo image is set).",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"logoImage": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Logo Image",
|
||||||
|
"description": "Optional brand image. Overrides logo text when set.",
|
||||||
|
"x-editor": "media",
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
schemas/footer_chrome.schema.json
Normal file
30
schemas/footer_chrome.schema.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Chrome Footer",
|
||||||
|
"description": "Beveled chrome strip with optional webring and copyright.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"menuName": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Menu",
|
||||||
|
"description": "Optional footer menu to render.",
|
||||||
|
"x-editor": "menu-select",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"showWebring": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Show Webring",
|
||||||
|
"description": "Render the 88x31 webring badge row.",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["yes", "no"],
|
||||||
|
"default": "yes"
|
||||||
|
},
|
||||||
|
"copyright": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Copyright",
|
||||||
|
"description": "Copyright line.",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
schemas/glitter_divider.schema.json
Normal file
15
schemas/glitter_divider.schema.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Glitter Divider",
|
||||||
|
"description": "Animated sparkle horizontal rule.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"variant": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Variant",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["sparkle", "stars", "hearts", "chrome"],
|
||||||
|
"default": "sparkle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
schemas/marquee.schema.json
Normal file
32
schemas/marquee.schema.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Marquee Ticker",
|
||||||
|
"description": "Scrolling text strip with chrome edges. Pauses on hover/focus.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Items",
|
||||||
|
"description": "Lines to scroll across the marquee.",
|
||||||
|
"x-editor": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"speed": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Speed",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["slow", "medium", "fast"],
|
||||||
|
"default": "medium"
|
||||||
|
},
|
||||||
|
"direction": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Direction",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["left", "right"],
|
||||||
|
"default": "left"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
schemas/merch_card.schema.json
Normal file
41
schemas/merch_card.schema.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Merch Card",
|
||||||
|
"description": "Plastic-bevel product card with a price sticker.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Product Title",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Price",
|
||||||
|
"description": "Display string e.g. $24.",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Image",
|
||||||
|
"x-editor": "media",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"buyHref": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Buy Link",
|
||||||
|
"x-editor": "link",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"sticker": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Sticker",
|
||||||
|
"description": "Corner sticker badge variant.",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["", "new", "hot", "sale", "sold-out"],
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
schemas/metaball_hero.schema.json
Normal file
44
schemas/metaball_hero.schema.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Metaball Hero",
|
||||||
|
"description": "Hero section with SVG metaballs on a gradient mesh canvas.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"headline": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Headline",
|
||||||
|
"description": "Display headline.",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"sub": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Sub-headline",
|
||||||
|
"description": "Supporting line under the headline.",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"ctaLabel": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "CTA Label",
|
||||||
|
"description": "Call-to-action button text.",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"ctaHref": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "CTA Link",
|
||||||
|
"description": "Where the CTA button points.",
|
||||||
|
"x-editor": "link",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"bgPreset": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Background Preset",
|
||||||
|
"description": "Which mesh anchor palette to use.",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["magenta-teal", "after-hours", "bubblegum"],
|
||||||
|
"default": "magenta-teal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
schemas/nft_gallery.schema.json
Normal file
41
schemas/nft_gallery.schema.json
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "NFT Gallery",
|
||||||
|
"description": "Holographic-foil hover gallery of NFT items (presentational only).",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Items",
|
||||||
|
"x-editor": "collection",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Title",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Image",
|
||||||
|
"x-editor": "media"
|
||||||
|
},
|
||||||
|
"chain": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Chain",
|
||||||
|
"x-editor": "select",
|
||||||
|
"enum": ["", "eth", "sol", "polygon", "base", "tezos"]
|
||||||
|
},
|
||||||
|
"href": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Link",
|
||||||
|
"x-editor": "link"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title"]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
schemas/tracklist.schema.json
Normal file
36
schemas/tracklist.schema.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Tracklist",
|
||||||
|
"description": "CD-jewel-case styled track table.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"tracks": {
|
||||||
|
"type": "array",
|
||||||
|
"title": "Tracks",
|
||||||
|
"x-editor": "collection",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Title",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Duration",
|
||||||
|
"description": "Display string e.g. 3:42.",
|
||||||
|
"x-editor": "text"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Audio URL",
|
||||||
|
"x-editor": "text"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["title"]
|
||||||
|
},
|
||||||
|
"default": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
schemas/waveform_player.schema.json
Normal file
34
schemas/waveform_player.schema.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Waveform Player",
|
||||||
|
"description": "Soundcloud-style audio block with a canvas waveform.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"trackTitle": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Track Title",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"audioUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Audio URL",
|
||||||
|
"description": "Direct URL to the audio file (mp3 / m4a / ogg).",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"artist": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Artist",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"coverImage": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Cover Image",
|
||||||
|
"description": "Square cover art shown next to the waveform.",
|
||||||
|
"x-editor": "media",
|
||||||
|
"default": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
schemas/webring_badge.schema.json
Normal file
27
schemas/webring_badge.schema.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Webring Badge",
|
||||||
|
"description": "Canonical 88x31 webring button row with prev/next/ring links.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"prevHref": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Previous Link",
|
||||||
|
"x-editor": "link",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"nextHref": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Next Link",
|
||||||
|
"x-editor": "link",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"ringName": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Ring Name",
|
||||||
|
"description": "Webring label displayed on the centre badge.",
|
||||||
|
"x-editor": "text",
|
||||||
|
"default": "the y2k.fm ring"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
244
template.templ
Normal file
244
template.templ
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
510
template_templ.go
Normal file
510
template_templ.go
Normal file
@ -0,0 +1,510 @@
|
|||||||
|
// 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
|
||||||
17
text_override.go
Normal file
17
text_override.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
9
text_override.templ
Normal file
9
text_override.templ
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
68
text_override_templ.go
Normal file
68
text_override_templ.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// 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
|
||||||
46
tracklist.go
Normal file
46
tracklist.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
39
tracklist.templ
Normal file
39
tracklist.templ
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
148
tracklist_templ.go
Normal file
148
tracklist_templ.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
// 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
|
||||||
42
waveform_player.go
Normal file
42
waveform_player.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
49
waveform_player.templ
Normal file
49
waveform_player.templ
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
168
waveform_player_templ.go
Normal file
168
waveform_player_templ.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// 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
|
||||||
37
webring_badge.go
Normal file
37
webring_badge.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
18
webring_badge.templ
Normal file
18
webring_badge.templ
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
88
webring_badge_templ.go
Normal file
88
webring_badge_templ.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// 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
|
||||||
Loading…
x
Reference in New Issue
Block a user