Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03751e936c | ||
|
|
7bb7727852 | ||
|
|
734f8e37c3 | ||
|
|
197eb9960b | ||
|
|
ffdd627192 | ||
|
|
82237ad9a6 | ||
|
|
71df6c38ac | ||
|
|
644a8083f7 | ||
|
|
1f74c5ac06 | ||
|
|
a5cf29efb8 | ||
|
|
849edc4b8c | ||
|
|
5221b310ff | ||
|
|
6787a01515 | ||
|
|
ffce8b5913 | ||
|
|
6a34a6c795 | ||
|
|
02a84ab31e | ||
|
|
8ea1477d37 | ||
|
|
b32e3f143a | ||
|
|
f908b38c01 | ||
|
|
96af6b26c3 |
1
.gitignore
vendored
@ -3,3 +3,4 @@
|
||||
tmp/
|
||||
.idea/
|
||||
.vscode/
|
||||
*.bnp
|
||||
|
||||
62
Makefile
@ -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:
|
||||
# make Local build: produces cyberpunk.so via CGO go build -buildmode=plugin
|
||||
# make clean Remove built artefacts
|
||||
# make templ Regenerate *_templ.go files (host templ)
|
||||
# make build # pack cyberpunk-<version>.bnp (codeless — no wasm)
|
||||
# make verify # validate the built .bnp (loader-identical checks)
|
||||
# make archive-check # prove a clean `git archive HEAD` still builds codeless
|
||||
# make publish # ninja plugin publish --bnp (to the active registry)
|
||||
# make clean # remove build artefacts
|
||||
|
||||
.PHONY: all clean templ help
|
||||
.PHONY: build verify archive-check publish clean help
|
||||
|
||||
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.
|
||||
all: $(PLUGIN_NAME).so
|
||||
# Pack the codeless .bnp (manifest.pb synthesized from plugin.mod + manifest.yaml
|
||||
# + blocks/ + templates/; no wasm compile, no DESCRIBE probe).
|
||||
build:
|
||||
$(NINJA) plugin build --dir .
|
||||
|
||||
# Local plugin build (no container).
|
||||
$(PLUGIN_NAME).so: $(wildcard *.go) plugin.mod go.mod
|
||||
CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o $(PLUGIN_NAME).so .
|
||||
# Validate the built .bnp exactly as the registry/loader will.
|
||||
verify:
|
||||
$(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:
|
||||
rm -f $(PLUGIN_NAME).so
|
||||
|
||||
# Regenerate templ Go files locally.
|
||||
templ:
|
||||
cd $(PLUGIN_SRC) && $(HOME)/go/bin/templ generate
|
||||
rm -f $(PLUGIN_NAME)-*.bnp plugin.wasm
|
||||
|
||||
help:
|
||||
@echo "Targets:"
|
||||
@echo " all Build $(PLUGIN_NAME).so locally (default)"
|
||||
@echo " templ Regenerate templ Go files"
|
||||
@echo " clean Remove built artefacts"
|
||||
@echo " build Pack the codeless .bnp (no wasm)"
|
||||
@echo " verify Validate the built .bnp (loader-identical checks)"
|
||||
@echo " archive-check Prove a clean git archive HEAD packs codeless"
|
||||
@echo " publish Publish the .bnp to the active registry"
|
||||
@echo " clean Remove build artefacts"
|
||||
|
||||
25
README.md
Normal 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.
|
||||
@ -1,33 +1,40 @@
|
||||
# Recommended fonts — Cyberpunk
|
||||
|
||||
This theme ships `fonts.json = []` per the wave-1 fonts policy
|
||||
(`themes/docs/FONTS.md`). No `.woff2` files are bundled inside the `.so`.
|
||||
This theme **bundles** three OFL (SIL Open Font License 1.1) latin-subset webfonts in
|
||||
`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
|
||||
three families below from the Google Fonts picker in the typography panel.
|
||||
|
||||
| Slot | Recommended | Source | One-line how-to |
|
||||
| Slot | Bundled family | File | Fallback stack (before assignment) |
|
||||
|---|---|---|---|
|
||||
| Heading (`--font-heading`) | Space Grotesk | `google:Space Grotesk` | Open the typography panel, pick **Space Grotesk** from the Google Fonts tab, assign to **Heading**. |
|
||||
| Body (`--font-body`) | Inter | `google:Inter` | Open the typography panel, pick **Inter** from the Google Fonts tab, assign to **Body**. |
|
||||
| Mono (`--font-mono`) | JetBrains Mono | `google:JetBrains Mono` | Open the typography panel, pick **JetBrains Mono** from the Google Fonts tab, assign to **Mono**. |
|
||||
| Heading (`--font-heading`) | **Orbitron** | `fonts/orbitron-latin.woff2` | `"Orbitron", system-ui, -apple-system, "Segoe UI", sans-serif` |
|
||||
| Mono (`--font-mono`) | **Share Tech Mono** | `fonts/share-tech-mono-latin.woff2` | `"Share Tech Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace` |
|
||||
| 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
|
||||
(declared via the CSS variables in `assets/css/cyberpunk.css`):
|
||||
Every `font-family` in the theme flows through a CSS variable with a bundled-family
|
||||
fallback — nothing is hardcoded, so the admin picker always wins:
|
||||
|
||||
- `--font-heading` → `"Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif`
|
||||
- `--font-body` → `"Inter", system-ui, -apple-system, "Segoe UI", sans-serif`
|
||||
- `--font-mono` → `"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace`
|
||||
- `.cyberpunk-display` -> `var(--font-heading, "Orbitron", ...)` — display headings, glitch H1/H2.
|
||||
- `.cyberpunk-mono` -> `var(--font-mono, "Share Tech Mono", ...)` — readouts, labels, prices, code.
|
||||
- `.cyberpunk-body` / `.cyberpunk-prose` -> `var(--font-body, "Inter", ...)` — body copy.
|
||||
|
||||
These fallbacks intentionally lead with the same family the picker would
|
||||
deliver, so the page already looks close to the intended aesthetic on systems
|
||||
that happen to have the font installed.
|
||||
## To assign (optional)
|
||||
|
||||
## 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
|
||||
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,
|
||||
not the theme).
|
||||
## Alternate direction
|
||||
|
||||
The locked WO-TF-015 direction pairs Orbitron (display) with Share Tech Mono (readouts)
|
||||
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
@ -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
@ -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 500–900) | 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 400–700) | 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.
|
||||
BIN
assets/fonts/inter-latin.woff2
Normal file
BIN
assets/fonts/orbitron-latin.woff2
Normal file
BIN
assets/fonts/share-tech-mono-latin.woff2
Normal file
38
blocks/blocks.yaml
Normal 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
@ -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>
|
||||
5
blocks/code_neon.sample.json
Normal 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"
|
||||
}
|
||||
19
blocks/glitch_hero.ninjatpl
Normal 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>
|
||||
14
blocks/glitch_hero.sample.json
Normal 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" }
|
||||
]
|
||||
}
|
||||
29
blocks/glitch_hero.schema.json
Normal 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" }
|
||||
}
|
||||
}
|
||||
21
blocks/patch_notes.ninjatpl
Normal 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>
|
||||
32
blocks/patch_notes.sample.json
Normal 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." }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
37
blocks/patch_notes.schema.json
Normal 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" }
|
||||
}
|
||||
}
|
||||
23
blocks/roster_grid.ninjatpl
Normal 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>
|
||||
13
blocks/roster_grid.sample.json
Normal 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 }
|
||||
]
|
||||
}
|
||||
28
blocks/roster_grid.schema.json
Normal 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" }
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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()
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
39
code_neon.go
@ -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()
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
@ -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
|
||||
@ -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()
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
@ -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
|
||||
@ -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;"> </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>
|
||||
}
|
||||
@ -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;\"> </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
|
||||
60
embed.go
@ -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),
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
@ -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
|
||||
29
fonts.json
@ -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" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@ -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()
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
@ -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
@ -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
@ -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=
|
||||
@ -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()
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
102
helpers.go
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
@ -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
@ -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
@ -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 }
|
||||
]
|
||||
}
|
||||
]
|
||||
@ -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()
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
@ -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
|
||||
@ -2,11 +2,8 @@
|
||||
name = "cyberpunk"
|
||||
display_name = "Cyberpunk"
|
||||
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."
|
||||
kind = "theme"
|
||||
categories = ["templates"]
|
||||
tags = ["dark", "neon", "glitch", "monospace", "saas", "developer", "tech", "crypto", "fintech"]
|
||||
|
||||
[compatibility]
|
||||
block_core = ">=0.11.0 <0.12.0"
|
||||
|
||||
125
presets.json
@ -5,6 +5,11 @@
|
||||
"description": "Magenta-led tri-accent, the Cyberpunk default",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:cyberpunk:Orbitron",
|
||||
"fontBody": "template:cyberpunk:Inter",
|
||||
"fontMono": "template:cyberpunk:Share Tech Mono"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "240 12% 97%",
|
||||
"foreground": "260 25% 10%",
|
||||
@ -55,6 +60,11 @@
|
||||
"description": "Cyan-led, cooler chrome variant",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:cyberpunk:Orbitron",
|
||||
"fontBody": "template:cyberpunk:Inter",
|
||||
"fontMono": "template:cyberpunk:Share Tech Mono"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "200 25% 97%",
|
||||
"foreground": "220 30% 10%",
|
||||
@ -105,6 +115,11 @@
|
||||
"description": "Lime-led, we shipped at 3am",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:cyberpunk:Orbitron",
|
||||
"fontBody": "template:cyberpunk:Inter",
|
||||
"fontMono": "template:cyberpunk:Share Tech Mono"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "100 15% 97%",
|
||||
"foreground": "120 20% 10%",
|
||||
@ -148,5 +163,115 @@
|
||||
"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%"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
BIN
preview.png
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 1.0 MiB |
190
register.go
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -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),
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
screenshots/01-home-light.png
Normal file
|
After Width: | Height: | Size: 997 KiB |
BIN
screenshots/02-home-dark.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
screenshots/03-about-light.png
Normal file
|
After Width: | Height: | Size: 302 KiB |
BIN
screenshots/04-about-dark.png
Normal file
|
After Width: | Height: | Size: 290 KiB |
BIN
screenshots/05-blog-making-it-rain-light.png
Normal file
|
After Width: | Height: | Size: 675 KiB |
BIN
screenshots/06-blog-making-it-rain-dark.png
Normal file
|
After Width: | Height: | Size: 640 KiB |
219
seed/seed.json
Normal 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><canvas></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
@ -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" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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>
|
||||
}
|
||||
@ -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
|
||||
237
template.templ
@ -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))
|
||||
}
|
||||
@ -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
|
||||
15
templates/cyberpunk/article.ninjatpl
Normal 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, "Inter", 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>
|
||||
14
templates/cyberpunk/auth.ninjatpl
Normal 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, "Inter", 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>
|
||||
12
templates/cyberpunk/blog-index.ninjatpl
Normal 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, "Inter", 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>
|
||||
12
templates/cyberpunk/contact.ninjatpl
Normal 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, "Inter", 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>
|
||||
12
templates/cyberpunk/default.ninjatpl
Normal 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, "Inter", 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>
|
||||
12
templates/cyberpunk/full-width.ninjatpl
Normal 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, "Inter", 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>
|
||||
12
templates/cyberpunk/landing.ninjatpl
Normal 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, "Inter", 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>
|
||||
54
templates/email/cyberpunk.ninjatpl
Normal 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>
|
||||
21
templates/overrides/cyberpunk/announcement-bar.ninjatpl
Normal 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 %}
|
||||
16
templates/overrides/cyberpunk/audio-embed.ninjatpl
Normal 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>
|
||||
77
templates/overrides/cyberpunk/auth-form.ninjatpl
Normal 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 %}
|
||||
36
templates/overrides/cyberpunk/auth-status.ninjatpl
Normal 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 %}
|
||||
22
templates/overrides/cyberpunk/author-bio-hero.ninjatpl
Normal 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 %}
|
||||
13
templates/overrides/cyberpunk/author-bio.ninjatpl
Normal 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 %}
|
||||
24
templates/overrides/cyberpunk/blog-hero.ninjatpl
Normal 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>
|
||||
15
templates/overrides/cyberpunk/breadcrumbs.ninjatpl
Normal 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>
|
||||
2
templates/overrides/cyberpunk/button.ninjatpl
Normal 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 %}
|
||||