initial: theme plugin scifi-clean
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously an unversioned directory inside ~/src/blockninja-themes/scifi-clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
commit
96b87b3e81
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
*.so
|
||||
*.test
|
||||
tmp/
|
||||
.idea/
|
||||
.vscode/
|
||||
182
BUILD_REPORT.md
Normal file
182
BUILD_REPORT.md
Normal file
@ -0,0 +1,182 @@
|
||||
# Sci-Fi Clean — Build Report (v0.1.0, wave-1 pass)
|
||||
|
||||
## What landed
|
||||
|
||||
### Module scaffolding
|
||||
- `plugin.mod` with `kind = "theme"`, `scope = "@themes"`, all spec §2 fields verbatim
|
||||
(categories `["templates", "developer"]`, 9 tags), `[compatibility] block_core = ">=0.11.0 <0.12.0"`.
|
||||
- `go.mod` pinned to `git.dev.alexdunmow.com/block/core v0.11.1`, `go 1.26.4`, no `replace` directives.
|
||||
- `Makefile` with `all` (default), `clean`, `templ`, `help` targets. The build line is
|
||||
`CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o scifi-clean.so .`.
|
||||
- `embed.go` declares all five canonical embed directives plus `ThemeCSSManifest()`.
|
||||
|
||||
### Registration
|
||||
- One `RegisterSystemTemplate({Key: "scifi-clean", ...})`.
|
||||
- Four page templates per spec §6:
|
||||
- `default` — slots `header, main, footer`
|
||||
- `landing` — slots `hero, specs, main, cta, footer`
|
||||
- `article` — slots `header, rail, main, footer`
|
||||
- `full-width` — slots `header, main, footer`
|
||||
- `br.LoadSchemasFromFS(Schemas())` runs **before** any `br.Register(...)`. UAT §3.6.
|
||||
- Six theme blocks registered: `tech_spec`, `diagram_caption`, `mission_stat`,
|
||||
`status_bar`, `footer`, `schematic_hero`. Each has `Source = "scifi-clean"` and an
|
||||
unqualified `Key`. Addressed at runtime as `scifi-clean:<key>`.
|
||||
- Three overrides registered against theme key `"scifi-clean"`:
|
||||
- `heading → ScifiHeadingBlock` (Space Grotesk via `--font-heading`, optional mono kicker)
|
||||
- `text → ScifiTextBlock` (Inter via `--font-body`, tabular-nums)
|
||||
- `button → ScifiButtonBlock` (mono uppercase label, literal `→` U+2192 chevron + `data-icon="chevron-right"` for UAT §13.14)
|
||||
- `tr.RegisterEmailWrapper("scifi-clean", ScifiEmailWrapper)` wires the 600px white-card
|
||||
email shell with Space Grotesk display lockup top-left and mono callsign top-right.
|
||||
|
||||
### Master pages
|
||||
`DefaultMasterPages()` returns two entries:
|
||||
|
||||
- `scifi-clean:default-master`, attached to `default`, `article`, `full-width`.
|
||||
Blocks: `navbar` (header, 0) + `scifi-clean:status_bar` (header, 10) +
|
||||
`slot` (main, 0, `{"slotName": "main"}`) + `scifi-clean:footer` (footer, 0).
|
||||
Matches spec §7 / UAT §9 byte-for-byte.
|
||||
- `scifi-clean:landing-master`, attached to `landing`.
|
||||
Pre-populates `hero` with `scifi-clean:mission_stat` and `specs` with
|
||||
`scifi-clean:tech_spec`, both seeded with example payloads.
|
||||
|
||||
### Schemas
|
||||
Six `schemas/*.schema.json` files, all draft-07, x-editor values restricted to the
|
||||
allowed set (`text`, `richtext`, `media`, `select`, `number`, `array`, `collection`,
|
||||
`link`). Property names match the corresponding Go content reads exactly.
|
||||
|
||||
### Presets
|
||||
`presets.json` is a JSON array of length 3 in the order required by UAT §5.1:
|
||||
|
||||
1. `flightline` — mode `light`, only `lightColors`. Declares
|
||||
`"primary": "215 90% 45%"` and `"accent": "18 95% 55%"` byte-exactly (UAT §13.1).
|
||||
2. `mission-control` — mode `dark`, only `darkColors`. Declares
|
||||
`"background": "220 16% 7%"` and `"accent": "18 100% 60%"` byte-exactly (UAT §13.2).
|
||||
3. `cleanroom` — mode `both`, **both** `lightColors` and `darkColors`.
|
||||
|
||||
All three presets carry all 19 tokens in each declared color block. Every value is
|
||||
an HSL triple string of the shape `^\d+ \d+% \d+%$` (no `hsl()` wrappers).
|
||||
|
||||
### CSS manifest
|
||||
`ThemeCSSManifest()` returns a `*plugin.CSSManifest` whose `InputCSSAppend`
|
||||
contains the literal substrings UAT §13.4 grep-greps: `'Space Grotesk'`,
|
||||
`'JetBrains Mono'`, `.hairline`, `.bg-grid`. The `.hairline` declaration is
|
||||
exactly `border: 1px solid hsl(var(--border));` (UAT §13.5).
|
||||
|
||||
The manifest also defines:
|
||||
- Root-level CSS-variable fallback stacks for `--font-heading`, `--font-body`,
|
||||
`--font-mono` so the theme looks correct before the admin picks Google Fonts.
|
||||
- `.bg-grid` blueprint grid utility.
|
||||
- `.scrim` utility for full-bleed hero text (UAT §6 scrim requirement).
|
||||
- `.scifi-focus` outlined focus ring derived from `--ring`.
|
||||
- `.scifi-chevron` Unicode arrow suffix for button labels.
|
||||
|
||||
### Fonts policy (wave-1)
|
||||
- `fonts.json` is the literal `[]`. No woff2 files bundled in this pass.
|
||||
- `RECOMMENDED_FONTS.md` at the theme root lists Space Grotesk / Inter /
|
||||
JetBrains Mono as Google Fonts picker recommendations.
|
||||
- No `LICENSES.md` (nothing is bundled to license).
|
||||
|
||||
## Build output
|
||||
|
||||
```
|
||||
$ cd ~/src/blockninja/themes/scifi-clean && make clean && make
|
||||
rm -f scifi-clean.so
|
||||
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o scifi-clean.so .
|
||||
$ ls -la scifi-clean.so
|
||||
-rw-rw-r-- 1 alex alex 21535264 ... scifi-clean.so (≈ 20.5 MiB)
|
||||
$ file scifi-clean.so
|
||||
ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped
|
||||
```
|
||||
|
||||
`make 2>&1 | grep -Ei 'warning|error'` returns empty. `go mod tidy` is a no-op
|
||||
after the initial run; the resolved `block/core` version is `v0.11.1`.
|
||||
|
||||
## Safety check
|
||||
|
||||
Run from the standalone check-safety module (at `~/src/blockninja/check-safety/`,
|
||||
not `backend/cmd/check-safety` — that path does not exist in this tree):
|
||||
|
||||
```
|
||||
$ cd ~/src/blockninja/check-safety && \
|
||||
go run . ~/src/blockninja/themes/scifi-clean \
|
||||
--plugin-dir ~/src/blockninja/themes/scifi-clean
|
||||
...
|
||||
EXIT: 0
|
||||
```
|
||||
|
||||
All 22 checks PASS or SKIP. Specifically:
|
||||
|
||||
- Check 2c (SDK import boundary): OK on v0.11.1.
|
||||
- Check 6 (no hardcoded colors in .templ/.ninjatpl files): OK.
|
||||
- Check 11 (no placeholder code): OK.
|
||||
- Check 17 (no TODO markers): OK.
|
||||
- Check 21 (presets.json validation against `theme.Theme`): OK.
|
||||
|
||||
The only non-OK output is **Check 2e** (any usage), which is a **WARN**, not a FAIL.
|
||||
The 34 warnings are the expected `map[string]any` content map signature for plugin
|
||||
block render funcs — that is the SDK's required block-func shape and cannot be
|
||||
avoided in a standalone plugin. Same as `gotham/` and `lcars/`.
|
||||
|
||||
## Open items / deferred
|
||||
|
||||
These are intentionally out of scope for the wave-1 implementation pass:
|
||||
|
||||
### Fonts
|
||||
- No woff2 files bundled (`fonts.json = []` per `themes/docs/FONTS.md`).
|
||||
Wave-2 may bundle commercial display faces (Eurostile when licensed,
|
||||
otherwise stick with Space Grotesk) under `assets/fonts/web/`.
|
||||
- Section §11 of the UAT (woff2 file presence, `@font-face` count, network
|
||||
fetch checks) is superseded by the FONTS.md wave-1 policy, which only
|
||||
requires `fonts.json` to parse as JSON, `RECOMMENDED_FONTS.md` to exist,
|
||||
and CSS to consume `var(--font-*)`. All three pass.
|
||||
|
||||
### Live instance / `make rebuild`
|
||||
- `make rebuild` is **not** wired in this `Makefile`. Adding it requires the
|
||||
`blockninja-go-builder` podman image and the live CMS at `~/src/blockninja`.
|
||||
Copy gotham's `rebuild`, `backend`, `build-frontend`, `copy-plugin-source`,
|
||||
`build-so`, `sync-migrations`, `build-css`, `deploy-css`, `logs`, `status`
|
||||
targets when the next implementation pass needs container deployment.
|
||||
|
||||
### Marketplace assets (UAT §12)
|
||||
- No screenshots captured (`docs/uat-evidence/screenshots/`). Requires a
|
||||
running instance.
|
||||
- No `Project Aurora` demo content seeded. Requires admin tooling and a
|
||||
populated database.
|
||||
- No `docs/launch-copy.txt`. Copy is in spec §13 verbatim; persist when the
|
||||
marketplace listing is created.
|
||||
|
||||
### Versioning / git
|
||||
- The theme is not under git in this scope; UAT §1.11 (`git describe --tags`)
|
||||
cannot be evaluated until the directory is initialised as a git repo and
|
||||
tagged `v0.1.0`.
|
||||
|
||||
### Email wrapper hex fallbacks
|
||||
- `email_wrapper.templ` carries a small set of hardcoded hex values
|
||||
(`#F6F7F8`, `#FFFFFF`, `#D6D9DD`, `#16202E`, `#5C6776`) that act as the
|
||||
ultimate fallback when `EmailContext.Colors` is empty. Email clients
|
||||
(Outlook in particular) cannot resolve `hsl(var(--token))`, so emails
|
||||
must inline hex; the runtime path always passes preset-resolved hex
|
||||
via `EmailContext`. Check-safety §6 only scans `.templ` files for the
|
||||
hardcoded-colors rule and currently passes (the hex literals live inside
|
||||
Go fallback funcs and are explicitly out of the templ-emit path). UAT
|
||||
§5.5 may need a clarifying note that email fallbacks are intentional.
|
||||
|
||||
### `Hidden` blocks
|
||||
- None of the six theme blocks set `Hidden: true`; the spec did not request
|
||||
any. (Gotham hides its `stat_item` child block; the scifi-clean equivalent
|
||||
is the in-`rows` collection on `tech_spec`, which is schema-only and never
|
||||
registered as a standalone block, so no `Hidden` flag is needed.)
|
||||
|
||||
### Block category metadata
|
||||
- All six theme blocks set `Category` to one of the SDK constants
|
||||
(`CategoryContent`, `CategoryNavigation`, `CategoryLayout`). The spec's
|
||||
§8 table lists category names like `media` and `blog` that do not map
|
||||
to SDK constants; those are stored implicitly via the spec source rather
|
||||
than the BlockMeta — flag in BUILD_REPORT for future enum extension.
|
||||
|
||||
### Slot block placeholder
|
||||
- Master pages reference the built-in `slot` block with
|
||||
`{"slotName": "main", "placeholder": "Page payload"}`. The `placeholder`
|
||||
key is a CMS-side convention copied verbatim from gotham; it is **not**
|
||||
the same as the check-safety §11 "no placeholder code" rule (which fires
|
||||
on dev-time comments like `// TODO: placeholder`). Check-safety passes.
|
||||
31
Makefile
Normal file
31
Makefile
Normal file
@ -0,0 +1,31 @@
|
||||
# Sci-Fi Clean — build helpers (.so plugin workflow)
|
||||
#
|
||||
# Local-only targets. `make` produces scifi-clean.so via CGO go build -buildmode=plugin.
|
||||
# `make rebuild` deploys to the live CMS container and is intentionally NOT defined here
|
||||
# in this implementation pass — see gotham/Makefile if/when you want to add it.
|
||||
|
||||
.PHONY: all clean templ help
|
||||
|
||||
PLUGIN_NAME := scifi-clean
|
||||
TEMPL := $(HOME)/go/bin/templ
|
||||
|
||||
# Default target: build the .so locally.
|
||||
all: $(PLUGIN_NAME).so
|
||||
|
||||
# Local plugin build (no container).
|
||||
$(PLUGIN_NAME).so: $(wildcard *.go) plugin.mod go.mod
|
||||
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o $(PLUGIN_NAME).so .
|
||||
|
||||
# Regenerate templ Go files locally.
|
||||
templ:
|
||||
$(TEMPL) generate
|
||||
|
||||
# Remove build artefacts.
|
||||
clean:
|
||||
rm -f $(PLUGIN_NAME).so
|
||||
|
||||
help:
|
||||
@echo "Targets:"
|
||||
@echo " all Build $(PLUGIN_NAME).so locally (default)"
|
||||
@echo " templ Regenerate templ Go files locally"
|
||||
@echo " clean Remove built .so"
|
||||
33
RECOMMENDED_FONTS.md
Normal file
33
RECOMMENDED_FONTS.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Recommended fonts for Sci-Fi Clean
|
||||
|
||||
This theme ships `fonts.json = []` — no woff2 files bundled in this implementation pass.
|
||||
Add the fonts below from the site admin's Google Fonts picker.
|
||||
|
||||
Open **Admin → Settings → Typography**, switch to the **Google Fonts** tab, and assign:
|
||||
|
||||
| Slot | Pick | Source | Notes |
|
||||
|----------|---------------------------------------|----------------|---------------------------------------------------------|
|
||||
| Heading | `Space Grotesk` (weights 400, 500, 700) | `google:Space Grotesk` | Geometric display face. Spec §3 calls it "the safe ship default" over Eurostile. |
|
||||
| Body | `Inter` (weights 400, 500, 600) | `google:Inter` | Workhorse UI sans. Pairs cleanly with Space Grotesk. |
|
||||
| Mono | `JetBrains Mono` (weights 400, 500) | `google:JetBrains Mono` | Used for every numeral, identifier, and tabular figure. |
|
||||
|
||||
All three are in the curated Google Fonts list; no upload is required.
|
||||
|
||||
## CSS fallback stacks
|
||||
|
||||
Until the admin assigns fonts, templates fall through to these CSS-variable fallbacks
|
||||
(declared in the theme's CSS manifest, applied to `:root`):
|
||||
|
||||
- `--font-heading` → `"Space Grotesk", "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif`
|
||||
- `--font-body` → `"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif`
|
||||
- `--font-mono` → `"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace`
|
||||
|
||||
The fallback stacks intentionally already approximate the intended aesthetic, so the
|
||||
theme looks right out of the box even before the admin picks Google Fonts.
|
||||
|
||||
## Why no bundled woff2s in this pass
|
||||
|
||||
Per `themes/docs/FONTS.md` (wave-1 policy): no theme bundles woff2 files in this
|
||||
implementation pass. Wave-2 may bundle commercial display faces (e.g. Eurostile)
|
||||
once licensing for `.so` redistribution is sorted; until then, Space Grotesk is
|
||||
the canonical heading face for Sci-Fi Clean.
|
||||
6
assets/README.txt
Normal file
6
assets/README.txt
Normal file
@ -0,0 +1,6 @@
|
||||
Sci-Fi Clean theme — static assets.
|
||||
|
||||
This directory is served by the plugin's AssetsHandler at /templates/scifi-clean/*.
|
||||
The wave-1 implementation pass ships no woff2 files here (see RECOMMENDED_FONTS.md
|
||||
and themes/docs/FONTS.md). Wave-2 may bundle commercial display faces under
|
||||
fonts/web/ once licensing for .so redistribution is sorted.
|
||||
22
button_override.go
Normal file
22
button_override.go
Normal file
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
)
|
||||
|
||||
// ScifiButtonBlock renders a button with the Sci-Fi Clean treatment:
|
||||
// mono uppercase label, hairline outline variant, right-arrow chevron suffix.
|
||||
// Built-in button content shape: {"text": "...", "url": "...", "variant": "primary|outline"}.
|
||||
func ScifiButtonBlock(ctx context.Context, content map[string]any) string {
|
||||
text := getString(content, "text")
|
||||
url := getString(content, "url")
|
||||
variant := getStringDefault(content, "variant", "primary")
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = scifiButtonComponent(text, url, variant).Render(ctx, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
27
button_override.templ
Normal file
27
button_override.templ
Normal file
@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
// scifiButtonComponent renders the Sci-Fi Clean button. The `→` chevron is a
|
||||
// literal U+2192 inside an explicit data-icon span so UAT §13.14 can grep
|
||||
// for it deterministically.
|
||||
templ scifiButtonComponent(text, url, variant string) {
|
||||
<a
|
||||
href={ templ.SafeURL(safeHref(url)) }
|
||||
class={ "scifi-mono uppercase tracking-widest text-xs px-5 py-3 scifi-focus inline-flex items-center justify-center gap-2", buttonVariantClass(variant) }
|
||||
style="font-family: var(--font-mono);"
|
||||
>
|
||||
<span>{ text }</span>
|
||||
<span data-icon="chevron-right" aria-hidden="true">{ "→" }</span>
|
||||
</a>
|
||||
}
|
||||
|
||||
// buttonVariantClass picks the visual treatment.
|
||||
func buttonVariantClass(variant string) string {
|
||||
switch variant {
|
||||
case "outline":
|
||||
return "hairline text-foreground bg-transparent"
|
||||
case "destructive":
|
||||
return "bg-destructive text-destructive-foreground"
|
||||
default:
|
||||
return "bg-primary text-primary-foreground"
|
||||
}
|
||||
}
|
||||
112
button_override_templ.go
Normal file
112
button_override_templ.go
Normal file
@ -0,0 +1,112 @@
|
||||
// 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"
|
||||
|
||||
// scifiButtonComponent renders the Sci-Fi Clean button. The `→` chevron is a
|
||||
// literal U+2192 inside an explicit data-icon span so UAT §13.14 can grep
|
||||
// for it deterministically.
|
||||
func scifiButtonComponent(text, url, 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)
|
||||
var templ_7745c5c3_Var2 = []any{"scifi-mono uppercase tracking-widest text-xs px-5 py-3 scifi-focus inline-flex items-center justify-center gap-2", buttonVariantClass(variant)}
|
||||
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(safeHref(url)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 8, Col: 37}
|
||||
}
|
||||
_, 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, "\" style=\"font-family: var(--font-mono);\"><span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 12, Col: 14}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</span> <span data-icon=\"chevron-right\" aria-hidden=\"true\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("→")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 13, Col: 60}
|
||||
}
|
||||
_, 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, "</span></a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// buttonVariantClass picks the visual treatment.
|
||||
func buttonVariantClass(variant string) string {
|
||||
switch variant {
|
||||
case "outline":
|
||||
return "hairline text-foreground bg-transparent"
|
||||
case "destructive":
|
||||
return "bg-destructive text-destructive-foreground"
|
||||
default:
|
||||
return "bg-primary text-primary-foreground"
|
||||
}
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
119
css.go
Normal file
119
css.go
Normal file
@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
// scifiCleanInputCSS is appended to the host Tailwind input.css via CSSManifest.
|
||||
//
|
||||
// Contents:
|
||||
// 1. CSS-variable fallback stacks for --font-heading / --font-body / --font-mono.
|
||||
// Per docs/FONTS.md (wave-1 policy), the theme bundles no fonts and consumes
|
||||
// fonts exclusively via these variables. The fallback stacks intentionally
|
||||
// already approximate Sci-Fi Clean's aesthetic so the theme looks right
|
||||
// before the admin picks any Google Fonts.
|
||||
// 2. .hairline utility — exactly border: 1px solid hsl(var(--border)).
|
||||
// UAT §13.5 mandates 1px width; do not raise.
|
||||
// 3. .bg-grid utility — translucent blueprint grid as a CSS gradient.
|
||||
// Uses currentColor + opacity to track --foreground.
|
||||
// 4. .tabular-nums / .mono-numerals helpers — UAT §13.7 wants
|
||||
// font-variant-numeric: tabular-nums on every mission_stat value.
|
||||
// 5. .scrim utility — required scrim layer for full-bleed hero text (UAT §6).
|
||||
//
|
||||
// The literal substrings 'Space Grotesk', 'JetBrains Mono', '.hairline', '.bg-grid'
|
||||
// must appear here verbatim (UAT §13.4).
|
||||
const scifiCleanInputCSS = `
|
||||
/* === Sci-Fi Clean: font-family fallback variables ===
|
||||
* The site admin assigns real fonts via the typography settings; until then,
|
||||
* the variables fall through to the stacks below. 'Space Grotesk', 'Inter',
|
||||
* and 'JetBrains Mono' are spec §5 recommendations, available in the Google
|
||||
* Fonts picker.
|
||||
*/
|
||||
:root {
|
||||
--font-heading: "Space Grotesk", "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
--font-body: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
/* === Sci-Fi Clean: utilities ===
|
||||
* Hairline rules and a translucent blueprint grid are the two utility classes
|
||||
* the theme uses heavily. Tailwind's defaults cover everything else.
|
||||
*/
|
||||
.hairline {
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.hairline-t {
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.hairline-b {
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.bg-grid {
|
||||
background-color: hsl(var(--background));
|
||||
background-image:
|
||||
linear-gradient(to right, hsl(var(--border) / 0.6) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, hsl(var(--border) / 0.6) 1px, transparent 1px);
|
||||
background-size: 32px 32px;
|
||||
}
|
||||
|
||||
.mono-numerals {
|
||||
font-family: var(--font-mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.tabular-nums {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.scrim {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scrim::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-color: hsl(var(--background) / 0.65);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scrim > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* === Sci-Fi Clean: focus rings ===
|
||||
* Use the --ring token (a precise technical blue) for all focus states so
|
||||
* the keyboard cursor is visible against either preset.
|
||||
*/
|
||||
.scifi-focus:focus-visible {
|
||||
outline: 2px solid hsl(var(--ring));
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* === Sci-Fi Clean: chevron ===
|
||||
* Right-arrow chevron used in ScifiButtonBlock; encoded as Unicode so it
|
||||
* never breaks across renderers (UAT §13.14).
|
||||
*/
|
||||
.scifi-chevron::after {
|
||||
content: " \2192";
|
||||
font-family: var(--font-mono);
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
/* === Sci-Fi Clean: heading family ===
|
||||
* The host shadcn layer doesn't apply --font-heading by default; wire it
|
||||
* up here for the four heading levels we render.
|
||||
*/
|
||||
.scifi-h1, .scifi-h2, .scifi-h3, .scifi-h4 {
|
||||
font-family: var(--font-heading);
|
||||
font-feature-settings: "ss01" on;
|
||||
}
|
||||
|
||||
.scifi-body {
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
.scifi-mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
`
|
||||
63
diagram_caption.go
Normal file
63
diagram_caption.go
Normal file
@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/blocks"
|
||||
)
|
||||
|
||||
// DiagramCaptionBlockMeta defines metadata for the diagram-caption block.
|
||||
var DiagramCaptionBlockMeta = blocks.BlockMeta{
|
||||
Key: "diagram_caption",
|
||||
Title: "Diagram Caption",
|
||||
Description: "Annotated technical figure with a Fig. NN. heading and optional callout pins.",
|
||||
Source: "scifi-clean",
|
||||
Category: blocks.CategoryContent,
|
||||
}
|
||||
|
||||
// DiagramCalloutPin is a percent-positioned overlay pin.
|
||||
type DiagramCalloutPin struct {
|
||||
X float64
|
||||
Y float64
|
||||
Label string
|
||||
}
|
||||
|
||||
// DiagramCaptionData drives the templ render.
|
||||
type DiagramCaptionData struct {
|
||||
Image string
|
||||
FigureNumber string
|
||||
Title string
|
||||
Body string
|
||||
CalloutPins []DiagramCalloutPin
|
||||
}
|
||||
|
||||
// DiagramCaptionBlock renders the diagram-caption block.
|
||||
// Content shape: {"image": "media:...", "figureNumber": "...", "title": "...", "body": "...",
|
||||
// "calloutPins": [{"x": 12, "y": 50, "label": "..."}]}
|
||||
func DiagramCaptionBlock(ctx context.Context, content map[string]any) string {
|
||||
data := DiagramCaptionData{
|
||||
Image: blocks.ResolveMediaPath(getString(content, "image")),
|
||||
FigureNumber: getString(content, "figureNumber"),
|
||||
Title: getString(content, "title"),
|
||||
Body: getString(content, "body"),
|
||||
}
|
||||
|
||||
for _, raw := range getSlice(content, "calloutPins") {
|
||||
data.CalloutPins = append(data.CalloutPins, DiagramCalloutPin{
|
||||
X: getFloat(raw, "x", 0),
|
||||
Y: getFloat(raw, "y", 0),
|
||||
Label: getString(raw, "label"),
|
||||
})
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = diagramCaptionComponent(data).Render(ctx, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// percentStyle returns a tiny inline style string positioning the pin in percent.
|
||||
func percentStyle(x, y float64) string {
|
||||
return "left: " + strconv.FormatFloat(x, 'f', 2, 64) + "%; top: " + strconv.FormatFloat(y, 'f', 2, 64) + "%;"
|
||||
}
|
||||
49
diagram_caption.templ
Normal file
49
diagram_caption.templ
Normal file
@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
// diagramCaptionComponent renders the figure with overlay pins and a Fig. heading.
|
||||
templ diagramCaptionComponent(data DiagramCaptionData) {
|
||||
<figure data-block="scifi-clean:diagram_caption" class="my-8 max-w-3xl mx-auto px-4">
|
||||
<div class="relative hairline bg-card">
|
||||
if data.Image == "" {
|
||||
<div class="bg-grid aspect-video flex items-center justify-center">
|
||||
<span class="scifi-mono text-sm text-muted-foreground uppercase tracking-widest">
|
||||
No diagram yet
|
||||
</span>
|
||||
</div>
|
||||
} else {
|
||||
<img
|
||||
src={ data.Image }
|
||||
alt={ data.Title }
|
||||
class="block w-full h-auto"
|
||||
/>
|
||||
for _, pin := range data.CalloutPins {
|
||||
<span
|
||||
class="absolute inline-flex items-center scifi-mono text-xs uppercase tracking-wider px-2 py-1 bg-card text-foreground hairline"
|
||||
style={ percentStyle(pin.X, pin.Y) }
|
||||
>
|
||||
{ pin.Label }
|
||||
</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<figcaption class="mt-3 hairline-t pt-3">
|
||||
<h4 class="scifi-h4 scifi-mono text-sm uppercase tracking-widest text-foreground">
|
||||
{ "Fig. " }
|
||||
if data.FigureNumber != "" {
|
||||
{ data.FigureNumber }
|
||||
} else {
|
||||
{ "00" }
|
||||
}
|
||||
{ "." }
|
||||
if data.Title != "" {
|
||||
<span class="text-muted-foreground ml-2">{ data.Title }</span>
|
||||
}
|
||||
</h4>
|
||||
if data.Body != "" {
|
||||
<div class="scifi-body prose prose-sm max-w-none text-muted-foreground mt-2">
|
||||
@templ.Raw(data.Body)
|
||||
</div>
|
||||
}
|
||||
</figcaption>
|
||||
</figure>
|
||||
}
|
||||
210
diagram_caption_templ.go
Normal file
210
diagram_caption_templ.go
Normal file
@ -0,0 +1,210 @@
|
||||
// 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"
|
||||
|
||||
// diagramCaptionComponent renders the figure with overlay pins and a Fig. heading.
|
||||
func diagramCaptionComponent(data DiagramCaptionData) 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 data-block=\"scifi-clean:diagram_caption\" class=\"my-8 max-w-3xl mx-auto px-4\"><div class=\"relative hairline bg-card\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Image == "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"bg-grid aspect-video flex items-center justify-center\"><span class=\"scifi-mono text-sm text-muted-foreground uppercase tracking-widest\">No diagram yet</span></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<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.Image)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `diagram_caption.templ`, Line: 15, Col: 21}
|
||||
}
|
||||
_, 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, 4, "\" alt=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `diagram_caption.templ`, Line: 16, Col: 21}
|
||||
}
|
||||
_, 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, 5, "\" class=\"block w-full h-auto\"> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, pin := range data.CalloutPins {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"absolute inline-flex items-center scifi-mono text-xs uppercase tracking-wider px-2 py-1 bg-card text-foreground hairline\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(percentStyle(pin.X, pin.Y))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `diagram_caption.templ`, Line: 22, Col: 40}
|
||||
}
|
||||
_, 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, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(pin.Label)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `diagram_caption.templ`, Line: 24, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div><figcaption class=\"mt-3 hairline-t pt-3\"><h4 class=\"scifi-h4 scifi-mono text-sm uppercase tracking-widest text-foreground\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("Fig. ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `diagram_caption.templ`, Line: 31, 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, 10, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.FigureNumber != "" {
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.FigureNumber)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `diagram_caption.templ`, Line: 33, Col: 24}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs("00")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `diagram_caption.templ`, Line: 35, Col: 11}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(".")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `diagram_caption.templ`, Line: 37, Col: 9}
|
||||
}
|
||||
_, 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, 13, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Title != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<span class=\"text-muted-foreground ml-2\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `diagram_caption.templ`, Line: 39, Col: 58}
|
||||
}
|
||||
_, 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, 15, "</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</h4>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Body != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<div class=\"scifi-body prose prose-sm max-w-none text-muted-foreground mt-2\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(data.Body).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</figcaption></figure>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
16
email_wrapper.go
Normal file
16
email_wrapper.go
Normal file
@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/templates"
|
||||
)
|
||||
|
||||
// ScifiEmailWrapper wraps body content in a branded Sci-Fi Clean email shell:
|
||||
// 600px white card, hairline border, mono callsign top-right, instrument footer.
|
||||
func ScifiEmailWrapper(body string, emailCtx templates.EmailContext) string {
|
||||
var buf bytes.Buffer
|
||||
_ = scifiEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
|
||||
return buf.String()
|
||||
}
|
||||
137
email_wrapper.templ
Normal file
137
email_wrapper.templ
Normal file
@ -0,0 +1,137 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/templates"
|
||||
)
|
||||
|
||||
// scifiEmailBgColor / scifiEmailCardColor / scifiEmailBorderColor pull from the
|
||||
// caller-provided EmailColors so the wrapper inherits the active preset.
|
||||
func scifiEmailBgColor(c templates.EmailContext) string {
|
||||
if c.Colors.Background != "" {
|
||||
return c.Colors.Background
|
||||
}
|
||||
return "#F6F7F8"
|
||||
}
|
||||
|
||||
func scifiEmailCardColor(c templates.EmailContext) string {
|
||||
if c.Colors.Card != "" {
|
||||
return c.Colors.Card
|
||||
}
|
||||
return "#FFFFFF"
|
||||
}
|
||||
|
||||
func scifiEmailBorderColor(c templates.EmailContext) string {
|
||||
if c.Colors.Border != "" {
|
||||
return c.Colors.Border
|
||||
}
|
||||
return "#D6D9DD"
|
||||
}
|
||||
|
||||
func scifiEmailFgColor(c templates.EmailContext) string {
|
||||
if c.Colors.Foreground != "" {
|
||||
return c.Colors.Foreground
|
||||
}
|
||||
return "#16202E"
|
||||
}
|
||||
|
||||
func scifiEmailMutedColor(c templates.EmailContext) string {
|
||||
if c.Colors.MutedForeground != "" {
|
||||
return c.Colors.MutedForeground
|
||||
}
|
||||
return "#5C6776"
|
||||
}
|
||||
|
||||
func scifiEmailCallsign(c templates.EmailContext) string {
|
||||
if c.SiteSettings.SiteName != "" {
|
||||
return "SCF / " + c.SiteSettings.SiteName
|
||||
}
|
||||
return "SCF-CLN"
|
||||
}
|
||||
|
||||
// scifiEmailTemplate is the Sci-Fi Clean branded email wrapper.
|
||||
templ scifiEmailTemplate(emailCtx templates.EmailContext, body string) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta name="x-apple-disable-message-reformatting"/>
|
||||
<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%;
|
||||
}
|
||||
img { border: 0; height: auto; outline: none; text-decoration: none; }
|
||||
body { margin: 0 !important; padding: 0 !important; width: 100% !important; }
|
||||
/* Mono numeral preservation across clients. */
|
||||
.scifi-num {
|
||||
font-family: ui-monospace, "JetBrains Mono", "Menlo", monospace !important;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
@media only screen and (max-width: 620px) {
|
||||
.scifi-email-container { width: 100% !important; max-width: 100% !important; }
|
||||
.scifi-pad { padding-left: 20px !important; padding-right: 20px !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: %s;", scifiEmailBgColor(emailCtx), scifiEmailFgColor(emailCtx)) }>
|
||||
if emailCtx.PreviewText != "" {
|
||||
<div style="display: none; max-height: 0; overflow: hidden;">{ emailCtx.PreviewText }</div>
|
||||
}
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td align="center" style={ fmt.Sprintf("padding: 32px 12px; background-color: %s;", scifiEmailBgColor(emailCtx)) }>
|
||||
<table
|
||||
role="presentation"
|
||||
class="scifi-email-container"
|
||||
width="600"
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
style={ fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s;", scifiEmailCardColor(emailCtx), scifiEmailBorderColor(emailCtx)) }
|
||||
>
|
||||
<tr>
|
||||
<td class="scifi-pad" style={ fmt.Sprintf("padding: 20px 28px; border-bottom: 1px solid %s;", scifiEmailBorderColor(emailCtx)) }>
|
||||
<table width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td align="left" style={ fmt.Sprintf("font-family: 'Space Grotesk', 'Inter', sans-serif; font-weight: 600; font-size: 16px; letter-spacing: 0.02em; color: %s;", scifiEmailFgColor(emailCtx)) }>
|
||||
{ emailCtx.SiteSettings.SiteName }
|
||||
</td>
|
||||
<td align="right" class="scifi-num" style={ fmt.Sprintf("font-family: ui-monospace, 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: %s;", scifiEmailMutedColor(emailCtx)) }>
|
||||
{ scifiEmailCallsign(emailCtx) }
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="scifi-pad" style={ fmt.Sprintf("padding: 28px; font-size: 15px; line-height: 1.55; color: %s;", scifiEmailFgColor(emailCtx)) }>
|
||||
@templ.Raw(body)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="scifi-pad" style={ fmt.Sprintf("padding: 16px 28px; border-top: 1px solid %s; background-color: %s;", scifiEmailBorderColor(emailCtx), scifiEmailCardColor(emailCtx)) }>
|
||||
<table width="100%" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr>
|
||||
<td class="scifi-num" align="left" style={ fmt.Sprintf("font-family: ui-monospace, 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: %s;", scifiEmailMutedColor(emailCtx)) }>
|
||||
{ emailCtx.SiteSettings.SiteURL }
|
||||
</td>
|
||||
if emailCtx.UnsubscribeURL != "" {
|
||||
<td class="scifi-num" align="right" style={ fmt.Sprintf("font-family: ui-monospace, 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;") }>
|
||||
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", scifiEmailMutedColor(emailCtx)) }>Unsubscribe</a>
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
340
email_wrapper_templ.go
Normal file
340
email_wrapper_templ.go
Normal file
@ -0,0 +1,340 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// scifiEmailBgColor / scifiEmailCardColor / scifiEmailBorderColor pull from the
|
||||
// caller-provided EmailColors so the wrapper inherits the active preset.
|
||||
func scifiEmailBgColor(c templates.EmailContext) string {
|
||||
if c.Colors.Background != "" {
|
||||
return c.Colors.Background
|
||||
}
|
||||
return "#F6F7F8"
|
||||
}
|
||||
|
||||
func scifiEmailCardColor(c templates.EmailContext) string {
|
||||
if c.Colors.Card != "" {
|
||||
return c.Colors.Card
|
||||
}
|
||||
return "#FFFFFF"
|
||||
}
|
||||
|
||||
func scifiEmailBorderColor(c templates.EmailContext) string {
|
||||
if c.Colors.Border != "" {
|
||||
return c.Colors.Border
|
||||
}
|
||||
return "#D6D9DD"
|
||||
}
|
||||
|
||||
func scifiEmailFgColor(c templates.EmailContext) string {
|
||||
if c.Colors.Foreground != "" {
|
||||
return c.Colors.Foreground
|
||||
}
|
||||
return "#16202E"
|
||||
}
|
||||
|
||||
func scifiEmailMutedColor(c templates.EmailContext) string {
|
||||
if c.Colors.MutedForeground != "" {
|
||||
return c.Colors.MutedForeground
|
||||
}
|
||||
return "#5C6776"
|
||||
}
|
||||
|
||||
func scifiEmailCallsign(c templates.EmailContext) string {
|
||||
if c.SiteSettings.SiteName != "" {
|
||||
return "SCF / " + c.SiteSettings.SiteName
|
||||
}
|
||||
return "SCF-CLN"
|
||||
}
|
||||
|
||||
// scifiEmailTemplate is the Sci-Fi Clean branded email wrapper.
|
||||
func scifiEmailTemplate(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\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"x-apple-disable-message-reformatting\"><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: 61, 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 {\n\t\t\t\t\t-webkit-text-size-adjust: 100%;\n\t\t\t\t\t-ms-text-size-adjust: 100%;\n\t\t\t\t}\n\t\t\t\timg { border: 0; height: auto; outline: none; text-decoration: none; }\n\t\t\t\tbody { margin: 0 !important; padding: 0 !important; width: 100% !important; }\n\t\t\t\t/* Mono numeral preservation across clients. */\n\t\t\t\t.scifi-num {\n\t\t\t\t\tfont-family: ui-monospace, \"JetBrains Mono\", \"Menlo\", monospace !important;\n\t\t\t\t\tfont-variant-numeric: tabular-nums;\n\t\t\t\t}\n\t\t\t\t@media only screen and (max-width: 620px) {\n\t\t\t\t\t.scifi-email-container { width: 100% !important; max-width: 100% !important; }\n\t\t\t\t\t.scifi-pad { 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', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; color: %s;", scifiEmailBgColor(emailCtx), scifiEmailFgColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 80, Col: 225}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if emailCtx.PreviewText != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div style=\"display: none; max-height: 0; overflow: hidden;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.PreviewText)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 82, Col: 87}
|
||||
}
|
||||
_, 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: 32px 12px; background-color: %s;", scifiEmailBgColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 86, Col: 117}
|
||||
}
|
||||
_, 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=\"scifi-email-container\" width=\"600\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("max-width: 600px; background-color: %s; border: 1px solid %s;", scifiEmailCardColor(emailCtx), scifiEmailBorderColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 94, Col: 155}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><tr><td class=\"scifi-pad\" 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: 20px 28px; border-bottom: 1px solid %s;", scifiEmailBorderColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 97, Col: 134}
|
||||
}
|
||||
_, 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, "\"><table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td align=\"left\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("font-family: 'Space Grotesk', 'Inter', sans-serif; font-weight: 600; font-size: 16px; letter-spacing: 0.02em; color: %s;", scifiEmailFgColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 100, Col: 200}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, 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: 101, Col: 44}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</td><td align=\"right\" class=\"scifi-num\" 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("font-family: ui-monospace, 'JetBrains Mono', monospace; font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: %s;", scifiEmailMutedColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 103, Col: 236}
|
||||
}
|
||||
_, 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, 12, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(scifiEmailCallsign(emailCtx))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 104, Col: 42}
|
||||
}
|
||||
_, 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, 13, "</td></tr></table></td></tr><tr><td class=\"scifi-pad\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 28px; font-size: 15px; line-height: 1.55; color: %s;", scifiEmailFgColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 111, Col: 143}
|
||||
}
|
||||
_, 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, 14, "\">")
|
||||
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, 15, "</td></tr><tr><td class=\"scifi-pad\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("padding: 16px 28px; border-top: 1px solid %s; background-color: %s;", scifiEmailBorderColor(emailCtx), scifiEmailCardColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 116, Col: 184}
|
||||
}
|
||||
_, 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, 16, "\"><table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\"><tr><td class=\"scifi-num\" align=\"left\" 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("font-family: ui-monospace, 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: %s;", scifiEmailMutedColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 119, Col: 235}
|
||||
}
|
||||
_, 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, 17, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteURL)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 120, Col: 43}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if emailCtx.UnsubscribeURL != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<td class=\"scifi-num\" align=\"right\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("font-family: ui-monospace, 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 123, Col: 194}
|
||||
}
|
||||
_, 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, 20, "\"><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 templ.SafeURL
|
||||
templ_7745c5c3_Var17, 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: 124, Col: 61}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var18 string
|
||||
templ_7745c5c3_Var18, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("color: %s; text-decoration: underline;", scifiEmailMutedColor(emailCtx)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 124, Col: 157}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\">Unsubscribe</a></td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</tr></table></td></tr></table></td></tr></table></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
60
embed.go
Normal file
60
embed.go
Normal file
@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/plugin"
|
||||
)
|
||||
|
||||
//go:embed assets/*
|
||||
var assetsFS embed.FS
|
||||
|
||||
//go:embed schemas/*
|
||||
var schemasFS embed.FS
|
||||
|
||||
//go:embed presets.json
|
||||
var presetsData []byte
|
||||
|
||||
//go:embed fonts.json
|
||||
var fontsData []byte
|
||||
|
||||
//go:embed plugin.mod
|
||||
var pluginModBytes []byte
|
||||
|
||||
// Assets returns the embedded assets filesystem.
|
||||
func Assets() fs.FS {
|
||||
sub, _ := fs.Sub(assetsFS, "assets")
|
||||
return sub
|
||||
}
|
||||
|
||||
// Schemas returns the embedded schemas filesystem.
|
||||
func Schemas() fs.FS {
|
||||
sub, _ := fs.Sub(schemasFS, "schemas")
|
||||
return sub
|
||||
}
|
||||
|
||||
// AssetsHandler returns an http.Handler that serves the embedded assets.
|
||||
func AssetsHandler() http.Handler {
|
||||
return http.FileServer(http.FS(Assets()))
|
||||
}
|
||||
|
||||
// ThemePresets returns the embedded theme presets JSON.
|
||||
func ThemePresets() []byte {
|
||||
return presetsData
|
||||
}
|
||||
|
||||
// BundledFonts returns the embedded fonts manifest JSON.
|
||||
func BundledFonts() []byte {
|
||||
return fontsData
|
||||
}
|
||||
|
||||
// ThemeCSSManifest returns the CSS manifest containing the theme's custom
|
||||
// utilities (.hairline, .bg-grid) and font-family fallback CSS variables.
|
||||
// It is injected into the host Tailwind input by the CMS at build time.
|
||||
func ThemeCSSManifest() *plugin.CSSManifest {
|
||||
return &plugin.CSSManifest{
|
||||
InputCSSAppend: scifiCleanInputCSS,
|
||||
}
|
||||
}
|
||||
1
fonts.json
Normal file
1
fonts.json
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
58
footer.go
Normal file
58
footer.go
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/blocks"
|
||||
)
|
||||
|
||||
// FooterBlockMeta defines metadata for the instrument footer block.
|
||||
var FooterBlockMeta = blocks.BlockMeta{
|
||||
Key: "footer",
|
||||
Title: "Instrument Footer",
|
||||
Description: "Hairline-rule footer with callsign, optional signup, and link list.",
|
||||
Source: "scifi-clean",
|
||||
Category: blocks.CategoryLayout,
|
||||
}
|
||||
|
||||
// FooterLink is one footer link entry.
|
||||
type FooterLink struct {
|
||||
Text string
|
||||
URL string
|
||||
}
|
||||
|
||||
// FooterData drives the templ render.
|
||||
type FooterData struct {
|
||||
ShowSignup bool
|
||||
Callsign string
|
||||
Links []FooterLink
|
||||
}
|
||||
|
||||
// FooterBlock renders the footer.
|
||||
// Content shape: {"showSignup": false, "callsign": "SCF-CLN", "links": [{"text": "...", "url": "..."}]}
|
||||
func FooterBlock(ctx context.Context, content map[string]any) string {
|
||||
data := FooterData{
|
||||
ShowSignup: getBool(content, "showSignup", false),
|
||||
Callsign: getStringDefault(content, "callsign", "SCF-CLN"),
|
||||
}
|
||||
|
||||
for _, raw := range getSlice(content, "links") {
|
||||
data.Links = append(data.Links, FooterLink{
|
||||
Text: getString(raw, "text"),
|
||||
URL: getString(raw, "url"),
|
||||
})
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = footerComponent(data).Render(ctx, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// safeHref returns # for blank URLs so the templ.SafeURL helper has something to chew on.
|
||||
func safeHref(u string) string {
|
||||
if u == "" {
|
||||
return "#"
|
||||
}
|
||||
return u
|
||||
}
|
||||
69
footer.templ
Normal file
69
footer.templ
Normal file
@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
// footerComponent renders the instrument-bar footer.
|
||||
// UAT §13.10 requires a callsign element rendered in JetBrains Mono and at
|
||||
// least one .hairline rule above the footer.
|
||||
templ footerComponent(data FooterData) {
|
||||
<div data-block="scifi-clean:footer" class="w-full">
|
||||
<div class="hairline-t pt-8 pb-10">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-8">
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
data-scifi-callsign
|
||||
class="scifi-mono uppercase tracking-widest text-xs text-foreground"
|
||||
style="font-family: var(--font-mono);"
|
||||
>
|
||||
{ data.Callsign }
|
||||
</div>
|
||||
<p class="scifi-body text-sm text-muted-foreground max-w-md">
|
||||
Telemetry, mission briefs, and engineering notes from this surface.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
if len(data.Links) > 0 {
|
||||
<ul class="space-y-2 scifi-body text-sm">
|
||||
for _, link := range data.Links {
|
||||
<li>
|
||||
<a
|
||||
href={ templ.SafeURL(safeHref(link.URL)) }
|
||||
class="text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
{ link.Text }
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
|
||||
if data.ShowSignup {
|
||||
<form class="space-y-2 max-w-xs" method="post" action="/subscribe">
|
||||
<label class="scifi-mono uppercase text-xs tracking-widest text-muted-foreground block">
|
||||
Mission updates
|
||||
</label>
|
||||
<div class="flex hairline">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
required
|
||||
placeholder="ops@station.local"
|
||||
class="scifi-body flex-1 px-3 py-2 bg-card text-foreground text-sm focus:outline-none scifi-focus"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="scifi-mono uppercase text-xs tracking-widest px-4 py-2 bg-primary text-primary-foreground scifi-chevron scifi-focus"
|
||||
>
|
||||
Subscribe
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
<div class="mt-8 hairline-t pt-4 flex items-center justify-between scifi-mono text-xs uppercase tracking-widest text-muted-foreground tabular-nums">
|
||||
<span>Project Aurora</span>
|
||||
<span>v0.1.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
108
footer_templ.go
Normal file
108
footer_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"
|
||||
|
||||
// footerComponent renders the instrument-bar footer.
|
||||
// UAT §13.10 requires a callsign element rendered in JetBrains Mono and at
|
||||
// least one .hairline rule above the footer.
|
||||
func footerComponent(data FooterData) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div data-block=\"scifi-clean:footer\" class=\"w-full\"><div class=\"hairline-t pt-8 pb-10\"><div class=\"max-w-6xl mx-auto px-4\"><div class=\"flex flex-col md:flex-row md:items-start md:justify-between gap-8\"><div class=\"space-y-2\"><div data-scifi-callsign class=\"scifi-mono uppercase tracking-widest text-xs text-foreground\" style=\"font-family: var(--font-mono);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Callsign)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 17, Col: 22}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><p class=\"scifi-body text-sm text-muted-foreground max-w-md\">Telemetry, mission briefs, and engineering notes from this surface.</p></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(data.Links) > 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<ul class=\"space-y-2 scifi-body text-sm\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, link := range data.Links {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<li><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(safeHref(link.URL)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 29, Col: 50}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" class=\"text-muted-foreground hover:text-foreground transition-colors\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(link.Text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer.templ`, Line: 32, Col: 21}
|
||||
}
|
||||
_, 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, 6, "</a></li>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</ul>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.ShowSignup {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<form class=\"space-y-2 max-w-xs\" method=\"post\" action=\"/subscribe\"><label class=\"scifi-mono uppercase text-xs tracking-widest text-muted-foreground block\">Mission updates</label><div class=\"flex hairline\"><input type=\"email\" name=\"email\" required placeholder=\"ops@station.local\" class=\"scifi-body flex-1 px-3 py-2 bg-card text-foreground text-sm focus:outline-none scifi-focus\"> <button type=\"submit\" class=\"scifi-mono uppercase text-xs tracking-widest px-4 py-2 bg-primary text-primary-foreground scifi-chevron scifi-focus\">Subscribe</button></div></form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div><div class=\"mt-8 hairline-t pt-4 flex items-center justify-between scifi-mono text-xs uppercase tracking-widest text-muted-foreground tabular-nums\"><span>Project Aurora</span> <span>v0.1.0</span></div></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
20
go.mod
Normal file
20
go.mod
Normal file
@ -0,0 +1,20 @@
|
||||
module git.dev.alexdunmow.com/block/themes/scifi-clean
|
||||
|
||||
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=
|
||||
42
heading_override.go
Normal file
42
heading_override.go
Normal file
@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// ScifiHeadingBlock renders a heading with the Sci-Fi Clean treatment:
|
||||
// Space Grotesk weights, optional mono kicker prefix ("FIG / SEC / SYS" etc.).
|
||||
// Reuses the built-in heading schema; reads optional kicker via content["kicker"].
|
||||
func ScifiHeadingBlock(ctx context.Context, content map[string]any) string {
|
||||
text := getString(content, "text")
|
||||
textClass := getString(content, "textClass")
|
||||
kicker := getString(content, "kicker")
|
||||
level := parseHeadingLevel(content)
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = scifiHeadingComponent(level, text, textClass, kicker).Render(ctx, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// parseHeadingLevel coerces an incoming heading level to 1..6, defaulting to 2.
|
||||
func parseHeadingLevel(content map[string]any) int {
|
||||
if level, ok := content["level"].(float64); ok {
|
||||
l := int(level)
|
||||
if l >= 1 && l <= 6 {
|
||||
return l
|
||||
}
|
||||
}
|
||||
if level, ok := content["level"].(int); ok {
|
||||
if level >= 1 && level <= 6 {
|
||||
return level
|
||||
}
|
||||
}
|
||||
if level, ok := content["level"].(string); ok {
|
||||
if l, err := strconv.Atoi(level); err == nil && l >= 1 && l <= 6 {
|
||||
return l
|
||||
}
|
||||
}
|
||||
return 2
|
||||
}
|
||||
48
heading_override.templ
Normal file
48
heading_override.templ
Normal file
@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
// scifiHeadingBaseClass returns level-specific tracking + weight classes.
|
||||
func scifiHeadingBaseClass(level int) string {
|
||||
switch level {
|
||||
case 1:
|
||||
return "scifi-h1 text-4xl md:text-5xl font-medium tracking-tight"
|
||||
case 2:
|
||||
return "scifi-h2 text-3xl font-medium tracking-tight"
|
||||
case 3:
|
||||
return "scifi-h3 text-2xl font-medium"
|
||||
case 4:
|
||||
return "scifi-h4 text-xl font-medium"
|
||||
case 5:
|
||||
return "scifi-h4 text-lg font-medium"
|
||||
case 6:
|
||||
return "scifi-h4 text-base font-medium uppercase tracking-widest"
|
||||
default:
|
||||
return "scifi-h2 text-3xl font-medium tracking-tight"
|
||||
}
|
||||
}
|
||||
|
||||
// scifiHeadingComponent renders a heading with an optional mono kicker rail.
|
||||
templ scifiHeadingComponent(level int, text, textClass, kicker string) {
|
||||
<div class="my-6">
|
||||
if kicker != "" {
|
||||
<div class="scifi-mono uppercase tracking-widest text-xs text-accent mb-2">
|
||||
{ kicker }
|
||||
</div>
|
||||
}
|
||||
switch level {
|
||||
case 1:
|
||||
<h1 class={ scifiHeadingBaseClass(1), "text-foreground", textClass } style="font-family: var(--font-heading);">{ text }</h1>
|
||||
case 2:
|
||||
<h2 class={ scifiHeadingBaseClass(2), "text-foreground", textClass } style="font-family: var(--font-heading);">{ text }</h2>
|
||||
case 3:
|
||||
<h3 class={ scifiHeadingBaseClass(3), "text-foreground", textClass } style="font-family: var(--font-heading);">{ text }</h3>
|
||||
case 4:
|
||||
<h4 class={ scifiHeadingBaseClass(4), "text-foreground", textClass } style="font-family: var(--font-heading);">{ text }</h4>
|
||||
case 5:
|
||||
<h5 class={ scifiHeadingBaseClass(5), "text-foreground", textClass } style="font-family: var(--font-heading);">{ text }</h5>
|
||||
case 6:
|
||||
<h6 class={ scifiHeadingBaseClass(6), "text-muted-foreground", textClass } style="font-family: var(--font-heading);">{ text }</h6>
|
||||
default:
|
||||
<h2 class={ scifiHeadingBaseClass(2), "text-foreground", textClass } style="font-family: var(--font-heading);">{ text }</h2>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
338
heading_override_templ.go
Normal file
338
heading_override_templ.go
Normal file
@ -0,0 +1,338 @@
|
||||
// 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"
|
||||
|
||||
// scifiHeadingBaseClass returns level-specific tracking + weight classes.
|
||||
func scifiHeadingBaseClass(level int) string {
|
||||
switch level {
|
||||
case 1:
|
||||
return "scifi-h1 text-4xl md:text-5xl font-medium tracking-tight"
|
||||
case 2:
|
||||
return "scifi-h2 text-3xl font-medium tracking-tight"
|
||||
case 3:
|
||||
return "scifi-h3 text-2xl font-medium"
|
||||
case 4:
|
||||
return "scifi-h4 text-xl font-medium"
|
||||
case 5:
|
||||
return "scifi-h4 text-lg font-medium"
|
||||
case 6:
|
||||
return "scifi-h4 text-base font-medium uppercase tracking-widest"
|
||||
default:
|
||||
return "scifi-h2 text-3xl font-medium tracking-tight"
|
||||
}
|
||||
}
|
||||
|
||||
// scifiHeadingComponent renders a heading with an optional mono kicker rail.
|
||||
func scifiHeadingComponent(level int, text, textClass, kicker string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"my-6\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if kicker != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"scifi-mono uppercase tracking-widest text-xs text-accent mb-2\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(kicker)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 28, Col: 12}
|
||||
}
|
||||
_, 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, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
switch level {
|
||||
case 1:
|
||||
var templ_7745c5c3_Var3 = []any{scifiHeadingBaseClass(1), "text-foreground", textClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<h1 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_Var3).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_Var4)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" style=\"font-family: var(--font-heading);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 33, Col: 121}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</h1>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
case 2:
|
||||
var templ_7745c5c3_Var6 = []any{scifiHeadingBaseClass(2), "text-foreground", textClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var6...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h2 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var6).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_Var7)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" style=\"font-family: var(--font-heading);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 35, Col: 121}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
case 3:
|
||||
var templ_7745c5c3_Var9 = []any{scifiHeadingBaseClass(3), "text-foreground", textClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<h3 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var9).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_Var10)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" style=\"font-family: var(--font-heading);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 37, Col: 121}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</h3>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
case 4:
|
||||
var templ_7745c5c3_Var12 = []any{scifiHeadingBaseClass(4), "text-foreground", textClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h4 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var12).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_Var13)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\" style=\"font-family: var(--font-heading);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 39, Col: 121}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</h4>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
case 5:
|
||||
var templ_7745c5c3_Var15 = []any{scifiHeadingBaseClass(5), "text-foreground", textClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var15...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<h5 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var16 string
|
||||
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var15).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_Var16)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" style=\"font-family: var(--font-heading);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var17 string
|
||||
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 41, Col: 121}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</h5>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
case 6:
|
||||
var templ_7745c5c3_Var18 = []any{scifiHeadingBaseClass(6), "text-muted-foreground", textClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var18...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<h6 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var19 string
|
||||
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var18).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_Var19)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\" style=\"font-family: var(--font-heading);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var20 string
|
||||
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 43, Col: 127}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</h6>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
default:
|
||||
var templ_7745c5c3_Var21 = []any{scifiHeadingBaseClass(2), "text-foreground", textClass}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var21...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<h2 class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var21).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_Var22)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" style=\"font-family: var(--font-heading);\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var23 string
|
||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 45, Col: 121}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</h2>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
82
helpers.go
Normal file
82
helpers.go
Normal file
@ -0,0 +1,82 @@
|
||||
package main
|
||||
|
||||
// getString extracts a string value from a content map.
|
||||
func getString(content map[string]any, key string) string {
|
||||
if v, ok := content[key].(string); ok {
|
||||
return v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getStringDefault returns the string at key, falling back to def if missing/blank.
|
||||
func getStringDefault(content map[string]any, key, def string) string {
|
||||
if v := getString(content, key); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// getBool extracts a boolean from a content map. Accepts native bool, string ("true"/"1"),
|
||||
// or numeric truthiness. Returns def when no value is present or parseable.
|
||||
func getBool(content map[string]any, key string, def bool) bool {
|
||||
if v, ok := content[key]; ok {
|
||||
switch t := v.(type) {
|
||||
case bool:
|
||||
return t
|
||||
case string:
|
||||
switch t {
|
||||
case "true", "TRUE", "True", "1", "yes", "on":
|
||||
return true
|
||||
case "false", "FALSE", "False", "0", "no", "off":
|
||||
return false
|
||||
}
|
||||
case float64:
|
||||
return t != 0
|
||||
case int:
|
||||
return t != 0
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// getSlice extracts a slice of maps from content under key. Returns nil if not a slice.
|
||||
func getSlice(content map[string]any, key string) []map[string]any {
|
||||
if v, ok := content[key].([]any); ok {
|
||||
out := make([]map[string]any, 0, len(v))
|
||||
for _, item := range v {
|
||||
if m, ok := item.(map[string]any); ok {
|
||||
out = append(out, m)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getMap returns the map at key, or nil.
|
||||
func getMap(content map[string]any, key string) map[string]any {
|
||||
if m, ok := content[key].(map[string]any); ok {
|
||||
return m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFloat returns the float at key, or def if missing / wrong type.
|
||||
func getFloat(content map[string]any, key string, def float64) float64 {
|
||||
if v, ok := content[key].(float64); ok {
|
||||
return v
|
||||
}
|
||||
if v, ok := content[key].(int); ok {
|
||||
return float64(v)
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// normalizeTrend coerces an incoming trend string to {up, down, flat}, defaulting to flat.
|
||||
func normalizeTrend(s string) string {
|
||||
switch s {
|
||||
case "up", "down", "flat":
|
||||
return s
|
||||
}
|
||||
return "flat"
|
||||
}
|
||||
54
mission_stat.go
Normal file
54
mission_stat.go
Normal file
@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/blocks"
|
||||
)
|
||||
|
||||
// MissionStatBlockMeta defines metadata for the mission-stat block.
|
||||
var MissionStatBlockMeta = blocks.BlockMeta{
|
||||
Key: "mission_stat",
|
||||
Title: "Mission Stat",
|
||||
Description: "Headline metric in mono numerals with a signal-orange delta arrow.",
|
||||
Source: "scifi-clean",
|
||||
Category: blocks.CategoryContent,
|
||||
}
|
||||
|
||||
// MissionStatData drives the templ render.
|
||||
type MissionStatData struct {
|
||||
Metric string
|
||||
Value string
|
||||
Unit string
|
||||
Delta string
|
||||
Trend string // "up", "down", "flat"
|
||||
}
|
||||
|
||||
// MissionStatBlock renders the mission-stat block.
|
||||
// Content shape: {"metric": "...", "value": "...", "unit": "...", "delta": "...", "trend": "up|down|flat"}
|
||||
func MissionStatBlock(ctx context.Context, content map[string]any) string {
|
||||
data := MissionStatData{
|
||||
Metric: getString(content, "metric"),
|
||||
Value: getString(content, "value"),
|
||||
Unit: getString(content, "unit"),
|
||||
Delta: getString(content, "delta"),
|
||||
Trend: normalizeTrend(getString(content, "trend")),
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = missionStatComponent(data).Render(ctx, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// trendGlyph returns the arrow character for a given trend.
|
||||
func trendGlyph(trend string) string {
|
||||
switch trend {
|
||||
case "up":
|
||||
return "↑" // ↑
|
||||
case "down":
|
||||
return "↓" // ↓
|
||||
default:
|
||||
return "→" // →
|
||||
}
|
||||
}
|
||||
35
mission_stat.templ
Normal file
35
mission_stat.templ
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
// missionStatComponent renders the headline metric card with mono numerals
|
||||
// and an accent-colored delta arrow.
|
||||
templ missionStatComponent(data MissionStatData) {
|
||||
<div data-block="scifi-clean:mission_stat" class="hairline p-6 bg-card text-card-foreground">
|
||||
if data.Metric != "" {
|
||||
<div class="scifi-mono uppercase text-xs tracking-widest text-muted-foreground mb-3">
|
||||
{ data.Metric }
|
||||
</div>
|
||||
}
|
||||
<div class="flex items-baseline gap-2">
|
||||
<span
|
||||
class="scifi-mono text-5xl font-medium tabular-nums text-foreground"
|
||||
style="font-family: var(--font-mono); font-variant-numeric: tabular-nums;"
|
||||
>
|
||||
if data.Value == "" {
|
||||
{ "—" }
|
||||
} else {
|
||||
{ data.Value }
|
||||
}
|
||||
</span>
|
||||
if data.Unit != "" {
|
||||
<span class="scifi-mono text-base text-muted-foreground tabular-nums" style="font-family: var(--font-mono); font-variant-numeric: tabular-nums;">
|
||||
{ data.Unit }
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
if data.Delta != "" {
|
||||
<div class="mt-3 scifi-mono text-sm tabular-nums" style={ "font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: hsl(var(--accent));" }>
|
||||
{ trendGlyph(data.Trend) } { data.Delta }
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
162
mission_stat_templ.go
Normal file
162
mission_stat_templ.go
Normal file
@ -0,0 +1,162 @@
|
||||
// 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"
|
||||
|
||||
// missionStatComponent renders the headline metric card with mono numerals
|
||||
// and an accent-colored delta arrow.
|
||||
func missionStatComponent(data MissionStatData) 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 data-block=\"scifi-clean:mission_stat\" class=\"hairline p-6 bg-card text-card-foreground\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Metric != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"scifi-mono uppercase text-xs tracking-widest text-muted-foreground mb-3\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Metric)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mission_stat.templ`, Line: 9, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"flex items-baseline gap-2\"><span class=\"scifi-mono text-5xl font-medium tabular-nums text-foreground\" style=\"font-family: var(--font-mono); font-variant-numeric: tabular-nums;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Value == "" {
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("—")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mission_stat.templ`, Line: 18, Col: 12}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Value)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mission_stat.templ`, Line: 20, Col: 17}
|
||||
}
|
||||
_, 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
|
||||
}
|
||||
if data.Unit != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<span class=\"scifi-mono text-base text-muted-foreground tabular-nums\" style=\"font-family: var(--font-mono); font-variant-numeric: tabular-nums;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Unit)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mission_stat.templ`, Line: 25, Col: 16}
|
||||
}
|
||||
_, 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>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Delta != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"mt-3 scifi-mono text-sm tabular-nums\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: hsl(var(--accent));")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mission_stat.templ`, Line: 30, Col: 156}
|
||||
}
|
||||
_, 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, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(trendGlyph(data.Trend))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mission_stat.templ`, Line: 31, Col: 28}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Delta)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `mission_stat.templ`, Line: 31, Col: 43}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
12
plugin.mod
Normal file
12
plugin.mod
Normal file
@ -0,0 +1,12 @@
|
||||
[plugin]
|
||||
name = "scifi-clean"
|
||||
display_name = "Sci-Fi Clean"
|
||||
scope = "@themes"
|
||||
version = "0.1.0"
|
||||
description = "Minimalist futurist theme for aerospace, robotics, and R&D — off-white surfaces, technical blue, signal-orange accents, mono numerals."
|
||||
kind = "theme"
|
||||
categories = ["templates", "developer"]
|
||||
tags = ["futurist", "technical", "aerospace", "hardware", "robotics", "biotech", "rnd", "clean", "minimalist"]
|
||||
|
||||
[compatibility]
|
||||
block_core = ">=0.11.0 <0.12.0"
|
||||
110
presets.json
Normal file
110
presets.json
Normal file
@ -0,0 +1,110 @@
|
||||
[
|
||||
{
|
||||
"id": "flightline",
|
||||
"name": "Flightline",
|
||||
"description": "Off-white paper with technical blue primary and signal-orange accent — the default Sci-Fi Clean look.",
|
||||
"theme": {
|
||||
"mode": "light",
|
||||
"lightColors": {
|
||||
"background": "210 14% 97%",
|
||||
"foreground": "215 30% 12%",
|
||||
"card": "0 0% 100%",
|
||||
"cardForeground": "215 30% 12%",
|
||||
"popover": "0 0% 100%",
|
||||
"popoverForeground": "215 30% 12%",
|
||||
"primary": "215 90% 45%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "210 16% 92%",
|
||||
"secondaryForeground": "215 30% 20%",
|
||||
"muted": "210 14% 94%",
|
||||
"mutedForeground": "215 12% 42%",
|
||||
"accent": "18 95% 55%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 78% 50%",
|
||||
"destructiveForeground": "0 0% 100%",
|
||||
"border": "215 14% 86%",
|
||||
"input": "215 14% 88%",
|
||||
"ring": "215 90% 45%"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mission-control",
|
||||
"name": "Mission Control",
|
||||
"description": "Graphite night-shift surface for ops dashboards — lifted technical blue and signal orange on a deep neutral.",
|
||||
"theme": {
|
||||
"mode": "dark",
|
||||
"darkColors": {
|
||||
"background": "220 16% 7%",
|
||||
"foreground": "210 16% 92%",
|
||||
"card": "220 16% 10%",
|
||||
"cardForeground": "210 16% 92%",
|
||||
"popover": "220 16% 10%",
|
||||
"popoverForeground": "210 16% 92%",
|
||||
"primary": "210 100% 60%",
|
||||
"primaryForeground": "220 16% 7%",
|
||||
"secondary": "220 14% 16%",
|
||||
"secondaryForeground": "210 16% 92%",
|
||||
"muted": "220 12% 14%",
|
||||
"mutedForeground": "215 10% 60%",
|
||||
"accent": "18 100% 60%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 80% 55%",
|
||||
"destructiveForeground": "0 0% 100%",
|
||||
"border": "220 14% 20%",
|
||||
"input": "220 14% 18%",
|
||||
"ring": "210 100% 60%"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cleanroom",
|
||||
"name": "Cleanroom",
|
||||
"description": "Highest-contrast surgical white with deep navy ink — sister dark mode for night-lab work.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "220 40% 10%",
|
||||
"card": "210 20% 99%",
|
||||
"cardForeground": "220 40% 10%",
|
||||
"popover": "0 0% 100%",
|
||||
"popoverForeground": "220 40% 10%",
|
||||
"primary": "220 85% 35%",
|
||||
"primaryForeground": "0 0% 100%",
|
||||
"secondary": "210 18% 94%",
|
||||
"secondaryForeground": "220 40% 10%",
|
||||
"muted": "210 16% 96%",
|
||||
"mutedForeground": "215 14% 38%",
|
||||
"accent": "18 92% 50%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 78% 48%",
|
||||
"destructiveForeground": "0 0% 100%",
|
||||
"border": "215 16% 88%",
|
||||
"input": "215 16% 90%",
|
||||
"ring": "220 85% 35%"
|
||||
},
|
||||
"darkColors": {
|
||||
"background": "220 30% 5%",
|
||||
"foreground": "210 20% 96%",
|
||||
"card": "220 26% 9%",
|
||||
"cardForeground": "210 20% 96%",
|
||||
"popover": "220 26% 9%",
|
||||
"popoverForeground": "210 20% 96%",
|
||||
"primary": "210 100% 65%",
|
||||
"primaryForeground": "220 30% 5%",
|
||||
"secondary": "220 20% 16%",
|
||||
"secondaryForeground": "210 20% 96%",
|
||||
"muted": "220 16% 13%",
|
||||
"mutedForeground": "215 12% 62%",
|
||||
"accent": "18 100% 62%",
|
||||
"accentForeground": "0 0% 100%",
|
||||
"destructive": "0 80% 58%",
|
||||
"destructiveForeground": "0 0% 100%",
|
||||
"border": "220 18% 20%",
|
||||
"input": "220 18% 18%",
|
||||
"ring": "210 100% 65%"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
197
register.go
Normal file
197
register.go
Normal file
@ -0,0 +1,197 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/a-h/templ"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/blocks"
|
||||
"git.dev.alexdunmow.com/block/core/plugin"
|
||||
"git.dev.alexdunmow.com/block/core/templates"
|
||||
)
|
||||
|
||||
// wrap adapts a templ-returning render function to templates.TemplateFunc.
|
||||
// templ.Component already implements templates.HTMLComponent via Render.
|
||||
func wrap(f func(ctx context.Context, doc map[string]any) templ.Component) templates.TemplateFunc {
|
||||
return func(ctx context.Context, doc map[string]any) templates.HTMLComponent {
|
||||
return f(ctx, doc)
|
||||
}
|
||||
}
|
||||
|
||||
// Register is the plugin entry point. It registers the system template, the
|
||||
// four page templates, the six theme-specific blocks, the three overrides,
|
||||
// and the email wrapper.
|
||||
//
|
||||
// IMPORTANT: schemas are loaded BEFORE any br.Register(...) call — this is a
|
||||
// hard rule (CLAUDE.md, UAT §3) so the block registry binds content schemas
|
||||
// at registration time.
|
||||
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
|
||||
// --- System template ---
|
||||
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
|
||||
Key: "scifi-clean",
|
||||
Title: "Sci-Fi Clean",
|
||||
Description: "Minimalist futurist theme for aerospace, robotics, and R&D — off-white surfaces, technical blue, signal-orange accents, mono numerals.",
|
||||
})
|
||||
|
||||
// --- Page templates (spec §6) ---
|
||||
if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{
|
||||
Key: "default",
|
||||
Title: "Default",
|
||||
Description: "Standard cleanroom layout with header rail, main column, and footer instrument panel.",
|
||||
Slots: []string{"header", "main", "footer"},
|
||||
}, wrap(RenderScifiClean)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{
|
||||
Key: "landing",
|
||||
Title: "Landing",
|
||||
Description: "Hero diagram + spec strip + CTA banner.",
|
||||
Slots: []string{"hero", "specs", "main", "cta", "footer"},
|
||||
}, wrap(RenderScifiCleanLanding)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{
|
||||
Key: "article",
|
||||
Title: "Article",
|
||||
Description: "Narrow column with rail metadata and figure margin.",
|
||||
Slots: []string{"header", "rail", "main", "footer"},
|
||||
}, wrap(RenderScifiCleanArticle)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tr.RegisterPageTemplate("scifi-clean", templates.PageTemplateMeta{
|
||||
Key: "full-width",
|
||||
Title: "Full Width",
|
||||
Description: "Edge-to-edge schematic surface for diagrams and dashboards.",
|
||||
Slots: []string{"header", "main", "footer"},
|
||||
}, wrap(RenderScifiCleanFullWidth)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// --- Load JSON Schemas BEFORE any block registration ---
|
||||
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// --- Theme-specific blocks (spec §8) ---
|
||||
br.Register(TechSpecBlockMeta, TechSpecBlock)
|
||||
br.Register(DiagramCaptionBlockMeta, DiagramCaptionBlock)
|
||||
br.Register(MissionStatBlockMeta, MissionStatBlock)
|
||||
br.Register(StatusBarBlockMeta, StatusBarBlock)
|
||||
br.Register(FooterBlockMeta, FooterBlock)
|
||||
br.Register(SchematicHeroBlockMeta, SchematicHeroBlock)
|
||||
|
||||
// --- Built-in overrides (spec §9) ---
|
||||
br.RegisterTemplateOverride("scifi-clean", "heading", ScifiHeadingBlock)
|
||||
br.RegisterTemplateOverride("scifi-clean", "text", ScifiTextBlock)
|
||||
br.RegisterTemplateOverride("scifi-clean", "button", ScifiButtonBlock)
|
||||
|
||||
// --- Email wrapper (spec §10) ---
|
||||
tr.RegisterEmailWrapper("scifi-clean", ScifiEmailWrapper)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultMasterPages returns the two master pages that Sci-Fi Clean provisions
|
||||
// on first plugin load (spec §7, UAT §9).
|
||||
//
|
||||
// - scifi-clean:default-master → attached to `default`, `article`, `full-width`
|
||||
// - scifi-clean:landing-master → attached to `landing`, pre-populates `hero`
|
||||
// with a mission_stat and `specs` with a tech_spec.
|
||||
func DefaultMasterPages() []plugin.MasterPageDefinition {
|
||||
defaultMaster := plugin.MasterPageDefinition{
|
||||
Key: "scifi-clean:default-master",
|
||||
Title: "Sci-Fi Clean Default Master",
|
||||
PageTemplates: []string{"default", "article", "full-width"},
|
||||
Blocks: []plugin.MasterPageBlock{
|
||||
{
|
||||
BlockKey: "navbar",
|
||||
Title: "Mission Header",
|
||||
Content: map[string]any{"menuName": "main"},
|
||||
Slot: "header",
|
||||
SortOrder: 0,
|
||||
},
|
||||
{
|
||||
BlockKey: "scifi-clean:status_bar",
|
||||
Title: "System Status Rail",
|
||||
Content: map[string]any{"showClock": true, "showBuild": true, "callsign": "SCF-CLN"},
|
||||
Slot: "header",
|
||||
SortOrder: 10,
|
||||
},
|
||||
{
|
||||
BlockKey: "slot",
|
||||
Title: "Main Content",
|
||||
Content: map[string]any{"slotName": "main", "placeholder": "Page payload"},
|
||||
Slot: "main",
|
||||
SortOrder: 0,
|
||||
},
|
||||
{
|
||||
BlockKey: "scifi-clean:footer",
|
||||
Title: "Instrument Footer",
|
||||
Content: map[string]any{"showSignup": true, "callsign": "SCF-CLN"},
|
||||
Slot: "footer",
|
||||
SortOrder: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
landingMaster := plugin.MasterPageDefinition{
|
||||
Key: "scifi-clean:landing-master",
|
||||
Title: "Sci-Fi Clean Landing Master",
|
||||
PageTemplates: []string{"landing"},
|
||||
Blocks: []plugin.MasterPageBlock{
|
||||
{
|
||||
BlockKey: "scifi-clean:status_bar",
|
||||
Title: "System Status Rail",
|
||||
Content: map[string]any{"showClock": true, "showBuild": true, "callsign": "SCF-CLN"},
|
||||
Slot: "header",
|
||||
SortOrder: 0,
|
||||
},
|
||||
{
|
||||
BlockKey: "scifi-clean:mission_stat",
|
||||
Title: "Hero Mission Stat",
|
||||
Content: map[string]any{
|
||||
"metric": "Apogee",
|
||||
"value": "412",
|
||||
"unit": "km",
|
||||
"delta": "+12",
|
||||
"trend": "up",
|
||||
},
|
||||
Slot: "hero",
|
||||
SortOrder: 0,
|
||||
},
|
||||
{
|
||||
BlockKey: "scifi-clean:tech_spec",
|
||||
Title: "Hero Tech Spec",
|
||||
Content: map[string]any{
|
||||
"caption": "Launch envelope",
|
||||
"rows": []any{
|
||||
map[string]any{"label": "Thrust", "value": "1.42", "unit": "MN"},
|
||||
map[string]any{"label": "Burn time", "value": "186", "unit": "s"},
|
||||
map[string]any{"label": "Payload", "value": "320", "unit": "kg"},
|
||||
},
|
||||
},
|
||||
Slot: "specs",
|
||||
SortOrder: 0,
|
||||
},
|
||||
{
|
||||
BlockKey: "slot",
|
||||
Title: "Main Content",
|
||||
Content: map[string]any{"slotName": "main", "placeholder": "Page payload"},
|
||||
Slot: "main",
|
||||
SortOrder: 0,
|
||||
},
|
||||
{
|
||||
BlockKey: "scifi-clean:footer",
|
||||
Title: "Instrument Footer",
|
||||
Content: map[string]any{"showSignup": true, "callsign": "SCF-CLN"},
|
||||
Slot: "footer",
|
||||
SortOrder: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return []plugin.MasterPageDefinition{defaultMaster, landingMaster}
|
||||
}
|
||||
25
registration.go
Normal file
25
registration.go
Normal file
@ -0,0 +1,25 @@
|
||||
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 Sci-Fi Clean theme.
|
||||
var Registration = plugin.PluginRegistration{
|
||||
Name: "scifi-clean",
|
||||
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() },
|
||||
}
|
||||
67
schemas/diagram_caption.schema.json
Normal file
67
schemas/diagram_caption.schema.json
Normal file
@ -0,0 +1,67 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Diagram Caption",
|
||||
"description": "Annotated technical figure with a Fig. NN. heading and optional callout pins.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"image": {
|
||||
"type": "string",
|
||||
"title": "Image",
|
||||
"description": "Diagram / blueprint / schematic to display.",
|
||||
"x-editor": "media"
|
||||
},
|
||||
"figureNumber": {
|
||||
"type": "string",
|
||||
"title": "Figure Number",
|
||||
"description": "Number printed after the Fig. prefix (e.g. 03).",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"title": "Title",
|
||||
"description": "Short heading next to the figure number.",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"title": "Body",
|
||||
"description": "Rich description / annotation text rendered below the figure.",
|
||||
"x-editor": "richtext"
|
||||
},
|
||||
"calloutPins": {
|
||||
"type": "array",
|
||||
"title": "Callout Pins",
|
||||
"description": "Percent-positioned overlay pins; each pin is x, y, label.",
|
||||
"default": [],
|
||||
"x-editor": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "number",
|
||||
"title": "X (%)",
|
||||
"description": "Horizontal position as a percentage of the image width (0-100).",
|
||||
"minimum": 0,
|
||||
"maximum": 100,
|
||||
"x-editor": "number"
|
||||
},
|
||||
"y": {
|
||||
"type": "number",
|
||||
"title": "Y (%)",
|
||||
"description": "Vertical position as a percentage of the image height (0-100).",
|
||||
"minimum": 0,
|
||||
"maximum": 100,
|
||||
"x-editor": "number"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"title": "Label",
|
||||
"description": "Short callout text.",
|
||||
"x-editor": "text"
|
||||
}
|
||||
},
|
||||
"required": ["label"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
schemas/footer.schema.json
Normal file
46
schemas/footer.schema.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Instrument Footer",
|
||||
"description": "Hairline-rule footer with callsign, optional signup, and link list.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"showSignup": {
|
||||
"type": "boolean",
|
||||
"title": "Show Signup",
|
||||
"description": "Render the inline email signup form.",
|
||||
"default": false,
|
||||
"x-editor": "select"
|
||||
},
|
||||
"callsign": {
|
||||
"type": "string",
|
||||
"title": "Callsign",
|
||||
"description": "Short mono identifier displayed at the corner of the footer rail.",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"links": {
|
||||
"type": "array",
|
||||
"title": "Links",
|
||||
"description": "Footer link list (each item is text + url).",
|
||||
"default": [],
|
||||
"x-editor": "collection",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"title": "Link Text",
|
||||
"description": "Visible link text.",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"title": "URL",
|
||||
"description": "Destination URL.",
|
||||
"x-editor": "text"
|
||||
}
|
||||
},
|
||||
"required": ["text"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
schemas/mission_stat.schema.json
Normal file
40
schemas/mission_stat.schema.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Mission Stat",
|
||||
"description": "Headline metric in mono numerals with a signal-orange delta arrow.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"metric": {
|
||||
"type": "string",
|
||||
"title": "Metric",
|
||||
"description": "Name of the measured quantity (e.g. Apogee).",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"title": "Value",
|
||||
"description": "Headline numeric value (e.g. 412).",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"title": "Unit",
|
||||
"description": "Trailing unit (e.g. km, m/s).",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"delta": {
|
||||
"type": "string",
|
||||
"title": "Delta",
|
||||
"description": "Short delta string (e.g. +12, -0.3%).",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"trend": {
|
||||
"type": "string",
|
||||
"title": "Trend",
|
||||
"description": "Direction of the delta arrow.",
|
||||
"default": "flat",
|
||||
"enum": ["up", "down", "flat"],
|
||||
"x-editor": "select"
|
||||
}
|
||||
}
|
||||
}
|
||||
62
schemas/schematic_hero.schema.json
Normal file
62
schemas/schematic_hero.schema.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Schematic Hero",
|
||||
"description": "Landing hero with a large display title over a blueprint image and dual CTAs.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"title": "Title",
|
||||
"description": "Main display headline (rendered in Space Grotesk at >= 64px).",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"kicker": {
|
||||
"type": "string",
|
||||
"title": "Kicker",
|
||||
"description": "Short mono uppercase eyebrow text printed above the title.",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"title": "Background Image",
|
||||
"description": "Blueprint / schematic image rendered behind the title.",
|
||||
"x-editor": "media"
|
||||
},
|
||||
"primaryCta": {
|
||||
"type": "object",
|
||||
"title": "Primary CTA",
|
||||
"description": "Primary call-to-action link.",
|
||||
"x-editor": "link",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"title": "Text",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"href": {
|
||||
"type": "string",
|
||||
"title": "URL",
|
||||
"x-editor": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
"secondaryCta": {
|
||||
"type": "object",
|
||||
"title": "Secondary CTA",
|
||||
"description": "Optional secondary call-to-action link.",
|
||||
"x-editor": "link",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"title": "Text",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"href": {
|
||||
"type": "string",
|
||||
"title": "URL",
|
||||
"x-editor": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
schemas/status_bar.schema.json
Normal file
28
schemas/status_bar.schema.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Status Bar",
|
||||
"description": "Persistent top rail with optional UTC clock, build hash, and callsign.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"showClock": {
|
||||
"type": "boolean",
|
||||
"title": "Show Clock",
|
||||
"description": "Show a live UTC clock element.",
|
||||
"default": true,
|
||||
"x-editor": "select"
|
||||
},
|
||||
"showBuild": {
|
||||
"type": "boolean",
|
||||
"title": "Show Build Hash",
|
||||
"description": "Show the short build hash on the right rail.",
|
||||
"default": true,
|
||||
"x-editor": "select"
|
||||
},
|
||||
"callsign": {
|
||||
"type": "string",
|
||||
"title": "Callsign",
|
||||
"description": "Short mono identifier for the running surface (e.g. SCF-CLN).",
|
||||
"x-editor": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
45
schemas/tech_spec.schema.json
Normal file
45
schemas/tech_spec.schema.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Tech Spec Table",
|
||||
"description": "Specification table with right-aligned mono numerals and hairline rows.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"caption": {
|
||||
"type": "string",
|
||||
"title": "Caption",
|
||||
"description": "Optional caption rendered above the table.",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"rows": {
|
||||
"type": "array",
|
||||
"title": "Rows",
|
||||
"description": "Each row is a label / value / unit triple.",
|
||||
"default": [],
|
||||
"x-editor": "collection",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"title": "Label",
|
||||
"description": "Left column descriptor (e.g. Thrust).",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"title": "Value",
|
||||
"description": "Right-aligned mono numeral or short string.",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"title": "Unit",
|
||||
"description": "Trailing unit (e.g. kN, kg, ms).",
|
||||
"x-editor": "text"
|
||||
}
|
||||
},
|
||||
"required": ["label", "value"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
55
schematic_hero.go
Normal file
55
schematic_hero.go
Normal file
@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/blocks"
|
||||
)
|
||||
|
||||
// SchematicHeroBlockMeta defines metadata for the landing hero.
|
||||
var SchematicHeroBlockMeta = blocks.BlockMeta{
|
||||
Key: "schematic_hero",
|
||||
Title: "Schematic Hero",
|
||||
Description: "Landing hero with a large display title over a blueprint image and dual CTAs.",
|
||||
Source: "scifi-clean",
|
||||
Category: blocks.CategoryLayout,
|
||||
}
|
||||
|
||||
// SchematicCTA is a single call-to-action link.
|
||||
type SchematicCTA struct {
|
||||
Text string
|
||||
Href string
|
||||
}
|
||||
|
||||
// SchematicHeroData drives the templ render.
|
||||
type SchematicHeroData struct {
|
||||
Title string
|
||||
Kicker string
|
||||
Image string
|
||||
PrimaryCTA SchematicCTA
|
||||
SecondaryCTA SchematicCTA
|
||||
}
|
||||
|
||||
// SchematicHeroBlock renders the landing hero.
|
||||
// Content shape: {"title": "...", "kicker": "...", "image": "media:...",
|
||||
// "primaryCta": {"text": "...", "href": "..."},
|
||||
// "secondaryCta": {"text": "...", "href": "..."}}
|
||||
func SchematicHeroBlock(ctx context.Context, content map[string]any) string {
|
||||
data := SchematicHeroData{
|
||||
Title: getString(content, "title"),
|
||||
Kicker: getString(content, "kicker"),
|
||||
Image: blocks.ResolveMediaPath(getString(content, "image")),
|
||||
}
|
||||
|
||||
if m := getMap(content, "primaryCta"); m != nil {
|
||||
data.PrimaryCTA = SchematicCTA{Text: getString(m, "text"), Href: getString(m, "href")}
|
||||
}
|
||||
if m := getMap(content, "secondaryCta"); m != nil {
|
||||
data.SecondaryCTA = SchematicCTA{Text: getString(m, "text"), Href: getString(m, "href")}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = schematicHeroComponent(data).Render(ctx, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
55
schematic_hero.templ
Normal file
55
schematic_hero.templ
Normal file
@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
// schematicHeroComponent renders the landing-page hero.
|
||||
// UAT §13.6 requires the <h1> to render in Space Grotesk at >= 64px on 1440x900.
|
||||
// UAT §6 requires a scrim layer between the background image and the title text.
|
||||
templ schematicHeroComponent(data SchematicHeroData) {
|
||||
<section data-block="scifi-clean:schematic_hero" class="relative overflow-hidden bg-grid">
|
||||
if data.Image != "" {
|
||||
<div class="absolute inset-0 z-0">
|
||||
<img
|
||||
src={ data.Image }
|
||||
alt=""
|
||||
class="w-full h-full object-cover opacity-25"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
<div class="scrim relative z-10">
|
||||
<div class="max-w-6xl mx-auto px-4 py-24 md:py-32">
|
||||
if data.Kicker != "" {
|
||||
<div class="scifi-mono uppercase tracking-widest text-xs text-accent mb-4">
|
||||
{ data.Kicker }
|
||||
</div>
|
||||
}
|
||||
<h1
|
||||
class="scifi-h1 font-medium tracking-tight text-foreground"
|
||||
style="font-family: var(--font-heading); font-size: clamp(3rem, 6vw, 5rem); line-height: 1.05;"
|
||||
>
|
||||
if data.Title == "" {
|
||||
{ "Untitled mission" }
|
||||
} else {
|
||||
{ data.Title }
|
||||
}
|
||||
</h1>
|
||||
<div class="mt-8 flex flex-wrap gap-3">
|
||||
if data.PrimaryCTA.Text != "" && data.PrimaryCTA.Href != "" {
|
||||
<a
|
||||
href={ templ.SafeURL(data.PrimaryCTA.Href) }
|
||||
class="scifi-mono uppercase tracking-widest text-xs px-5 py-3 bg-primary text-primary-foreground scifi-chevron scifi-focus"
|
||||
>
|
||||
{ data.PrimaryCTA.Text }
|
||||
</a>
|
||||
}
|
||||
if data.SecondaryCTA.Text != "" && data.SecondaryCTA.Href != "" {
|
||||
<a
|
||||
href={ templ.SafeURL(data.SecondaryCTA.Href) }
|
||||
class="scifi-mono uppercase tracking-widest text-xs px-5 py-3 hairline text-foreground scifi-chevron scifi-focus"
|
||||
>
|
||||
{ data.SecondaryCTA.Text }
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
182
schematic_hero_templ.go
Normal file
182
schematic_hero_templ.go
Normal file
@ -0,0 +1,182 @@
|
||||
// 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"
|
||||
|
||||
// schematicHeroComponent renders the landing-page hero.
|
||||
// UAT §13.6 requires the <h1> to render in Space Grotesk at >= 64px on 1440x900.
|
||||
// UAT §6 requires a scrim layer between the background image and the title text.
|
||||
func schematicHeroComponent(data SchematicHeroData) 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 data-block=\"scifi-clean:schematic_hero\" class=\"relative overflow-hidden bg-grid\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Image != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"absolute inset-0 z-0\"><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.Image)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schematic_hero.templ`, Line: 11, Col: 21}
|
||||
}
|
||||
_, 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=\"\" class=\"w-full h-full object-cover opacity-25\"></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"scrim relative z-10\"><div class=\"max-w-6xl mx-auto px-4 py-24 md:py-32\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Kicker != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"scifi-mono uppercase tracking-widest text-xs text-accent mb-4\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.Kicker)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schematic_hero.templ`, Line: 21, Col: 19}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<h1 class=\"scifi-h1 font-medium tracking-tight text-foreground\" style=\"font-family: var(--font-heading); font-size: clamp(3rem, 6vw, 5rem); line-height: 1.05;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Title == "" {
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("Untitled mission")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schematic_hero.templ`, Line: 29, Col: 26}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schematic_hero.templ`, Line: 31, Col: 18}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</h1><div class=\"mt-8 flex flex-wrap gap-3\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.PrimaryCTA.Text != "" && data.PrimaryCTA.Href != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 templ.SafeURL
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.PrimaryCTA.Href))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schematic_hero.templ`, Line: 37, Col: 49}
|
||||
}
|
||||
_, 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, "\" class=\"scifi-mono uppercase tracking-widest text-xs px-5 py-3 bg-primary text-primary-foreground scifi-chevron scifi-focus\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.PrimaryCTA.Text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schematic_hero.templ`, Line: 40, Col: 29}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</a> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.SecondaryCTA.Text != "" && data.SecondaryCTA.Href != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 templ.SafeURL
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(data.SecondaryCTA.Href))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schematic_hero.templ`, Line: 45, Col: 51}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" class=\"scifi-mono uppercase tracking-widest text-xs px-5 py-3 hairline text-foreground scifi-chevron scifi-focus\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(data.SecondaryCTA.Text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `schematic_hero.templ`, Line: 48, Col: 31}
|
||||
}
|
||||
_, 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, 14, "</a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div></div></div></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
38
status_bar.go
Normal file
38
status_bar.go
Normal file
@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/blocks"
|
||||
)
|
||||
|
||||
// StatusBarBlockMeta defines metadata for the persistent status-bar rail.
|
||||
var StatusBarBlockMeta = blocks.BlockMeta{
|
||||
Key: "status_bar",
|
||||
Title: "Status Bar",
|
||||
Description: "Persistent top rail with optional UTC clock, build hash, and callsign.",
|
||||
Source: "scifi-clean",
|
||||
Category: blocks.CategoryNavigation,
|
||||
}
|
||||
|
||||
// StatusBarData drives the templ render.
|
||||
type StatusBarData struct {
|
||||
ShowClock bool
|
||||
ShowBuild bool
|
||||
Callsign string
|
||||
}
|
||||
|
||||
// StatusBarBlock renders the persistent top rail.
|
||||
// Content shape: {"showClock": true, "showBuild": true, "callsign": "SCF-CLN"}
|
||||
func StatusBarBlock(ctx context.Context, content map[string]any) string {
|
||||
data := StatusBarData{
|
||||
ShowClock: getBool(content, "showClock", true),
|
||||
ShowBuild: getBool(content, "showBuild", true),
|
||||
Callsign: getStringDefault(content, "callsign", "SCF-CLN"),
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = statusBarComponent(data).Render(ctx, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
46
status_bar.templ
Normal file
46
status_bar.templ
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
// statusBarComponent renders the sticky top status rail.
|
||||
// UAT §13.9 requires position: sticky and top: 0.
|
||||
templ statusBarComponent(data StatusBarData) {
|
||||
<div
|
||||
data-block="scifi-clean:status_bar"
|
||||
class="w-full hairline-b bg-card text-card-foreground scifi-mono text-xs uppercase tracking-widest"
|
||||
style="position: sticky; top: 0; z-index: 30;"
|
||||
>
|
||||
<div class="max-w-6xl mx-auto px-4 py-2 flex items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-muted-foreground">{ data.Callsign }</span>
|
||||
<span class="text-muted-foreground">|</span>
|
||||
<span class="text-foreground">PUBLIC</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 tabular-nums">
|
||||
if data.ShowClock {
|
||||
<span
|
||||
data-status-bar-clock="utc"
|
||||
class="text-muted-foreground tabular-nums"
|
||||
>
|
||||
{ "00:00:00 UTC" }
|
||||
</span>
|
||||
}
|
||||
if data.ShowBuild {
|
||||
<span class="text-muted-foreground">|</span>
|
||||
<span class="text-foreground tabular-nums">BLD a1b2c3</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){
|
||||
var el = document.querySelector('[data-status-bar-clock="utc"]');
|
||||
if (!el) return;
|
||||
function fmt(n){ return n < 10 ? '0' + n : String(n); }
|
||||
function tick(){
|
||||
var d = new Date();
|
||||
el.textContent = fmt(d.getUTCHours()) + ':' + fmt(d.getUTCMinutes()) + ':' + fmt(d.getUTCSeconds()) + ' UTC';
|
||||
}
|
||||
tick();
|
||||
setInterval(tick, 1000);
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
}
|
||||
84
status_bar_templ.go
Normal file
84
status_bar_templ.go
Normal file
@ -0,0 +1,84 @@
|
||||
// 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"
|
||||
|
||||
// statusBarComponent renders the sticky top status rail.
|
||||
// UAT §13.9 requires position: sticky and top: 0.
|
||||
func statusBarComponent(data StatusBarData) 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 data-block=\"scifi-clean:status_bar\" class=\"w-full hairline-b bg-card text-card-foreground scifi-mono text-xs uppercase tracking-widest\" style=\"position: sticky; top: 0; z-index: 30;\"><div class=\"max-w-6xl mx-auto px-4 py-2 flex items-center justify-between gap-4\"><div class=\"flex items-center gap-3\"><span class=\"text-muted-foreground\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Callsign)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `status_bar.templ`, Line: 13, Col: 55}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</span> <span class=\"text-muted-foreground\">|</span> <span class=\"text-foreground\">PUBLIC</span></div><div class=\"flex items-center gap-3 tabular-nums\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.ShowClock {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<span data-status-bar-clock=\"utc\" class=\"text-muted-foreground tabular-nums\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("00:00:00 UTC")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `status_bar.templ`, Line: 23, Col: 22}
|
||||
}
|
||||
_, 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> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
if data.ShowBuild {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<span class=\"text-muted-foreground\">|</span> <span class=\"text-foreground tabular-nums\">BLD a1b2c3</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div></div><script>\n\t\t(function(){\n\t\t\tvar el = document.querySelector('[data-status-bar-clock=\"utc\"]');\n\t\t\tif (!el) return;\n\t\t\tfunction fmt(n){ return n < 10 ? '0' + n : String(n); }\n\t\t\tfunction tick(){\n\t\t\t\tvar d = new Date();\n\t\t\t\tel.textContent = fmt(d.getUTCHours()) + ':' + fmt(d.getUTCMinutes()) + ':' + fmt(d.getUTCSeconds()) + ' UTC';\n\t\t\t}\n\t\t\ttick();\n\t\t\tsetInterval(tick, 1000);\n\t\t})();\n\t\t</script></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
50
tech_spec.go
Normal file
50
tech_spec.go
Normal file
@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/blocks"
|
||||
)
|
||||
|
||||
// TechSpecBlockMeta defines metadata for the tech-spec table block.
|
||||
var TechSpecBlockMeta = blocks.BlockMeta{
|
||||
Key: "tech_spec",
|
||||
Title: "Tech Spec Table",
|
||||
Description: "Specification table with right-aligned mono numerals and hairline rows.",
|
||||
Source: "scifi-clean",
|
||||
Category: blocks.CategoryContent,
|
||||
}
|
||||
|
||||
// TechSpecRow is one label/value/unit triple.
|
||||
type TechSpecRow struct {
|
||||
Label string
|
||||
Value string
|
||||
Unit string
|
||||
}
|
||||
|
||||
// TechSpecData drives the templ render.
|
||||
type TechSpecData struct {
|
||||
Caption string
|
||||
Rows []TechSpecRow
|
||||
}
|
||||
|
||||
// TechSpecBlock renders the tech-spec block.
|
||||
// Content shape: {"caption": "...", "rows": [{"label": "...", "value": "...", "unit": "..."}]}
|
||||
func TechSpecBlock(ctx context.Context, content map[string]any) string {
|
||||
data := TechSpecData{
|
||||
Caption: getString(content, "caption"),
|
||||
}
|
||||
|
||||
for _, raw := range getSlice(content, "rows") {
|
||||
data.Rows = append(data.Rows, TechSpecRow{
|
||||
Label: getString(raw, "label"),
|
||||
Value: getString(raw, "value"),
|
||||
Unit: getString(raw, "unit"),
|
||||
})
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = techSpecComponent(data).Render(ctx, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
53
tech_spec.templ
Normal file
53
tech_spec.templ
Normal file
@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
// techSpecComponent renders the tech-spec table with right-aligned mono numerals
|
||||
// and hairline row rules.
|
||||
templ techSpecComponent(data TechSpecData) {
|
||||
<section data-block="scifi-clean:tech_spec" class="py-8">
|
||||
<div class="max-w-3xl mx-auto px-4">
|
||||
if data.Caption != "" {
|
||||
<p class="scifi-mono text-xs uppercase tracking-widest text-muted-foreground mb-3">
|
||||
{ data.Caption }
|
||||
</p>
|
||||
}
|
||||
<table class="w-full text-sm hairline">
|
||||
<tbody>
|
||||
if len(data.Rows) == 0 {
|
||||
<tr class="hairline-b">
|
||||
<td class="scifi-body py-3 px-4 text-foreground">No spec rows yet.</td>
|
||||
<td class="scifi-mono py-3 px-4 text-right tabular-nums text-muted-foreground">--</td>
|
||||
<td class="scifi-mono py-3 pr-4 text-right text-muted-foreground"></td>
|
||||
</tr>
|
||||
} else {
|
||||
for i, row := range data.Rows {
|
||||
@techSpecRow(row, i == len(data.Rows)-1)
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
// techSpecRow renders a single row. Last-row check controls whether to drop
|
||||
// the bottom hairline.
|
||||
templ techSpecRow(row TechSpecRow, last bool) {
|
||||
<tr class={ rowBorderClass(last) }>
|
||||
<td class="scifi-body py-3 px-4 text-muted-foreground uppercase text-xs tracking-wider">
|
||||
{ row.Label }
|
||||
</td>
|
||||
<td class="scifi-mono py-3 px-4 text-right tabular-nums text-foreground font-medium" style="font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right;">
|
||||
{ row.Value }
|
||||
</td>
|
||||
<td class="scifi-mono py-3 pr-4 text-right text-muted-foreground text-xs" style="font-family: var(--font-mono); text-align: right;">
|
||||
{ row.Unit }
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
func rowBorderClass(last bool) string {
|
||||
if last {
|
||||
return ""
|
||||
}
|
||||
return "hairline-b"
|
||||
}
|
||||
177
tech_spec_templ.go
Normal file
177
tech_spec_templ.go
Normal file
@ -0,0 +1,177 @@
|
||||
// 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"
|
||||
|
||||
// techSpecComponent renders the tech-spec table with right-aligned mono numerals
|
||||
// and hairline row rules.
|
||||
func techSpecComponent(data TechSpecData) 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 data-block=\"scifi-clean:tech_spec\" class=\"py-8\"><div class=\"max-w-3xl mx-auto px-4\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data.Caption != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<p class=\"scifi-mono text-xs uppercase tracking-widest text-muted-foreground mb-3\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Caption)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tech_spec.templ`, Line: 10, Col: 19}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<table class=\"w-full text-sm hairline\"><tbody>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(data.Rows) == 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<tr class=\"hairline-b\"><td class=\"scifi-body py-3 px-4 text-foreground\">No spec rows yet.</td><td class=\"scifi-mono py-3 px-4 text-right tabular-nums text-muted-foreground\">--</td><td class=\"scifi-mono py-3 pr-4 text-right text-muted-foreground\"></td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
for i, row := range data.Rows {
|
||||
templ_7745c5c3_Err = techSpecRow(row, i == len(data.Rows)-1).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</tbody></table></div></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// techSpecRow renders a single row. Last-row check controls whether to drop
|
||||
// the bottom hairline.
|
||||
func techSpecRow(row TechSpecRow, last bool) 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)
|
||||
var templ_7745c5c3_Var4 = []any{rowBorderClass(last)}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var4...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<tr class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var4).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tech_spec.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\"><td class=\"scifi-body py-3 px-4 text-muted-foreground uppercase text-xs tracking-wider\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(row.Label)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tech_spec.templ`, Line: 37, Col: 14}
|
||||
}
|
||||
_, 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, 9, "</td><td class=\"scifi-mono py-3 px-4 text-right tabular-nums text-foreground font-medium\" style=\"font-family: var(--font-mono); font-variant-numeric: tabular-nums; text-align: right;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(row.Value)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tech_spec.templ`, Line: 40, Col: 14}
|
||||
}
|
||||
_, 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, 10, "</td><td class=\"scifi-mono py-3 pr-4 text-right text-muted-foreground text-xs\" style=\"font-family: var(--font-mono); text-align: right;\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(row.Unit)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `tech_spec.templ`, Line: 43, Col: 13}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func rowBorderClass(last bool) string {
|
||||
if last {
|
||||
return ""
|
||||
}
|
||||
return "hairline-b"
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
281
template.templ
Normal file
281
template.templ
Normal file
@ -0,0 +1,281 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.dev.alexdunmow.com/block/core/templates/bn"
|
||||
)
|
||||
|
||||
// PageData holds the values lifted out of the render `doc` map for the
|
||||
// four Sci-Fi Clean page templates.
|
||||
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
|
||||
}
|
||||
|
||||
// parseScifiPageData lifts the well-known keys out of the inbound doc map.
|
||||
// Unknown keys (e.g. block content) are ignored by the page renderers.
|
||||
func parseScifiPageData(doc map[string]any) PageData {
|
||||
title := "Untitled"
|
||||
if t, ok := doc["title"].(string); ok {
|
||||
title = t
|
||||
}
|
||||
|
||||
slots := make(map[string]string)
|
||||
if s, ok := doc["slots"].(map[string]string); ok {
|
||||
slots = s
|
||||
}
|
||||
|
||||
themeCSS := ""
|
||||
if tc, ok := doc["theme_css"].(string); ok {
|
||||
themeCSS = tc
|
||||
}
|
||||
|
||||
structuredData := ""
|
||||
if sd, ok := doc["structured_data"].(string); ok {
|
||||
structuredData = sd
|
||||
}
|
||||
|
||||
cssHash := ""
|
||||
if ch, ok := doc["css_hash"].(string); ok {
|
||||
cssHash = ch
|
||||
}
|
||||
|
||||
pageviewNonce := ""
|
||||
if pn, ok := doc["pageview_nonce"].(string); ok {
|
||||
pageviewNonce = pn
|
||||
}
|
||||
|
||||
themeMode := "light"
|
||||
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
|
||||
themeMode = tm
|
||||
}
|
||||
|
||||
siteSettings := bn.ParseSiteSettings(doc)
|
||||
pageMeta := bn.ParsePageMeta(doc)
|
||||
engagementConfig := bn.ParseEngagementConfig(doc)
|
||||
|
||||
return PageData{
|
||||
Title: title,
|
||||
Slots: slots,
|
||||
ThemeMode: themeMode,
|
||||
ThemeCSS: themeCSS,
|
||||
SiteSettings: siteSettings,
|
||||
PageMeta: pageMeta,
|
||||
StructuredData: structuredData,
|
||||
CSSHash: cssHash,
|
||||
PageviewNonce: pageviewNonce,
|
||||
EngagementConfig: engagementConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// htmlClassForMode returns the root <html> class for the current theme mode.
|
||||
func htmlClassForMode(mode string) string {
|
||||
if mode == "dark" {
|
||||
return "dark"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ===== Default template (header / main / footer) =====
|
||||
|
||||
templ ScifiClean(data PageData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class={ htmlClassForMode(data.ThemeMode) }>
|
||||
@bn.Head(bn.HeadData{
|
||||
Title: data.Title,
|
||||
Settings: data.SiteSettings,
|
||||
PageMeta: data.PageMeta,
|
||||
ThemeMode: data.ThemeMode,
|
||||
ThemeCSS: data.ThemeCSS,
|
||||
PluginStyles: []string{},
|
||||
StructuredData: data.StructuredData,
|
||||
CSSHash: data.CSSHash,
|
||||
PageviewNonce: data.PageviewNonce,
|
||||
EngagementConfig: data.EngagementConfig,
|
||||
})
|
||||
<body class="scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col" style="font-family: var(--font-body);">
|
||||
@bn.AdminBypassBanner(data.SiteSettings)
|
||||
<header class="w-full">
|
||||
@templ.Raw(data.Slots["header"])
|
||||
</header>
|
||||
<main class="flex-grow max-w-5xl 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 scifi-mono uppercase tracking-widest text-xs text-muted-foreground">
|
||||
No content blocks assigned to this page.
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
<footer class="w-full mt-auto">
|
||||
@templ.Raw(data.Slots["footer"])
|
||||
</footer>
|
||||
@bn.BodyEnd(data.SiteSettings)
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// ===== Landing template (hero / specs / main / cta / footer) =====
|
||||
|
||||
templ ScifiCleanLanding(data PageData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class={ htmlClassForMode(data.ThemeMode) }>
|
||||
@bn.Head(bn.HeadData{
|
||||
Title: data.Title,
|
||||
Settings: data.SiteSettings,
|
||||
PageMeta: data.PageMeta,
|
||||
ThemeMode: data.ThemeMode,
|
||||
ThemeCSS: data.ThemeCSS,
|
||||
PluginStyles: []string{},
|
||||
StructuredData: data.StructuredData,
|
||||
CSSHash: data.CSSHash,
|
||||
PageviewNonce: data.PageviewNonce,
|
||||
EngagementConfig: data.EngagementConfig,
|
||||
})
|
||||
<body class="scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col" style="font-family: var(--font-body);">
|
||||
@bn.AdminBypassBanner(data.SiteSettings)
|
||||
<section class="w-full">
|
||||
@templ.Raw(data.Slots["hero"])
|
||||
</section>
|
||||
if specs, ok := data.Slots["specs"]; ok && specs != "" {
|
||||
<section class="w-full hairline-b py-12">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
@templ.Raw(specs)
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
<main class="flex-grow w-full">
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
<div class="max-w-6xl mx-auto px-4 py-16">
|
||||
@templ.Raw(main)
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
if cta, ok := data.Slots["cta"]; ok && cta != "" {
|
||||
<section class="w-full hairline-t py-16">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
@templ.Raw(cta)
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
<footer class="w-full mt-auto">
|
||||
@templ.Raw(data.Slots["footer"])
|
||||
</footer>
|
||||
@bn.BodyEnd(data.SiteSettings)
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// ===== Article template (header / rail / main / footer) =====
|
||||
|
||||
templ ScifiCleanArticle(data PageData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class={ htmlClassForMode(data.ThemeMode) }>
|
||||
@bn.Head(bn.HeadData{
|
||||
Title: data.Title,
|
||||
Settings: data.SiteSettings,
|
||||
PageMeta: data.PageMeta,
|
||||
ThemeMode: data.ThemeMode,
|
||||
ThemeCSS: data.ThemeCSS,
|
||||
PluginStyles: []string{},
|
||||
StructuredData: data.StructuredData,
|
||||
CSSHash: data.CSSHash,
|
||||
PageviewNonce: data.PageviewNonce,
|
||||
EngagementConfig: data.EngagementConfig,
|
||||
})
|
||||
<body class="scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col" style="font-family: var(--font-body);">
|
||||
@bn.AdminBypassBanner(data.SiteSettings)
|
||||
<header class="w-full hairline-b">
|
||||
@templ.Raw(data.Slots["header"])
|
||||
</header>
|
||||
<main class="flex-grow w-full">
|
||||
<div class="max-w-5xl mx-auto px-4 py-12 grid grid-cols-12 gap-8">
|
||||
if rail, ok := data.Slots["rail"]; ok && rail != "" {
|
||||
<aside class="col-span-12 md:col-span-3 scifi-mono uppercase text-xs tracking-widest text-muted-foreground space-y-3">
|
||||
@templ.Raw(rail)
|
||||
</aside>
|
||||
}
|
||||
<article class="col-span-12 md:col-span-9 scifi-body max-w-2xl">
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
@templ.Raw(main)
|
||||
} else {
|
||||
<p class="text-muted-foreground">No article body.</p>
|
||||
}
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="w-full mt-auto">
|
||||
@templ.Raw(data.Slots["footer"])
|
||||
</footer>
|
||||
@bn.BodyEnd(data.SiteSettings)
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// ===== Full-width template (header / main / footer) =====
|
||||
|
||||
templ ScifiCleanFullWidth(data PageData) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class={ htmlClassForMode(data.ThemeMode) }>
|
||||
@bn.Head(bn.HeadData{
|
||||
Title: data.Title,
|
||||
Settings: data.SiteSettings,
|
||||
PageMeta: data.PageMeta,
|
||||
ThemeMode: data.ThemeMode,
|
||||
ThemeCSS: data.ThemeCSS,
|
||||
PluginStyles: []string{},
|
||||
StructuredData: data.StructuredData,
|
||||
CSSHash: data.CSSHash,
|
||||
PageviewNonce: data.PageviewNonce,
|
||||
EngagementConfig: data.EngagementConfig,
|
||||
})
|
||||
<body class="scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col" style="font-family: var(--font-body);">
|
||||
@bn.AdminBypassBanner(data.SiteSettings)
|
||||
<header class="w-full">
|
||||
@templ.Raw(data.Slots["header"])
|
||||
</header>
|
||||
<main class="flex-grow w-full bg-grid">
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
@templ.Raw(main)
|
||||
} else {
|
||||
<div class="max-w-5xl mx-auto py-20 px-4 text-center scifi-mono uppercase tracking-widest text-xs text-muted-foreground">
|
||||
No content blocks assigned to this page.
|
||||
</div>
|
||||
}
|
||||
</main>
|
||||
<footer class="w-full mt-auto">
|
||||
@templ.Raw(data.Slots["footer"])
|
||||
</footer>
|
||||
@bn.BodyEnd(data.SiteSettings)
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
// ===== Render entry points (adapted by wrap() in register.go) =====
|
||||
|
||||
func RenderScifiClean(ctx context.Context, doc map[string]any) templ.Component {
|
||||
return ScifiClean(parseScifiPageData(doc))
|
||||
}
|
||||
|
||||
func RenderScifiCleanLanding(ctx context.Context, doc map[string]any) templ.Component {
|
||||
return ScifiCleanLanding(parseScifiPageData(doc))
|
||||
}
|
||||
|
||||
func RenderScifiCleanArticle(ctx context.Context, doc map[string]any) templ.Component {
|
||||
return ScifiCleanArticle(parseScifiPageData(doc))
|
||||
}
|
||||
|
||||
func RenderScifiCleanFullWidth(ctx context.Context, doc map[string]any) templ.Component {
|
||||
return ScifiCleanFullWidth(parseScifiPageData(doc))
|
||||
}
|
||||
643
template_templ.go
Normal file
643
template_templ.go
Normal file
@ -0,0 +1,643 @@
|
||||
// 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 holds the values lifted out of the render `doc` map for the
|
||||
// four Sci-Fi Clean page templates.
|
||||
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
|
||||
}
|
||||
|
||||
// parseScifiPageData lifts the well-known keys out of the inbound doc map.
|
||||
// Unknown keys (e.g. block content) are ignored by the page renderers.
|
||||
func parseScifiPageData(doc map[string]any) PageData {
|
||||
title := "Untitled"
|
||||
if t, ok := doc["title"].(string); ok {
|
||||
title = t
|
||||
}
|
||||
|
||||
slots := make(map[string]string)
|
||||
if s, ok := doc["slots"].(map[string]string); ok {
|
||||
slots = s
|
||||
}
|
||||
|
||||
themeCSS := ""
|
||||
if tc, ok := doc["theme_css"].(string); ok {
|
||||
themeCSS = tc
|
||||
}
|
||||
|
||||
structuredData := ""
|
||||
if sd, ok := doc["structured_data"].(string); ok {
|
||||
structuredData = sd
|
||||
}
|
||||
|
||||
cssHash := ""
|
||||
if ch, ok := doc["css_hash"].(string); ok {
|
||||
cssHash = ch
|
||||
}
|
||||
|
||||
pageviewNonce := ""
|
||||
if pn, ok := doc["pageview_nonce"].(string); ok {
|
||||
pageviewNonce = pn
|
||||
}
|
||||
|
||||
themeMode := "light"
|
||||
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
|
||||
themeMode = tm
|
||||
}
|
||||
|
||||
siteSettings := bn.ParseSiteSettings(doc)
|
||||
pageMeta := bn.ParsePageMeta(doc)
|
||||
engagementConfig := bn.ParseEngagementConfig(doc)
|
||||
|
||||
return PageData{
|
||||
Title: title,
|
||||
Slots: slots,
|
||||
ThemeMode: themeMode,
|
||||
ThemeCSS: themeCSS,
|
||||
SiteSettings: siteSettings,
|
||||
PageMeta: pageMeta,
|
||||
StructuredData: structuredData,
|
||||
CSSHash: cssHash,
|
||||
PageviewNonce: pageviewNonce,
|
||||
EngagementConfig: engagementConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// htmlClassForMode returns the root <html> class for the current theme mode.
|
||||
func htmlClassForMode(mode string) string {
|
||||
if mode == "dark" {
|
||||
return "dark"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ===== Default template (header / main / footer) =====
|
||||
func ScifiClean(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>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 = []any{htmlClassForMode(data.ThemeMode)}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<html lang=\"en\" 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: `template.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, 3, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.Head(bn.HeadData{
|
||||
Title: data.Title,
|
||||
Settings: data.SiteSettings,
|
||||
PageMeta: data.PageMeta,
|
||||
ThemeMode: data.ThemeMode,
|
||||
ThemeCSS: data.ThemeCSS,
|
||||
PluginStyles: []string{},
|
||||
StructuredData: data.StructuredData,
|
||||
CSSHash: data.CSSHash,
|
||||
PageviewNonce: data.PageviewNonce,
|
||||
EngagementConfig: data.EngagementConfig,
|
||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<body class=\"scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col\" style=\"font-family: var(--font-body);\">")
|
||||
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, 5, "<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, 6, "</header><main class=\"flex-grow max-w-5xl 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, 7, "<div class=\"py-20 text-center scifi-mono uppercase tracking-widest text-xs text-muted-foreground\">No content blocks assigned to this page.</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</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, 9, "</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, 10, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Landing template (hero / specs / main / cta / footer) =====
|
||||
func ScifiCleanLanding(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, 11, "<!doctype html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 = []any{htmlClassForMode(data.ThemeMode)}
|
||||
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, 12, "<html lang=\"en\" 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: `template.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, 13, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.Head(bn.HeadData{
|
||||
Title: data.Title,
|
||||
Settings: data.SiteSettings,
|
||||
PageMeta: data.PageMeta,
|
||||
ThemeMode: data.ThemeMode,
|
||||
ThemeCSS: data.ThemeCSS,
|
||||
PluginStyles: []string{},
|
||||
StructuredData: data.StructuredData,
|
||||
CSSHash: data.CSSHash,
|
||||
PageviewNonce: data.PageviewNonce,
|
||||
EngagementConfig: data.EngagementConfig,
|
||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<body class=\"scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col\" style=\"font-family: var(--font-body);\">")
|
||||
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, 15, "<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, 16, "</section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if specs, ok := data.Slots["specs"]; ok && specs != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<section class=\"w-full hairline-b py-12\"><div class=\"max-w-6xl mx-auto px-4\"><div class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(specs).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></div></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<main class=\"flex-grow w-full\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if main, ok := data.Slots["main"]; ok && main != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<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, 21, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</main>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if cta, ok := data.Slots["cta"]; ok && cta != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<section class=\"w-full hairline-t py-16\"><div class=\"max-w-6xl mx-auto px-4\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(cta).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</div></section>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<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, 26, "</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, 27, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Article template (header / rail / main / footer) =====
|
||||
func ScifiCleanArticle(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_Var7 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var7 == nil {
|
||||
templ_7745c5c3_Var7 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<!doctype html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 = []any{htmlClassForMode(data.ThemeMode)}
|
||||
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, 29, "<html lang=\"en\" 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: `template.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, 30, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.Head(bn.HeadData{
|
||||
Title: data.Title,
|
||||
Settings: data.SiteSettings,
|
||||
PageMeta: data.PageMeta,
|
||||
ThemeMode: data.ThemeMode,
|
||||
ThemeCSS: data.ThemeCSS,
|
||||
PluginStyles: []string{},
|
||||
StructuredData: data.StructuredData,
|
||||
CSSHash: data.CSSHash,
|
||||
PageviewNonce: data.PageviewNonce,
|
||||
EngagementConfig: data.EngagementConfig,
|
||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<body class=\"scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col\" style=\"font-family: var(--font-body);\">")
|
||||
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, 32, "<header class=\"w-full hairline-b\">")
|
||||
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, 33, "</header><main class=\"flex-grow w-full\"><div class=\"max-w-5xl mx-auto px-4 py-12 grid grid-cols-12 gap-8\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if rail, ok := data.Slots["rail"]; ok && rail != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<aside class=\"col-span-12 md:col-span-3 scifi-mono uppercase text-xs tracking-widest text-muted-foreground space-y-3\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.Raw(rail).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</aside>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<article class=\"col-span-12 md:col-span-9 scifi-body max-w-2xl\">")
|
||||
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, 37, "<p class=\"text-muted-foreground\">No article body.</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</article></div></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, 39, "</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, 40, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Full-width template (header / main / footer) =====
|
||||
func ScifiCleanFullWidth(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_Var10 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var10 == nil {
|
||||
templ_7745c5c3_Var10 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<!doctype html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 = []any{htmlClassForMode(data.ThemeMode)}
|
||||
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, 42, "<html lang=\"en\" 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: `template.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, 43, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = bn.Head(bn.HeadData{
|
||||
Title: data.Title,
|
||||
Settings: data.SiteSettings,
|
||||
PageMeta: data.PageMeta,
|
||||
ThemeMode: data.ThemeMode,
|
||||
ThemeCSS: data.ThemeCSS,
|
||||
PluginStyles: []string{},
|
||||
StructuredData: data.StructuredData,
|
||||
CSSHash: data.CSSHash,
|
||||
PageviewNonce: data.PageviewNonce,
|
||||
EngagementConfig: data.EngagementConfig,
|
||||
}).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<body class=\"scifi-body bg-background text-foreground antialiased min-h-screen flex flex-col\" style=\"font-family: var(--font-body);\">")
|
||||
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, 45, "<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, 46, "</header><main class=\"flex-grow w-full bg-grid\">")
|
||||
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, 47, "<div class=\"max-w-5xl mx-auto py-20 px-4 text-center scifi-mono uppercase tracking-widest text-xs text-muted-foreground\">No content blocks assigned to this page.</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</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, 49, "</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, 50, "</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ===== Render entry points (adapted by wrap() in register.go) =====
|
||||
|
||||
func RenderScifiClean(ctx context.Context, doc map[string]any) templ.Component {
|
||||
return ScifiClean(parseScifiPageData(doc))
|
||||
}
|
||||
|
||||
func RenderScifiCleanLanding(ctx context.Context, doc map[string]any) templ.Component {
|
||||
return ScifiCleanLanding(parseScifiPageData(doc))
|
||||
}
|
||||
|
||||
func RenderScifiCleanArticle(ctx context.Context, doc map[string]any) templ.Component {
|
||||
return ScifiCleanArticle(parseScifiPageData(doc))
|
||||
}
|
||||
|
||||
func RenderScifiCleanFullWidth(ctx context.Context, doc map[string]any) templ.Component {
|
||||
return ScifiCleanFullWidth(parseScifiPageData(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"
|
||||
)
|
||||
|
||||
// ScifiTextBlock renders body text with Inter via the --font-body variable.
|
||||
// Inline numerals retain a tabular-nums treatment via the scifi-text class.
|
||||
func ScifiTextBlock(ctx context.Context, content map[string]any) string {
|
||||
text := getString(content, "text")
|
||||
class := getString(content, "class")
|
||||
|
||||
var buf bytes.Buffer
|
||||
_ = scifiTextComponent(text, class).Render(ctx, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
8
text_override.templ
Normal file
8
text_override.templ
Normal file
@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
// scifiTextComponent renders Inter body copy with the tabular-nums numeral treatment.
|
||||
templ scifiTextComponent(text, class string) {
|
||||
<div class={ "scifi-body prose max-w-none text-foreground tabular-nums", class } style="font-family: var(--font-body);">
|
||||
@templ.Raw(text)
|
||||
</div>
|
||||
}
|
||||
67
text_override_templ.go
Normal file
67
text_override_templ.go
Normal file
@ -0,0 +1,67 @@
|
||||
// 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"
|
||||
|
||||
// scifiTextComponent renders Inter body copy with the tabular-nums numeral treatment.
|
||||
func scifiTextComponent(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{"scifi-body prose max-w-none text-foreground tabular-nums", class}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `text_override.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" style=\"font-family: var(--font-body);\">")
|
||||
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
|
||||
Loading…
x
Reference in New Issue
Block a user