Compare commits

..

20 Commits
v0.1.1 ... main

Author SHA1 Message Date
Alex Dunmow
03751e936c bump to 0.5.0 2026-07-06 21:06:48 +08:00
Alex Dunmow
7bb7727852 feat: Wave B gallery screenshots (home/about/article x light+dark, dark preview)
Recapture after the bundled-fonts retrofit: headings now render in the
Orbitron display face, mono readouts in Share Tech Mono, body in Inter.
Digital-rain canvas hero renders in both modes (0.4.0 fix intact).
Dark-canonical: preview.png = 02-home-dark.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:06:35 +08:00
Alex Dunmow
734f8e37c3 bump to 0.4.1 2026-07-06 21:01:43 +08:00
Alex Dunmow
197eb9960b fix(cyberpunk): bundle fonts via theme @font-face + preset typography refs
The host does not consume LoadedPlugin.BundledFonts (dead code), so the
three fonts.json families (Orbitron/Share Tech Mono/Inter) never loaded and
rendered as fallback sans. Declare @font-face against the served
/templates/cyberpunk/fonts/*.woff2 paths (weights matching fonts.json
exactly) and point every preset's typography.fontHeading/body/mono at
template:cyberpunk:<Family> so --font-heading/body/mono resolve to the
bundled faces. gotham@1.2.0 precedent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 21:01:18 +08:00
Alex Dunmow
ffdd627192 bump to 0.4.0 2026-07-06 16:26:19 +08:00
Alex Dunmow
82237ad9a6 feat: Wave B gallery screenshots (home/about/article × light+dark)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:26:05 +08:00
Alex Dunmow
71df6c38ac bump to 0.3.1 2026-07-06 16:10:58 +08:00
Alex Dunmow
644a8083f7 fix(cyberpunk): rain first-paint density, seed depth, HUD bracket inset, sized icon SVGs
- hero rain canvas: seed drops across full height so the digital-rain reads
  dense from the first painted frame (was starting all drops above the
  viewport -> near-blank within a short capture settle).
- seed: expand the About story + give the pull-quote a real attribution
  (was the two-word 'The studio' stub); expand all three devlog articles,
  making-it-rain to ~10 paragraphs, so no article strands a void above the
  footer.
- manifest CSS: inset the HUD corner brackets 5px so they clear rounded /
  overflow-hidden panels (fixes the clipped cyan tick on the code_neon block).
- SVG geometry guard: explicit width/height on all 37 icon <use>/inline SVGs
  (px = tailwind unit x4) so a purged utility can't balloon them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 16:10:38 +08:00
Alex Dunmow
1f74c5ac06 bump to 0.3.0 2026-07-06 09:24:42 +08:00
Alex Dunmow
a5cf29efb8 feat: Wave B gallery screenshots (home/about/article × light+dark) 2026-07-06 09:24:25 +08:00
Alex Dunmow
849edc4b8c chore: rename dev host localdev.blockninjacms.com -> blockninja.dev 2026-07-05 20:50:30 +08:00
Alex Dunmow
5221b310ff fix(cyberpunk): repair blank-hero .text bug + move responsive layout to Tailwind
Hero blank-hero bug: eyebrow/headline/subheadline/description read
field.text|default:field, which hard-errors in the ninjatpl engine on
bare-string seed values (attribute access on a string), blanking the whole
hero. Switch to field|get:"text"|default:field — the process-global `get`
filter degrades gracefully on both {text,...} objects and bare strings.

Responsive layout back to Tailwind (supersedes the prior CSS-class approach
in 6787a01): the host Tailwind build now scans theme .ninjatpl, so the
cp-grid / cp-cols / cp-span / cp-split / cp-article-grid classes were exact
reimplementations of stock utilities. Restore plain Tailwind in the
templates (grid grid-cols-1 sm:/md:/lg:grid-cols-*, gap-*, columns-*,
col-span-*, before:* timeline connector) across feature-grid, pricing,
stats, team, testimonial, contact-card, featured/related-posts, blog-hero,
menu-list, event-list, hours-location, timeline, footer, gallery,
rich-section, roster_grid, article + hero split section, and delete the
duplicate CSS from manifest css.input_css_append.

Kept as genuinely bespoke design CSS: cp-hero-* bounded clamp() min-heights
(the anti-balloon whitespace fix — not a stock min-h-* utility; restoring
min-h-screen would reintroduce the empty neon void) and the reduced-motion /
high-contrast blocks.

Scroll-reveal: cyberpunk ships no opacity-hidden .reveal content — its two
IntersectionObservers (hero digital-rain, stats countUp) fire on the IO
initial callback when already in view and both have static, visible no-JS /
reduced-motion fallbacks, so content is never hidden behind JS.

make (codeless .bnp) OK; check-safety 32 checks 19 ok 13 skip -> OK.
No version bump. No push.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:58:34 +08:00
Alex Dunmow
6787a01515 fix(theme): drive responsive layout from theme-owned CSS
Host Tailwind JIT does not scan theme templates, so responsive grid/hero
utilities (sm:/md:/lg:grid-cols-*, min-h-screen, arbitrary values) are
dropped and multi-column layouts collapse to one column. Ship the
load-bearing responsive layout as compiled theme CSS and point the
override templates at it. Layout only — colours stay on semantic tokens.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:40:17 +08:00
Alex Dunmow
ffce8b5913 bump to 0.2.1 2026-07-05 16:44:48 +08:00
Alex Dunmow
6a34a6c795 docs: publishing README 2026-07-05 13:43:18 +08:00
Alex Dunmow
02a84ab31e feat(cyberpunk): Wave A build — 49 builtin overrides, 7 templates, canvas hero, seed
WO-TF-015 (game-studio / hacker-collective persona, GO-BIG motion).

- Overrides: all 49 mandatory builtin keys in templates/overrides/cyberpunk/
  (dashed keys), each restyled to neon-HUD chrome preserving builtin field
  reads + scripts. Legacy button/card/heading/text reconciled to new field
  names (label/link/style, text/media). Auth surface (auth-form,
  auth-status, password-reset-form) reproduces the builtin hardcoded HTMX
  endpoints + Cap widget verbatim. page-suggestions ships themed 404 chrome.
- Page templates: 7 (default, full-width, landing, article, blog-index,
  contact, auth), dual-mode via theme_mode, scanline overlay chrome.
- Hero override: dependency-free canvas digital-rain showpiece — lazy-init
  via IntersectionObserver, prefers-reduced-motion honored, static neon
  gradient no-JS fallback, token-derived colors only.
- Blocks: deleted 6 builtin-duplicating theme blocks (now overrides); kept
  code_neon (+ copy handler); added persona blocks glitch_hero, roster_grid,
  patch_notes with schemas + sample data.
- Presets: 5 (neon-noir, acid-rain, toxic-bloom, hotline, ghost-protocol),
  all 19 tokens, light+dark both tuned.
- Fonts: bundled OFL woff2 (Orbitron, Share Tech Mono, Inter latin subsets)
  in assets/fonts + fonts.json + LICENSES; all font-family via CSS vars.
- Email: preset-aware email-safe wrapper (tables, inline styles, colors.*).
- Master pages + demo seed (home, about, blog+3 posts, contact with
  data-table + row_inserted email-admins workflow, login).
- manifest CSS: hud-frame, clip-cut, cyberpunk-eyebrow, canvas-rain utils.
- plugin.mod + manifest required_icon_packs = [lucide, simple-icons].

make build + verify exit 0; check-safety exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 10:57:00 +08:00
Alex Dunmow
8ea1477d37 feat: convert to codeless .bnp — templates + manifest, Go/templ deleted (WO-WZ-021)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 15:26:00 +08:00
Alex Dunmow
b32e3f143a chore: repo hygiene for the wasm era (WO-WZ-015 fix round)
Replace the retired .so Makefile with the wasm/.bnp convention (build /
verify / archive-check / publish via ninja plugin build), gitignore *.so
and *.bnp artifacts, and refresh the stale [compatibility] block_core
constraint to the v0.15 era.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 00:14:37 +08:00
Alex Dunmow
f908b38c01 wasm port (WO-WZ-015): reactor-mode wasip1 guest, core v0.15.2
Add wasmguest.Serve(Registration) boilerplate and bump block/core to v0.15.2
for the .so->wasm migration. Built + published as .bnp to the dev registry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 22:11:12 +08:00
Alex Dunmow
96af6b26c3 bump to 0.1.2 2026-07-03 22:09:34 +08:00
143 changed files with 2559 additions and 4471 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
tmp/ tmp/
.idea/ .idea/
.vscode/ .vscode/
*.bnp

View File

@ -1,31 +1,55 @@
# Cyberpunk — build & deploy helpers (.so plugin workflow) # cyberpunk — build & publish helpers (CODELESS .bnp workflow, WO-WZ-021)
#
# This theme is a CODELESS .bnp: pure declaration the host runs — blocks
# (blocks/blocks.yaml + .ninjatpl), page/system templates, template overrides,
# an email wrapper, master pages, presets, fonts and CSS in manifest.yaml. No
# Go, no templ, no plugin.wasm, no block/core dependency. `ninja plugin build`
# classifies the repo as codeless (no Go at the root) and packs the artifact
# with manifest.pb (codeless: true) and NO plugin.wasm.
# #
# Usage: # Usage:
# make Local build: produces cyberpunk.so via CGO go build -buildmode=plugin # make build # pack cyberpunk-<version>.bnp (codeless — no wasm)
# make clean Remove built artefacts # make verify # validate the built .bnp (loader-identical checks)
# make templ Regenerate *_templ.go files (host templ) # make archive-check # prove a clean `git archive HEAD` still builds codeless
# make publish # ninja plugin publish --bnp (to the active registry)
# make clean # remove build artefacts
.PHONY: all clean templ help .PHONY: build verify archive-check publish clean help
PLUGIN_NAME := cyberpunk PLUGIN_NAME := cyberpunk
PLUGIN_SRC := $(CURDIR) NINJA := ninja
BNP := $(PLUGIN_NAME)-$(shell grep '^version' plugin.mod | sed 's/.*"\(.*\)"/\1/').bnp
# Default target: build the .so locally for development. # Pack the codeless .bnp (manifest.pb synthesized from plugin.mod + manifest.yaml
all: $(PLUGIN_NAME).so # + blocks/ + templates/; no wasm compile, no DESCRIBE probe).
build:
$(NINJA) plugin build --dir .
# Local plugin build (no container). # Validate the built .bnp exactly as the registry/loader will.
$(PLUGIN_NAME).so: $(wildcard *.go) plugin.mod go.mod verify:
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o $(PLUGIN_NAME).so . $(NINJA) plugin verify $(BNP)
# Prove a clean `git archive HEAD` (what publish ships) still packs codeless.
archive-check:
@T=$$(mktemp -d /tmp/archive-check-$(PLUGIN_NAME).XXXXXX) && \
trap 'rm -rf "$$T"' EXIT && \
git archive HEAD | tar -x -C "$$T" && \
cd "$$T" && $(NINJA) plugin build --dir . && \
echo "archive-check OK"
# Publish the prebuilt .bnp to the active registry.
# Pass --host https://my.blockninja.dev for the dev orchestrator.
publish: build
$(NINJA) plugin publish --bnp $(BNP)
# Remove build artefacts.
clean: clean:
rm -f $(PLUGIN_NAME).so rm -f $(PLUGIN_NAME)-*.bnp plugin.wasm
# Regenerate templ Go files locally.
templ:
cd $(PLUGIN_SRC) && $(HOME)/go/bin/templ generate
help: help:
@echo "Targets:" @echo "Targets:"
@echo " all Build $(PLUGIN_NAME).so locally (default)" @echo " build Pack the codeless .bnp (no wasm)"
@echo " templ Regenerate templ Go files" @echo " verify Validate the built .bnp (loader-identical checks)"
@echo " clean Remove built artefacts" @echo " archive-check Prove a clean git archive HEAD packs codeless"
@echo " publish Publish the .bnp to the active registry"
@echo " clean Remove build artefacts"

25
README.md Normal file
View File

@ -0,0 +1,25 @@
# Cyberpunk
A high-voltage theme built on neon glow over near-black. Magenta, cyan, and lime accents cut across scanlines, glitch splits, and HUD brackets, so a page reads like the interface of a late-night game studio. The hero carries an animated canvas that rains code down the screen. It looks loud, and the type stays legible while it does.
## Good for
We built Cyberpunk for teams that want a screen to feel switched on. Treat these as starting points, not limits. If the mood fits your brand, use it:
- Indie game studios and dev collectives
- Developer tools and API products
- Crypto, fintech, and Web3 launches
- Hackathons and tech event pages
## What you get
- Five color presets, each with a tuned light mode and a tuned dark mode: Neon Noir, Acid Rain, Toxic Bloom, Hotline, and Ghost Protocol.
- Orbitron for display headings, Share Tech Mono for code and labels, and Inter for body text. All three ship bundled. Fonts stay admin-controlled, so you can swap them from the typography settings.
- An animated canvas hero that rains code behind the headline. It waits until it scrolls into view, honors reduced-motion, and falls back to a static neon gradient when scripting is off.
- Four persona blocks: a glitch hero, a neon code panel, a team roster grid, and a patch-notes feed.
- A demo site that installs when you activate the theme: a home page, an about page, a short blog, and a contact page whose form emails your admins. It comes seeded as a studio called Neon Forge.
- Every core block restyled to match, plus a themed login screen and a themed 404 page.
## Install
Install from the BlockNinja registry, then pick a preset and assign your fonts in the admin. Activate the demo content if you want a fully dressed starting point.

View File

@ -1,33 +1,40 @@
# Recommended fonts — Cyberpunk # Recommended fonts — Cyberpunk
This theme ships `fonts.json = []` per the wave-1 fonts policy This theme **bundles** three OFL (SIL Open Font License 1.1) latin-subset webfonts in
(`themes/docs/FONTS.md`). No `.woff2` files are bundled inside the `.so`. `assets/fonts/` and declares them in `fonts.json`. On install they become
`source=template` rows in the `fonts` table, so their `@font-face` declarations are
auto-emitted and they are selectable in the admin typography picker. No manual font setup
is required to get the intended look.
To get the intended aesthetic from spec §5, the site admin should add the | Slot | Bundled family | File | Fallback stack (before assignment) |
three families below from the Google Fonts picker in the typography panel.
| Slot | Recommended | Source | One-line how-to |
|---|---|---|---| |---|---|---|---|
| Heading (`--font-heading`) | Space Grotesk | `google:Space Grotesk` | Open the typography panel, pick **Space Grotesk** from the Google Fonts tab, assign to **Heading**. | | Heading (`--font-heading`) | **Orbitron** | `fonts/orbitron-latin.woff2` | `"Orbitron", system-ui, -apple-system, "Segoe UI", sans-serif` |
| Body (`--font-body`) | Inter | `google:Inter` | Open the typography panel, pick **Inter** from the Google Fonts tab, assign to **Body**. | | Mono (`--font-mono`) | **Share Tech Mono** | `fonts/share-tech-mono-latin.woff2` | `"Share Tech Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace` |
| Mono (`--font-mono`) | JetBrains Mono | `google:JetBrains Mono` | Open the typography panel, pick **JetBrains Mono** from the Google Fonts tab, assign to **Mono**. | | Body (`--font-body`) | **Inter** | `fonts/inter-latin.woff2` | `"Inter", system-ui, -apple-system, "Segoe UI", sans-serif` |
## Fallback stacks ## How the theme consumes them
Before the admin picks anything, the theme falls back to the following stacks Every `font-family` in the theme flows through a CSS variable with a bundled-family
(declared via the CSS variables in `assets/css/cyberpunk.css`): fallback — nothing is hardcoded, so the admin picker always wins:
- `--font-heading` → `"Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif` - `.cyberpunk-display` -> `var(--font-heading, "Orbitron", ...)` — display headings, glitch H1/H2.
- `--font-body` → `"Inter", system-ui, -apple-system, "Segoe UI", sans-serif` - `.cyberpunk-mono` -> `var(--font-mono, "Share Tech Mono", ...)` — readouts, labels, prices, code.
- `--font-mono` → `"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace` - `.cyberpunk-body` / `.cyberpunk-prose` -> `var(--font-body, "Inter", ...)` — body copy.
These fallbacks intentionally lead with the same family the picker would ## To assign (optional)
deliver, so the page already looks close to the intended aesthetic on systems
that happen to have the font installed.
## Why not bundle them? Open the typography panel and assign **Orbitron -> Heading**, **Share Tech Mono -> Mono**,
**Inter -> Body**. All three appear in the picker under the theme's bundled fonts. If you
prefer alternates, **Rajdhani** (OFL) is a lighter display alternative to Orbitron, and
**JetBrains Mono** (OFL) a more legible mono — both are in the curated Google Fonts tab.
All three are OFL-clean and could be bundled in a future pass. For wave 1 we ## Alternate direction
ship no `.woff2` to keep the `.so` small and to keep font management in the
hands of the site admin (overrides + uploads stay routed through the picker, The locked WO-TF-015 direction pairs Orbitron (display) with Share Tech Mono (readouts)
not the theme). and Inter (body). Rajdhani may be swapped in for Orbitron where a condensed, less
geometric display face reads better at long headline lengths.
## Licensing
All three are OFL 1.1 and cleared for redistribution inside this artifact. Details and
attribution: `assets/fonts/LICENSES.md`.

24
RECOMMENDED_ICONS.md Normal file
View File

@ -0,0 +1,24 @@
# Recommended icons — Cyberpunk
Icons render through the CMS sprite pipeline: `<svg><use href="/icons/<pack>.svg#<name>"/></svg>`
(or the `::pack:name:size::` shorthand). The theme's overrides reference two bundled packs.
## Required packs
Declared in `plugin.mod` as `required_icon_packs = ["lucide", "simple-icons"]`
(forward-declared — the loader does not yet auto-install; install manually until it does).
| Pack | Used for | Install |
|---|---|---|
| **lucide** | UI glyphs the builtin/override templates use: `arrow-right`, `check`, `chevron-down`, `chevron-left`, `chevron-right`, `x`, `star`, `flag`, `navigation`, `map-pin`, `download`, `play`, `asterisk`, `globe` | Ships bundled with the CMS — no action needed. |
| **simple-icons** | Author/social brand glyphs: `x` (Twitter), `linkedin`, `github` | Ships bundled with the CMS — no action needed. |
Both packs ship bundled with the CMS, so a fresh instance renders every override icon with
no manual step. If you author blocks that reference other packs (e.g. `tabler`, `phosphor`),
install them from `/admin/icons` (Browse -> install -> enable) before activation.
## Note
Icon values in block content are stored as `"pack:name"` strings and split with
`|split:":"|first` / `|last`. Never inline raw SVG path data for content icons — that
bypasses the sprite system and the admin pack manager.

21
assets/fonts/LICENSES.md Normal file
View File

@ -0,0 +1,21 @@
# Bundled font licenses — Cyberpunk theme
All three families are bundled as latin-subset `.woff2` and are licensed under the
SIL Open Font License 1.1 (OFL). Redistribution inside this theme artifact is permitted.
| File | Family | License | Source |
|---|---|---|---|
| `orbitron-latin.woff2` | Orbitron (variable, wght 500900) | OFL 1.1 | Google Fonts (fonts.gstatic.com, v35), latin subset |
| `share-tech-mono-latin.woff2` | Share Tech Mono (400) | OFL 1.1 | Google Fonts (fonts.gstatic.com, v16), latin subset |
| `inter-latin.woff2` | Inter (variable, wght 400700) | OFL 1.1 | Google Fonts (fonts.gstatic.com, v20), latin subset |
OFL 1.1 full text: https://openfontlicense.org
- Orbitron © The Orbitron Project Authors (Matt McInerney).
- Share Tech Mono © Carrois Apostrophe.
- Inter © The Inter Project Authors (Rasmus Andersson).
These are the Google-hosted latin subsets (Latin + Latin-1 punctuation), chosen to keep
the bundle small (~73 KB total). They cover English content; sites needing extended Latin,
Cyrillic, Greek, or Vietnamese glyphs should assign the full family from the admin Google
Fonts picker instead.

Binary file not shown.

Binary file not shown.

Binary file not shown.

38
blocks/blocks.yaml Normal file
View File

@ -0,0 +1,38 @@
# Cyberpunk theme persona block definitions (codeless .bnp, WO-TF-015).
# Keys are fully qualified (cyberpunk:<key>) because the codeless loader
# registers definitions verbatim — there is no PluginBlockRegistry prefixing
# in the codeless path (unlike the wasm/templ path). The qualified keys match
# the data-block attributes and the master_pages.json / seed references.
#
# Builtin-duplicating blocks (hero, cta, navbar, footer, feature grid, stats)
# are NOT theme blocks — they are template overrides in
# templates/overrides/cyberpunk/. Only true persona furniture lives here.
blocks:
- key: cyberpunk:glitch_hero
title: Glitch Boot Hero
description: Terminal boot-console hero — glitch headline, typed boot lines, neon CTAs. Persona furniture (distinct from the builtin hero override).
category: layout
schema: glitch_hero.schema.json
template: glitch_hero.ninjatpl
sample_data: glitch_hero.sample.json
- key: cyberpunk:roster_grid
title: Roster Grid
description: Crew / studio roster — neon HUD cards with handle, role, and status dot.
category: layout
schema: roster_grid.schema.json
template: roster_grid.ninjatpl
sample_data: roster_grid.sample.json
- key: cyberpunk:patch_notes
title: Patch Notes
description: Changelog feed — versioned entries with tag chips and a mono item list.
category: content
schema: patch_notes.schema.json
template: patch_notes.ninjatpl
sample_data: patch_notes.sample.json
- key: cyberpunk:code_neon
title: Code Block
description: Server-rendered code block with language label, neon edge, and a copy button.
category: content
schema: code_neon.schema.json
template: code_neon.ninjatpl
sample_data: code_neon.sample.json

12
blocks/code_neon.ninjatpl Normal file
View File

@ -0,0 +1,12 @@
<figure data-block="cyberpunk:code_neon" class="cyberpunk-code hud-frame w-full my-6 rounded-md overflow-hidden neon-edge-magenta" style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground));"><figcaption class="flex items-center justify-between px-4 py-2 cyberpunk-mono text-xs uppercase tracking-widest" style="background-color: hsl(var(--secondary)); color: hsl(var(--muted-foreground));"><span>{{ language|default:'text' }}</span>{% if copy != false and copy != "false" and copy != "off" and copy != "no" and copy != "0" and copy != "" %} <button type="button" class="cyberpunk-code-copy cyberpunk-mono uppercase tracking-widest px-2 py-1 rounded" style="border: 1px solid hsl(var(--border)); color: hsl(var(--accent));" data-copy="{{ code }}">COPY</button>{% endif %}</figcaption><pre class="cyberpunk-mono overflow-x-auto p-4 text-sm leading-relaxed" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-mono, 'Share Tech Mono', ui-monospace, monospace);"><code class="language-{{ language|default:'text' }}">{{ code }}</code></pre></figure><script>
(function(){
if(window.__cyberpunkCodeCopy)return;window.__cyberpunkCodeCopy=1;
document.addEventListener('click',function(e){
var btn=e.target.closest('.cyberpunk-code-copy');if(!btn)return;
var text=btn.getAttribute('data-copy')||'';
function done(){var o=btn.textContent;btn.textContent='COPIED';setTimeout(function(){btn.textContent=o;},1400);}
if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(text).then(done).catch(function(){});}
else{var ta=document.createElement('textarea');ta.value=text;ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.select();try{document.execCommand('copy');done();}catch(err){}document.body.removeChild(ta);}
});
})();
</script>

View File

@ -0,0 +1,5 @@
{
"language": "bash",
"code": "npm install @neon-forge/sdk\nnpx neon-forge init my-game\nnpx neon-forge ship --stage prod",
"copy": "on"
}

View File

@ -0,0 +1,19 @@
{# cyberpunk:glitch_hero — persona boot-console hero. Terminal boot lines, glitch wordmark, neon CTAs. No canvas (the builtin hero override owns the digital-rain showpiece). #}
<section data-block="cyberpunk:glitch_hero" class="relative overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div aria-hidden="true" class="scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-4xl px-4 py-20 md:py-28">
<div class="hud-frame rounded-md border border-primary/40 bg-card/60 p-6 md:p-10 neon-edge-magenta">
{% if bootLines %}<div class="cyberpunk-mono mb-6 space-y-1 text-sm text-muted-foreground">
{% for line in bootLines %}<div><span class="text-accent">$</span> {{ line.text }}</div>{% endfor %}
<div><span class="text-accent">$</span> <span class="caret-blink text-primary">_</span></div>
</div>{% endif %}
{% if eyebrow %}<p class="cyberpunk-eyebrow mb-3">{{ eyebrow }}</p>{% endif %}
{% if headline %}<h1 class="cyberpunk-display glitch text-4xl md:text-6xl font-bold tracking-tight" data-text="{{ headline }}">{{ headline }}</h1>{% endif %}
{% if subhead %}<p class="mt-4 max-w-2xl text-lg text-muted-foreground">{{ subhead }}</p>{% endif %}
{% if primaryLabel or secondaryLabel %}<div class="mt-8 flex flex-col sm:flex-row gap-4">
{% if primaryLabel and primaryUrl %}<a href="{{ primaryUrl }}" class="cyberpunk-mono clip-cut rgb-split inline-flex items-center justify-center gap-2 rounded-md bg-primary px-6 py-3 font-semibold uppercase tracking-wider text-primary-foreground transition-opacity hover:opacity-90">{{ primaryLabel }}</a>{% endif %}
{% if secondaryLabel and secondaryUrl %}<a href="{{ secondaryUrl }}" class="cyberpunk-mono inline-flex items-center justify-center rounded-md border border-accent/60 px-6 py-3 font-semibold uppercase tracking-wider text-accent transition-colors hover:bg-accent/10">{{ secondaryLabel }}</a>{% endif %}
</div>{% endif %}
</div>
</div>
</section>

View File

@ -0,0 +1,14 @@
{
"eyebrow": "Build 2.049.0",
"headline": "Ship the impossible",
"subhead": "A game studio and the tools that ship it. Engine, live ops, and a build farm that never sleeps.",
"primaryLabel": "Get the SDK",
"primaryUrl": "/contact",
"secondaryLabel": "Read the docs",
"secondaryUrl": "/blog",
"bootLines": [
{ "text": "init neon-forge core ... ok" },
{ "text": "mount render pipeline ... ok" },
{ "text": "link shader cache ... ok" }
]
}

View File

@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Glitch Boot Hero",
"description": "Terminal boot-console hero with a glitch wordmark and neon CTAs.",
"type": "object",
"properties": {
"eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" },
"headline": { "type": "string", "title": "Headline", "x-editor": "text" },
"subhead": { "type": "string", "title": "Subheadline", "x-editor": "textarea" },
"primaryLabel": { "type": "string", "title": "Primary button label", "x-editor": "text" },
"primaryUrl": { "type": "string", "title": "Primary button URL", "x-editor": "link" },
"secondaryLabel": { "type": "string", "title": "Secondary button label", "x-editor": "text" },
"secondaryUrl": { "type": "string", "title": "Secondary button URL", "x-editor": "link" },
"bootLines": {
"type": "array",
"title": "Boot lines",
"description": "Faux terminal boot lines shown above the headline",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"text": { "type": "string", "title": "Line", "x-editor": "text" }
}
}
},
"class": { "type": "string", "title": "CSS Classes", "x-editor": "text" }
}
}

View File

@ -0,0 +1,21 @@
{# cyberpunk:patch_notes — changelog feed. Versioned entries with tag chip + mono item list. #}
<section data-block="cyberpunk:patch_notes" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-3xl px-4">
{% if heading %}<h2 class="cyberpunk-display glitch text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mt-3 text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<ol class="mt-10 space-y-6">
{% for entry in entries %}
<li class="hud-frame rounded-md border border-primary/40 bg-card p-5 text-card-foreground neon-edge-magenta">
<div class="flex flex-wrap items-center gap-3">
{% if entry.version %}<span class="cyberpunk-mono text-lg font-bold text-primary">{{ entry.version }}</span>{% endif %}
{% if entry.tag %}<span class="cyberpunk-mono clip-cut rounded border border-accent/50 px-2 py-0.5 text-xs uppercase tracking-wide text-accent">{{ entry.tag }}</span>{% endif %}
{% if entry.date %}<span class="cyberpunk-mono ml-auto text-xs text-muted-foreground">{{ entry.date }}</span>{% endif %}
</div>
{% if entry.items %}<ul class="mt-3 space-y-1.5">
{% for item in entry.items %}<li class="cyberpunk-mono flex gap-2 text-sm text-muted-foreground"><span class="text-primary">+</span><span>{{ item.text }}</span></li>{% endfor %}
</ul>{% endif %}
</li>
{% endfor %}
</ol>
</div>
</section>

View File

@ -0,0 +1,32 @@
{
"heading": "Patch notes",
"intro": "What changed, build by build.",
"entries": [
{
"version": "v2.049.0",
"date": "2049.04.01",
"tag": "FEATURE",
"items": [
{ "text": "New render path: volumetric neon fog on all night maps." },
{ "text": "Build farm now caches shaders across branches." }
]
},
{
"version": "v2.048.3",
"date": "2049.03.18",
"tag": "FIX",
"items": [
{ "text": "Fixed a stall when hot-reloading materials." },
{ "text": "Netcode: reduced desync on 200ms links." }
]
},
{
"version": "v2.048.0",
"date": "2049.03.02",
"tag": "BALANCE",
"items": [
{ "text": "Tuned dash cooldown from 4s to 3.2s." }
]
}
]
}

View File

@ -0,0 +1,37 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Patch Notes",
"description": "Changelog feed of versioned entries with tag chips.",
"type": "object",
"properties": {
"heading": { "type": "string", "title": "Heading", "x-editor": "text" },
"intro": { "type": "string", "title": "Intro", "x-editor": "textarea" },
"entries": {
"type": "array",
"title": "Entries",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"version": { "type": "string", "title": "Version", "description": "e.g. v2.049.0", "x-editor": "text" },
"date": { "type": "string", "title": "Date", "x-editor": "text" },
"tag": { "type": "string", "title": "Tag", "description": "e.g. FEATURE / FIX / BALANCE", "x-editor": "text" },
"items": {
"type": "array",
"title": "Items",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"text": { "type": "string", "title": "Line", "x-editor": "text" }
}
}
}
}
}
},
"class": { "type": "string", "title": "CSS Classes", "x-editor": "text" }
}
}

View File

@ -0,0 +1,23 @@
{# cyberpunk:roster_grid — crew / studio roster. Neon HUD cards with handle, role, status dot. #}
<section data-block="cyberpunk:roster_grid" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if eyebrow %}<p class="cyberpunk-eyebrow text-center">{{ eyebrow }}</p>{% endif %}
{% if heading %}<h2 class="cyberpunk-display glitch mt-2 text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-12 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{% for member in members %}
<div class="hud-frame flex items-center gap-4 rounded-md border border-primary/40 bg-card p-5 text-card-foreground neon-edge-magenta">
{% if member.avatar %}<span class="clip-cut h-14 w-14 shrink-0 overflow-hidden rounded-md border border-primary/40 bg-muted">{% img member.avatar alt=member.name class="h-full w-full object-cover" %}</span>{% else %}<span class="clip-cut flex h-14 w-14 shrink-0 items-center justify-center rounded-md border border-primary/40 bg-primary/10 text-primary"><svg width="24" height="24" class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#user"></use></svg></span>{% endif %}
<div class="min-w-0">
{% if member.name %}<h3 class="cyberpunk-display text-base font-semibold leading-tight">{{ member.name }}</h3>{% endif %}
{% if member.handle %}<p class="cyberpunk-mono text-sm text-primary">{{ member.handle }}</p>{% endif %}
<p class="mt-1 flex items-center gap-2 text-xs text-muted-foreground">
{% if member.status %}<span class="cyberpunk-status-dot inline-block h-2 w-2 rounded-full bg-accent"></span>{% endif %}
{% if member.role %}<span class="cyberpunk-mono uppercase tracking-wide">{{ member.role }}</span>{% endif %}
</p>
</div>
</div>
{% endfor %}
</div>
</div>
</section>

View File

@ -0,0 +1,13 @@
{
"eyebrow": "The crew",
"heading": "Who ships this",
"intro": "A small team that builds the engine, the game, and the pipeline in between.",
"members": [
{ "name": "Nova Reyes", "handle": "@nova", "role": "Engine lead", "status": true },
{ "name": "Kai Okafor", "handle": "@kaisdf", "role": "Graphics", "status": true },
{ "name": "Sable Lin", "handle": "@sable", "role": "Live ops", "status": false },
{ "name": "Juno Vale", "handle": "@juno", "role": "Gameplay", "status": true },
{ "name": "Rin Castellano", "handle": "@rin", "role": "Tools", "status": true },
{ "name": "Ash Mori", "handle": "@ash", "role": "Netcode", "status": false }
]
}

View File

@ -0,0 +1,28 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Roster Grid",
"description": "Crew / studio roster of neon HUD cards.",
"type": "object",
"properties": {
"eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" },
"heading": { "type": "string", "title": "Heading", "x-editor": "text" },
"intro": { "type": "string", "title": "Intro", "x-editor": "textarea" },
"members": {
"type": "array",
"title": "Members",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"name": { "type": "string", "title": "Name", "x-editor": "text" },
"handle": { "type": "string", "title": "Handle", "description": "e.g. @nova", "x-editor": "text" },
"role": { "type": "string", "title": "Role", "x-editor": "text" },
"avatar": { "type": "string", "title": "Avatar", "x-editor": "media" },
"status": { "type": "boolean", "title": "Show online dot", "default": true, "x-editor": "checkbox" }
}
}
},
"class": { "type": "string", "title": "CSS Classes", "x-editor": "text" }
}
}

View File

@ -1,18 +0,0 @@
package main
import (
"bytes"
"context"
)
// CyberpunkButtonBlock renders the built-in button with cyberpunk styling:
// terminal-cursor caret suffix, RGB-split hover via the .rgb-split class,
// mono label and uppercase tracking.
func CyberpunkButtonBlock(ctx context.Context, content map[string]any) string {
label := getStringWithDefault(content, "label", "Button")
href := safeHref(getString(content, "href"))
variant := getStringWithDefault(content, "variant", "primary")
var buf bytes.Buffer
_ = cyberpunkButtonComponent(label, href, variant).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,36 +0,0 @@
package main
// cyberpunkButtonComponent renders the override for the built-in button block.
// Primary buttons get .rgb-split for the chromatic-aberration hover.
// Secondary / ghost variants use the accent/border tokens.
templ cyberpunkButtonComponent(label, href, variant string) {
switch variant {
case "secondary":
<a
href={ templ.SafeURL(href) }
class="cyberpunk-btn cyberpunk-mono inline-flex items-center gap-2 px-5 py-2.5 rounded-md font-semibold uppercase tracking-wider"
style="background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground));"
>
<span>{ label }</span>
<span aria-hidden="true" class="caret-blink">_</span>
</a>
case "ghost":
<a
href={ templ.SafeURL(href) }
class="cyberpunk-btn-ghost cyberpunk-mono inline-flex items-center gap-2 px-5 py-2.5 rounded-md font-semibold uppercase tracking-wider"
style="border: 1px solid hsl(var(--accent)); color: hsl(var(--accent));"
>
<span>{ label }</span>
<span aria-hidden="true" class="caret-blink">_</span>
</a>
default:
<a
href={ templ.SafeURL(href) }
class="cyberpunk-btn rgb-split cyberpunk-mono inline-flex items-center gap-2 px-5 py-2.5 rounded-md font-semibold uppercase tracking-wider"
style="background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));"
>
<span>{ label }</span>
<span aria-hidden="true" class="caret-blink">_</span>
</a>
}
}

View File

@ -1,134 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// cyberpunkButtonComponent renders the override for the built-in button block.
// Primary buttons get .rgb-split for the chromatic-aberration hover.
// Secondary / ghost variants use the accent/border tokens.
func cyberpunkButtonComponent(label, href, 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)
switch variant {
case "secondary":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 10, Col: 30}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"cyberpunk-btn cyberpunk-mono inline-flex items-center gap-2 px-5 py-2.5 rounded-md font-semibold uppercase tracking-wider\" style=\"background-color: hsl(var(--secondary)); color: hsl(var(--secondary-foreground));\"><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 14, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</span> <span aria-hidden=\"true\" class=\"caret-blink\">_</span></a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "ghost":
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 19, Col: 30}
}
_, 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, "\" class=\"cyberpunk-btn-ghost cyberpunk-mono inline-flex items-center gap-2 px-5 py-2.5 rounded-md font-semibold uppercase tracking-wider\" style=\"border: 1px solid hsl(var(--accent)); color: hsl(var(--accent));\"><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 23, 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, 6, "</span> <span aria-hidden=\"true\" class=\"caret-blink\">_</span></a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<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(href))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 28, Col: 30}
}
_, 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, "\" class=\"cyberpunk-btn rgb-split cyberpunk-mono inline-flex items-center gap-2 px-5 py-2.5 rounded-md font-semibold uppercase tracking-wider\" style=\"background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));\"><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `button_override.templ`, Line: 32, Col: 17}
}
_, 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, "</span> <span aria-hidden=\"true\" class=\"caret-blink\">_</span></a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,18 +0,0 @@
package main
import (
"bytes"
"context"
)
// CyberpunkCardBlock renders the built-in card with a dashed neon border-image
// and hover-lift / chromatic-aberration shadow.
func CyberpunkCardBlock(ctx context.Context, content map[string]any) string {
title := getString(content, "title")
body := getString(content, "body")
href := getString(content, "href")
media := getString(content, "media")
var buf bytes.Buffer
_ = cyberpunkCardComponent(title, body, href, media).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,44 +0,0 @@
package main
// cyberpunkCardComponent renders the override for the built-in card block:
// dashed neon border, hover-lift with chromatic-aberration shadow.
templ cyberpunkCardComponent(title, body, href, media string) {
if href != "" {
<a
href={ templ.SafeURL(safeHref(href)) }
class="cyberpunk-card neon-edge-magenta block p-6 rounded-lg h-full transition-transform hover:-translate-y-0.5"
style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px dashed hsl(var(--primary));"
>
@cyberpunkCardInner(title, body, media)
</a>
} else {
<article
class="cyberpunk-card neon-edge-magenta block p-6 rounded-lg h-full"
style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px dashed hsl(var(--primary));"
>
@cyberpunkCardInner(title, body, media)
</article>
}
}
// cyberpunkCardInner renders the shared card body markup.
templ cyberpunkCardInner(title, body, media string) {
if media != "" {
<img
src={ media }
alt=""
class="cyberpunk-card-media w-full h-40 object-cover rounded mb-4"
onerror="this.style.display='none'"
/>
}
if title != "" {
<h3 class="cyberpunk-card-title text-lg font-semibold mb-2" style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);">
{ title }
</h3>
}
if body != "" {
<div class="cyberpunk-card-body text-sm leading-relaxed" style="color: hsl(var(--muted-foreground));">
@templ.Raw(body)
</div>
}
}

View File

@ -1,156 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// cyberpunkCardComponent renders the override for the built-in card block:
// dashed neon border, hover-lift with chromatic-aberration shadow.
func cyberpunkCardComponent(title, body, href, media 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)
if href != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(safeHref(href)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 8, Col: 39}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" class=\"cyberpunk-card neon-edge-magenta block p-6 rounded-lg h-full transition-transform hover:-translate-y-0.5\" style=\"background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px dashed hsl(var(--primary));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = cyberpunkCardInner(title, body, media).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<article class=\"cyberpunk-card neon-edge-magenta block p-6 rounded-lg h-full\" style=\"background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px dashed hsl(var(--primary));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = cyberpunkCardInner(title, body, media).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
// cyberpunkCardInner renders the shared card body markup.
func cyberpunkCardInner(title, body, media string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if media != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(media)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 28, Col: 14}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" alt=\"\" class=\"cyberpunk-card-media w-full h-40 object-cover rounded mb-4\" onerror=\"this.style.display='none'\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<h3 class=\"cyberpunk-card-title text-lg font-semibold mb-2\" style=\"color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `card_override.templ`, Line: 36, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if body != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"cyberpunk-card-body text-sm leading-relaxed\" style=\"color: hsl(var(--muted-foreground));\">")
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, 11, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,39 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// CodeNeonMeta defines metadata for the cyberpunk:code_neon block.
var CodeNeonMeta = blocks.BlockMeta{
Key: "code_neon",
Title: "Code Block",
Description: "Server-rendered code block with neon edge and optional copy button",
Category: blocks.CategoryContent,
Source: "cyberpunk",
}
// CodeNeonData is the typed view of the code_neon content map.
type CodeNeonData struct {
Language string
Code string
Copy bool
}
// CodeNeonBlock renders the code_neon block.
//
// Content shape:
// {language:"bash"|"go"|..., code, copy: bool|"on"|"off"}
func CodeNeonBlock(ctx context.Context, content map[string]any) string {
data := CodeNeonData{
Language: getStringWithDefault(content, "language", "text"),
Code: getString(content, "code"),
Copy: getBool(content, "copy", true),
}
var buf bytes.Buffer
_ = codeNeonComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,25 +0,0 @@
package main
// codeNeonComponent renders the cyberpunk:code_neon block.
templ codeNeonComponent(data CodeNeonData) {
<figure
data-block="cyberpunk:code_neon"
class="cyberpunk-code w-full my-6 rounded-lg overflow-hidden neon-edge-magenta"
style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground));"
>
<figcaption class="flex items-center justify-between px-4 py-2 cyberpunk-mono text-xs uppercase tracking-widest" style="background-color: hsl(var(--secondary)); color: hsl(var(--muted-foreground));">
<span>{ data.Language }</span>
if data.Copy {
<button
type="button"
class="cyberpunk-code-copy cyberpunk-mono uppercase tracking-widest px-2 py-1 rounded"
style="border: 1px solid hsl(var(--border)); color: hsl(var(--accent));"
data-copy={ data.Code }
>
COPY
</button>
}
</figcaption>
<pre class="cyberpunk-mono overflow-x-auto p-4 text-sm leading-relaxed" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-mono, 'JetBrains Mono', ui-monospace, monospace);"><code class={ "language-" + data.Language }>{ data.Code }</code></pre>
</figure>
}

View File

@ -1,112 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// codeNeonComponent renders the cyberpunk:code_neon block.
func codeNeonComponent(data CodeNeonData) 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=\"cyberpunk:code_neon\" class=\"cyberpunk-code w-full my-6 rounded-lg overflow-hidden neon-edge-magenta\" style=\"background-color: hsl(var(--card)); color: hsl(var(--card-foreground));\"><figcaption class=\"flex items-center justify-between px-4 py-2 cyberpunk-mono text-xs uppercase tracking-widest\" style=\"background-color: hsl(var(--secondary)); color: hsl(var(--muted-foreground));\"><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Language)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `code_neon.templ`, Line: 11, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Copy {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<button type=\"button\" class=\"cyberpunk-code-copy cyberpunk-mono uppercase tracking-widest px-2 py-1 rounded\" style=\"border: 1px solid hsl(var(--border)); color: hsl(var(--accent));\" data-copy=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Code)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `code_neon.templ`, Line: 17, Col: 26}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\">COPY</button>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</figcaption><pre class=\"cyberpunk-mono overflow-x-auto p-4 text-sm leading-relaxed\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-mono, 'JetBrains Mono', ui-monospace, monospace);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 = []any{"language-" + data.Language}
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, 6, "<code 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: `code_neon.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, 7, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Code)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `code_neon.templ`, Line: 23, Col: 284}
}
_, 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, "</code></pre></figure>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,41 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// CTATerminalMeta defines metadata for the cyberpunk:cta_terminal block.
var CTATerminalMeta = blocks.BlockMeta{
Key: "cta_terminal",
Title: "Terminal CTA",
Description: "Faux $ prompt with blinking caret and copy button",
Category: blocks.CategoryLayout,
Source: "cyberpunk",
}
// CTATerminalData is the typed view of the cta_terminal content map.
type CTATerminalData struct {
Prompt string
Button string
Href string
Copyable bool
}
// CTATerminalBlock renders the cta_terminal block.
//
// Content shape:
// {prompt, button, href, copyable: bool|"true"|"false"}
func CTATerminalBlock(ctx context.Context, content map[string]any) string {
data := CTATerminalData{
Prompt: getStringWithDefault(content, "prompt", "$ npm i your-thing"),
Button: getStringWithDefault(content, "button", "Copy"),
Href: getString(content, "href"),
Copyable: getBool(content, "copyable", true),
}
var buf bytes.Buffer
_ = ctaTerminalComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,37 +0,0 @@
package main
// ctaTerminalComponent renders the cyberpunk:cta_terminal block as a faux terminal prompt.
templ ctaTerminalComponent(data CTATerminalData) {
<section
data-block="cyberpunk:cta_terminal"
class="cyberpunk-cta-terminal w-full py-16"
style="background-color: hsl(var(--card));"
>
<div class="max-w-4xl mx-auto px-4">
<div
class="cyberpunk-mono rounded-lg p-6 md:p-8 flex flex-col md:flex-row items-stretch md:items-center gap-4"
style="background-color: hsl(var(--background)); border: 1px dashed hsl(var(--primary));"
>
<div class="flex-1 flex items-center gap-2 text-base md:text-lg overflow-hidden">
<span aria-hidden="true" style="color: hsl(var(--accent));">$</span>
<code class="cyberpunk-mono whitespace-pre-wrap break-all" style="color: hsl(var(--foreground));" data-caret-blink>
{ data.Prompt }
</code>
<span aria-hidden="true" class="caret-blink" data-caret-blink style="color: hsl(var(--primary));">▍</span>
</div>
if data.Button != "" {
<a
href={ templ.SafeURL(safeHref(data.Href)) }
class="cyberpunk-btn rgb-split cyberpunk-mono inline-flex items-center justify-center gap-2 px-4 py-2 rounded-md font-semibold uppercase tracking-wider"
style="background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));"
if data.Copyable {
data-copy={ data.Prompt }
}
>
{ data.Button }
</a>
}
</div>
</div>
</section>
}

View File

@ -1,113 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// ctaTerminalComponent renders the cyberpunk:cta_terminal block as a faux terminal prompt.
func ctaTerminalComponent(data CTATerminalData) 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=\"cyberpunk:cta_terminal\" class=\"cyberpunk-cta-terminal w-full py-16\" style=\"background-color: hsl(var(--card));\"><div class=\"max-w-4xl mx-auto px-4\"><div class=\"cyberpunk-mono rounded-lg p-6 md:p-8 flex flex-col md:flex-row items-stretch md:items-center gap-4\" style=\"background-color: hsl(var(--background)); border: 1px dashed hsl(var(--primary));\"><div class=\"flex-1 flex items-center gap-2 text-base md:text-lg overflow-hidden\"><span aria-hidden=\"true\" style=\"color: hsl(var(--accent));\">$</span> <code class=\"cyberpunk-mono whitespace-pre-wrap break-all\" style=\"color: hsl(var(--foreground));\" data-caret-blink>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Prompt)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cta_terminal.templ`, Line: 18, 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, 2, "</code> <span aria-hidden=\"true\" class=\"caret-blink\" data-caret-blink style=\"color: hsl(var(--primary));\">▍</span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Button != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<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(data.Href)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cta_terminal.templ`, Line: 24, Col: 47}
}
_, 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, "\" class=\"cyberpunk-btn rgb-split cyberpunk-mono inline-flex items-center justify-center gap-2 px-4 py-2 rounded-md font-semibold uppercase tracking-wider\" style=\"background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Copyable {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " data-copy=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Prompt)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cta_terminal.templ`, Line: 28, Col: 30}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
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(data.Button)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `cta_terminal.templ`, Line: 31, Col: 19}
}
_, 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, "</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></div></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,102 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/templates"
)
// CyberpunkEmailWrapper is the registered cyberpunk:email_wrapper.
// It produces a dark email with a mono preheader, magenta hairline divider,
// and a literal "[esc] unsubscribe" link per UAT §10.
//
// UAT §10 mandates `body { background:#0a0a12 }`. That literal hex lives in the
// <style> head block (a <style> element is not a `style=` attribute and so does
// not trip the no-inline-hex-style rule). All other colour values use hsl()
// notation, which most modern mail clients render fine.
func CyberpunkEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = cyberpunkEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}
// cyberpunkEmailTemplate is the Cyberpunk-branded HTML email shell.
templ cyberpunkEmailTemplate(emailCtx templates.EmailContext, body string) {
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="x-apple-disable-message-reformatting"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>{ emailCtx.SiteSettings.SiteName }</title>
<style type="text/css">
body { background:#0a0a12; margin:0; padding:0; color: hsl(260 25% 92%); }
table, td { mso-table-lspace:0pt; mso-table-rspace:0pt; }
a { color: inherit; }
.cyberpunk-preheader { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.cyberpunk-divider { border-top:1px solid hsl(320 100% 60%); }
@media only screen and (max-width: 620px) {
.email-container { width:100% !important; max-width:100% !important; }
.content-padding { padding-left:24px !important; padding-right:24px !important; }
}
</style>
</head>
<body bgcolor="#0a0a12" style="margin:0;padding:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;">
if emailCtx.PreviewText != "" {
<div class="cyberpunk-preheader" style="display:none;max-height:0;overflow:hidden;mso-hide:all;font-family: ui-monospace, SFMono-Regular, Menlo, monospace;">
$ from: { emailCtx.SiteSettings.SiteName } — { emailCtx.PreviewText }
</div>
} else {
<div class="cyberpunk-preheader" style="display:none;max-height:0;overflow:hidden;mso-hide:all;font-family: ui-monospace, SFMono-Regular, Menlo, monospace;">
$ from: { emailCtx.SiteSettings.SiteName }
</div>
}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#0a0a12">
<tr>
<td align="center" style="padding:48px 12px;">
<table role="presentation" class="email-container" width="600" cellpadding="0" cellspacing="0" border="0" bgcolor="#101020" style="max-width:600px;border:1px solid hsl(258 28% 20%);">
<tr>
<td class="content-padding" align="center" style="padding:32px 40px;border-bottom:1px solid hsl(320 100% 60%);">
<div class="cyberpunk-preheader" style="font-family: ui-monospace, SFMono-Regular, Menlo, monospace;font-size:12px;letter-spacing:0.2em;text-transform:uppercase;">
$ from: { emailCtx.SiteSettings.SiteName }
</div>
if emailCtx.SiteSettings.LogoURL != "" {
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="margin-top:12px;max-height:48px;width:auto;display:inline-block;"/>
}
</td>
</tr>
<tr>
<td class="content-padding" style="padding:40px 48px;font-size:16px;line-height:1.7;">
@templ.Raw(body)
</td>
</tr>
<tr>
<td class="cyberpunk-divider" height="1" style="border-top:1px solid hsl(320 100% 60%);font-size:0;line-height:0;">&nbsp;</td>
</tr>
<tr>
<td class="content-padding" align="center" style="padding:24px 48px 40px;font-family: ui-monospace, SFMono-Regular, Menlo, monospace;font-size:12px;letter-spacing:0.15em;text-transform:uppercase;">
if emailCtx.SiteSettings.SiteURL != "" {
<p style="margin:0 0 12px;">
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style="text-decoration:none;">
{ emailCtx.SiteSettings.SiteURL }
</a>
</p>
}
if emailCtx.UnsubscribeURL != "" {
<p style="margin:0;">
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style="text-decoration:none;">[esc] unsubscribe</a>
</p>
} else {
<p style="margin:0;">[esc] unsubscribe</p>
}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
}

View File

@ -1,246 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/templates"
)
// CyberpunkEmailWrapper is the registered cyberpunk:email_wrapper.
// It produces a dark email with a mono preheader, magenta hairline divider,
// and a literal "[esc] unsubscribe" link per UAT §10.
//
// UAT §10 mandates `body { background:#0a0a12 }`. That literal hex lives in the
// <style> head block (a <style> element is not a `style=` attribute and so does
// not trip the no-inline-hex-style rule). All other colour values use hsl()
// notation, which most modern mail clients render fine.
func CyberpunkEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = cyberpunkEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}
// cyberpunkEmailTemplate is the Cyberpunk-branded HTML email shell.
func cyberpunkEmailTemplate(emailCtx templates.EmailContext, body string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:o=\"urn:schemas-microsoft-com:office:office\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><meta name=\"x-apple-disable-message-reformatting\"><meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 33, 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 { background:#0a0a12; margin:0; padding:0; color: hsl(260 25% 92%); }\n\t\t\t\ttable, td { mso-table-lspace:0pt; mso-table-rspace:0pt; }\n\t\t\t\ta { color: inherit; }\n\t\t\t\t.cyberpunk-preheader { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }\n\t\t\t\t.cyberpunk-divider { border-top:1px solid hsl(320 100% 60%); }\n\t\t\t\t@media only screen and (max-width: 620px) {\n\t\t\t\t\t.email-container { width:100% !important; max-width:100% !important; }\n\t\t\t\t\t.content-padding { padding-left:24px !important; padding-right:24px !important; }\n\t\t\t\t}\n\t\t\t</style></head><body bgcolor=\"#0a0a12\" style=\"margin:0;padding:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.PreviewText != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"cyberpunk-preheader\" style=\"display:none;max-height:0;overflow:hidden;mso-hide:all;font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\">$ from: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, 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: 49, Col: 45}
}
_, 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, " — ")
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: 49, Col: 74}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"cyberpunk-preheader\" style=\"display:none;max-height:0;overflow:hidden;mso-hide:all;font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\">$ from: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, 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: 53, Col: 45}
}
_, 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, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" bgcolor=\"#0a0a12\"><tr><td align=\"center\" style=\"padding:48px 12px;\"><table role=\"presentation\" class=\"email-container\" width=\"600\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" bgcolor=\"#101020\" style=\"max-width:600px;border:1px solid hsl(258 28% 20%);\"><tr><td class=\"content-padding\" align=\"center\" style=\"padding:32px 40px;border-bottom:1px solid hsl(320 100% 60%);\"><div class=\"cyberpunk-preheader\" style=\"font-family: ui-monospace, SFMono-Regular, Menlo, monospace;font-size:12px;letter-spacing:0.2em;text-transform:uppercase;\">$ from: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, 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: 63, Col: 50}
}
_, 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, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.LogoURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.LogoURL)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 66, Col: 50}
}
_, 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, 11, "\" alt=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(emailCtx.SiteSettings.SiteName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 66, Col: 89}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\" style=\"margin-top:12px;max-height:48px;width:auto;display:inline-block;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</td></tr><tr><td class=\"content-padding\" style=\"padding:40px 48px;font-size:16px;line-height:1.7;\">")
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, 14, "</td></tr><tr><td class=\"cyberpunk-divider\" height=\"1\" style=\"border-top:1px solid hsl(320 100% 60%);font-size:0;line-height:0;\">&nbsp;</td></tr><tr><td class=\"content-padding\" align=\"center\" style=\"padding:24px 48px 40px;font-family: ui-monospace, SFMono-Regular, Menlo, monospace;font-size:12px;letter-spacing:0.15em;text-transform:uppercase;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if emailCtx.SiteSettings.SiteURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<p style=\"margin:0 0 12px;\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 templ.SafeURL
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(emailCtx.SiteSettings.SiteURL))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `email_wrapper.templ`, Line: 82, Col: 65}
}
_, 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, 16, "\" style=\"text-decoration:none;\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, 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: 83, Col: 43}
}
_, 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, 17, "</a></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if emailCtx.UnsubscribeURL != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<p style=\"margin:0;\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 templ.SafeURL
templ_7745c5c3_Var11, 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: 89, Col: 59}
}
_, 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, 19, "\" style=\"text-decoration:none;\">[esc] unsubscribe</a></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<p style=\"margin:0;\">[esc] unsubscribe</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</td></tr></table></td></tr></table></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,60 +0,0 @@
package main
import (
"embed"
"io/fs"
"net/http"
"git.dev.alexdunmow.com/block/core/plugin"
)
//go:embed assets/*
var assetsFS embed.FS
//go:embed schemas/*
var schemasFS embed.FS
//go:embed presets.json
var presetsData []byte
//go:embed fonts.json
var fontsData []byte
//go:embed plugin.mod
var pluginModBytes []byte
// Assets returns the embedded assets filesystem (rooted at "assets/").
func Assets() fs.FS {
sub, _ := fs.Sub(assetsFS, "assets")
return sub
}
// Schemas returns the embedded schemas filesystem (rooted at "schemas/").
func Schemas() fs.FS {
sub, _ := fs.Sub(schemasFS, "schemas")
return sub
}
// AssetsHandler returns an http.Handler that serves the embedded assets at /templates/cyberpunk/...
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 exposes the theme's custom CSS (scanlines, glitch, neon-edge,
// caret-blink keyframes, font-family fallbacks) to the host Tailwind input so the
// utilities survive prod purging.
func ThemeCSSManifest() *plugin.CSSManifest {
css, err := assetsFS.ReadFile("assets/css/cyberpunk.css")
if err != nil {
return &plugin.CSSManifest{}
}
return &plugin.CSSManifest{
InputCSSAppend: string(css),
}
}

View File

@ -1,51 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// FeatureCardNeonMeta defines metadata for the cyberpunk:feature_card_neon block.
var FeatureCardNeonMeta = blocks.BlockMeta{
Key: "feature_card_neon",
Title: "Neon Feature Card",
Description: "Single feature card with per-card neon edge glow (magenta/cyan/lime)",
Category: blocks.CategoryLayout,
Source: "cyberpunk",
}
// FeatureCardNeonData is the typed view of the feature_card_neon content map.
type FeatureCardNeonData struct {
Icon string
Title string
Body string
Accent string
}
// FeatureCardNeonBlock renders the feature_card_neon block.
//
// Content shape:
// {icon, title, body, accent:"magenta"|"cyan"|"lime"}
func FeatureCardNeonBlock(ctx context.Context, content map[string]any) string {
data := FeatureCardNeonData{
Icon: getString(content, "icon"),
Title: getStringWithDefault(content, "title", "Feature"),
Body: getString(content, "body"),
Accent: parseAccent(getString(content, "accent")),
}
var buf bytes.Buffer
_ = featureCardNeonComponent(data).Render(ctx, &buf)
return buf.String()
}
// parseAccent normalises the accent value to one of the three known accents.
func parseAccent(v string) string {
switch v {
case "magenta", "cyan", "lime":
return v
default:
return "magenta"
}
}

View File

@ -1,37 +0,0 @@
package main
// neonEdgeClass returns the per-accent neon-edge utility class.
func neonEdgeClass(accent string) string {
switch accent {
case "cyan":
return "neon-edge-cyan"
case "lime":
return "neon-edge-lime"
default:
return "neon-edge-magenta"
}
}
// featureCardNeonComponent renders the cyberpunk:feature_card_neon block.
templ featureCardNeonComponent(data FeatureCardNeonData) {
<article
data-block="cyberpunk:feature_card_neon"
data-accent={ data.Accent }
class={ "cyberpunk-feature-card p-6 rounded-lg h-full flex flex-col gap-4", neonEdgeClass(data.Accent) }
style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));"
>
if data.Icon != "" {
<div class="cyberpunk-feature-icon w-10 h-10 flex items-center justify-center rounded-md" style="background-color: hsl(var(--secondary));">
<img src={ data.Icon } alt="" class="w-6 h-6" onerror="this.style.display='none'"/>
</div>
}
<h3 class="cyberpunk-feature-title text-lg font-semibold" style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);">
{ data.Title }
</h3>
if data.Body != "" {
<div class="cyberpunk-feature-body text-sm leading-relaxed" style="color: hsl(var(--muted-foreground));">
@templ.Raw(data.Body)
</div>
}
</article>
}

View File

@ -1,138 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// neonEdgeClass returns the per-accent neon-edge utility class.
func neonEdgeClass(accent string) string {
switch accent {
case "cyan":
return "neon-edge-cyan"
case "lime":
return "neon-edge-lime"
default:
return "neon-edge-magenta"
}
}
// featureCardNeonComponent renders the cyberpunk:feature_card_neon block.
func featureCardNeonComponent(data FeatureCardNeonData) 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{"cyberpunk-feature-card p-6 rounded-lg h-full flex flex-col gap-4", neonEdgeClass(data.Accent)}
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, "<article data-block=\"cyberpunk:feature_card_neon\" data-accent=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Accent)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `feature_card_neon.templ`, Line: 19, Col: 27}
}
_, 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, "\" 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: `feature_card_neon.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=\"background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Icon != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"cyberpunk-feature-icon w-10 h-10 flex items-center justify-center rounded-md\" style=\"background-color: hsl(var(--secondary));\"><img src=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `feature_card_neon.templ`, Line: 25, Col: 24}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" alt=\"\" class=\"w-6 h-6\" onerror=\"this.style.display='none'\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<h3 class=\"cyberpunk-feature-title text-lg font-semibold\" style=\"color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `feature_card_neon.templ`, Line: 29, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Body != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<div class=\"cyberpunk-feature-body text-sm leading-relaxed\" style=\"color: hsl(var(--muted-foreground));\">")
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, 9, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1 +1,28 @@
[] [
{
"name": "Orbitron",
"family": "Orbitron",
"variants": [
{ "weight": "500", "style": "normal", "file": "fonts/orbitron-latin.woff2" },
{ "weight": "700", "style": "normal", "file": "fonts/orbitron-latin.woff2" },
{ "weight": "900", "style": "normal", "file": "fonts/orbitron-latin.woff2" }
]
},
{
"name": "Share Tech Mono",
"family": "Share Tech Mono",
"variants": [
{ "weight": "400", "style": "normal", "file": "fonts/share-tech-mono-latin.woff2" }
]
},
{
"name": "Inter",
"family": "Inter",
"variants": [
{ "weight": "400", "style": "normal", "file": "fonts/inter-latin.woff2" },
{ "weight": "500", "style": "normal", "file": "fonts/inter-latin.woff2" },
{ "weight": "600", "style": "normal", "file": "fonts/inter-latin.woff2" },
{ "weight": "700", "style": "normal", "file": "fonts/inter-latin.woff2" }
]
}
]

View File

@ -1,62 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// FooterGridMeta defines metadata for the cyberpunk:footer_grid block.
var FooterGridMeta = blocks.BlockMeta{
Key: "footer_grid",
Title: "Grid Footer",
Description: "Mono columns, build-hash + uptime status pill",
Category: blocks.CategoryNavigation,
Source: "cyberpunk",
}
// FooterColumn is one column of footer links.
type FooterColumn struct {
Title string
Links []Link
}
// FooterGridData is the typed view of the footer_grid content map.
type FooterGridData struct {
ShowStatus bool
BuildHash string
Columns []FooterColumn
}
// FooterGridBlock renders the footer_grid block.
//
// Content shape:
// {showStatus, buildHash, columns:[{title, links:[{label, href}]}]}
func FooterGridBlock(ctx context.Context, content map[string]any) string {
cols := getSlice(content, "columns")
columns := make([]FooterColumn, 0, len(cols))
for _, col := range cols {
linkObjs := getSlice(col, "links")
links := make([]Link, 0, len(linkObjs))
for _, l := range linkObjs {
links = append(links, Link{
Label: getString(l, "label"),
Href: getString(l, "href"),
})
}
columns = append(columns, FooterColumn{
Title: getString(col, "title"),
Links: links,
})
}
data := FooterGridData{
ShowStatus: getBool(content, "showStatus", true),
BuildHash: getStringWithDefault(content, "buildHash", "dev"),
Columns: columns,
}
var buf bytes.Buffer
_ = footerGridComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,77 +0,0 @@
package main
// footerGridColumns returns a tailwind grid-column class for n columns.
func footerGridColumns(n int) string {
switch n {
case 0, 1:
return "grid-cols-1"
case 2:
return "grid-cols-1 sm:grid-cols-2"
case 3:
return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"
default:
return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
}
}
// footerGridComponent renders the cyberpunk:footer_grid block.
templ footerGridComponent(data FooterGridData) {
<div
data-block="cyberpunk:footer_grid"
class="cyberpunk-footer w-full"
style="background-color: hsl(var(--card)); color: hsl(var(--muted-foreground));"
>
<div class="max-w-6xl mx-auto px-4 py-12">
if len(data.Columns) > 0 {
<div class={ "grid gap-8 mb-10 cyberpunk-mono text-sm", footerGridColumns(len(data.Columns)) }>
for _, col := range data.Columns {
<div>
if col.Title != "" {
<p class="cyberpunk-mono uppercase tracking-widest text-xs mb-4" style="color: hsl(var(--accent));">
{ col.Title }
</p>
}
if len(col.Links) > 0 {
<ul class="space-y-2">
for _, link := range col.Links {
<li>
<a
href={ templ.SafeURL(safeHref(link.Href)) }
class="cyberpunk-footer-link hover:underline"
style="color: hsl(var(--foreground));"
>
{ link.Label }
</a>
</li>
}
</ul>
}
</div>
}
</div>
} else {
<p class="cyberpunk-mono mb-6 text-xs uppercase tracking-widest" style="color: hsl(var(--muted-foreground));">
{ "// add columns to populate this grid" }
</p>
}
<div class="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 pt-6" style="border-top: 1px dashed hsl(var(--border));">
<p class="cyberpunk-mono text-xs uppercase tracking-widest" style="color: hsl(var(--muted-foreground));">
BUILD <span style="color: hsl(var(--foreground));">{ data.BuildHash }</span>
</p>
if data.ShowStatus {
<span
class="cyberpunk-status-pill cyberpunk-mono inline-flex items-center gap-2 px-3 py-1 rounded-full text-xs uppercase tracking-widest"
style="border: 1px solid hsl(var(--accent)); color: hsl(var(--accent));"
>
<span
aria-hidden="true"
class="cyberpunk-status-dot inline-block w-2 h-2 rounded-full"
style="background-color: hsl(var(--accent)); box-shadow: 0 0 6px hsl(var(--accent));"
></span>
OK
</span>
}
</div>
</div>
</div>
}

View File

@ -1,199 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// footerGridColumns returns a tailwind grid-column class for n columns.
func footerGridColumns(n int) string {
switch n {
case 0, 1:
return "grid-cols-1"
case 2:
return "grid-cols-1 sm:grid-cols-2"
case 3:
return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"
default:
return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
}
}
// footerGridComponent renders the cyberpunk:footer_grid block.
func footerGridComponent(data FooterGridData) 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=\"cyberpunk:footer_grid\" class=\"cyberpunk-footer w-full\" style=\"background-color: hsl(var(--card)); color: hsl(var(--muted-foreground));\"><div class=\"max-w-6xl mx-auto px-4 py-12\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(data.Columns) > 0 {
var templ_7745c5c3_Var2 = []any{"grid gap-8 mb-10 cyberpunk-mono text-sm", footerGridColumns(len(data.Columns))}
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, "<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: `footer_grid.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
}
for _, col := range data.Columns {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if col.Title != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<p class=\"cyberpunk-mono uppercase tracking-widest text-xs mb-4\" style=\"color: hsl(var(--accent));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(col.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer_grid.templ`, Line: 31, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(col.Links) > 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<ul class=\"space-y-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, link := range col.Links {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<li><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 templ.SafeURL
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(safeHref(link.Href)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer_grid.templ`, Line: 39, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" class=\"cyberpunk-footer-link hover:underline\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(link.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer_grid.templ`, Line: 43, Col: 24}
}
_, 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, "</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</ul>")
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
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<p class=\"cyberpunk-mono mb-6 text-xs uppercase tracking-widest\" style=\"color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs("// add columns to populate this grid")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer_grid.templ`, Line: 54, Col: 45}
}
_, 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, 15, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<div class=\"flex flex-col md:flex-row items-start md:items-center justify-between gap-3 pt-6\" style=\"border-top: 1px dashed hsl(var(--border));\"><p class=\"cyberpunk-mono text-xs uppercase tracking-widest\" style=\"color: hsl(var(--muted-foreground));\">BUILD <span style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.BuildHash)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `footer_grid.templ`, Line: 59, Col: 72}
}
_, 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, 17, "</span></p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.ShowStatus {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<span class=\"cyberpunk-status-pill cyberpunk-mono inline-flex items-center gap-2 px-3 py-1 rounded-full text-xs uppercase tracking-widest\" style=\"border: 1px solid hsl(var(--accent)); color: hsl(var(--accent));\"><span aria-hidden=\"true\" class=\"cyberpunk-status-dot inline-block w-2 h-2 rounded-full\" style=\"background-color: hsl(var(--accent)); box-shadow: 0 0 6px hsl(var(--accent));\"></span> OK</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

20
go.mod
View File

@ -1,20 +0,0 @@
module git.dev.alexdunmow.com/block/themes/cyberpunk
go 1.26.4
require (
git.dev.alexdunmow.com/block/core v0.14.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
)

44
go.sum
View File

@ -1,44 +0,0 @@
connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
git.dev.alexdunmow.com/block/core v0.14.0 h1:cO6QQQPndhxwh1zbDvDOzebwrFS8Ka3WsxD8JBJLw/Y=
git.dev.alexdunmow.com/block/core v0.14.0/go.mod h1:S0ZfpGZ9BQhhmuTUd78ailH7m2vo7QRCTIionvCVm9s=
git.dev.alexdunmow.com/block/core v0.14.1 h1:63b25yzWoqrhBd3wdDFhsJzyGNM99wYmdh4WkN35hDk=
git.dev.alexdunmow.com/block/core v0.14.1/go.mod h1:S0ZfpGZ9BQhhmuTUd78ailH7m2vo7QRCTIionvCVm9s=
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=

View File

@ -1,18 +0,0 @@
package main
import (
"bytes"
"context"
)
// CyberpunkHeadingBlock renders the built-in heading block with cyberpunk styling.
// H1/H2 get the glitch text-shadow (magenta + cyan offsets); smaller levels get
// mono small-caps eyebrow class.
func CyberpunkHeadingBlock(ctx context.Context, content map[string]any) string {
text := getString(content, "text")
textClass := getString(content, "textClass")
level := parseHeadingLevel(content)
var buf bytes.Buffer
_ = cyberpunkHeadingComponent(level, text, textClass).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,81 +0,0 @@
package main
// cyberpunkHeadingBaseClass returns default Tailwind classes for each heading level.
func cyberpunkHeadingBaseClass(level int) string {
switch level {
case 1:
return "text-4xl md:text-5xl font-bold tracking-tight"
case 2:
return "text-3xl md:text-4xl font-semibold tracking-tight"
case 3:
return "text-2xl font-semibold"
case 4:
return "text-xl font-semibold"
case 5:
return "text-lg font-medium"
case 6:
return "text-base font-medium"
default:
return "text-3xl font-semibold tracking-tight"
}
}
// cyberpunkHeadingComponent renders the override for the built-in heading block.
// H1 and H2 get the .glitch class which gives them the RGB-split text-shadow.
// H3-H6 get the mono small-caps eyebrow class.
templ cyberpunkHeadingComponent(level int, text, textClass string) {
switch level {
case 1:
<h1
class={ "cyberpunk-display glitch", cyberpunkHeadingBaseClass(1), textClass }
data-text={ text }
style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);"
>
{ text }
</h1>
case 2:
<h2
class={ "cyberpunk-display glitch", cyberpunkHeadingBaseClass(2), textClass }
data-text={ text }
style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);"
>
{ text }
</h2>
case 3:
<h3
class={ "cyberpunk-display", cyberpunkHeadingBaseClass(3), textClass }
style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);"
>
{ text }
</h3>
case 4:
<h4
class={ "cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest", cyberpunkHeadingBaseClass(4), textClass }
style="color: hsl(var(--accent));"
>
{ text }
</h4>
case 5:
<h5
class={ "cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest", cyberpunkHeadingBaseClass(5), textClass }
style="color: hsl(var(--accent));"
>
{ text }
</h5>
case 6:
<h6
class={ "cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest", cyberpunkHeadingBaseClass(6), textClass }
style="color: hsl(var(--accent));"
>
{ text }
</h6>
default:
<h2
class={ "cyberpunk-display glitch", cyberpunkHeadingBaseClass(2), textClass }
data-text={ text }
style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);"
>
{ text }
</h2>
}
}

View File

@ -1,352 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// cyberpunkHeadingBaseClass returns default Tailwind classes for each heading level.
func cyberpunkHeadingBaseClass(level int) string {
switch level {
case 1:
return "text-4xl md:text-5xl font-bold tracking-tight"
case 2:
return "text-3xl md:text-4xl font-semibold tracking-tight"
case 3:
return "text-2xl font-semibold"
case 4:
return "text-xl font-semibold"
case 5:
return "text-lg font-medium"
case 6:
return "text-base font-medium"
default:
return "text-3xl font-semibold tracking-tight"
}
}
// cyberpunkHeadingComponent renders the override for the built-in heading block.
// H1 and H2 get the .glitch class which gives them the RGB-split text-shadow.
// H3-H6 get the mono small-caps eyebrow class.
func cyberpunkHeadingComponent(level int, text, textClass string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch level {
case 1:
var templ_7745c5c3_Var2 = []any{"cyberpunk-display glitch", cyberpunkHeadingBaseClass(1), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h1 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var2).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" data-text=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 31, Col: 20}
}
_, 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=\"color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);\">")
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: 34, Col: 10}
}
_, 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, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 2:
var templ_7745c5c3_Var6 = []any{"cyberpunk-display glitch", cyberpunkHeadingBaseClass(2), 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, 5, "<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, 6, "\" data-text=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.ResolveAttributeValue(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 39, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" style=\"color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 42, Col: 10}
}
_, 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, 8, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 3:
var templ_7745c5c3_Var10 = []any{"cyberpunk-display", cyberpunkHeadingBaseClass(3), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<h3 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var10).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\" style=\"color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 49, Col: 10}
}
_, 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, 11, "</h3>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 4:
var templ_7745c5c3_Var13 = []any{"cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest", cyberpunkHeadingBaseClass(4), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var13...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<h4 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var13).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var14)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" style=\"color: hsl(var(--accent));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 56, Col: 10}
}
_, 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, 14, "</h4>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 5:
var templ_7745c5c3_Var16 = []any{"cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest", cyberpunkHeadingBaseClass(5), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var16...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<h5 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var16).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var17)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\" style=\"color: hsl(var(--accent));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 63, Col: 10}
}
_, 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, 17, "</h5>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 6:
var templ_7745c5c3_Var19 = []any{"cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest", cyberpunkHeadingBaseClass(6), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var19...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<h6 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var19).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var20)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" style=\"color: hsl(var(--accent));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 70, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</h6>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
var templ_7745c5c3_Var22 = []any{"cyberpunk-display glitch", cyberpunkHeadingBaseClass(2), textClass}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var22...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<h2 class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var22).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "\" data-text=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.ResolveAttributeValue(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 75, Col: 20}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" style=\"color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `heading_override.templ`, Line: 78, Col: 10}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</h2>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,102 +0,0 @@
package main
import "strconv"
// getString extracts a string value from a content map. Returns "" on miss/wrong-type.
func getString(content map[string]any, key string) string {
if v, ok := content[key].(string); ok {
return v
}
return ""
}
// getStringWithDefault is like getString but returns dflt if the key is missing or non-string.
func getStringWithDefault(content map[string]any, key, dflt string) string {
if v, ok := content[key].(string); ok && v != "" {
return v
}
return dflt
}
// getBool extracts a boolean value from a content map. Accepts bool, "true"/"false",
// "on"/"off", "yes"/"no", and numerics. Returns dflt when missing or unparsable.
func getBool(content map[string]any, key string, dflt bool) bool {
switch v := content[key].(type) {
case bool:
return v
case string:
switch v {
case "true", "on", "yes", "1":
return true
case "false", "off", "no", "0", "":
return false
}
case float64:
return v != 0
case int:
return v != 0
}
return dflt
}
// getSlice extracts a slice of object-shaped entries from a content map.
func getSlice(content map[string]any, key string) []map[string]any {
v, ok := content[key].([]any)
if !ok {
return nil
}
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
}
// getLink reads a {label, href} link object. Tolerates string values
// (interpreted as href, label = href) and missing keys.
func getLink(content map[string]any, key string) Link {
v := content[key]
switch x := v.(type) {
case map[string]any:
return Link{Label: getString(x, "label"), Href: getString(x, "href")}
case string:
return Link{Label: x, Href: x}
}
return Link{}
}
// Link is a {label, href} pair used by hero / nav / footer blocks.
type Link struct {
Label string
Href string
}
// safeHref returns "#" when href is empty so templ.SafeURL never receives "".
func safeHref(href string) string {
if href == "" {
return "#"
}
return href
}
// parseHeadingLevel parses a 1..6 heading level. Defaults to 2 for safety.
func parseHeadingLevel(content map[string]any) int {
switch v := content["level"].(type) {
case float64:
l := int(v)
if l >= 1 && l <= 6 {
return l
}
case int:
if v >= 1 && v <= 6 {
return v
}
case string:
if l, err := strconv.Atoi(v); err == nil && l >= 1 && l <= 6 {
return l
}
}
return 2
}

View File

@ -1,55 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// HeroGlitchMeta defines metadata for the cyberpunk glitch hero block.
var HeroGlitchMeta = blocks.BlockMeta{
Key: "hero_glitch",
Title: "Glitch Hero",
Description: "RGB-split H1, neon CTA, optional scanline overlay toggle",
Category: blocks.CategoryLayout,
Source: "cyberpunk",
}
// HeroGlitchData is the typed view of the hero_glitch content map.
type HeroGlitchData struct {
Eyebrow string
Headline string
Sub string
CTA Link
SecondaryCTA Link
Scanlines bool
}
// HeroGlitchBlock renders the hero_glitch block.
//
// Content shape (matches schemas/hero_glitch.schema.json):
// {eyebrow, headline, sub, cta:{label,href}, secondaryCta:{label,href}, scanlines:"on"|"off"}
func HeroGlitchBlock(ctx context.Context, content map[string]any) string {
data := HeroGlitchData{
Eyebrow: getString(content, "eyebrow"),
Headline: getStringWithDefault(content, "headline", "// hero headline"),
Sub: getString(content, "sub"),
CTA: getLink(content, "cta"),
SecondaryCTA: getLink(content, "secondaryCta"),
Scanlines: parseScanlines(content),
}
var buf bytes.Buffer
_ = heroGlitchComponent(data).Render(ctx, &buf)
return buf.String()
}
func parseScanlines(content map[string]any) bool {
if v, ok := content["scanlines"].(string); ok {
return v == "on" || v == "true"
}
if v, ok := content["scanlines"].(bool); ok {
return v
}
return true
}

View File

@ -1,54 +0,0 @@
package main
// heroGlitchComponent renders the cyberpunk:hero_glitch block with an RGB-split H1.
templ heroGlitchComponent(data HeroGlitchData) {
<section
data-block="cyberpunk:hero_glitch"
class="cyberpunk-hero relative w-full overflow-hidden py-24 md:py-32"
style="background-color: hsl(var(--background));"
>
if data.Scanlines {
<div aria-hidden="true" class="scanlines pointer-events-none absolute inset-0" data-scanlines-local></div>
}
<div class="relative z-10 max-w-5xl mx-auto px-4 text-center">
if data.Eyebrow != "" {
<p class="cyberpunk-eyebrow cyberpunk-mono inline-block px-3 py-1 mb-6 text-xs uppercase tracking-[0.35em]" style="color: hsl(var(--accent)); border: 1px dashed hsl(var(--accent));">
{ data.Eyebrow }
</p>
}
<h1
class="glitch cyberpunk-display text-5xl md:text-7xl font-bold leading-[1.05] tracking-tight mb-8"
data-text={ data.Headline }
style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);"
>
{ data.Headline }
</h1>
if data.Sub != "" {
<p class="cyberpunk-sub mx-auto max-w-2xl text-lg md:text-xl mb-10" style="color: hsl(var(--muted-foreground));">
{ data.Sub }
</p>
}
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center">
if data.CTA.Label != "" {
<a
href={ templ.SafeURL(safeHref(data.CTA.Href)) }
class="cyberpunk-btn rgb-split cyberpunk-mono inline-flex items-center gap-2 px-6 py-3 rounded-md font-semibold uppercase tracking-wider"
style="background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));"
>
<span>{ data.CTA.Label }</span>
<span aria-hidden="true" class="caret-blink">_</span>
</a>
}
if data.SecondaryCTA.Label != "" {
<a
href={ templ.SafeURL(safeHref(data.SecondaryCTA.Href)) }
class="cyberpunk-btn-ghost cyberpunk-mono inline-flex items-center gap-2 px-6 py-3 rounded-md font-semibold uppercase tracking-wider"
style="border: 1px solid hsl(var(--accent)); color: hsl(var(--accent));"
>
<span>{ data.SecondaryCTA.Label }</span>
</a>
}
</div>
</div>
</section>
}

View File

@ -1,191 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// heroGlitchComponent renders the cyberpunk:hero_glitch block with an RGB-split H1.
func heroGlitchComponent(data HeroGlitchData) 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=\"cyberpunk:hero_glitch\" class=\"cyberpunk-hero relative w-full overflow-hidden py-24 md:py-32\" style=\"background-color: hsl(var(--background));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Scanlines {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div aria-hidden=\"true\" class=\"scanlines pointer-events-none absolute inset-0\" data-scanlines-local></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div class=\"relative z-10 max-w-5xl mx-auto px-4 text-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Eyebrow != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<p class=\"cyberpunk-eyebrow cyberpunk-mono inline-block px-3 py-1 mb-6 text-xs uppercase tracking-[0.35em]\" style=\"color: hsl(var(--accent)); border: 1px dashed hsl(var(--accent));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.Eyebrow)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 16, 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, 5, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<h1 class=\"glitch cyberpunk-display text-5xl md:text-7xl font-bold leading-[1.05] tracking-tight mb-8\" data-text=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Headline)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 21, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" style=\"color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Headline)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 24, Col: 19}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.Sub != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<p class=\"cyberpunk-sub mx-auto max-w-2xl text-lg md:text-xl mb-10\" style=\"color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Sub)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 28, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"flex flex-col sm:flex-row gap-4 justify-center items-center\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.CTA.Label != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<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(safeHref(data.CTA.Href)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 34, Col: 51}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\" class=\"cyberpunk-btn rgb-split cyberpunk-mono inline-flex items-center gap-2 px-6 py-3 rounded-md font-semibold uppercase tracking-wider\" style=\"background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground));\"><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.CTA.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 38, 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, 14, "</span> <span aria-hidden=\"true\" class=\"caret-blink\">_</span></a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if data.SecondaryCTA.Label != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<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(safeHref(data.SecondaryCTA.Href)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 44, Col: 60}
}
_, 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, 16, "\" class=\"cyberpunk-btn-ghost cyberpunk-mono inline-flex items-center gap-2 px-6 py-3 rounded-md font-semibold uppercase tracking-wider\" style=\"border: 1px solid hsl(var(--accent)); color: hsl(var(--accent));\"><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(data.SecondaryCTA.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `hero_glitch.templ`, Line: 48, Col: 37}
}
_, 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, 17, "</span></a>")
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
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

405
manifest.yaml Normal file
View File

@ -0,0 +1,405 @@
# Cyberpunk theme — codeless .bnp manifest (WO-TF-015, Wave A).
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
theme_presets: presets.json
bundled_fonts: fonts.json
master_pages: master_pages.json
required_icon_packs: ["lucide", "simple-icons"]
system_templates:
- key: cyberpunk
title: Cyberpunk
description: "Neon-on-black game-studio / hacker-collective theme — RGB-split glitch, CRT scanlines, HUD brackets, angled clip cuts, and a canvas digital-rain hero."
page_templates:
- system: cyberpunk
key: default
title: Default
description: "Standard dark page with scanline overlay and a sticky terminal-style header"
slots: [header, main, footer]
- system: cyberpunk
key: full-width
title: Full Width
description: "Edge-to-edge dashboard / showcase layout, no max-width"
slots: [header, main, footer]
- system: cyberpunk
key: landing
title: Landing
description: "Canvas digital-rain hero, neon feature grid, glowing CTA, footer"
slots: [header, main, footer]
- system: cyberpunk
key: article
title: Article
description: "Mono-metadata header, prose column, HUD-framed aside"
slots: [header, main, aside, footer]
- system: cyberpunk
key: blog-index
title: Blog Index
description: "Patch-notes feed layout with neon post cards and sidebar"
slots: [header, main, footer]
- system: cyberpunk
key: contact
title: Contact
description: "Terminal contact console — form panel plus HUD contact card"
slots: [header, main, footer]
- system: cyberpunk
key: auth
title: Auth
description: "Centered neon auth console for login / register / reset"
slots: [header, main, footer]
# All 49 mandatory-tier builtin overrides (OVERRIDE-CONTRACT §1), dashed keys.
template_overrides:
# Chrome (4)
- { template: cyberpunk, block: navbar }
- { template: cyberpunk, block: footer }
- { template: cyberpunk, block: utility-bar }
- { template: cyberpunk, block: announcement-bar }
# Marketing (12)
- { template: cyberpunk, block: hero }
- { template: cyberpunk, block: feature-grid }
- { template: cyberpunk, block: rich-section }
- { template: cyberpunk, block: stats }
- { template: cyberpunk, block: testimonial }
- { template: cyberpunk, block: logo-strip }
- { template: cyberpunk, block: pricing }
- { template: cyberpunk, block: team }
- { template: cyberpunk, block: faq }
- { template: cyberpunk, block: timeline }
- { template: cyberpunk, block: cta }
- { template: cyberpunk, block: countdown }
# Primitives (11)
- { template: cyberpunk, block: heading }
- { template: cyberpunk, block: text }
- { template: cyberpunk, block: rich-text }
- { template: cyberpunk, block: image }
- { template: cyberpunk, block: quote }
- { template: cyberpunk, block: divider }
- { template: cyberpunk, block: button }
- { template: cyberpunk, block: card }
- { template: cyberpunk, block: gallery }
- { template: cyberpunk, block: video-embed }
- { template: cyberpunk, block: audio-embed }
# Commerce / local (6)
- { template: cyberpunk, block: menu-list }
- { template: cyberpunk, block: event-list }
- { template: cyberpunk, block: contact-form }
- { template: cyberpunk, block: contact-card }
- { template: cyberpunk, block: hours-location }
- { template: cyberpunk, block: social-links }
# Blog family (12)
- { template: cyberpunk, block: blog-hero }
- { template: cyberpunk, block: featured-posts }
- { template: cyberpunk, block: related-posts }
- { template: cyberpunk, block: popular-posts }
- { template: cyberpunk, block: category-list }
- { template: cyberpunk, block: post-hero }
- { template: cyberpunk, block: post-metadata }
- { template: cyberpunk, block: author-bio }
- { template: cyberpunk, block: author-bio-hero }
- { template: cyberpunk, block: newsletter-signup }
- { template: cyberpunk, block: breadcrumbs }
- { template: cyberpunk, block: sidebar-nav }
# Auth surface (3)
- { template: cyberpunk, block: auth-form }
- { template: cyberpunk, block: password-reset-form }
- { template: cyberpunk, block: auth-status }
# System (1)
- { template: cyberpunk, block: page-suggestions }
email_wrappers:
- cyberpunk
css:
input_css_append: |
/* ============================================================
Cyberpunk — Neon-on-black game-studio / hacker-collective theme
(WO-TF-015). Scanlines / glitch / RGB-split / caret-blink /
neon-edge / HUD-bracket / clip-cut / digital-rain utilities.
All colours consume the host shadcn HSL tokens via hsl(var(--token)).
No hardcoded hex / rgb / named colours.
============================================================ */
/* --- Typography ------------------------------------------- */
/* Families flow through the admin-bound CSS variables; the fallback
leads with the bundled OFL family so the intended look survives
before the admin assigns a slot.
Bundled faces: the host does NOT emit @font-face for bundled_fonts
(LoadedPlugin.BundledFonts is unconsumed dead code), so the theme
declares its own @font-face against the served artifact paths
(/templates/cyberpunk/fonts/...). Weights match fonts.json exactly;
presets set typography.fontHeading/body/mono to
template:cyberpunk:<Family> so --font-heading/body/mono resolve here. */
@font-face { font-family: 'Orbitron'; src: url('/templates/cyberpunk/fonts/orbitron-latin.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: 'Orbitron'; src: url('/templates/cyberpunk/fonts/orbitron-latin.woff2') format('woff2'); font-weight: 700; font-style: normal; font-display: swap; }
@font-face { font-family: 'Orbitron'; src: url('/templates/cyberpunk/fonts/orbitron-latin.woff2') format('woff2'); font-weight: 900; font-style: normal; font-display: swap; }
@font-face { font-family: 'Share Tech Mono'; src: url('/templates/cyberpunk/fonts/share-tech-mono-latin.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Inter'; src: url('/templates/cyberpunk/fonts/inter-latin.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: 'Inter'; src: url('/templates/cyberpunk/fonts/inter-latin.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; }
@font-face { font-family: 'Inter'; src: url('/templates/cyberpunk/fonts/inter-latin.woff2') format('woff2'); font-weight: 600; font-style: normal; font-display: swap; }
@font-face { font-family: 'Inter'; src: url('/templates/cyberpunk/fonts/inter-latin.woff2') format('woff2'); font-weight: 700; font-style: normal; font-display: swap; }
.cyberpunk-body {
font-family: var(--font-body, "Inter", system-ui, -apple-system, "Segoe UI", sans-serif);
}
.cyberpunk-mono,
.cyberpunk-mono code,
pre.cyberpunk-mono,
code.cyberpunk-mono {
font-family: var(--font-mono, "Share Tech Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace);
}
.cyberpunk-display {
font-family: var(--font-heading, "Orbitron", system-ui, -apple-system, "Segoe UI", sans-serif);
letter-spacing: 0.01em;
}
.cyberpunk-prose,
.cyberpunk-prose p,
.cyberpunk-prose li {
font-family: var(--font-body, "Inter", system-ui, sans-serif);
line-height: 1.75;
}
/* Eyebrow / kicker: mono, uppercase, wide-tracked accent readout. Carries
enough on its own that it is never a silent no-op when used alone. */
.cyberpunk-eyebrow {
font-family: var(--font-mono, "Share Tech Mono", ui-monospace, monospace);
text-transform: uppercase;
letter-spacing: 0.2em;
font-size: 0.75rem;
color: hsl(var(--accent));
}
.cyberpunk-prose code {
color: hsl(var(--accent));
background-color: hsl(var(--muted));
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
font-family: var(--font-mono, "Share Tech Mono", ui-monospace, monospace);
font-size: 0.875em;
}
/* --- Scanlines -------------------------------------------- */
.scanlines {
opacity: 0.05;
background-image: linear-gradient(
to bottom,
hsl(var(--foreground)) 0px,
hsl(var(--foreground)) 1px,
transparent 1px,
transparent 3px
);
background-size: 100% 3px;
animation: scanline-drift 6s linear infinite;
}
@keyframes scanline-drift {
0% { background-position: 0 0; }
100% { background-position: 0 3px; }
}
/* --- Glitch (RGB-split text-shadow) on big headings ------- */
.glitch {
position: relative;
text-shadow:
-2px 0 hsl(var(--primary)),
2px 0 hsl(var(--accent));
animation: glitch-x 3.6s steps(1, end) infinite;
}
@keyframes glitch-x {
0%, 92%, 100% {
text-shadow: -2px 0 hsl(var(--primary)), 2px 0 hsl(var(--accent));
}
93% {
text-shadow: -3px 0 hsl(var(--primary)), 3px 0 hsl(var(--accent)), 0 1px hsl(var(--accent));
}
95% {
text-shadow: -1px 0 hsl(var(--accent)), 4px 0 hsl(var(--primary));
}
97% {
text-shadow: -2px 0 hsl(var(--primary)), 2px 0 hsl(var(--accent));
}
}
/* --- RGB-split hover/focus/active on primary buttons ------ */
.rgb-split {
transition: transform 120ms ease, box-shadow 120ms ease;
}
.rgb-split:hover,
.rgb-split:focus-visible {
box-shadow: -2px 0 0 hsl(var(--primary)), 2px 0 0 hsl(var(--accent));
transform: translateY(-1px);
}
.cyberpunk-btn:active,
.rgb-split:active,
button.cyberpunk-btn:active {
box-shadow: -2px 0 0 hsl(var(--primary)), 2px 0 0 hsl(var(--accent));
}
.cyberpunk-btn:focus-visible,
.cyberpunk-btn-ghost:focus-visible,
button:focus-visible,
a:focus-visible {
outline: 2px solid hsl(var(--ring));
outline-offset: 2px;
}
/* --- Caret blink ------------------------------------------ */
.caret-blink {
display: inline-block;
animation: caret 1.05s steps(2, end) infinite;
}
@keyframes caret {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
/* --- Neon edge utilities (magenta / cyan / lime channels) - */
/* --primary is the lead accent, --accent the secondary, --ring
the third neon channel (present in every preset). */
.neon-edge-magenta {
box-shadow: 0 0 0 1px hsl(var(--primary) / 0.4), 0 0 18px hsl(var(--primary) / 0.22);
transition: box-shadow 180ms ease, transform 180ms ease;
}
.neon-edge-magenta:hover,
.neon-edge-magenta:focus-within {
box-shadow: 0 0 0 1px hsl(var(--primary) / 0.85), 0 0 28px hsl(var(--primary) / 0.42);
transform: translateY(-1px);
}
.neon-edge-cyan {
box-shadow: 0 0 0 1px hsl(var(--accent) / 0.4), 0 0 18px hsl(var(--accent) / 0.22);
transition: box-shadow 180ms ease, transform 180ms ease;
}
.neon-edge-cyan:hover,
.neon-edge-cyan:focus-within {
box-shadow: 0 0 0 1px hsl(var(--accent) / 0.85), 0 0 28px hsl(var(--accent) / 0.42);
transform: translateY(-1px);
}
.neon-edge-lime {
box-shadow: 0 0 0 1px hsl(var(--ring) / 0.4), 0 0 18px hsl(var(--ring) / 0.22);
transition: box-shadow 180ms ease, transform 180ms ease;
}
.neon-edge-lime:hover,
.neon-edge-lime:focus-within {
box-shadow: 0 0 0 1px hsl(var(--ring) / 0.85), 0 0 28px hsl(var(--ring) / 0.42);
transform: translateY(-1px);
}
/* --- HUD corner brackets ---------------------------------- */
/* Draw two L-brackets (top-left, bottom-right) around any panel. */
.hud-frame {
position: relative;
}
.hud-frame::before,
.hud-frame::after {
content: "";
position: absolute;
width: 14px;
height: 14px;
pointer-events: none;
z-index: 1;
}
/* Inset 5px so the L-brackets clear any rounded / overflow-hidden panel
corner (e.g. the code_neon figure) instead of being sliced by it. */
.hud-frame::before {
top: 5px;
left: 5px;
border-top: 2px solid hsl(var(--primary));
border-left: 2px solid hsl(var(--primary));
}
.hud-frame::after {
bottom: 5px;
right: 5px;
border-bottom: 2px solid hsl(var(--accent));
border-right: 2px solid hsl(var(--accent));
}
/* --- Angled clip-path cut (arcade panel silhouette) ------- */
.clip-cut {
clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
}
/* --- Status pill / dot pulse ------------------------------ */
.cyberpunk-status-dot {
animation: cyberpunk-pulse 2.2s ease-in-out infinite;
}
@keyframes cyberpunk-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* --- Digital-rain canvas showpiece (hero override only) --- */
/* The <canvas> lazy-inits from the hero's inline script. This block
styles the container + the static no-JS neon fallback layer. */
.cyberpunk-rain-wrap {
position: absolute;
inset: 0;
overflow: hidden;
pointer-events: none;
}
.cyberpunk-rain-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
opacity: 0.5;
}
/* Static fallback: a neon vertical-gradient wash shown until (or instead
of) the canvas. Tokens only. */
.cyberpunk-rain-fallback {
position: absolute;
inset: 0;
background:
radial-gradient(120% 80% at 50% 0%, hsl(var(--primary) / 0.18), transparent 60%),
radial-gradient(120% 80% at 100% 100%, hsl(var(--accent) / 0.16), transparent 55%),
linear-gradient(to bottom, transparent, hsl(var(--background) / 0.6));
}
/* --- Hero heights (bespoke bounded clamp — anti-balloon) -------------- */
/* Responsive grids/columns/spans/splits are plain Tailwind utilities in the
templates (grid-cols-*, columns-*, col-span-*, gap-*) — the host build
scans theme .ninjatpl, so no CSS reimplementation of those.
These hero min-heights stay CSS: a bespoke clamp() that caps a sparse
hero so it never balloons into an empty neon void while a populated hero
still reads full-bleed — not a stock min-h-* utility. */
.cp-hero-tall { min-height: clamp(32rem, 82vh, 50rem); }
.cp-hero-mid { min-height: clamp(28rem, 70vh, 44rem); }
.cp-hero-short { min-height: clamp(20rem, 50vh, 30rem); }
.cp-hero-auto { min-height: 22rem; }
.cp-hero-split { min-height: clamp(20rem, 46vh, 28rem); }
/* --- Reduced motion: kill all motion --------------------- */
@media (prefers-reduced-motion: reduce) {
.glitch,
.caret-blink,
.scanlines,
.cyberpunk-status-dot,
.cyberpunk-rain-canvas {
animation: none !important;
animation-duration: 0.01ms !important;
}
.glitch { text-shadow: none; }
.scanlines { opacity: 0; }
.cyberpunk-rain-canvas { display: none; }
.rgb-split:hover,
.rgb-split:focus-visible { transform: none; }
}
/* --- High-contrast: kill scanlines + rain ---------------- */
@media (prefers-contrast: more) {
.scanlines,
.cyberpunk-rain-canvas { display: none; }
}

22
master_pages.json Normal file
View File

@ -0,0 +1,22 @@
[
{
"key": "cyberpunk:site-master",
"title": "Cyberpunk Site Master",
"page_templates": ["default", "full-width", "landing", "article", "blog-index", "contact"],
"blocks": [
{ "block_key": "navbar", "title": "Terminal Navbar", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "NEON//FORGE" }, "companyName": "Neon Forge", "stickyBehavior": "sticky", "cta": { "text": "Get the SDK", "url": "/contact" } }, "slot": "header", "sort_order": 0 },
{ "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "// page content" }, "slot": "main", "sort_order": 0 },
{ "block_key": "footer", "title": "Grid Footer", "content": { "layout": "columns", "companyName": "Neon Forge", "tagline": "We build games and the tools that ship them.", "copyright": "Neon Forge Collective" }, "slot": "footer", "sort_order": 0 }
]
},
{
"key": "cyberpunk:auth-master",
"title": "Cyberpunk Auth Master",
"page_templates": ["auth"],
"blocks": [
{ "block_key": "navbar", "title": "Terminal Navbar", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "NEON//FORGE" }, "companyName": "Neon Forge" }, "slot": "header", "sort_order": 0 },
{ "block_key": "slot", "title": "Auth Console", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
{ "block_key": "footer", "title": "Grid Footer", "content": { "layout": "minimal", "companyName": "Neon Forge", "tagline": "We build games and the tools that ship them.", "copyright": "Neon Forge Collective" }, "slot": "footer", "sort_order": 0 }
]
}
]

View File

@ -1,43 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// NavbarTerminalMeta defines metadata for the cyberpunk:navbar_terminal block.
var NavbarTerminalMeta = blocks.BlockMeta{
Key: "navbar_terminal",
Title: "Terminal Navbar",
Description: "Mono-spaced navbar with $ command prefix and optional status dot",
Category: blocks.CategoryNavigation,
Source: "cyberpunk",
}
// NavbarTerminalData is the typed view of the navbar_terminal content map.
type NavbarTerminalData struct {
MenuName string
CommandPrefix string
StatusDot bool
}
// NavbarTerminalBlock renders the navbar_terminal block.
//
// Content shape:
// {menuName, commandPrefix, statusDot:"on"|"off"|bool}
//
// menuName is the slug of a CMS-managed menu; the actual <ul> items are
// resolved by the host menu lookup. This block emits the chrome wrapper plus a
// data-menu-name hook the host renderer reads at runtime.
func NavbarTerminalBlock(ctx context.Context, content map[string]any) string {
data := NavbarTerminalData{
MenuName: getStringWithDefault(content, "menuName", "main"),
CommandPrefix: getStringWithDefault(content, "commandPrefix", "~/"),
StatusDot: getBool(content, "statusDot", true),
}
var buf bytes.Buffer
_ = navbarTerminalComponent(data).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,50 +0,0 @@
package main
// navbarTerminalComponent renders the cyberpunk:navbar_terminal block.
// At ≤768px the link list collapses behind an aria-expanded toggle button.
templ navbarTerminalComponent(data NavbarTerminalData) {
<nav
data-block="cyberpunk:navbar_terminal"
class="cyberpunk-navbar w-full"
style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));"
>
<div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between gap-4">
<div class="flex items-center gap-3 min-w-0">
if data.StatusDot {
<span
aria-hidden="true"
class="cyberpunk-status-dot inline-block w-2 h-2 rounded-full"
style="background-color: hsl(var(--accent)); box-shadow: 0 0 8px hsl(var(--accent));"
></span>
}
<span class="cyberpunk-mono text-sm" style="color: hsl(var(--accent));">
{ data.CommandPrefix }
</span>
<span class="cyberpunk-mono text-sm truncate" style="color: hsl(var(--foreground));">
{ data.MenuName }
</span>
<span aria-hidden="true" class="caret-blink cyberpunk-mono text-sm" style="color: hsl(var(--primary));">_</span>
</div>
<button
type="button"
aria-expanded="false"
aria-controls="cyberpunk-navbar-links"
class="cyberpunk-navbar-toggle md:hidden inline-flex items-center justify-center px-3 py-2 rounded-md cyberpunk-mono uppercase text-xs tracking-widest"
style="border: 1px solid hsl(var(--border)); color: hsl(var(--foreground));"
data-cyberpunk-navbar-toggle
>
MENU
</button>
<ul
id="cyberpunk-navbar-links"
class="cyberpunk-navbar-links hidden md:flex items-center gap-6 cyberpunk-mono text-sm uppercase tracking-widest"
style="color: hsl(var(--muted-foreground));"
data-menu-name={ data.MenuName }
>
<li class="cyberpunk-navbar-hint" style="color: hsl(var(--muted-foreground));">
{ "// menu items resolve at runtime" }
</li>
</ul>
</div>
</nav>
}

View File

@ -1,104 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// navbarTerminalComponent renders the cyberpunk:navbar_terminal block.
// At ≤768px the link list collapses behind an aria-expanded toggle button.
func navbarTerminalComponent(data NavbarTerminalData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<nav data-block=\"cyberpunk:navbar_terminal\" class=\"cyberpunk-navbar w-full\" style=\"background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));\"><div class=\"max-w-6xl mx-auto px-4 py-3 flex items-center justify-between gap-4\"><div class=\"flex items-center gap-3 min-w-0\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.StatusDot {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<span aria-hidden=\"true\" class=\"cyberpunk-status-dot inline-block w-2 h-2 rounded-full\" style=\"background-color: hsl(var(--accent)); box-shadow: 0 0 8px hsl(var(--accent));\"></span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<span class=\"cyberpunk-mono text-sm\" style=\"color: hsl(var(--accent));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(data.CommandPrefix)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `navbar_terminal.templ`, Line: 21, Col: 25}
}
_, 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, 4, "</span> <span class=\"cyberpunk-mono text-sm truncate\" style=\"color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.MenuName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `navbar_terminal.templ`, Line: 24, Col: 20}
}
_, 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, "</span> <span aria-hidden=\"true\" class=\"caret-blink cyberpunk-mono text-sm\" style=\"color: hsl(var(--primary));\">_</span></div><button type=\"button\" aria-expanded=\"false\" aria-controls=\"cyberpunk-navbar-links\" class=\"cyberpunk-navbar-toggle md:hidden inline-flex items-center justify-center px-3 py-2 rounded-md cyberpunk-mono uppercase text-xs tracking-widest\" style=\"border: 1px solid hsl(var(--border)); color: hsl(var(--foreground));\" data-cyberpunk-navbar-toggle>MENU</button><ul id=\"cyberpunk-navbar-links\" class=\"cyberpunk-navbar-links hidden md:flex items-center gap-6 cyberpunk-mono text-sm uppercase tracking-widest\" style=\"color: hsl(var(--muted-foreground));\" data-menu-name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.MenuName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `navbar_terminal.templ`, Line: 42, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var4)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"><li class=\"cyberpunk-navbar-hint\" style=\"color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("// menu items resolve at runtime")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `navbar_terminal.templ`, Line: 45, Col: 41}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</li></ul></div></nav>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -2,11 +2,8 @@
name = "cyberpunk" name = "cyberpunk"
display_name = "Cyberpunk" display_name = "Cyberpunk"
scope = "@themes" scope = "@themes"
version = "0.1.1" version = "0.5.0"
description = "Neon-on-black BlockNinja theme with glitch motifs, monospace accents, and magenta/cyan/lime tri-accent palette for SaaS, dev tools, and crypto." description = "Neon-on-black BlockNinja theme with glitch motifs, monospace accents, and magenta/cyan/lime tri-accent palette for SaaS, dev tools, and crypto."
kind = "theme" kind = "theme"
categories = ["templates"] categories = ["templates"]
tags = ["dark", "neon", "glitch", "monospace", "saas", "developer", "tech", "crypto", "fintech"] tags = ["dark", "neon", "glitch", "monospace", "saas", "developer", "tech", "crypto", "fintech"]
[compatibility]
block_core = ">=0.11.0 <0.12.0"

View File

@ -5,6 +5,11 @@
"description": "Magenta-led tri-accent, the Cyberpunk default", "description": "Magenta-led tri-accent, the Cyberpunk default",
"theme": { "theme": {
"mode": "both", "mode": "both",
"typography": {
"fontHeading": "template:cyberpunk:Orbitron",
"fontBody": "template:cyberpunk:Inter",
"fontMono": "template:cyberpunk:Share Tech Mono"
},
"lightColors": { "lightColors": {
"background": "240 12% 97%", "background": "240 12% 97%",
"foreground": "260 25% 10%", "foreground": "260 25% 10%",
@ -55,6 +60,11 @@
"description": "Cyan-led, cooler chrome variant", "description": "Cyan-led, cooler chrome variant",
"theme": { "theme": {
"mode": "both", "mode": "both",
"typography": {
"fontHeading": "template:cyberpunk:Orbitron",
"fontBody": "template:cyberpunk:Inter",
"fontMono": "template:cyberpunk:Share Tech Mono"
},
"lightColors": { "lightColors": {
"background": "200 25% 97%", "background": "200 25% 97%",
"foreground": "220 30% 10%", "foreground": "220 30% 10%",
@ -105,6 +115,11 @@
"description": "Lime-led, we shipped at 3am", "description": "Lime-led, we shipped at 3am",
"theme": { "theme": {
"mode": "both", "mode": "both",
"typography": {
"fontHeading": "template:cyberpunk:Orbitron",
"fontBody": "template:cyberpunk:Inter",
"fontMono": "template:cyberpunk:Share Tech Mono"
},
"lightColors": { "lightColors": {
"background": "100 15% 97%", "background": "100 15% 97%",
"foreground": "120 20% 10%", "foreground": "120 20% 10%",
@ -148,5 +163,115 @@
"ring": "90 100% 55%" "ring": "90 100% 55%"
} }
} }
},
{
"id": "hotline",
"name": "Hotline",
"description": "Sunset pink over teal — vaporwave arcade at dusk",
"theme": {
"mode": "both",
"typography": {
"fontHeading": "template:cyberpunk:Orbitron",
"fontBody": "template:cyberpunk:Inter",
"fontMono": "template:cyberpunk:Share Tech Mono"
},
"lightColors": {
"background": "30 40% 97%",
"foreground": "300 30% 12%",
"card": "30 35% 99%",
"cardForeground": "300 30% 12%",
"popover": "30 35% 99%",
"popoverForeground": "300 30% 12%",
"primary": "330 95% 55%",
"primaryForeground": "0 0% 100%",
"secondary": "30 35% 92%",
"secondaryForeground": "300 30% 12%",
"muted": "30 25% 94%",
"mutedForeground": "300 15% 40%",
"accent": "170 85% 40%",
"accentForeground": "0 0% 100%",
"destructive": "0 90% 55%",
"destructiveForeground": "0 0% 100%",
"border": "30 30% 87%",
"input": "30 30% 87%",
"ring": "330 95% 55%"
},
"darkColors": {
"background": "285 35% 7%",
"foreground": "20 40% 94%",
"card": "285 38% 10%",
"cardForeground": "20 40% 94%",
"popover": "285 38% 10%",
"popoverForeground": "20 40% 94%",
"primary": "330 100% 64%",
"primaryForeground": "285 35% 7%",
"secondary": "285 30% 15%",
"secondaryForeground": "20 40% 94%",
"muted": "285 28% 13%",
"mutedForeground": "20 25% 66%",
"accent": "170 90% 55%",
"accentForeground": "285 35% 7%",
"destructive": "0 95% 62%",
"destructiveForeground": "285 35% 7%",
"border": "300 35% 20%",
"input": "300 35% 18%",
"ring": "330 100% 64%"
}
}
},
{
"id": "ghost-protocol",
"name": "Ghost Protocol",
"description": "Monochrome terminal — white phosphor on black, restrained neon",
"theme": {
"mode": "both",
"typography": {
"fontHeading": "template:cyberpunk:Orbitron",
"fontBody": "template:cyberpunk:Inter",
"fontMono": "template:cyberpunk:Share Tech Mono"
},
"lightColors": {
"background": "0 0% 98%",
"foreground": "0 0% 8%",
"card": "0 0% 100%",
"cardForeground": "0 0% 8%",
"popover": "0 0% 100%",
"popoverForeground": "0 0% 8%",
"primary": "0 0% 12%",
"primaryForeground": "0 0% 98%",
"secondary": "0 0% 92%",
"secondaryForeground": "0 0% 8%",
"muted": "0 0% 94%",
"mutedForeground": "0 0% 38%",
"accent": "160 90% 38%",
"accentForeground": "0 0% 100%",
"destructive": "0 85% 50%",
"destructiveForeground": "0 0% 100%",
"border": "0 0% 86%",
"input": "0 0% 86%",
"ring": "160 90% 38%"
},
"darkColors": {
"background": "0 0% 4%",
"foreground": "150 15% 92%",
"card": "0 0% 7%",
"cardForeground": "150 15% 92%",
"popover": "0 0% 7%",
"popoverForeground": "150 15% 92%",
"primary": "150 90% 60%",
"primaryForeground": "0 0% 4%",
"secondary": "0 0% 12%",
"secondaryForeground": "150 15% 92%",
"muted": "0 0% 10%",
"mutedForeground": "150 8% 62%",
"accent": "150 100% 66%",
"accentForeground": "0 0% 4%",
"destructive": "0 95% 62%",
"destructiveForeground": "0 0% 4%",
"border": "150 15% 16%",
"input": "150 15% 14%",
"ring": "150 90% 60%"
}
}
} }
] ]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -1,190 +0,0 @@
package main
import (
"context"
"github.com/a-h/templ"
"git.dev.alexdunmow.com/block/core/blocks"
"git.dev.alexdunmow.com/block/core/plugin"
"git.dev.alexdunmow.com/block/core/templates"
)
// wrap adapts a templ-returning render function to templates.TemplateFunc.
// templ.Component already implements templates.HTMLComponent via Render.
func wrap(f func(ctx context.Context, doc map[string]any) templ.Component) templates.TemplateFunc {
return func(ctx context.Context, doc map[string]any) templates.HTMLComponent {
return f(ctx, doc)
}
}
// Register is the plugin entry point that registers the Cyberpunk system template,
// page templates, theme-owned blocks, built-in overrides, and the email wrapper.
func Register(tr templates.TemplateRegistry, br blocks.BlockRegistry) error {
// 1. System template metadata.
tr.RegisterSystemTemplate(templates.SystemTemplateMeta{
Key: "cyberpunk",
Title: "Cyberpunk",
Description: "Neon-on-black BlockNinja theme with glitch motifs, monospace accents, and magenta/cyan/lime tri-accent palette for SaaS, dev tools, and crypto.",
})
// 2. Page templates — slot lists match docs/works/cyberpunk.md byte-for-byte.
if err := tr.RegisterPageTemplate("cyberpunk", templates.PageTemplateMeta{
Key: "default",
Title: "Default",
Description: "Standard dark page with scanline overlay, sticky terminal-style header",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderCyberpunk)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("cyberpunk", templates.PageTemplateMeta{
Key: "landing",
Title: "Landing",
Description: "Hero with glitch H1, neon CTA, feature grid, social proof, footer",
Slots: []string{"hero", "features", "cta", "footer"},
}, wrap(RenderCyberpunkLanding)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("cyberpunk", templates.PageTemplateMeta{
Key: "article",
Title: "Article",
Description: "Mono-metadata header, prose column, code-friendly aside",
Slots: []string{"header", "main", "aside", "footer"},
}, wrap(RenderCyberpunkArticle)); err != nil {
return err
}
if err := tr.RegisterPageTemplate("cyberpunk", templates.PageTemplateMeta{
Key: "full-width",
Title: "Full Width",
Description: "Edge-to-edge dashboard / showcase layout, no max-width",
Slots: []string{"header", "main", "footer"},
}, wrap(RenderCyberpunkFullWidth)); err != nil {
return err
}
// 3. Load block schemas BEFORE registering any blocks.
if err := br.LoadSchemasFromFS(Schemas()); err != nil {
return err
}
// 4. Theme-owned blocks (seven, per spec section "Blocks to build").
br.Register(HeroGlitchMeta, HeroGlitchBlock)
br.Register(CTATerminalMeta, CTATerminalBlock)
br.Register(NavbarTerminalMeta, NavbarTerminalBlock)
br.Register(FooterGridMeta, FooterGridBlock)
br.Register(FeatureCardNeonMeta, FeatureCardNeonBlock)
br.Register(StatsGlowMeta, StatsGlowBlock)
br.Register(CodeNeonMeta, CodeNeonBlock)
// 5. Built-in block overrides — applied only when this theme is active.
br.RegisterTemplateOverride("cyberpunk", "heading", CyberpunkHeadingBlock)
br.RegisterTemplateOverride("cyberpunk", "text", CyberpunkTextBlock)
br.RegisterTemplateOverride("cyberpunk", "button", CyberpunkButtonBlock)
br.RegisterTemplateOverride("cyberpunk", "card", CyberpunkCardBlock)
// 6. Email wrapper.
tr.RegisterEmailWrapper("cyberpunk", CyberpunkEmailWrapper)
return nil
}
// DefaultMasterPages returns the three master pages Cyberpunk seeds on first load.
// Spec table — section "Master pages" — drives keys, blocks, slots and sort orders.
func DefaultMasterPages() []plugin.MasterPageDefinition {
return []plugin.MasterPageDefinition{
{
Key: "cyberpunk:default-master",
Title: "Cyberpunk Default Master",
PageTemplates: []string{"default", "article"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "cyberpunk:navbar_terminal",
Title: "Top Nav",
Content: map[string]any{"menuName": "main", "commandPrefix": "~/"},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{"slotName": "main", "placeholder": "// content"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "cyberpunk:footer_grid",
Title: "Site Footer",
Content: map[string]any{"showStatus": true, "buildHash": "auto"},
Slot: "footer",
SortOrder: 0,
},
},
},
{
Key: "cyberpunk:landing-master",
Title: "Cyberpunk Landing Master",
PageTemplates: []string{"landing"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "cyberpunk:hero_glitch",
Title: "Glitch Hero",
Content: map[string]any{"eyebrow": "NEW", "headline": "Ship like it's 2049", "cta": map[string]any{"label": "Get the SDK", "href": "#"}},
Slot: "hero",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Features Slot",
Content: map[string]any{"slotName": "features"},
Slot: "features",
SortOrder: 0,
},
{
BlockKey: "cyberpunk:cta_terminal",
Title: "CTA Strip",
Content: map[string]any{"prompt": "$ npm i your-thing", "button": "Copy"},
Slot: "cta",
SortOrder: 0,
},
{
BlockKey: "cyberpunk:footer_grid",
Title: "Site Footer",
Content: map[string]any{"showStatus": true},
Slot: "footer",
SortOrder: 0,
},
},
},
{
Key: "cyberpunk:full-master",
Title: "Cyberpunk Full Master",
PageTemplates: []string{"full-width"},
Blocks: []plugin.MasterPageBlock{
{
BlockKey: "cyberpunk:navbar_terminal",
Title: "Top Nav",
Content: map[string]any{"menuName": "main"},
Slot: "header",
SortOrder: 0,
},
{
BlockKey: "slot",
Title: "Main Slot",
Content: map[string]any{"slotName": "main"},
Slot: "main",
SortOrder: 0,
},
{
BlockKey: "cyberpunk:footer_grid",
Title: "Site Footer",
Content: map[string]any{"showStatus": false},
Slot: "footer",
SortOrder: 0,
},
},
},
}
}

View File

@ -1,26 +0,0 @@
package main
import (
"io/fs"
"net/http"
"git.dev.alexdunmow.com/block/core/blocks"
"git.dev.alexdunmow.com/block/core/plugin"
"git.dev.alexdunmow.com/block/core/templates"
)
// Registration is the compile-time plugin registration for the Cyberpunk theme.
var Registration = plugin.PluginRegistration{
Name: "cyberpunk",
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() },
RequiredIconPacks: plugin.ParseRequiredIconPacks(pluginModBytes),
}

View File

@ -1,34 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Terminal CTA",
"description": "Faux $ prompt strip with blinking caret and optional copy-to-clipboard.",
"type": "object",
"properties": {
"prompt": {
"type": "string",
"title": "Prompt",
"description": "The command text following the $ glyph (e.g. npm i your-thing)",
"x-editor": "text"
},
"button": {
"type": "string",
"title": "Button label",
"description": "Label for the action button (e.g. Copy)",
"x-editor": "text"
},
"href": {
"type": "string",
"title": "Button URL",
"description": "Optional URL the button points at when copy is off",
"x-editor": "link"
},
"copyable": {
"type": "string",
"title": "Copy to clipboard",
"description": "When 'on' the button copies the prompt text instead of navigating",
"x-editor": "select",
"enum": ["on", "off"],
"default": "on"
}
}
}

View File

@ -1,34 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Neon Feature Card",
"description": "Single feature card with icon, title, body and per-card neon edge glow.",
"type": "object",
"properties": {
"icon": {
"type": "string",
"title": "Icon",
"description": "Media reference for the feature icon",
"x-editor": "media"
},
"title": {
"type": "string",
"title": "Title",
"description": "Card heading",
"x-editor": "text"
},
"body": {
"type": "string",
"title": "Body",
"description": "Supporting rich text",
"x-editor": "richtext"
},
"accent": {
"type": "string",
"title": "Accent",
"description": "Neon edge colour: magenta, cyan or lime",
"x-editor": "select",
"enum": ["magenta", "cyan", "lime"],
"default": "magenta"
}
}
}

View File

@ -1,52 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Grid Footer",
"description": "Multi-column footer with mono column titles, optional status pill, and a build-hash readout.",
"type": "object",
"properties": {
"showStatus": {
"type": "string",
"title": "Show status pill",
"description": "Display the OK/uptime status pill at the bottom right",
"x-editor": "select",
"enum": ["on", "off"],
"default": "on"
},
"buildHash": {
"type": "string",
"title": "Build hash",
"description": "Short build hash or version readout (e.g. 'a1b2c3' or 'auto')",
"x-editor": "text"
},
"columns": {
"type": "array",
"title": "Columns",
"description": "Footer columns with links",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Column title",
"x-editor": "text"
},
"links": {
"type": "array",
"title": "Links",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"label": { "type": "string", "title": "Label", "x-editor": "text" },
"href": { "type": "string", "title": "Href", "x-editor": "text" }
}
}
}
}
}
}
}
}

View File

@ -1,54 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Glitch Hero",
"description": "Cyberpunk hero section with RGB-split H1, eyebrow, sub, primary + secondary CTAs and an optional scanline overlay.",
"type": "object",
"properties": {
"eyebrow": {
"type": "string",
"title": "Eyebrow",
"description": "Short mono caps label above the headline (e.g. NEW)",
"x-editor": "text"
},
"headline": {
"type": "string",
"title": "Headline",
"description": "Main H1; rendered with RGB-split glitch text-shadow",
"x-editor": "text"
},
"sub": {
"type": "string",
"title": "Sub",
"description": "Supporting paragraph beneath the headline",
"x-editor": "textarea"
},
"cta": {
"type": "object",
"title": "Primary CTA",
"description": "Primary call to action {label, href}",
"x-editor": "link",
"properties": {
"label": { "type": "string", "title": "Label", "x-editor": "text" },
"href": { "type": "string", "title": "Href", "x-editor": "text" }
}
},
"secondaryCta": {
"type": "object",
"title": "Secondary CTA",
"description": "Optional secondary call to action {label, href}",
"x-editor": "link",
"properties": {
"label": { "type": "string", "title": "Label", "x-editor": "text" },
"href": { "type": "string", "title": "Href", "x-editor": "text" }
}
},
"scanlines": {
"type": "string",
"title": "Scanlines",
"description": "Toggle the 4% scanline overlay inside the hero section",
"x-editor": "select",
"enum": ["on", "off"],
"default": "on"
}
}
}

View File

@ -1,29 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Terminal Navbar",
"description": "Replaces the built-in navbar with a mono-spaced $ prompt navbar, optional status dot, and a tap-friendly mobile menu toggle.",
"type": "object",
"properties": {
"menuName": {
"type": "string",
"title": "Menu",
"description": "Slug of the CMS menu to render (e.g. 'main')",
"x-editor": "menu-select"
},
"commandPrefix": {
"type": "string",
"title": "Command prefix",
"description": "Leading prompt characters (default '~/')",
"x-editor": "text",
"default": "~/"
},
"statusDot": {
"type": "string",
"title": "Status dot",
"description": "Show a glowing status indicator (uptime hint)",
"x-editor": "select",
"enum": ["on", "off"],
"default": "on"
}
}
}

View File

@ -1,38 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Glow Stats",
"description": "Big mono numerals with accent underline and short labels.",
"type": "object",
"properties": {
"items": {
"type": "array",
"title": "Items",
"description": "Stats to display",
"default": [],
"x-editor": "collection",
"items": {
"type": "object",
"properties": {
"value": {
"type": "string",
"title": "Value",
"description": "Big numeric or string value (e.g. 99.99)",
"x-editor": "text"
},
"label": {
"type": "string",
"title": "Label",
"description": "Short uppercase caption beneath the value",
"x-editor": "text"
},
"suffix": {
"type": "string",
"title": "Suffix",
"description": "Trailing unit or symbol (e.g. % or ms), accent-coloured",
"x-editor": "text"
}
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 997 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

219
seed/seed.json Normal file
View File

@ -0,0 +1,219 @@
{
"demo": true,
"settings": {
"merge": {
"companyName": "Neon Forge",
"tagline": "We build games and the tools that ship them."
}
},
"data_tables": [
{
"key": "contact_submissions",
"name": "Contact Enquiries",
"description": "Seeded contact-form submissions for the Neon Forge demo. A submission becomes a row here and emails the site admins.",
"columns": [
{ "key": "name", "label": "Name", "type": "text", "required": true },
{ "key": "email", "label": "Email", "type": "email", "required": true },
{ "key": "studio", "label": "Studio", "type": "text", "required": false },
{ "key": "message", "label": "Message", "type": "text", "required": true }
]
}
],
"pages": [
{
"slug": "/",
"title": "Neon Forge",
"template_key": "landing",
"blocks": [
{
"block_key": "hero",
"title": "Hero",
"slot": "main",
"sort_order": 1,
"content": {
"template": "centered",
"minHeight": "screen",
"eyebrow": "Build 2.049",
"headline": "Ship the impossible",
"subheadline": "A game studio and the tools that ship it.",
"description": "We build a rendering engine, live ops, and a build farm that never sleeps. Then we make games on top of it.",
"ctaPrimary": { "text": "Get the SDK", "url": "/contact" },
"ctaSecondary": { "text": "Read the devlog", "url": "/blog" }
}
},
{
"block_key": "feature-grid",
"title": "What we ship",
"slot": "main",
"sort_order": 2,
"content": {
"eyebrow": "The stack",
"heading": "One pipeline, end to end",
"intro": "Engine, tooling, and live service. Built in house, shipped together.",
"layout": "card",
"columns": 3,
"items": [
{ "icon": "lucide:cpu", "title": "Render engine", "text": "Neon lighting, volumetric fog, and a deferred path tuned for night maps." },
{ "icon": "lucide:terminal", "title": "Build farm", "text": "Shader caching across branches. Green builds in minutes, not hours." },
{ "icon": "lucide:radio", "title": "Live ops", "text": "Ship events, patch balance, and roll back without a redeploy." }
]
}
},
{
"block_key": "cyberpunk:roster_grid",
"title": "The crew",
"slot": "main",
"sort_order": 3,
"content": {
"eyebrow": "The crew",
"heading": "Who ships this",
"intro": "A small team that builds the engine, the game, and the pipeline in between.",
"members": [
{ "name": "Nova Reyes", "handle": "@nova", "role": "Engine lead", "status": true },
{ "name": "Kai Okafor", "handle": "@kaisdf", "role": "Graphics", "status": true },
{ "name": "Sable Lin", "handle": "@sable", "role": "Live ops", "status": false },
{ "name": "Juno Vale", "handle": "@juno", "role": "Gameplay", "status": true },
{ "name": "Rin Castellano", "handle": "@rin", "role": "Tools", "status": true },
{ "name": "Ash Mori", "handle": "@ash", "role": "Netcode", "status": false }
]
}
},
{
"block_key": "cyberpunk:patch_notes",
"title": "Patch notes",
"slot": "main",
"sort_order": 4,
"content": {
"heading": "Patch notes",
"intro": "What changed, build by build.",
"entries": [
{ "version": "v2.049.0", "date": "2049.04.01", "tag": "FEATURE", "items": [ { "text": "Volumetric neon fog on all night maps." }, { "text": "Build farm caches shaders across branches." } ] },
{ "version": "v2.048.3", "date": "2049.03.18", "tag": "FIX", "items": [ { "text": "Fixed a stall when hot-reloading materials." }, { "text": "Reduced desync on high-latency links." } ] },
{ "version": "v2.048.0", "date": "2049.03.02", "tag": "BALANCE", "items": [ { "text": "Tuned dash cooldown from 4s to 3.2s." } ] }
]
}
},
{
"block_key": "cta",
"title": "CTA",
"slot": "main",
"sort_order": 5,
"content": {
"background": "band",
"heading": "Start building tonight",
"text": "Grab the SDK and ship your first build before the coffee runs out.",
"primaryLabel": "Get the SDK",
"primaryUrl": "/contact"
}
}
]
},
{
"slug": "/about",
"title": "The studio",
"template_key": "default",
"blocks": [
{ "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "The studio", "level": 1 } },
{ "block_key": "rich-text", "title": "Story", "slot": "main", "sort_order": 2, "content": { "html": "<p>Neon Forge started as a build tool for a game that shipped two years late. The game came and went. The tool outlived it. We kept sharpening it in the dark between projects, then built more games to prove it could take the punishment.</p><p>Now we run both halves at once. The engine and the live service on one side, the games that stress-test them on the other. Nothing ships to a player before it has shipped to us first, on the same pipeline, under the same load.</p><p>We are a small crew by design. Fourteen people who would rather own the whole stack than manage a hundred who own slices of it. Everyone here writes code that runs in production, and everyone here has been paged at 3am by something they wrote. That keeps us honest.</p><p>The look of this place is not an accident either. Neon on black, mono readouts, a little glitch in the type. It is how our tools have always looked on the wall of screens in the build room, and we never saw a reason to dress it up for the website.</p>" } },
{ "block_key": "cyberpunk:code_neon", "title": "Quickstart", "slot": "main", "sort_order": 3, "content": { "language": "bash", "code": "npm install @neon-forge/sdk\nnpx neon-forge init my-game\nnpx neon-forge ship --stage prod", "copy": "on" } },
{ "block_key": "divider", "title": "Divider", "slot": "main", "sort_order": 4, "content": { "variant": "line", "weight": "medium" } },
{ "block_key": "quote", "title": "Quote", "slot": "main", "sort_order": 5, "content": { "style": "pull", "quote": "Build the tool. Ship the game. Run both in production before you trust either.", "attribution": "Nova Reyes", "role": "Engine lead, Neon Forge" } }
]
},
{
"slug": "/blog",
"title": "Devlog",
"template_key": "blog-index",
"blocks": [
{ "block_key": "card", "title": "Post: Rain", "slot": "main", "sort_order": 1, "content": { "layout": "vertical", "title": "Making it rain", "text": "<p>How we built a dependency-free digital-rain effect that stays smooth at 60 fps.</p>", "link": "/blog/making-it-rain", "linkLabel": "Read on" } },
{ "block_key": "card", "title": "Post: Shaders", "slot": "main", "sort_order": 2, "content": { "layout": "vertical", "title": "Caching shaders across branches", "text": "<p>Green builds in minutes. What we changed in the build farm and why.</p>", "link": "/blog/caching-shaders", "linkLabel": "Read on" } },
{ "block_key": "card", "title": "Post: Netcode", "slot": "main", "sort_order": 3, "content": { "layout": "vertical", "title": "Netcode on bad links", "text": "<p>Cutting desync on 200ms connections without rewriting the sim.</p>", "link": "/blog/netcode-bad-links", "linkLabel": "Read on" } }
]
},
{
"slug": "/blog/making-it-rain",
"title": "Making it rain",
"template_key": "article",
"parent_slug": "/blog",
"blocks": [
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Making it rain", "level": 1 } },
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "<p>The hero on our home page runs a digital-rain canvas. No libraries, no sprite sheets, no video. Just a few dozen lines that draw falling glyphs onto a single <code>&lt;canvas&gt;</code> and never touch the network again.</p><p>We wanted the front door of the studio to feel like the wall of screens in our build room: green-on-black readouts, columns of characters sliding down, the faint sense that something is compiling behind the glass. A GIF would have looked dead. A video would have cost a megabyte and stuttered on the first scroll. So we drew it live.</p><p>The whole effect is one idea repeated: divide the width into columns, give each column a <em>drop</em> that falls one row per tick, and paint a random glyph wherever that drop currently sits. When a drop runs off the bottom, roll a die and occasionally send it back to the top. That randomness is what stops the rain from marching in lockstep.</p><p>The trick that makes it read as rain instead of confetti is the fade. Every frame we do not clear the canvas. We paint a translucent rectangle of the background colour over the whole thing, just barely opaque, then draw the new glyphs on top. Yesterday's characters do not vanish; they dim, one step per frame, leaving a comet tail behind each drop. Clear the canvas outright and you get sharp dots with no soul. Fade it and you get rain.</p><p>Colour comes from the theme, not from a hex code baked into the script. On boot we read the CSS custom properties off the document root, the primary and accent channels, and pick one of them at random per glyph. Retheme the site and the rain retints itself for free. That single decision is why the effect survives every palette we throw at it.</p><p>Performance was the part we actually sweated. A canvas that repaints as fast as the browser allows will happily cook a laptop fan for a background nobody is looking at. So we cap the loop at roughly eighteen frames a second. Rain does not need sixty. The <code>requestAnimationFrame</code> callback fires as often as the display wants, but we bail early unless enough milliseconds have passed since the last real paint. Half the frames do nothing but check a clock.</p><p>We also refused to let it run when it should not. The canvas only wakes up once it scrolls into view, wired through an <code>IntersectionObserver</code>, so a page that never shows the hero never spends a cycle on it. If the visitor has asked for reduced motion, we skip the animation entirely and let a static neon gradient stand in. The rain is a flourish, never a requirement.</p><p>Sizing is handled the boring, correct way. A <code>ResizeObserver</code> watches the hero and rebuilds the column list whenever the box changes, multiplying the backing store by the device pixel ratio so the glyphs stay crisp on retina panels without rendering four times the pixels on a phone. Resize the window and the rain reflows mid-fall.</p><p>None of this is clever, and that is the point. The failure mode of a signature animation is a dependency you have to keep patched and a bundle that grows every time someone adds a feature. This one has no dependencies, ships as inline script inside the theme, and has not changed since the day it worked. It will still run when the framework it was born next to is three major versions gone.</p><p>If you are building your own, start with the fade and the frame cap. Everything else is decoration. Get a single column falling with a trailing tail at a sane frame rate, and you already have the effect. The rest is just more columns.</p>" } }
]
},
{
"slug": "/blog/caching-shaders",
"title": "Caching shaders across branches",
"template_key": "article",
"parent_slug": "/blog",
"blocks": [
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Caching shaders across branches", "level": 1 } },
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "<p>Every branch used to recompile the whole shader set from scratch. That is minutes per build, times every push, times every engineer. On a busy day the build farm spent more time on shaders nobody had touched than on the code that changed.</p><p>The insight was embarrassingly simple once we saw it. A shader does not know what branch it lives on. If the source bytes are identical, the compiled output is identical. So we keyed the cache on a hash of the shader source and its compile flags, not on the branch name.</p><p>Now two branches that share a shader share the compile. The first branch to build a given shader pays for it; everyone after pulls the artifact from the cache in milliseconds. A feature branch that only touches gameplay code inherits every shader the mainline already compiled.</p><p>Green builds went from tens of minutes back down to single digits. The farm stopped melting under merge storms. Nobody has thought about shader compile times since, which is the highest compliment we can pay a piece of infrastructure.</p>" } }
]
},
{
"slug": "/blog/netcode-bad-links",
"title": "Netcode on bad links",
"template_key": "article",
"parent_slug": "/blog",
"blocks": [
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Netcode on bad links", "level": 1 } },
{ "block_key": "rich-text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "html": "<p>Players on slow connections kept desyncing, and the complaints all rhymed: the world would freeze for a beat, then snap somewhere they did not expect. Rewriting the simulation to be network-native was a six-month job we did not have. We needed a fix that lived on top of the sim we already trusted.</p><p>So we added client prediction with a short rollback window. The client stops waiting for the server to bless every input and starts guessing. Press dash, and you dash now, locally, immediately. The input also goes to the server, which is still the only authority that counts.</p><p>When the server's version of events comes back, the client compares it against what it predicted. Most of the time they agree and nothing happens. When they disagree, the client quietly rewinds the last few frames, replays them with the corrected state, and rolls forward again, all inside a window short enough that the eye never catches the seam.</p><p>The result is a game that feels instant on a bad link and stays authoritative on the server. Desync on two-hundred-millisecond connections dropped to something players stopped reporting. The simulation never changed. We just stopped making it wait.</p>" } }
]
},
{
"slug": "/contact",
"title": "Contact",
"template_key": "contact",
"blocks": [
{
"block_key": "contact-form",
"title": "Contact form",
"slot": "main",
"sort_order": 1,
"content": {
"heading": "Request access",
"description": "Tell us what you are building. We reply within one business day.",
"submitLabel": "Request access",
"successMessage": "Message received. We will reply within one business day.",
"fields": [
{ "name": "name", "label": "Your name", "type": "text", "required": true, "width": "half" },
{ "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" },
{ "name": "studio", "label": "Studio", "type": "text", "required": false, "width": "full" },
{ "name": "message", "label": "What are you building", "type": "textarea", "required": true, "width": "full" }
],
"formConfig": { "enabled": true, "captchaEnabled": false, "targetTable": "contact_submissions" }
}
},
{
"block_key": "contact-card",
"title": "Reach us",
"slot": "main",
"sort_order": 2,
"content": {
"heading": "Other ways in",
"columns": 3,
"channels": [
{ "icon": "lucide:mail", "label": "Email", "value": "crew@neonforge.dev", "link": "mailto:crew@neonforge.dev" },
{ "icon": "simple-icons:github", "label": "Source", "value": "github.com/neon-forge", "link": "https://github.com/neon-forge" },
{ "icon": "lucide:message-square", "label": "Discord", "value": "Join the build channel", "link": "#" }
]
}
}
]
},
{
"slug": "/login",
"title": "Sign in",
"template_key": "auth",
"blocks": [
{ "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } }
]
}
],
"menu_items": [
{ "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 },
{ "menu": "main", "label": "Studio", "page_slug": "/about", "sort_order": 2 },
{ "menu": "main", "label": "Devlog", "page_slug": "/blog", "sort_order": 3 },
{ "menu": "main", "label": "Contact", "page_slug": "/contact", "sort_order": 4 }
]
}

19
seed/workflows.json Normal file
View File

@ -0,0 +1,19 @@
{
"workflows": [
{
"key": "contact_notify_admins",
"name": "Contact enquiry to admins",
"description": "On a contact-form submission, email the site admins.",
"enabled": true,
"trigger": { "type": "row_inserted", "table": "contact_submissions" },
"steps": [
{
"type": "email",
"name": "Email admins",
"template": "Form Submission Notification",
"recipients": { "mode": "admins", "scope": "all" }
}
]
}
]
}

View File

@ -1,43 +0,0 @@
package main
import (
"bytes"
"context"
"git.dev.alexdunmow.com/block/core/blocks"
)
// StatsGlowMeta defines metadata for the cyberpunk:stats_glow block.
var StatsGlowMeta = blocks.BlockMeta{
Key: "stats_glow",
Title: "Glow Stats",
Description: "Big mono numerals with neon accent underline",
Category: "display",
Source: "cyberpunk",
}
// StatGlowItem is one stat in the grid.
type StatGlowItem struct {
Value string
Label string
Suffix string
}
// StatsGlowBlock renders the stats_glow block.
//
// Content shape:
// {items:[{value, label, suffix}]}
func StatsGlowBlock(ctx context.Context, content map[string]any) string {
raw := getSlice(content, "items")
items := make([]StatGlowItem, 0, len(raw))
for _, item := range raw {
items = append(items, StatGlowItem{
Value: getString(item, "value"),
Label: getString(item, "label"),
Suffix: getString(item, "suffix"),
})
}
var buf bytes.Buffer
_ = statsGlowComponent(items).Render(ctx, &buf)
return buf.String()
}

View File

@ -1,52 +0,0 @@
package main
// statsGridColumns returns the tailwind grid-column class for n stats.
func statsGridColumns(n int) string {
switch n {
case 0, 1:
return "grid-cols-1"
case 2:
return "grid-cols-1 sm:grid-cols-2"
case 3:
return "grid-cols-1 sm:grid-cols-3"
default:
return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
}
}
// statsGlowComponent renders the cyberpunk:stats_glow block.
templ statsGlowComponent(items []StatGlowItem) {
<section
data-block="cyberpunk:stats_glow"
class="cyberpunk-stats w-full py-16"
style="background-color: hsl(var(--background));"
>
<div class="max-w-6xl mx-auto px-4">
if len(items) == 0 {
<div class="text-center cyberpunk-mono text-xs uppercase tracking-widest" style="color: hsl(var(--muted-foreground));">
{ "// add items to render stats" }
</div>
} else {
<div class={ "grid gap-8 text-center", statsGridColumns(len(items)) }>
for _, stat := range items {
<div class="cyberpunk-stat-cell">
<div
class="cyberpunk-mono cyberpunk-stat-number text-5xl md:text-6xl font-bold tracking-tight"
style="color: hsl(var(--foreground)); font-family: var(--font-mono, 'JetBrains Mono', ui-monospace, monospace);"
>
<span>{ stat.Value }</span>
if stat.Suffix != "" {
<span style="color: hsl(var(--primary));">{ stat.Suffix }</span>
}
</div>
<div class="cyberpunk-stat-underline mx-auto mt-2 mb-3 h-px w-12" style="background-color: hsl(var(--accent));"></div>
<div class="cyberpunk-mono cyberpunk-stat-label text-xs uppercase tracking-widest" style="color: hsl(var(--muted-foreground));">
{ stat.Label }
</div>
</div>
}
</div>
}
</div>
</section>
}

View File

@ -1,160 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// statsGridColumns returns the tailwind grid-column class for n stats.
func statsGridColumns(n int) string {
switch n {
case 0, 1:
return "grid-cols-1"
case 2:
return "grid-cols-1 sm:grid-cols-2"
case 3:
return "grid-cols-1 sm:grid-cols-3"
default:
return "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
}
}
// statsGlowComponent renders the cyberpunk:stats_glow block.
func statsGlowComponent(items []StatGlowItem) 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=\"cyberpunk:stats_glow\" class=\"cyberpunk-stats w-full py-16\" style=\"background-color: hsl(var(--background));\"><div class=\"max-w-6xl mx-auto px-4\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if len(items) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"text-center cyberpunk-mono text-xs uppercase tracking-widest\" style=\"color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs("// add items to render stats")
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats_glow.templ`, Line: 27, Col: 37}
}
_, 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
}
} else {
var templ_7745c5c3_Var3 = []any{"grid gap-8 text-center", statsGridColumns(len(items))}
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, "<div 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: `stats_glow.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, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, stat := range items {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"cyberpunk-stat-cell\"><div class=\"cyberpunk-mono cyberpunk-stat-number text-5xl md:text-6xl font-bold tracking-tight\" style=\"color: hsl(var(--foreground)); font-family: var(--font-mono, 'JetBrains Mono', ui-monospace, monospace);\"><span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats_glow.templ`, Line: 37, Col: 26}
}
_, 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
}
if stat.Suffix != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<span style=\"color: hsl(var(--primary));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Suffix)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats_glow.templ`, Line: 39, Col: 64}
}
_, 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, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</div><div class=\"cyberpunk-stat-underline mx-auto mt-2 mb-3 h-px w-12\" style=\"background-color: hsl(var(--accent));\"></div><div class=\"cyberpunk-mono cyberpunk-stat-label text-xs uppercase tracking-widest\" style=\"color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(stat.Label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `stats_glow.templ`, Line: 44, Col: 20}
}
_, 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, "</div></div>")
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></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

View File

@ -1,237 +0,0 @@
package main
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PageData carries the rendered slots and assorted page chrome data passed
// from the CMS into a Cyberpunk page template.
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
}
// parseCyberpunkPageData converts the loose doc map the renderer is handed into a typed PageData.
func parseCyberpunkPageData(doc map[string]any) PageData {
title := "Untitled"
if t, ok := doc["title"].(string); ok && t != "" {
title = t
}
slots := map[string]string{}
if s, ok := doc["slots"].(map[string]string); ok {
slots = s
}
themeCSS, _ := doc["theme_css"].(string)
structuredData, _ := doc["structured_data"].(string)
cssHash, _ := doc["css_hash"].(string)
pageviewNonce, _ := doc["pageview_nonce"].(string)
themeMode := "dark"
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
themeMode = tm
}
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: bn.ParseSiteSettings(doc),
PageMeta: bn.ParsePageMeta(doc),
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: bn.ParseEngagementConfig(doc),
}
}
// scanlineOverlay renders the global scanline overlay element (fixed, pointer-events: none).
templ scanlineOverlay() {
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
}
// Cyberpunk renders the default page template.
templ Cyberpunk(data PageData) {
<!DOCTYPE html>
<html lang="en" class="dark">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
@scanlineOverlay()
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">
@templ.Raw(data.Slots["header"])
</header>
<main class="flex-grow w-full max-w-6xl mx-auto px-4 py-12">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="py-20 text-center cyberpunk-empty" style="color: hsl(var(--muted-foreground));">
<p class="cyberpunk-mono">No content blocks assigned to this page.</p>
</div>
}
</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// CyberpunkLanding renders the marketing landing template with hero/features/cta/footer slots.
templ CyberpunkLanding(data PageData) {
<!DOCTYPE html>
<html lang="en" class="dark">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
@scanlineOverlay()
<section class="w-full">
@templ.Raw(data.Slots["hero"])
</section>
<section class="w-full max-w-6xl mx-auto px-4 py-16">
@templ.Raw(data.Slots["features"])
</section>
<section class="w-full">
@templ.Raw(data.Slots["cta"])
</section>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// CyberpunkArticle renders the article template with mono-metadata header and a code-friendly aside.
templ CyberpunkArticle(data PageData) {
<!DOCTYPE html>
<html lang="en" class="dark">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
@scanlineOverlay()
<header class="w-full" style="border-bottom: 1px solid hsl(var(--border));">
<div class="max-w-4xl mx-auto px-4 py-6 cyberpunk-mono text-xs uppercase tracking-widest">
@templ.Raw(data.Slots["header"])
</div>
</header>
<main class="flex-grow w-full max-w-4xl mx-auto px-4 py-12 grid gap-10 lg:grid-cols-[1fr_240px]">
<article class="cyberpunk-prose prose prose-invert max-w-none">
@templ.Raw(data.Slots["main"])
</article>
<aside class="cyberpunk-aside cyberpunk-mono text-sm" style="color: hsl(var(--muted-foreground));">
@templ.Raw(data.Slots["aside"])
</aside>
</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// CyberpunkFullWidth renders the edge-to-edge template (no max-width clamping on main).
templ CyberpunkFullWidth(data PageData) {
<!DOCTYPE html>
<html lang="en" class="dark">
@bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
})
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground));">
@bn.AdminBypassBanner(data.SiteSettings)
@scanlineOverlay()
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">
@templ.Raw(data.Slots["header"])
</header>
<main class="flex-grow w-full">
if main, ok := data.Slots["main"]; ok && main != "" {
@templ.Raw(main)
} else {
<div class="py-20 text-center cyberpunk-mono" style="color: hsl(var(--muted-foreground));">
<p>No content blocks assigned to this page.</p>
</div>
}
</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">
@templ.Raw(data.Slots["footer"])
</footer>
@bn.BodyEnd(data.SiteSettings)
</body>
</html>
}
// RenderCyberpunk renders the default Cyberpunk page template.
func RenderCyberpunk(ctx context.Context, doc map[string]any) templ.Component {
return Cyberpunk(parseCyberpunkPageData(doc))
}
// RenderCyberpunkLanding renders the Cyberpunk landing page template.
func RenderCyberpunkLanding(ctx context.Context, doc map[string]any) templ.Component {
return CyberpunkLanding(parseCyberpunkPageData(doc))
}
// RenderCyberpunkArticle renders the Cyberpunk article page template.
func RenderCyberpunkArticle(ctx context.Context, doc map[string]any) templ.Component {
return CyberpunkArticle(parseCyberpunkPageData(doc))
}
// RenderCyberpunkFullWidth renders the Cyberpunk full-width page template.
func RenderCyberpunkFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return CyberpunkFullWidth(parseCyberpunkPageData(doc))
}

View File

@ -1,520 +0,0 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package main
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"context"
"git.dev.alexdunmow.com/block/core/templates/bn"
)
// PageData carries the rendered slots and assorted page chrome data passed
// from the CMS into a Cyberpunk page template.
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
}
// parseCyberpunkPageData converts the loose doc map the renderer is handed into a typed PageData.
func parseCyberpunkPageData(doc map[string]any) PageData {
title := "Untitled"
if t, ok := doc["title"].(string); ok && t != "" {
title = t
}
slots := map[string]string{}
if s, ok := doc["slots"].(map[string]string); ok {
slots = s
}
themeCSS, _ := doc["theme_css"].(string)
structuredData, _ := doc["structured_data"].(string)
cssHash, _ := doc["css_hash"].(string)
pageviewNonce, _ := doc["pageview_nonce"].(string)
themeMode := "dark"
if tm, ok := doc["theme_mode"].(string); ok && tm != "" {
themeMode = tm
}
return PageData{
Title: title,
Slots: slots,
ThemeMode: themeMode,
ThemeCSS: themeCSS,
SiteSettings: bn.ParseSiteSettings(doc),
PageMeta: bn.ParsePageMeta(doc),
StructuredData: structuredData,
CSSHash: cssHash,
PageviewNonce: pageviewNonce,
EngagementConfig: bn.ParseEngagementConfig(doc),
}
}
// scanlineOverlay renders the global scanline overlay element (fixed, pointer-events: none).
func scanlineOverlay() 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 aria-hidden=\"true\" class=\"scanlines pointer-events-none fixed inset-0 z-50\" data-cyberpunk-scanlines></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Cyberpunk renders the default page template.
func Cyberpunk(data PageData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<!doctype html><html lang=\"en\" class=\"dark\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<body class=\"cyberpunk-body min-h-screen flex flex-col antialiased\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = scanlineOverlay().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<header class=\"cyberpunk-chrome w-full sticky top-0 z-40\" style=\"background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));\">")
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, 5, "</header><main class=\"flex-grow w-full max-w-6xl mx-auto px-4 py-12\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"py-20 text-center cyberpunk-empty\" style=\"color: hsl(var(--muted-foreground));\"><p class=\"cyberpunk-mono\">No content blocks assigned to this page.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</main><footer class=\"w-full mt-auto\" style=\"border-top: 1px solid hsl(var(--border));\">")
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, 8, "</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, 9, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// CyberpunkLanding renders the marketing landing template with hero/features/cta/footer slots.
func CyberpunkLanding(data PageData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<!doctype html><html lang=\"en\" class=\"dark\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<body class=\"cyberpunk-body min-h-screen flex flex-col antialiased\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = scanlineOverlay().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<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, 13, "</section><section class=\"w-full max-w-6xl mx-auto px-4 py-16\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["features"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</section><section class=\"w-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["cta"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</section><footer class=\"w-full mt-auto\" style=\"border-top: 1px solid hsl(var(--border));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["footer"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.BodyEnd(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// CyberpunkArticle renders the article template with mono-metadata header and a code-friendly aside.
func CyberpunkArticle(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, 18, "<!doctype html><html lang=\"en\" class=\"dark\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<body class=\"cyberpunk-body min-h-screen flex flex-col antialiased\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = scanlineOverlay().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<header class=\"w-full\" style=\"border-bottom: 1px solid hsl(var(--border));\"><div class=\"max-w-4xl mx-auto px-4 py-6 cyberpunk-mono text-xs uppercase tracking-widest\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["header"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div></header><main class=\"flex-grow w-full max-w-4xl mx-auto px-4 py-12 grid gap-10 lg:grid-cols-[1fr_240px]\"><article class=\"cyberpunk-prose prose prose-invert max-w-none\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["main"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</article><aside class=\"cyberpunk-aside cyberpunk-mono text-sm\" style=\"color: hsl(var(--muted-foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ.Raw(data.Slots["aside"]).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</aside></main><footer class=\"w-full mt-auto\" style=\"border-top: 1px solid hsl(var(--border));\">")
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, 24, "</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, 25, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// CyberpunkFullWidth renders the edge-to-edge template (no max-width clamping on main).
func CyberpunkFullWidth(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_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<!doctype html><html lang=\"en\" class=\"dark\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.Head(bn.HeadData{
Title: data.Title,
Settings: data.SiteSettings,
PageMeta: data.PageMeta,
ThemeMode: data.ThemeMode,
ThemeCSS: data.ThemeCSS,
PluginStyles: []string{"/templates/cyberpunk/css/cyberpunk.css"},
StructuredData: data.StructuredData,
CSSHash: data.CSSHash,
PageviewNonce: data.PageviewNonce,
EngagementConfig: data.EngagementConfig,
}).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<body class=\"cyberpunk-body min-h-screen flex flex-col antialiased\" style=\"background-color: hsl(var(--background)); color: hsl(var(--foreground));\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = bn.AdminBypassBanner(data.SiteSettings).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = scanlineOverlay().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<header class=\"cyberpunk-chrome w-full sticky top-0 z-40\" style=\"background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));\">")
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, 29, "</header><main class=\"flex-grow w-full\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if main, ok := data.Slots["main"]; ok && main != "" {
templ_7745c5c3_Err = templ.Raw(main).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<div class=\"py-20 text-center cyberpunk-mono\" style=\"color: hsl(var(--muted-foreground));\"><p>No content blocks assigned to this page.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</main><footer class=\"w-full mt-auto\" style=\"border-top: 1px solid hsl(var(--border));\">")
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, 32, "</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, 33, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// RenderCyberpunk renders the default Cyberpunk page template.
func RenderCyberpunk(ctx context.Context, doc map[string]any) templ.Component {
return Cyberpunk(parseCyberpunkPageData(doc))
}
// RenderCyberpunkLanding renders the Cyberpunk landing page template.
func RenderCyberpunkLanding(ctx context.Context, doc map[string]any) templ.Component {
return CyberpunkLanding(parseCyberpunkPageData(doc))
}
// RenderCyberpunkArticle renders the Cyberpunk article page template.
func RenderCyberpunkArticle(ctx context.Context, doc map[string]any) templ.Component {
return CyberpunkArticle(parseCyberpunkPageData(doc))
}
// RenderCyberpunkFullWidth renders the Cyberpunk full-width page template.
func RenderCyberpunkFullWidth(ctx context.Context, doc map[string]any) templ.Component {
return CyberpunkFullWidth(parseCyberpunkPageData(doc))
}
var _ = templruntime.GeneratedTemplate

View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, &quot;Inter&quot;, system-ui, sans-serif);">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
<main class="flex-grow w-full max-w-5xl mx-auto px-4 py-12{% if slots.aside %} grid gap-10 lg:grid-cols-[1fr_260px]{% endif %}">
<article class="cyberpunk-prose prose prose-invert max-w-none min-w-0">{{ slots.main|safe }}</article>
{% if slots.aside %}<aside class="cyberpunk-aside cyberpunk-mono hud-frame rounded-md border border-primary/30 p-5 text-sm h-max" style="color: hsl(var(--muted-foreground)); background-color: hsl(var(--card));">{{ slots.aside|safe }}</aside>{% endif %}
</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, &quot;Inter&quot;, system-ui, sans-serif);">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
<header class="cyberpunk-chrome w-full" style="border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
<main class="flex-grow w-full flex items-center justify-center px-4 py-16">
<div class="w-full max-w-md">{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="cyberpunk-mono text-center uppercase tracking-widest" style="color: hsl(var(--muted-foreground));">// auth console</p>{% endif %}</div>
</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, &quot;Inter&quot;, system-ui, sans-serif);">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
<main class="flex-grow w-full max-w-6xl mx-auto px-4 py-12">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center cyberpunk-mono uppercase tracking-widest" style="color: hsl(var(--muted-foreground));"><p>// no posts yet</p></div>{% endif %}</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, &quot;Inter&quot;, system-ui, sans-serif);">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
<main class="flex-grow w-full max-w-5xl mx-auto px-4 py-12">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center cyberpunk-mono uppercase tracking-widest" style="color: hsl(var(--muted-foreground));"><p>// contact console offline</p></div>{% endif %}</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, &quot;Inter&quot;, system-ui, sans-serif);">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
<main class="flex-grow w-full max-w-6xl mx-auto px-4 py-12">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center cyberpunk-empty" style="color: hsl(var(--muted-foreground));"><p class="cyberpunk-mono uppercase tracking-widest">// no content blocks assigned</p></div>{% endif %}</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, &quot;Inter&quot;, system-ui, sans-serif);">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
<main class="flex-grow w-full">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center cyberpunk-mono uppercase tracking-widest" style="color: hsl(var(--muted-foreground));"><p>// no content blocks assigned</p></div>{% endif %}</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,12 @@
<!doctype html>
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
{{ head_html|safe }}
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, &quot;Inter&quot;, system-ui, sans-serif);">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
<header class="cyberpunk-chrome w-full absolute top-0 left-0 right-0 z-40">{{ slots.header|safe }}</header>
<main class="flex-grow w-full">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center cyberpunk-mono uppercase tracking-widest" style="color: hsl(var(--muted-foreground));"><p>// no content blocks assigned</p></div>{% endif %}</main>
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
{{ body_end_html|safe }}
</body>
</html>

View File

@ -0,0 +1,54 @@
<!doctype html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="x-apple-disable-message-reformatting">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--[if mso]>
<noscript><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml></noscript>
<![endif]-->
<title>{{ site_name }}</title>
<style type="text/css">
body, table, td, p, a, li, blockquote { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; }
img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; }
body { margin: 0 !important; padding: 0 !important; width: 100% !important; }
a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; }
@media only screen and (max-width: 620px) {
.email-container { width: 100% !important; max-width: 100% !important; }
.content-padding { padding-left: 24px !important; padding-right: 24px !important; }
}
</style>
</head>
<body style="background-color: {{ colors.background }}; margin: 0; padding: 0; font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;">
{% if preview_text %}<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">{{ preview_text }}</div>{% endif %}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center" style="padding: 32px 10px; background-color: {{ colors.background }};">
<table role="presentation" class="email-container" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px; background-color: {{ colors.card }}; border: 1px solid {{ colors.primary }};">
<tr>
<td class="content-padding" align="left" style="padding: 24px 40px; background-color: {{ colors.background }}; border-bottom: 2px solid {{ colors.primary }};">
<div style="font-family: 'Share Tech Mono', ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; letter-spacing: 0.2em; text-transform: uppercase; color: {{ colors.accent }};">$ from: {{ site_name }}</div>
{% if logo_url %}<img src="{{ logo_url }}" alt="{{ site_name }}" style="margin-top: 12px; max-height: 44px; width: auto; display: block;">{% else %}<div style="margin-top: 8px; font-family: 'Share Tech Mono', ui-monospace, Menlo, monospace; font-size: 22px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: {{ colors.foreground }};">{{ site_name }}</div>{% endif %}
</td>
</tr>
<tr>
<td class="content-padding" style="padding: 40px 48px; color: {{ colors.foreground }}; font-size: 16px; line-height: 1.7;">{{ body|safe }}</td>
</tr>
<tr>
<td style="padding: 0 48px;"><hr style="border: none; border-top: 1px solid {{ colors.accent }}; margin: 0;"></td>
</tr>
<tr>
<td class="content-padding" align="center" style="padding: 24px 48px 36px; color: {{ colors.mutedForeground }}; font-family: 'Share Tech Mono', ui-monospace, Menlo, monospace; font-size: 12px; letter-spacing: 0.14em; text-transform: uppercase;">
{% if site_url %}<p style="margin: 0 0 12px;"><a href="{{ site_url }}" style="color: {{ colors.accent }}; text-decoration: none;">{{ site_url }}</a></p>{% endif %}
{% if support_email %}<p style="margin: 0 0 12px; color: {{ colors.mutedForeground }};">{{ support_email }}</p>{% endif %}
{% if unsubscribe_url %}<p style="margin: 0;"><a href="{{ unsubscribe_url }}" style="color: {{ colors.mutedForeground }}; text-decoration: none;">[esc] unsubscribe</a></p>{% else %}<p style="margin: 0;">[esc] unsubscribe</p>{% endif %}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,21 @@
{# announcement_bar (WO-TF-006): dismissable banner; dismissal persists in localStorage keyed by a content hash (variant + slugified message) so it reappears when the message changes; no-JS fallback is a static non-dismissable strip. Cyberpunk override: mono broadcast readout — data-* + script preserved. #}
{% set anncls = "bg-primary/10 text-foreground" %}
{% if variant == "promo" %}{% set anncls = "bg-primary text-primary-foreground" %}{% elif variant == "alert" %}{% set anncls = "bg-destructive text-destructive-foreground" %}{% endif %}
<div data-bn-announcement data-bn-annkey="{{ variant|default:'info' }}:{{ message|slugify }}" class="bn-announcement w-full {{ anncls }} {{ class }}">
<div class="max-w-7xl mx-auto px-10 py-2 relative flex items-center justify-center gap-2 text-sm text-center">
<p class="cyberpunk-mono uppercase tracking-wide font-medium">{{ message }}{% if linkText and linkUrl %} <a href="{{ linkUrl }}" class="underline underline-offset-2 hover:opacity-80">{{ linkText }}</a>{% endif %}</p>
{% if dismissable %}<button type="button" data-bn-ann-dismiss aria-label="Dismiss announcement" class="absolute right-3 top-1/2 -translate-y-1/2 inline-flex items-center justify-center p-1 rounded hover:bg-black/10 transition-colors"><svg width="16" height="16" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></button>{% endif %}
</div>
</div>
{% if dismissable %}<script>
(function(){
var bars=document.querySelectorAll('[data-bn-announcement]:not([data-bn-ann-wired])');
bars.forEach(function(bar){
bar.setAttribute('data-bn-ann-wired','1');
var key='bnann:'+bar.getAttribute('data-bn-annkey');
try{if(localStorage.getItem(key)==='1'){bar.style.display='none';return;}}catch(e){}
var btn=bar.querySelector('[data-bn-ann-dismiss]');
if(btn)btn.addEventListener('click',function(){bar.style.display='none';try{localStorage.setItem(key,'1');}catch(e){}});
});
})();
</script>{% endif %}

View File

@ -0,0 +1,16 @@
{# audio-embed cyberpunk override: neon panel, mono meta. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
<figure class="bn-audio my-6 overflow-hidden rounded-md border border-primary/40 bg-card text-card-foreground neon-edge-magenta{% if class %} {{ class }}{% endif %}" data-bn-audio data-audio-url="{{ url|default:"" }}" data-audio-media="{% if media %}{{ media|mediaURL }}{% endif %}"><div class="flex items-center gap-4 p-4">{% if artwork %}<div class="h-16 w-16 shrink-0 overflow-hidden rounded-md border border-primary/30 bg-muted">{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}</div>{% endif %}<div class="min-w-0 flex-1">{% if title %}<p class="cyberpunk-display truncate font-medium text-foreground">{{ title }}</p>{% endif %}{% if artist %}<p class="cyberpunk-mono truncate text-sm text-muted-foreground">{{ artist }}</p>{% endif %}<div class="mt-2 flex items-center gap-3" data-bn-audio-slot><button type="button" data-bn-audio-play class="cyberpunk-mono clip-cut inline-flex items-center gap-2 rounded-md bg-primary px-4 py-1.5 text-sm font-medium uppercase tracking-wide text-primary-foreground transition-colors hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring" aria-label="Play{% if title %}: {{ title }}{% endif %}">::lucide:play:sm:: Play</button>{% if download %}<a href="{% if media %}{{ media|mediaURL }}{% else %}{{ url }}{% endif %}" download class="cyberpunk-mono inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground">::lucide:download:sm:: Download</a>{% endif %}</div></div></div></figure><script>
(function(){
if(window.__bnAudioFacade)return;window.__bnAudioFacade=true;
document.addEventListener('click',function(e){
var btn=e.target.closest('[data-bn-audio-play]');if(!btn)return;
var fig=btn.closest('[data-bn-audio]');if(!fig)return;
e.preventDefault();
var src=fig.getAttribute('data-audio-media')||fig.getAttribute('data-audio-url')||'';
if(!src)return;
var slot=btn.closest('[data-bn-audio-slot]');
var a=document.createElement('audio');a.src=src;a.controls=true;a.autoplay=true;a.preload='none';a.style.cssText='width:100%;min-width:12rem;height:2.25rem;';
btn.remove();slot.insertBefore(a,slot.firstChild);
});
})();
</script>

View File

@ -0,0 +1,77 @@
{# auth-form cyberpunk override — reproduces the builtin's security-critical wiring: hardcoded HTMX endpoints /api/auth/login + /api/auth/register (target #auth-message), /forgot-password link, OAuth start URLs gated on context.site.*, and the exact Cap widget markup when captchaEnabled. Only the chrome is themed. #}
{% if context.isPublicLoggedIn %}
<div class="auth-form-logged-in mx-auto max-w-md text-center py-6">
<p class="cyberpunk-mono text-sm uppercase tracking-wide text-muted-foreground">Session active as <strong class="text-primary">{{ context.publicUsername }}</strong></p>
<a href="/account" class="cyberpunk-mono clip-cut mt-3 inline-flex items-center rounded-md bg-primary px-4 py-2 text-sm font-medium uppercase tracking-wide text-primary-foreground hover:bg-primary/90">My Account</a>
</div>
{% else %}
{% set loginActive = "border-primary text-primary" %}
{% set registerActive = "border-transparent text-muted-foreground" %}
{% set loginHidden = "" %}
{% set registerHidden = "hidden" %}
{% if default_tab == "register" %}{% set loginActive = "border-transparent text-muted-foreground" %}{% set registerActive = "border-primary text-primary" %}{% set loginHidden = "hidden" %}{% set registerHidden = "" %}{% endif %}
{% set tabbase = "flex-1 py-2 text-center cyberpunk-mono text-sm font-medium uppercase tracking-wider border-b-2 transition-colors" %}
{% macro captcha() %}<script>
window.CAP_CUSTOM_WASM_URL = "/assets/captcha/cap_wasm_bg.wasm";
window.CAP_PAKO_URL = "/assets/captcha/pako_inflate.min.js";
</script>
<cap-widget data-cap-api-endpoint="/api/captcha/" data-cap-worker-count="2"></cap-widget>
<script src="/assets/captcha/cap.min.js" defer></script>
<script>
(function(){
var s = document.currentScript;
var form = s && s.closest ? s.closest('form') : null;
if (!form) return;
form.addEventListener('htmx:afterRequest', function(){
var w = form.querySelector('cap-widget');
if (w && typeof w.reset === 'function') w.reset();
});
})();
</script>{% endmacro %}
{% macro social() %}{% if show_social != false and (context.site.google_oauth_client_id or context.site.facebook_app_id) %}<div class="auth-social my-4">
<div class="relative my-4"><hr class="border-border"/><span class="cyberpunk-mono absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-3 text-xs uppercase tracking-widest text-muted-foreground">or</span></div>
{% if context.site.google_oauth_client_id %}<a href="/api/auth/google/start" class="cyberpunk-mono mb-2 flex w-full items-center justify-center gap-3 rounded-md border border-border px-4 py-2.5 uppercase tracking-wide transition-colors hover:bg-accent/10"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#google"></use></svg><span class="text-sm font-medium text-foreground/80">Continue with Google</span></a>{% endif %}
{% if context.site.facebook_app_id %}<a href="/api/auth/facebook/start" class="cyberpunk-mono flex w-full items-center justify-center gap-3 rounded-md border border-border px-4 py-2.5 uppercase tracking-wide transition-colors hover:bg-accent/10"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#facebook"></use></svg><span class="text-sm font-medium text-foreground/80">Continue with Facebook</span></a>{% endif %}
</div>{% endif %}{% endmacro %}
<div class="auth-form hud-frame mx-auto max-w-md rounded-md border border-primary/40 bg-card p-6 text-card-foreground neon-edge-magenta">
<div id="auth-message"></div>
<div class="mb-5 flex border-b border-border">
<button type="button" data-auth-tab="login" data-tabbase="{{ tabbase }}" class="{{ tabbase }} {{ loginActive }}">Login</button>
<button type="button" data-auth-tab="register" data-tabbase="{{ tabbase }}" class="{{ tabbase }} {{ registerActive }}">Register</button>
</div>
<div id="login-panel" class="{{ loginHidden }}">
<form hx-post="/api/auth/login" hx-target="#auth-message" hx-swap="innerHTML">
<div class="mb-3"><label class="cyberpunk-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Email</label><input type="email" name="email" required class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<div class="mb-4"><label class="cyberpunk-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Password</label><input type="password" name="password" required class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
{% if captchaEnabled %}{{ captcha() }}{% endif %}
<button type="submit" class="cyberpunk-mono clip-cut rgb-split w-full rounded-md bg-primary px-4 py-2.5 font-semibold uppercase tracking-wider text-primary-foreground hover:bg-primary/90" hx-disabled-elt="this">Login</button>
<p class="mt-3 text-center text-sm"><a href="/forgot-password" class="cyberpunk-mono uppercase tracking-wide text-primary hover:underline">Forgot password?</a></p>
</form>
{{ social() }}
</div>
<div id="register-panel" class="{{ registerHidden }}">
<form hx-post="/api/auth/register" hx-target="#auth-message" hx-swap="innerHTML">
<div class="mb-3"><label class="cyberpunk-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Username</label><input type="text" name="username" required pattern="[a-zA-Z0-9_-]{3,30}" class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<div class="mb-3"><label class="cyberpunk-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Email</label><input type="email" name="email" required class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
<div class="mb-4"><label class="cyberpunk-mono mb-1 block text-xs font-medium uppercase tracking-wide text-accent">Password</label><input type="password" name="password" required minlength="8" class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"/></div>
{% if captchaEnabled %}{{ captcha() }}{% endif %}
<button type="submit" class="cyberpunk-mono clip-cut rgb-split w-full rounded-md bg-primary px-4 py-2.5 font-semibold uppercase tracking-wider text-primary-foreground hover:bg-primary/90" hx-disabled-elt="this">Create Account</button>
</form>
{{ social() }}
</div>
</div>
<script>
(function(){
var f=document.currentScript.closest('.auth-form')||document.querySelector('.auth-form');
if(!f)return;
var tabs=f.querySelectorAll('[data-auth-tab]');
tabs.forEach(function(btn){btn.addEventListener('click',function(){
var t=btn.getAttribute('data-auth-tab');
var lp=f.querySelector('#login-panel'),rp=f.querySelector('#register-panel');
if(lp)lp.classList.toggle('hidden',t!=='login');
if(rp)rp.classList.toggle('hidden',t!=='register');
tabs.forEach(function(b){var on=b===btn;b.className=b.getAttribute('data-tabbase')+(on?' border-primary text-primary':' border-transparent text-muted-foreground');});
});});
})();
</script>
{% endif %}

View File

@ -0,0 +1,36 @@
{# auth-status cyberpunk override — reproduces the builtin's hardcoded wiring: /login + /login?tab=register when logged out; /account, /account/reviews, and hx-post /api/auth/logout dropdown when logged in; hx-post /api/auth/resend-verification banner when unverified. State from context.*. #}
{% if not context.isPublicLoggedIn %}
<div class="auth-status flex items-center gap-2">
<a href="/login" class="cyberpunk-mono rounded-md px-3 py-1.5 text-sm font-medium uppercase tracking-wide opacity-80 transition-colors hover:bg-primary/10 hover:opacity-100">Login</a>
<a href="/login?tab=register" class="cyberpunk-mono clip-cut rounded-md bg-accent px-3 py-1.5 text-sm font-medium uppercase tracking-wide text-accent-foreground hover:bg-accent/90">Sign Up</a>
</div>
{% else %}
{% if not context.publicEmailVerified %}<div class="fixed top-16 inset-x-0 z-40 border-y border-destructive/50 bg-destructive/15 px-4 py-2 text-center text-sm text-destructive">
<span class="cyberpunk-mono uppercase tracking-wide">Verify your email address.</span> <button hx-post="/api/auth/resend-verification" hx-swap="outerHTML" class="underline font-medium">Resend verification email</button>
</div>{% endif %}
<div class="auth-status relative">
<button type="button" onclick="this.nextElementSibling.classList.toggle('hidden')" class="flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium opacity-90 transition-colors hover:bg-primary/10 hover:opacity-100">
<span class="clip-cut flex h-7 w-7 items-center justify-center rounded-md bg-accent text-accent-foreground"><svg width="16" height="16" class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#user"></use></svg></span>
<span class="cyberpunk-mono uppercase tracking-wide">{{ context.publicDisplayName|default:context.publicUsername }}</span>
<svg width="16" height="16" class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<div id="auth-dropdown-menu" class="hud-frame hidden absolute right-0 top-full mt-1 w-48 rounded-md border border-primary/40 bg-card py-1 text-card-foreground neon-edge-magenta z-50">
<a href="/account" class="cyberpunk-mono block px-4 py-2 text-sm uppercase tracking-wide text-card-foreground hover:bg-primary/10">My Profile</a>
<a href="/account/reviews" class="cyberpunk-mono block px-4 py-2 text-sm uppercase tracking-wide text-card-foreground hover:bg-primary/10">My Reviews</a>
<hr class="my-1 border-border"/>
<button hx-post="/api/auth/logout" hx-swap="none" class="cyberpunk-mono block w-full px-4 py-2 text-left text-sm uppercase tracking-wide text-destructive hover:bg-primary/10">Logout</button>
</div>
<script>
(function(){
document.addEventListener('click', function(e) {
var menu = document.getElementById('auth-dropdown-menu');
var btn = document.querySelector('.auth-status > button');
if (!menu || !btn) return;
if (!btn.contains(e.target) && !menu.contains(e.target)) {
menu.classList.add('hidden');
}
});
})();
</script>
</div>
{% endif %}

View File

@ -0,0 +1,22 @@
{# author-bio-hero (cyberpunk override) — author-page header from the `authors` provider. #}
{% if author %}
<section class="relative overflow-hidden border-b border-primary/30 bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
<div class="scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto flex max-w-3xl flex-col items-center px-4 text-center">
{% if showAvatar and author.avatar_url %}<div class="mb-5 h-24 w-24 overflow-hidden rounded-full bg-muted ring-2 ring-primary neon-edge-magenta">{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}</div>{% endif %}
<h1 class="cyberpunk-display glitch text-3xl font-bold tracking-tight md:text-4xl" data-text="{{ author.display_name }}">{{ author.display_name }}</h1>
{% if author.job_title or author.company %}<p class="cyberpunk-mono mt-1 text-sm uppercase tracking-wide text-accent">{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}</p>{% endif %}
{% if author.bio %}<p class="cyberpunk-body mt-4 max-w-2xl leading-relaxed text-muted-foreground">{{ author.bio }}</p>{% endif %}
<div class="mt-5 flex flex-wrap items-center justify-center gap-4 cyberpunk-mono text-sm uppercase tracking-wide text-accent">
{% if showPostCount %}<span>{{ author.post_count|default:0 }} post{% if author.post_count != 1 %}s{% endif %}</span>{% endif %}
{% if author.location %}<span>{{ author.location }}</span>{% endif %}
</div>
{% if showSocial %}<div class="mt-5 flex items-center justify-center gap-3">
{% if author.website_url %}<a href="{{ author.website_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Website"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#globe"></use></svg></a>{% endif %}
{% if author.twitter_handle %}<a href="https://twitter.com/{{ author.twitter_handle|cut:"@" }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Twitter"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#x"></use></svg></a>{% endif %}
{% if author.linkedin_url %}<a href="{{ author.linkedin_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="LinkedIn"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"></use></svg></a>{% endif %}
{% if author.github_url %}<a href="{{ author.github_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="GitHub"><svg width="20" height="20" class="h-5 w-5" aria-hidden="true"><use href="/icons/simple-icons.svg#github"></use></svg></a>{% endif %}
</div>{% endif %}
</div>
</section>
{% endif %}

View File

@ -0,0 +1,13 @@
{# author-bio (cyberpunk override) — byline card sourced from the `post` provider's author_* fields. #}
{% if post.author_name %}
<aside class="hud-frame rounded-md border border-dashed border-primary/40 bg-card p-6 text-card-foreground neon-edge-magenta{% if class %} {{ class }}{% endif %}">
{% if heading %}<p class="cyberpunk-eyebrow cyberpunk-mono mb-4 text-xs font-semibold uppercase tracking-widest text-accent">{{ heading }}</p>{% endif %}
<div class="flex items-start gap-4">
{% if showAvatar and post.author_avatar %}<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-full border border-primary/40 bg-muted">{% img post.author_avatar alt=post.author_name class="h-full w-full object-cover" %}</div>{% endif %}
<div class="min-w-0">
<h3 class="cyberpunk-display text-base font-semibold">{% if post.author_slug %}<a href="/author/{{ post.author_slug }}" class="transition-colors hover:text-primary">{{ post.author_name }}</a>{% else %}{{ post.author_name }}{% endif %}</h3>
{% if post.author_bio %}<p class="cyberpunk-body mt-1 text-sm text-muted-foreground">{{ post.author_bio }}</p>{% endif %}
</div>
</div>
</aside>
{% endif %}

View File

@ -0,0 +1,24 @@
{# blog-hero (cyberpunk override) — blog-index header + featured posts from the `posts` provider. #}
<section class="relative overflow-hidden bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
<div class="scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-6xl px-4">
<div class="max-w-2xl">
{% if title %}<h1 class="cyberpunk-display glitch text-4xl font-bold tracking-tight md:text-5xl" data-text="{{ title }}">{{ title }}</h1>{% endif %}
{% if subtitle %}<p class="cyberpunk-body mt-3 text-lg text-muted-foreground">{{ subtitle }}</p>{% endif %}
</div>
{% if posts %}
<div class="mt-10 grid grid-cols-1 gap-6 md:grid-cols-3">
{% for post in posts %}
<article class="hud-frame group flex flex-col rounded-md border border-dashed border-primary/40 bg-card p-4 text-card-foreground neon-edge-magenta{% if forloop.First %} md:col-span-3 md:flex-row md:items-stretch md:gap-6{% endif %}">
{% if post.featured_image_url %}<a href="{{ post.url }}" class="mb-3 block overflow-hidden rounded-md bg-muted{% if forloop.First %} md:mb-0 md:w-1/2{% endif %}">{% img post.featured_image_url alt=post.title class="aspect-video h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
<div class="flex flex-col justify-center{% if forloop.First %} md:w-1/2{% endif %}">
<h2 class="cyberpunk-display {% if forloop.First %}text-2xl{% else %}text-lg{% endif %} font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h2>
{% if showExcerpt and post.excerpt %}<p class="cyberpunk-body mt-2 text-sm text-muted-foreground">{{ post.excerpt }}</p>{% endif %}
<div class="mt-3 flex flex-wrap items-center gap-x-3 cyberpunk-mono text-xs uppercase tracking-wide text-accent">{% if post.author_name %}<span class="font-medium text-foreground">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"M j, Y" }}</span>{% endif %}</div>
</div>
</article>
{% endfor %}
</div>
{% endif %}
</div>
</section>

View File

@ -0,0 +1,15 @@
{# breadcrumbs (cyberpunk override) — trail from the `breadcrumbs` provider (page ancestry). #}
{# showHome prepends a Home link; separator is text ("/") for default or a chevron #}
{# for the arrows template. The trailing is_current item renders as plain text. #}
{% set arrows = template == "arrows" %}
<nav aria-label="Breadcrumb" class="py-3{% if class %} {{ class }}{% endif %}">
<ol class="flex flex-wrap items-center gap-1 cyberpunk-mono text-sm uppercase tracking-wide">
{% if showHome %}<li class="flex items-center"><a href="/" class="text-accent transition-colors hover:text-primary">{{ homeLabel|default:"Home" }}</a></li>{% endif %}
{% for item in breadcrumbs %}
<li class="flex items-center">
{% if showHome or not forloop.First %}{% if arrows %}<svg width="16" height="16" class="mx-2 h-4 w-4 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>{% else %}<span class="mx-2 text-primary">{{ separator|default:"/" }}</span>{% endif %}{% endif %}
{% if item.is_current %}{% if showCurrent %}<span class="font-medium text-foreground" aria-current="page">{{ item.label }}</span>{% endif %}{% else %}<a href="{{ item.url }}" class="text-accent transition-colors hover:text-primary">{{ item.label }}</a>{% endif %}
</li>
{% endfor %}
</ol>
</nav>

View File

@ -0,0 +1,2 @@
{# cyberpunk button — keep {% button %} tag intact; cyberpunk chrome on hugging wrapper #}
{% with align=align|default:"left" %}<div class="bn-button flex {% if align == "center" %}justify-center{% elif align == "right" %}justify-end{% else %}justify-start{% endif %}{% if class %} {{ class }}{% endif %}"><span class="cyberpunk-btn cyberpunk-mono inline-flex uppercase tracking-wider font-semibold clip-cut rgb-split">{% if newTab %}{% button style|default:"primary" text=label|default:"Learn more" href=link|default:"#" size=size|default:"md" icon=icon|default:"" icon_position=iconPosition|default:"left" target="_blank" %}{% else %}{% button style|default:"primary" text=label|default:"Learn more" href=link|default:"#" size=size|default:"md" icon=icon|default:"" icon_position=iconPosition|default:"left" %}{% endif %}</span></div>{% endwith %}

Some files were not shown because too many files have changed in this diff Show More