feat(cyberpunk): Wave A build — 49 builtin overrides, 7 templates, canvas hero, seed

WO-TF-015 (game-studio / hacker-collective persona, GO-BIG motion).

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

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Alex Dunmow 2026-07-05 10:57:00 +08:00
parent 8ea1477d37
commit 02a84ab31e
94 changed files with 2195 additions and 492 deletions

View File

@ -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
View File

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

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

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,48 +1,38 @@
# Cyberpunk theme block definitions (codeless .bnp, WO-WZ-021).
# 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 hardcoded data-block attributes and the master_pages.json references.
# 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:hero_glitch
title: Glitch Hero
description: RGB-split H1, neon CTA, optional scanline overlay toggle.
- 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: hero_glitch.schema.json
template: hero_glitch.ninjatpl
- key: cyberpunk:cta_terminal
title: Terminal CTA
description: Faux $ prompt with blinking caret and copy button.
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: cta_terminal.schema.json
template: cta_terminal.ninjatpl
- key: cyberpunk:navbar_terminal
title: Terminal Navbar
description: Mono-spaced navbar with $ command prefix and optional status dot.
category: navigation
schema: navbar_terminal.schema.json
template: navbar_terminal.ninjatpl
- key: cyberpunk:footer_grid
title: Grid Footer
description: Mono columns, build-hash + uptime status pill.
category: navigation
schema: footer_grid.schema.json
template: footer_grid.ninjatpl
- key: cyberpunk:feature_card_neon
title: Neon Feature Card
description: Single feature card with per-card neon edge glow (magenta/cyan/lime).
category: layout
schema: feature_card_neon.schema.json
template: feature_card_neon.ninjatpl
- key: cyberpunk:stats_glow
title: Glow Stats
description: Big mono numerals with neon accent underline.
category: display
schema: stats_glow.schema.json
template: stats_glow.ninjatpl
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 neon edge and optional copy button.
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

View File

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

View File

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

View File

@ -1 +0,0 @@
<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>{{ prompt|default:'$ npm i your-thing' }}</code> <span aria-hidden="true" class="caret-blink" data-caret-blink style="color: hsl(var(--primary));">▍</span></div>{% if button|default:'Copy' %}<a href="{{ href|default:'#' }}" 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 copyable != false and copyable != "false" and copyable != "off" and copyable != "no" and copyable != "0" and copyable != "" %} data-copy="{{ prompt|default:'$ npm i your-thing' }}"{% endif %}>{{ button|default:'Copy' }}</a>{% endif %}</div></div></section>

View File

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

View File

@ -1 +0,0 @@
<article data-block="cyberpunk:feature_card_neon" data-accent="{% if accent == "cyan" %}cyan{% elif accent == "lime" %}lime{% else %}magenta{% endif %}" class="cyberpunk-feature-card p-6 rounded-lg h-full flex flex-col gap-4 {% if accent == "cyan" %}neon-edge-cyan{% elif accent == "lime" %}neon-edge-lime{% else %}neon-edge-magenta{% endif %}" style="background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border));">{% if 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="{{ icon }}" alt="" class="w-6 h-6" onerror="this.style.display='none'"></div>{% endif %}<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);">{{ title|default:'Feature' }}</h3>{% if body %}<div class="cyberpunk-feature-body text-sm leading-relaxed" style="color: hsl(var(--muted-foreground));">{{ body|safe }}</div>{% endif %}</article>

View File

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

View File

@ -1 +0,0 @@
<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 columns %}<div class="grid gap-8 mb-10 cyberpunk-mono text-sm {% if columns|length == 2 %}grid-cols-1 sm:grid-cols-2{% elif columns|length == 3 %}grid-cols-1 sm:grid-cols-2 lg:grid-cols-3{% elif columns|length >= 4 %}grid-cols-1 sm:grid-cols-2 lg:grid-cols-4{% else %}grid-cols-1{% endif %}">{% for col in columns %}<div>{% if col.title %}<p class="cyberpunk-mono uppercase tracking-widest text-xs mb-4" style="color: hsl(var(--accent));">{{ col.title }}</p>{% endif %}{% if col.links %}<ul class="space-y-2">{% for link in col.links %}<li><a href="{{ link.href|default:'#' }}" class="cyberpunk-footer-link hover:underline" style="color: hsl(var(--foreground));">{{ link.label }}</a></li>{% endfor %}</ul>{% endif %}</div>{% endfor %}</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>{% endif %}<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));">{{ buildHash|default:'dev' }}</span></p>{% if showStatus != false and showStatus != "false" and showStatus != "off" and showStatus != "no" and showStatus != "0" and 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>{% endif %}</div></div></div>

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +0,0 @@
<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 scanlines != false and scanlines != "false" and scanlines != "off" and scanlines != "no" and scanlines != "0" and scanlines != "" %}<div aria-hidden="true" class="scanlines pointer-events-none absolute inset-0" data-scanlines-local></div>{% endif %}<div class="relative z-10 max-w-5xl mx-auto px-4 text-center">{% if 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));">{{ eyebrow }}</p>{% endif %}<h1 class="glitch cyberpunk-display text-5xl md:text-7xl font-bold leading-[1.05] tracking-tight mb-8" data-text="{{ headline|default:'// hero headline' }}" style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);">{{ headline|default:'// hero headline' }}</h1>{% if sub %}<p class="cyberpunk-sub mx-auto max-w-2xl text-lg md:text-xl mb-10" style="color: hsl(var(--muted-foreground));">{{ sub }}</p>{% endif %}<div class="flex flex-col sm:flex-row gap-4 justify-center items-center">{% if cta.label %}<a href="{{ cta.href|default:'#' }}" 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>{{ cta.label }}</span> <span aria-hidden="true" class="caret-blink">_</span></a>{% endif %}{% if secondaryCta.label %}<a href="{{ secondaryCta.href|default:'#' }}" 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>{{ secondaryCta.label }}</span></a>{% endif %}</div></div></section>

View File

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

View File

@ -1 +0,0 @@
<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 statusDot != false and statusDot != "false" and statusDot != "off" and statusDot != "no" and statusDot != "0" and 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>{% endif %}<span class="cyberpunk-mono text-sm" style="color: hsl(var(--accent));">{{ commandPrefix|default:'~/' }}</span> <span class="cyberpunk-mono text-sm truncate" style="color: hsl(var(--foreground));">{{ menuName|default:'main' }}</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="{{ menuName|default:'main' }}"><li class="cyberpunk-navbar-hint" style="color: hsl(var(--muted-foreground));">// menu items resolve at runtime</li></ul></div></nav>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +0,0 @@
<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 items %}<div class="grid gap-8 text-center {% if items|length == 2 %}grid-cols-1 sm:grid-cols-2{% elif items|length == 3 %}grid-cols-1 sm:grid-cols-3{% elif items|length >= 4 %}grid-cols-1 sm:grid-cols-2 lg:grid-cols-4{% else %}grid-cols-1{% endif %}">{% for stat in 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>{% endif %}</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>{% endfor %}</div>{% else %}<div class="text-center cyberpunk-mono text-xs uppercase tracking-widest" style="color: hsl(var(--muted-foreground));">// add items to render stats</div>{% endif %}</div></section>

View File

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

View File

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

View File

@ -1,41 +1,110 @@
# Cyberpunk theme — codeless .bnp manifest (WO-WZ-021 Phase B).
# 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 BlockNinja theme with glitch motifs, monospace accents, and magenta/cyan/lime tri-accent palette for SaaS, dev tools, and crypto."
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, sticky terminal-style header"
description: "Standard dark page with scanline overlay and a sticky terminal-style header"
slots: [header, main, footer]
- system: cyberpunk
key: landing
title: Landing
description: "Hero with glitch H1, neon CTA, feature grid, social proof, footer"
slots: [hero, features, cta, footer]
- system: cyberpunk
key: article
title: Article
description: "Mono-metadata header, prose column, code-friendly aside"
slots: [header, main, aside, 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
@ -43,51 +112,65 @@ email_wrappers:
css:
input_css_append: |
/* ============================================================
Cyberpunk — Neon-on-black BlockNinja theme
Scanlines / glitch / RGB-split / caret-blink / neon-edge utilities.
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 fallbacks --------------------------------- */
/* --- 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. @font-face for the bundled
families is auto-emitted from fonts.json (source=template rows). */
.cyberpunk-body {
font-family: var(--font-body, "Inter", system-ui, -apple-system, "Segoe UI", sans-serif);
}
.cyberpunk-mono,
.cyberpunk-mono *,
.cyberpunk-mono code,
pre.cyberpunk-mono,
code.cyberpunk-mono {
font-family: var(--font-mono, "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace);
font-family: var(--font-mono, "Share Tech Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace);
}
.cyberpunk-display {
font-family: var(--font-heading, "Space Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif);
letter-spacing: -0.01em;
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, "JetBrains Mono", ui-monospace, monospace);
font-family: var(--font-mono, "Share Tech Mono", ui-monospace, monospace);
font-size: 0.875em;
}
/* --- Scanlines -------------------------------------------- */
.scanlines {
opacity: 0.04;
opacity: 0.05;
background-image: linear-gradient(
to bottom,
hsl(var(--foreground)) 0px,
@ -98,14 +181,14 @@ css:
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 H1/H2 -------------- */
/* --- Glitch (RGB-split text-shadow) on big headings ------- */
.glitch {
position: relative;
text-shadow:
@ -113,55 +196,37 @@ css:
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));
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));
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));
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));
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));
box-shadow: -2px 0 0 hsl(var(--primary)), 2px 0 0 hsl(var(--accent));
transform: translateY(-1px);
}
/* Touch tap-flash fallback (iOS strips :hover after touchend) */
.cyberpunk-btn:active,
.rgb-split:active,
button.cyberpunk-btn:active {
box-shadow:
-2px 0 0 hsl(var(--primary)),
2px 0 0 hsl(var(--accent));
box-shadow: -2px 0 0 hsl(var(--primary)), 2px 0 0 hsl(var(--accent));
}
/* :focus-visible ring uses the ring token, not browser default */
.cyberpunk-btn:focus-visible,
.cyberpunk-btn-ghost:focus-visible,
button:focus-visible,
@ -169,109 +234,144 @@ css:
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; }
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
/* --- Neon edge utilities (magenta / cyan / lime) ---------- */
/* --- 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.25);
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.45);
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.25);
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.45);
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 {
/* Lime accent is the toxic-bloom primary; magenta-noir uses the primary
token. We reuse --ring as a third neon channel so this utility renders
visibly across all three presets. */
box-shadow:
0 0 0 1px hsl(var(--ring) / 0.4),
0 0 18px hsl(var(--ring) / 0.25);
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.45);
box-shadow: 0 0 0 1px hsl(var(--ring) / 0.85), 0 0 28px hsl(var(--ring) / 0.42);
transform: translateY(-1px);
}
/* --- Card override dashed border --------------------------- */
.cyberpunk-card {
/* --- HUD corner brackets ---------------------------------- */
/* Draw two L-brackets (top-left, bottom-right) around any panel. */
.hud-frame {
position: relative;
}
/* --- Status pill / dot animation -------------------------- */
.hud-frame::before,
.hud-frame::after {
content: "";
position: absolute;
width: 14px;
height: 14px;
pointer-events: none;
z-index: 1;
}
.hud-frame::before {
top: -1px;
left: -1px;
border-top: 2px solid hsl(var(--primary));
border-left: 2px solid hsl(var(--primary));
}
.hud-frame::after {
bottom: -1px;
right: -1px;
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; }
}
/* --- Reduced motion: disable glitch, caret, scanline ------ */
/* --- 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));
}
/* --- Reduced motion: kill all motion --------------------- */
@media (prefers-reduced-motion: reduce) {
.glitch,
.caret-blink,
.scanlines,
.cyberpunk-status-dot {
.cyberpunk-status-dot,
.cyberpunk-rain-canvas {
animation: none !important;
animation-duration: 0.01ms !important;
}
.glitch {
text-shadow: none;
}
.scanlines {
opacity: 0;
}
.glitch { text-shadow: none; }
.scanlines { opacity: 0; }
.cyberpunk-rain-canvas { display: none; }
.rgb-split:hover,
.rgb-split:focus-visible {
transform: none;
}
.rgb-split:focus-visible { transform: none; }
}
/* --- High-contrast: kill scanlines ------------------------ */
/* --- High-contrast: kill scanlines + rain ---------------- */
@media (prefers-contrast: more) {
.scanlines {
display: none;
}
.scanlines,
.cyberpunk-rain-canvas { display: none; }
}

View File

@ -1,33 +1,22 @@
[
{
"key": "cyberpunk:default-master",
"title": "Cyberpunk Default Master",
"page_templates": ["default", "article"],
"key": "cyberpunk:site-master",
"title": "Cyberpunk Site Master",
"page_templates": ["default", "full-width", "landing", "article", "blog-index", "contact"],
"blocks": [
{ "block_key": "cyberpunk:navbar_terminal", "title": "Top Nav", "content": { "menuName": "main", "commandPrefix": "~/" }, "slot": "header", "sort_order": 0 },
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main", "placeholder": "// content" }, "slot": "main", "sort_order": 0 },
{ "block_key": "cyberpunk:footer_grid", "title": "Site Footer", "content": { "showStatus": true, "buildHash": "auto" }, "slot": "footer", "sort_order": 0 }
{ "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:landing-master",
"title": "Cyberpunk Landing Master",
"page_templates": ["landing"],
"key": "cyberpunk:auth-master",
"title": "Cyberpunk Auth Master",
"page_templates": ["auth"],
"blocks": [
{ "block_key": "cyberpunk:hero_glitch", "title": "Glitch Hero", "content": { "eyebrow": "NEW", "headline": "Ship like it's 2049", "cta": { "label": "Get the SDK", "href": "#" } }, "slot": "hero", "sort_order": 0 },
{ "block_key": "slot", "title": "Features Slot", "content": { "slotName": "features" }, "slot": "features", "sort_order": 0 },
{ "block_key": "cyberpunk:cta_terminal", "title": "CTA Strip", "content": { "prompt": "$ npm i your-thing", "button": "Copy" }, "slot": "cta", "sort_order": 0 },
{ "block_key": "cyberpunk:footer_grid", "title": "Site Footer", "content": { "showStatus": true }, "slot": "footer", "sort_order": 0 }
]
},
{
"key": "cyberpunk:full-master",
"title": "Cyberpunk Full Master",
"page_templates": ["full-width"],
"blocks": [
{ "block_key": "cyberpunk:navbar_terminal", "title": "Top Nav", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 },
{ "block_key": "slot", "title": "Main Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
{ "block_key": "cyberpunk:footer_grid", "title": "Site Footer", "content": { "showStatus": false }, "slot": "footer", "sort_order": 0 }
{ "block_key": "navbar", "title": "Terminal Navbar", "content": { "menuName": "main", "logoType": "text", "logoText": { "text": "NEON//FORGE" }, "companyName": "Neon Forge" }, "slot": "header", "sort_order": 0 },
{ "block_key": "slot", "title": "Auth Console", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 },
{ "block_key": "footer", "title": "Grid Footer", "content": { "layout": "minimal", "companyName": "Neon Forge", "tagline": "We build games and the tools that ship them.", "copyright": "Neon Forge Collective" }, "slot": "footer", "sort_order": 0 }
]
}
]

View File

@ -7,3 +7,4 @@ description = "Neon-on-black BlockNinja theme with glitch motifs, monospace acce
kind = "theme"
categories = ["templates"]
tags = ["dark", "neon", "glitch", "monospace", "saas", "developer", "tech", "crypto", "fintech"]
required_icon_packs = ["lucide", "simple-icons"]

View File

@ -148,5 +148,105 @@
"ring": "90 100% 55%"
}
}
},
{
"id": "hotline",
"name": "Hotline",
"description": "Sunset pink over teal — vaporwave arcade at dusk",
"theme": {
"mode": "both",
"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",
"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%"
}
}
}
]

219
seed/seed.json Normal file
View File

@ -0,0 +1,219 @@
{
"demo": true,
"settings": {
"merge": {
"companyName": "Neon Forge",
"tagline": "We build games and the tools that ship them."
}
},
"data_tables": [
{
"key": "contact_submissions",
"name": "Contact Enquiries",
"description": "Seeded contact-form submissions for the Neon Forge demo. A submission becomes a row here and emails the site admins.",
"columns": [
{ "key": "name", "label": "Name", "type": "text", "required": true },
{ "key": "email", "label": "Email", "type": "email", "required": true },
{ "key": "studio", "label": "Studio", "type": "text", "required": false },
{ "key": "message", "label": "Message", "type": "text", "required": true }
]
}
],
"pages": [
{
"slug": "/",
"title": "Neon Forge",
"template_key": "landing",
"blocks": [
{
"block_key": "hero",
"title": "Hero",
"slot": "main",
"sort_order": 1,
"content": {
"template": "centered",
"minHeight": "screen",
"eyebrow": "Build 2.049",
"headline": "Ship the impossible",
"subheadline": "A game studio and the tools that ship it.",
"description": "We build a rendering engine, live ops, and a build farm that never sleeps. Then we make games on top of it.",
"ctaPrimary": { "text": "Get the SDK", "url": "/contact" },
"ctaSecondary": { "text": "Read the devlog", "url": "/blog" }
}
},
{
"block_key": "feature-grid",
"title": "What we ship",
"slot": "main",
"sort_order": 2,
"content": {
"eyebrow": "The stack",
"heading": "One pipeline, end to end",
"intro": "Engine, tooling, and live service. Built in house, shipped together.",
"layout": "card",
"columns": 3,
"items": [
{ "icon": "lucide:cpu", "title": "Render engine", "text": "Neon lighting, volumetric fog, and a deferred path tuned for night maps." },
{ "icon": "lucide:terminal", "title": "Build farm", "text": "Shader caching across branches. Green builds in minutes, not hours." },
{ "icon": "lucide:radio", "title": "Live ops", "text": "Ship events, patch balance, and roll back without a redeploy." }
]
}
},
{
"block_key": "cyberpunk:roster_grid",
"title": "The crew",
"slot": "main",
"sort_order": 3,
"content": {
"eyebrow": "The crew",
"heading": "Who ships this",
"intro": "A small team that builds the engine, the game, and the pipeline in between.",
"members": [
{ "name": "Nova Reyes", "handle": "@nova", "role": "Engine lead", "status": true },
{ "name": "Kai Okafor", "handle": "@kaisdf", "role": "Graphics", "status": true },
{ "name": "Sable Lin", "handle": "@sable", "role": "Live ops", "status": false },
{ "name": "Juno Vale", "handle": "@juno", "role": "Gameplay", "status": true },
{ "name": "Rin Castellano", "handle": "@rin", "role": "Tools", "status": true },
{ "name": "Ash Mori", "handle": "@ash", "role": "Netcode", "status": false }
]
}
},
{
"block_key": "cyberpunk:patch_notes",
"title": "Patch notes",
"slot": "main",
"sort_order": 4,
"content": {
"heading": "Patch notes",
"intro": "What changed, build by build.",
"entries": [
{ "version": "v2.049.0", "date": "2049.04.01", "tag": "FEATURE", "items": [ { "text": "Volumetric neon fog on all night maps." }, { "text": "Build farm caches shaders across branches." } ] },
{ "version": "v2.048.3", "date": "2049.03.18", "tag": "FIX", "items": [ { "text": "Fixed a stall when hot-reloading materials." }, { "text": "Reduced desync on high-latency links." } ] },
{ "version": "v2.048.0", "date": "2049.03.02", "tag": "BALANCE", "items": [ { "text": "Tuned dash cooldown from 4s to 3.2s." } ] }
]
}
},
{
"block_key": "cta",
"title": "CTA",
"slot": "main",
"sort_order": 5,
"content": {
"background": "band",
"heading": "Start building tonight",
"text": "Grab the SDK and ship your first build before the coffee runs out.",
"primaryLabel": "Get the SDK",
"primaryUrl": "/contact"
}
}
]
},
{
"slug": "/about",
"title": "The studio",
"template_key": "default",
"blocks": [
{ "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "The studio", "level": 1 } },
{ "block_key": "rich-text", "title": "Story", "slot": "main", "sort_order": 2, "content": { "html": "<p>Neon Forge started as a build tool for a game that shipped late. The tool outlived the game. We kept building it, then built more games to prove it out.</p><p>Now we run both. The engine and live service on one side, the games that stress test them on the other. Everything we make, we run in production first.</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. Repeat.", "attribution": "The studio" } }
]
},
{
"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 images, just a small script that draws falling glyphs.</p><p>The trick is to fade the last frame instead of clearing it. That leaves a trail. Cap the frame rate and it stays cheap, even on a laptop.</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. That is minutes per build, times every push.</p><p>We keyed the cache on the shader source, not the branch. Two branches that share a shader now share the compile. Builds got short again.</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. Rewriting the simulation was off the table.</p><p>We added client prediction with a short rollback window. The client guesses, the server corrects, and the gap closes before anyone notices.</p>" } }
]
},
{
"slug": "/contact",
"title": "Contact",
"template_key": "contact",
"blocks": [
{
"block_key": "contact-form",
"title": "Contact form",
"slot": "main",
"sort_order": 1,
"content": {
"heading": "Request access",
"description": "Tell us what you are building. We reply within one business day.",
"submitLabel": "Request access",
"successMessage": "Message received. We will reply within one business day.",
"fields": [
{ "name": "name", "label": "Your name", "type": "text", "required": true, "width": "half" },
{ "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" },
{ "name": "studio", "label": "Studio", "type": "text", "required": false, "width": "full" },
{ "name": "message", "label": "What are you building", "type": "textarea", "required": true, "width": "full" }
],
"formConfig": { "enabled": true, "captchaEnabled": false, "targetTable": "contact_submissions" }
}
},
{
"block_key": "contact-card",
"title": "Reach us",
"slot": "main",
"sort_order": 2,
"content": {
"heading": "Other ways in",
"columns": 3,
"channels": [
{ "icon": "lucide:mail", "label": "Email", "value": "crew@neonforge.dev", "link": "mailto:crew@neonforge.dev" },
{ "icon": "simple-icons:github", "label": "Source", "value": "github.com/neon-forge", "link": "https://github.com/neon-forge" },
{ "icon": "lucide:message-square", "label": "Discord", "value": "Join the build channel", "link": "#" }
]
}
}
]
},
{
"slug": "/login",
"title": "Sign in",
"template_key": "auth",
"blocks": [
{ "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } }
]
}
],
"menu_items": [
{ "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 },
{ "menu": "main", "label": "Studio", "page_slug": "/about", "sort_order": 2 },
{ "menu": "main", "label": "Devlog", "page_slug": "/blog", "sort_order": 3 },
{ "menu": "main", "label": "Contact", "page_slug": "/contact", "sort_order": 4 }
]
}

19
seed/workflows.json Normal file
View File

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

View File

@ -1,11 +1,14 @@
<!doctype html>
<html lang="en" class="dark">
<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));">
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, &quot;Inter&quot;, system-ui, sans-serif);">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
<header class="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">{{ slots.header|safe }}</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">{{ slots.main|safe }}</article><aside class="cyberpunk-aside cyberpunk-mono text-sm" style="color: hsl(var(--muted-foreground));">{{ slots.aside|safe }}</aside></main>
<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 grid gap-10 lg:grid-cols-[1fr_260px]">
<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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,11 @@
<!doctype html>
<html lang="en" class="dark">
<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));">
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, &quot;Inter&quot;, system-ui, sans-serif);">
{{ admin_banner_html|safe }}
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
<section class="w-full">{{ slots.hero|safe }}</section>
<section class="w-full max-w-6xl mx-auto px-4 py-16">{{ slots.features|safe }}</section>
<section class="w-full">{{ slots.cta|safe }}</section>
<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>

View File

@ -5,41 +5,45 @@
<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 { 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>
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 bgcolor="#0a0a12" style="margin:0;padding:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;">
{% if preview_text %}<div class="cyberpunk-preheader" style="display:none;max-height:0;overflow:hidden;mso-hide:all;font-family: ui-monospace, SFMono-Regular, Menlo, monospace;">$ from: {{ site_name }} — {{ preview_text }}</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: {{ site_name }}</div>{% endif %}
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="#0a0a12">
<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: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%);">
<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="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: {{ site_name }}</div>
{% if logo_url %}<img src="{{ logo_url }}" alt="{{ site_name }}" style="margin-top:12px;max-height:48px;width:auto;display:inline-block;">{% endif %}
<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;font-size:16px;line-height:1.7;">{{ body|safe }}</td>
<td class="content-padding" style="padding: 40px 48px; color: {{ colors.foreground }}; font-size: 16px; line-height: 1.7;">{{ body|safe }}</td>
</tr>
<tr>
<td class="cyberpunk-divider" height="1" style="border-top:1px solid hsl(320 100% 60%);font-size:0;line-height:0;">&nbsp;</td>
<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 40px;font-family: ui-monospace, SFMono-Regular, Menlo, monospace;font-size:12px;letter-spacing:0.15em;text-transform:uppercase;">
{% if site_url %}<p style="margin:0 0 12px;"><a href="{{ site_url }}" style="text-decoration:none;">{{ site_url }}</a></p>{% endif %}
{% if unsubscribe_url %}<p style="margin:0;"><a href="{{ unsubscribe_url }}" style="text-decoration:none;">[esc] unsubscribe</a></p>{% else %}<p style="margin:0;">[esc] unsubscribe</p>{% endif %}
<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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1 +1,2 @@
{% if variant == "secondary" %}<a href="{{ href|default:'#' }}" 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|default:'Button' }}</span> <span aria-hidden="true" class="caret-blink">_</span></a>{% elif variant == "ghost" %}<a href="{{ href|default:'#' }}" 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|default:'Button' }}</span> <span aria-hidden="true" class="caret-blink">_</span></a>{% else %}<a href="{{ href|default:'#' }}" 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|default:'Button' }}</span> <span aria-hidden="true" class="caret-blink">_</span></a>{% endif %}
{# 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 %}

View File

@ -1 +1,2 @@
{% if href %}<a href="{{ href|default:'#' }}" 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 media %}<img src="{{ media }}" alt="" class="cyberpunk-card-media w-full h-40 object-cover rounded mb-4" onerror="this.style.display='none'">{% endif %}{% 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>{% endif %}{% if body %}<div class="cyberpunk-card-body text-sm leading-relaxed" style="color: hsl(var(--muted-foreground));">{{ body|safe }}</div>{% endif %}</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));">{% if media %}<img src="{{ media }}" alt="" class="cyberpunk-card-media w-full h-40 object-cover rounded mb-4" onerror="this.style.display='none'">{% endif %}{% 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>{% endif %}{% if body %}<div class="cyberpunk-card-body text-sm leading-relaxed" style="color: hsl(var(--muted-foreground));">{{ body|safe }}</div>{% endif %}</article>{% endif %}
{# cyberpunk card — dashed neon panel; fields layout/link/media/title/text/linkLabel #}
{% with layout=layout|default:"vertical" %}<article class="bn-card cyberpunk-card overflow-hidden rounded-md border border-dashed border-primary/40 bg-card text-card-foreground neon-edge-magenta {% if layout == "horizontal" %}flex flex-col sm:flex-row{% endif %}{% if class %} {{ class }}{% endif %}">{% if link %}<a href="{{ link }}" class="contents">{% endif %}{% if media %}<div class="overflow-hidden bg-muted {% if layout == "horizontal" %}sm:w-2/5 sm:shrink-0{% endif %}">{% if layout == "horizontal" %}{% img media alt=title|default:"" class="h-full w-full object-cover" %}{% else %}{% img media alt=title|default:"" class="aspect-video h-full w-full object-cover" %}{% endif %}</div>{% endif %}<div class="flex flex-col gap-2 p-5 md:p-6 {% if layout == "horizontal" %}justify-center{% endif %}">{% if title %}<h3 class="text-lg font-semibold tracking-tight text-foreground cyberpunk-display">{{ title }}</h3>{% endif %}{% if text %}<div class="text-sm leading-relaxed text-muted-foreground cyberpunk-body">{{ text|safe }}</div>{% endif %}{% if linkLabel and link %}<span class="mt-2 inline-flex items-center gap-1 text-sm font-medium text-primary cyberpunk-mono uppercase tracking-wide">{{ linkLabel }}::lucide:arrow-right:sm::</span>{% endif %}</div>{% if link %}</a>{% endif %}</article>{% endwith %}

View File

@ -0,0 +1,18 @@
{# category-list (cyberpunk override) — from the `categories` provider. list or pills. #}
{% set pills = style == "pills" %}
<section class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
{% if heading %}<h2 class="cyberpunk-eyebrow cyberpunk-mono mb-4 text-xs font-semibold uppercase tracking-widest text-accent">{{ heading }}</h2>{% endif %}
{% if pills %}
<div class="flex flex-wrap gap-2">
{% for cat in categories %}
<a href="{{ cat.url }}" class="clip-cut inline-flex items-center gap-1.5 border border-primary/50 bg-card px-3 py-1 cyberpunk-mono text-sm uppercase tracking-wide text-card-foreground neon-edge-magenta transition-colors hover:text-primary">{{ cat.name }}{% if showCount %}<span class="text-xs text-muted-foreground">{{ cat.post_count }}</span>{% endif %}</a>
{% endfor %}
</div>
{% else %}
<ul class="space-y-1">
{% for cat in categories %}
<li><a href="{{ cat.url }}" class="flex items-center justify-between rounded-md px-3 py-2 cyberpunk-mono text-sm uppercase tracking-wide text-muted-foreground transition-colors hover:bg-accent hover:text-primary"><span>{{ cat.name }}</span>{% if showCount %}<span class="rounded-full bg-muted px-2 py-0.5 text-xs text-muted-foreground">{{ cat.post_count }}</span>{% endif %}</a></li>
{% endfor %}
</ul>
{% endif %}
</section>

View File

@ -0,0 +1,18 @@
{# contact-card cyberpunk override: neon channel panels. All field reads + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-4xl 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 description %}<p class="mt-3 max-w-2xl text-lg text-muted-foreground">{{ description }}</p>{% endif %}
<div class="mt-10 grid grid-cols-1 gap-4 {% if columns == 3 %}sm:grid-cols-2 lg:grid-cols-3{% elif columns == 1 %}{% else %}sm:grid-cols-2{% endif %}">
{% for channel in channels %}
<div class="hud-frame flex items-start gap-4 rounded-md border border-primary/40 bg-card p-5 text-card-foreground neon-edge-magenta">
{% if channel.icon %}<span class="clip-cut inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ channel.icon|split:":"|first }}.svg#{{ channel.icon|split:":"|last }}"></use></svg></span>{% endif %}
<div class="min-w-0">
{% if channel.label %}<div class="cyberpunk-mono text-sm font-medium uppercase tracking-wide text-accent">{{ channel.label }}</div>{% endif %}
{% if channel.value %}<div class="mt-0.5 break-words font-medium">{% if channel.link %}<a href="{{ channel.link }}" class="transition-colors hover:text-primary">{{ channel.value }}</a>{% else %}{{ channel.value }}{% endif %}</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>

View File

@ -0,0 +1,40 @@
{# contact-form cyberpunk override: terminal console chrome. The {% form %} block, honeypot, field loop, form.submitted/errors reads and _block_id/bid submit wiring are preserved byte-for-byte — only presentational classes change. #}
{% with bid=_block_id|default:context.blockId %}
<section id="contact-form-{{ bid }}" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-2xl px-4">
{% if form.submitted %}
<div class="hud-frame rounded-md border border-primary/50 bg-card p-8 text-center text-card-foreground neon-edge-magenta">
<div class="clip-cut mx-auto mb-4 inline-flex h-12 w-12 items-center justify-center rounded-md bg-primary/10 text-primary"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg></div>
<p class="cyberpunk-display text-lg font-medium">{{ form.message|default:successMessage|default:"Thanks — your message has been sent." }}</p>
</div>
{% else %}
{% if heading %}<h2 class="cyberpunk-display glitch text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if description %}<p class="mt-3 text-lg text-muted-foreground">{{ description }}</p>{% endif %}
{% if form.errors._form %}<div class="mt-6 rounded-md border border-destructive/50 bg-destructive/10 px-4 py-3 text-sm text-destructive">{{ form.errors._form }}</div>{% endif %}
{% with formAction="/api/blocks/"|add:bid|add:"/submit" formTarget="#contact-form-"|add:bid %}
{% form action=formAction hx_target=formTarget hx_swap="outerHTML" class="mt-8 grid grid-cols-1 gap-5 sm:grid-cols-2" %}
{% for field in fields %}
<div class="{% if field.width == "half" %}sm:col-span-1{% else %}sm:col-span-2{% endif %}">
{% if field.label %}<label for="cf-{{ bid }}-{{ field.name }}" class="cyberpunk-mono mb-1.5 block text-sm font-medium uppercase tracking-wide text-accent">{{ field.label }}{% if field.required %} <span class="text-destructive">*</span>{% endif %}</label>{% endif %}
{% if field.type == "textarea" %}
<textarea id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}" rows="5"{% if field.required %} required{% endif %}{% if field.placeholder %} placeholder="{{ field.placeholder }}"{% endif %} class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"></textarea>
{% elif field.type == "select" %}
<select id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}"{% if field.required %} required{% endif %} 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">
{% if field.placeholder %}<option value="">{{ field.placeholder }}</option>{% endif %}
{% for opt in field.options %}<option value="{{ opt.value }}">{{ opt.label|default:opt.value }}</option>{% endfor %}
</select>
{% else %}
<input id="cf-{{ bid }}-{{ field.name }}" name="{{ field.name }}" type="{{ field.type|default:"text" }}"{% if field.required %} required{% endif %}{% if field.placeholder %} placeholder="{{ field.placeholder }}"{% endif %} class="w-full rounded-md border border-primary/40 bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring">
{% endif %}
</div>
{% endfor %}
<div class="hidden" aria-hidden="true"><label>Leave this field empty<input type="text" name="website" tabindex="-1" autocomplete="off"></label></div>
<div class="sm:col-span-2">
<button type="submit" class="cyberpunk-mono clip-cut rgb-split inline-flex items-center justify-center rounded-md bg-primary px-6 py-2.5 text-sm font-medium uppercase tracking-wider text-primary-foreground transition-colors hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2">{{ submitLabel|default:"Send message" }}</button>
</div>
{% endform %}
{% endwith %}
{% endif %}
</div>
</section>
{% endwith %}

View File

@ -0,0 +1,50 @@
{# countdown cyberpunk override: neon HUD digit boxes, glitch heading. Timer script + data attributes preserved byte-for-byte. #}
<section class="relative overflow-hidden py-16 md:py-24 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 text-center">
{% 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="mx-auto mt-4 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10" data-countdown>
<div class="flex items-start justify-center gap-3 sm:gap-6" data-countdown-timer data-target="{{ target }}" role="timer" aria-label="Time remaining">
<div class="flex flex-col items-center {% if variant == "boxes" %}clip-cut min-w-[4.5rem] rounded-md border border-primary/50 bg-card px-3 py-4 text-card-foreground neon-edge-magenta{% endif %}"><span class="cyberpunk-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="days">--</span><span class="cyberpunk-mono mt-1 text-xs font-medium uppercase tracking-widest text-muted-foreground">{{ labelDays|default:"Days" }}</span></div>
<div class="flex flex-col items-center {% if variant == "boxes" %}clip-cut min-w-[4.5rem] rounded-md border border-primary/50 bg-card px-3 py-4 text-card-foreground neon-edge-magenta{% endif %}"><span class="cyberpunk-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="hours">--</span><span class="cyberpunk-mono mt-1 text-xs font-medium uppercase tracking-widest text-muted-foreground">{{ labelHours|default:"Hours" }}</span></div>
<div class="flex flex-col items-center {% if variant == "boxes" %}clip-cut min-w-[4.5rem] rounded-md border border-primary/50 bg-card px-3 py-4 text-card-foreground neon-edge-magenta{% endif %}"><span class="cyberpunk-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="minutes">--</span><span class="cyberpunk-mono mt-1 text-xs font-medium uppercase tracking-widest text-muted-foreground">{{ labelMinutes|default:"Minutes" }}</span></div>
<div class="flex flex-col items-center {% if variant == "boxes" %}clip-cut min-w-[4.5rem] rounded-md border border-primary/50 bg-card px-3 py-4 text-card-foreground neon-edge-magenta{% endif %}"><span class="cyberpunk-mono text-4xl font-bold tabular-nums sm:text-5xl text-primary" data-unit="seconds">--</span><span class="cyberpunk-mono mt-1 text-xs font-medium uppercase tracking-widest text-muted-foreground">{{ labelSeconds|default:"Seconds" }}</span></div>
</div>
<div class="mt-8 hidden" data-countdown-expired>
<p class="cyberpunk-display text-xl font-medium">{{ expiredMessage|default:"This has started." }}</p>
{% if expiredUrl and expiredLabel %}<a href="{{ expiredUrl }}" class="cyberpunk-mono clip-cut rgb-split mt-4 inline-flex items-center gap-2 rounded-md bg-primary px-5 py-3 font-semibold uppercase tracking-wider text-primary-foreground transition-opacity hover:opacity-90">{{ expiredLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
</div>
</div>
<noscript><p class="mt-6 text-muted-foreground">Counting down to <time datetime="{{ target }}">{{ target }}</time>.</p></noscript>
</div>
</section>
<script>
(function(){
var timers = document.querySelectorAll('[data-countdown-timer]');
timers.forEach(function(el){
var target = new Date(el.getAttribute('data-target')).getTime();
if (isNaN(target)) return;
var wrap = el.closest('[data-countdown]');
var expired = wrap ? wrap.querySelector('[data-countdown-expired]') : null;
function pad(n){ return (n < 10 ? '0' : '') + n; }
function set(unit, value){ var node = el.querySelector('[data-unit="' + unit + '"]'); if (node) node.textContent = value; }
var interval;
function tick(){
var diff = target - Date.now();
if (diff <= 0){
el.style.display = 'none';
if (expired) expired.classList.remove('hidden');
if (interval) clearInterval(interval);
return;
}
set('days', Math.floor(diff / 86400000));
set('hours', pad(Math.floor(diff % 86400000 / 3600000)));
set('minutes', pad(Math.floor(diff % 3600000 / 60000)));
set('seconds', pad(Math.floor(diff % 60000 / 1000)));
}
tick();
interval = setInterval(tick, 1000);
});
})();
</script>

View File

@ -0,0 +1,16 @@
{# cta cyberpunk override: scanline overlay, glitch heading, clip-cut rgb-split CTAs. All field reads preserved. #}
<section class="relative overflow-hidden py-16 md:py-24 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-6xl px-4">
<div class="hud-frame {% if background == "band" %}clip-cut rounded-md bg-primary px-6 py-14 text-center text-primary-foreground md:px-12{% elif background == "card" %}rounded-md border border-primary/50 bg-card px-6 py-14 text-center text-card-foreground neon-edge-magenta md:px-12{% else %}flex flex-col items-center gap-8 rounded-md border border-primary/30 bg-muted px-6 py-12 text-foreground md:flex-row md:justify-between md:px-12 md:text-left{% endif %}">
<div class="{% if background != "split" %}mx-auto max-w-2xl{% endif %}">
{% if heading %}<h2 class="cyberpunk-display glitch text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if text %}<p class="mt-3 text-lg {% if background == "band" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}">{{ text }}</p>{% endif %}
</div>
<div class="flex flex-wrap items-center justify-center gap-3 {% if background != "split" %}mt-8{% else %}shrink-0{% endif %}">
{% if primaryUrl and primaryLabel %}<a href="{{ primaryUrl }}" class="cyberpunk-mono clip-cut rgb-split inline-flex items-center gap-2 rounded-md px-6 py-3 font-semibold uppercase tracking-wider transition-opacity hover:opacity-90 {% if background == "band" %}bg-background text-foreground{% else %}bg-primary text-primary-foreground{% endif %}">{{ primaryLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
{% if secondaryUrl and secondaryLabel %}<a href="{{ secondaryUrl }}" class="cyberpunk-mono inline-flex items-center rounded-md border px-6 py-3 font-semibold uppercase tracking-wider transition-colors {% if background == "band" %}border-primary-foreground/40 text-primary-foreground hover:bg-primary-foreground/10{% else %}border-accent/60 text-accent hover:bg-accent/10{% endif %}">{{ secondaryLabel }}</a>{% endif %}
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,2 @@
{# cyberpunk divider — neon gradient rule / primary-tinted variants #}
{% with variant=variant|default:"line" weight=weight|default:"thin" width=width|default:"full" %}{% if variant == "ornament" %}<div class="bn-divider mx-auto my-8 flex items-center gap-4 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><span class="h-px flex-1 bg-gradient-to-r from-transparent via-primary to-transparent"></span><span class="shrink-0 text-primary">::{{ icon|default:"lucide:asterisk" }}:md text-primary::</span><span class="h-px flex-1 bg-gradient-to-l from-transparent via-primary to-transparent"></span></div>{% elif variant == "spacer-rule" %}<div class="bn-divider mx-auto py-10 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator"><hr class="mx-auto w-16 border-0 border-t {% if weight == "thick" %}border-t-4{% elif weight == "medium" %}border-t-2{% endif %} border-primary/60"></div>{% else %}<hr class="bn-divider mx-auto my-8 border-0 border-t {% if weight == "thick" %}border-t-4{% elif weight == "medium" %}border-t-2{% endif %} border-primary/60 {% if width == "narrow" %}max-w-[200px]{% elif width == "wide" %}max-w-3xl{% else %}w-full{% endif %}{% if class %} {{ class }}{% endif %}" role="separator">{% endif %}{% endwith %}

View File

@ -0,0 +1,41 @@
{# event-list cyberpunk override: neon cards, mono datetime. All field reads + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl 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 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
{% if layout == "cards" %}
<div class="mt-10 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{% for event in events %}{% if not (hidePast and event.past) %}
<article class="hud-frame flex flex-col overflow-hidden rounded-md border border-primary/40 bg-card text-card-foreground neon-edge-magenta{% if event.past %} opacity-60{% endif %}">
{% if event.media %}<div class="aspect-video overflow-hidden bg-muted">{% img event.media alt=event.title class="h-full w-full object-cover" %}</div>{% endif %}
<div class="flex flex-1 flex-col gap-2 p-5">
<div class="cyberpunk-mono text-sm font-medium uppercase tracking-wide text-primary"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}{% if event.end %} &ndash; {{ event.end }}{% endif %}</time>{% if event.timezone %} <span class="text-muted-foreground">{{ event.timezone }}</span>{% endif %}</div>
<h3 class="cyberpunk-display text-lg font-semibold">{{ event.title }}</h3>
{% if event.venue %}<p class="text-sm text-muted-foreground">{{ event.venue }}</p>{% endif %}
{% if event.description %}<p class="text-sm text-muted-foreground">{{ event.description }}</p>{% endif %}
{% if event.link %}<a href="{{ event.link }}" class="cyberpunk-mono mt-auto inline-flex items-center gap-1 pt-2 text-sm font-medium uppercase tracking-wide text-primary hover:underline">{{ event.linkLabel|default:"Details" }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
</div>
</article>
{% endif %}{% endfor %}
</div>
{% else %}
<ul class="mt-10 divide-y divide-primary/30 border-y border-primary/30">
{% for event in events %}{% if not (hidePast and event.past) %}
<li class="flex flex-col gap-3 py-5 sm:flex-row sm:items-center sm:gap-6{% if event.past %} opacity-60{% endif %}">
<div class="shrink-0 sm:w-52">
<div class="cyberpunk-mono text-sm font-semibold text-primary"><time{% if event.datetime %} datetime="{{ event.datetime }}"{% endif %}>{{ event.start }}</time></div>
{% if event.end %}<div class="cyberpunk-mono text-xs text-muted-foreground">to {{ event.end }}</div>{% endif %}
{% if event.timezone %}<div class="cyberpunk-mono text-xs text-muted-foreground">{{ event.timezone }}</div>{% endif %}
</div>
<div class="min-w-0 flex-1">
<h3 class="cyberpunk-display text-base font-semibold">{{ event.title }}</h3>
{% if event.venue %}<p class="text-sm text-muted-foreground">{{ event.venue }}</p>{% endif %}
{% if event.description %}<p class="mt-1 text-sm text-muted-foreground">{{ event.description }}</p>{% endif %}
</div>
{% if event.link %}<a href="{{ event.link }}" class="cyberpunk-mono clip-cut inline-flex shrink-0 items-center rounded-md border border-primary/50 px-3 py-1.5 text-sm font-medium uppercase tracking-wide text-primary hover:bg-primary/10">{{ event.linkLabel|default:"Details" }}</a>{% endif %}
</li>
{% endif %}{% endfor %}
</ul>
{% endif %}
</div>
</section>

View File

@ -0,0 +1,18 @@
{# faq cyberpunk override: glitch heading, neon-tinted dividers, display questions. Chevron svg, {% for %}, and answer prose preserved. #}
<section 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-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-10 divide-y divide-primary/20 border-y border-primary/30">
{% for item in items %}
<details class="group py-2"{% if variant == "open-list" %} open{% endif %}>
<summary class="flex cursor-pointer list-none items-center justify-between gap-4 py-3 text-lg font-medium marker:content-none [&::-webkit-details-marker]:hidden">
<span class="cyberpunk-display">{{ item.question }}</span>
<svg class="h-5 w-5 shrink-0 text-primary transition-transform duration-200 group-open:rotate-180" aria-hidden="true"><use href="/icons/lucide.svg#chevron-down"></use></svg>
</summary>
<div class="pb-4 text-muted-foreground [&_a]:text-primary [&_a]:underline">{{ item.answer|safe }}</div>
</details>
{% endfor %}
</div>
</div>
</section>

View File

@ -0,0 +1,19 @@
{# feature_grid cyberpunk override: scanline hero overlay, mono eyebrow, glitch heading, neon HUD cards. All field reads + control flow preserved. #}
<section class="relative overflow-hidden py-16 md:py-24 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-6xl px-4">
{% if eyebrow %}<p class="cyberpunk-mono text-center text-xs font-semibold uppercase tracking-widest text-accent">{{ 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 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}">
{% for item in items %}
<div class="{% if layout == "card" %}hud-frame rounded-md border border-primary/40 bg-card p-6 text-card-foreground neon-edge-magenta transition-transform hover:-translate-y-0.5{% elif layout == "centered" %}text-center{% endif %}">
{% if item.icon %}<div class="clip-cut mb-4 inline-flex h-11 w-11 items-center justify-center rounded-md bg-primary/10 text-primary{% if layout == "centered" %} mx-auto{% endif %}"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg></div>{% endif %}
<h3 class="cyberpunk-display text-lg font-semibold">{% if item.linkUrl %}<a href="{{ item.linkUrl }}" class="transition-colors hover:text-primary">{{ item.title }}</a>{% else %}{{ item.title }}{% endif %}</h3>
{% if item.text %}<p class="mt-2 text-muted-foreground">{{ item.text }}</p>{% endif %}
{% if item.linkUrl and item.linkLabel %}<a href="{{ item.linkUrl }}" class="cyberpunk-mono uppercase tracking-wide mt-3 inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline">{{ item.linkLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
</div>
{% endfor %}
</div>
</div>
</section>

View File

@ -0,0 +1,23 @@
{# featured-posts (cyberpunk override) — grid of cards from the `posts` provider #}
{# (params: limit, featured=true). Neon HUD card: image, title, excerpt, meta. #}
<section class="py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="cyberpunk-display glitch mb-8 text-2xl font-semibold tracking-tight md:text-3xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}">
{% for post in posts %}
<article class="hud-frame group flex flex-col overflow-hidden rounded-md border border-dashed border-primary/40 bg-card text-card-foreground neon-edge-magenta transition-transform hover:-translate-y-0.5">
{% if showFeaturedImage and post.featured_image_url %}<a href="{{ post.url }}" class="block aspect-video overflow-hidden bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
<div class="flex flex-1 flex-col p-5">
<h3 class="cyberpunk-display text-lg font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
{% if showExcerpt and post.excerpt %}<p class="cyberpunk-body mt-2 flex-1 text-sm text-muted-foreground">{{ post.excerpt }}</p>{% endif %}
<div class="mt-4 flex flex-wrap items-center gap-x-3 gap-y-1 cyberpunk-mono text-xs uppercase tracking-wide text-accent">
{% if showAuthor and post.author_name %}<span class="font-medium text-foreground">{{ post.author_name }}</span>{% endif %}
{% if showDate and post.published_at %}<span>{{ post.published_at|date:"M j, Y" }}</span>{% endif %}
{% if post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}
</div>
</div>
</article>
{% endfor %}
</div>
</div>
</section>

View File

@ -0,0 +1,39 @@
{# footer (WO-TF-006): layouts columns|minimal|centered; menu columns, legal bar, and social row from the `footer_menus` provider; newsletter section reuses the {% signup_form %} core (no duplicated form). Cyberpunk override: mono column headers, neon top rule, display wordmark. #}
{% set fl = layout %}
{% if not fl %}{% if template == "centered" %}{% set fl = "centered" %}{% else %}{% set fl = "columns" %}{% endif %}{% endif %}
{% set fbg = backgroundColor|default:"bg-muted/50" %}
{% macro sociallinks(cls) %}{% if social_items %}<div class="flex items-center gap-3 {{ cls }}">{% for s in social_items %}<a href="{{ s.url }}" target="_blank" rel="noopener" title="{{ s.label }}" class="text-muted-foreground hover:text-primary transition-colors"><span class="sr-only">{{ s.label }}</span>{% if s.icon %}::{{ s.icon }}::{% else %}{{ s.label }}{% endif %}</a>{% endfor %}</div>{% endif %}{% endmacro %}
{% macro newsletter() %}{% if showNewsletter %}<div class="max-w-sm"><h3 class="cyberpunk-mono uppercase tracking-widest text-xs font-semibold text-accent">{{ newsletterTitle|default:"Stay in the loop" }}</h3>{% if newsletterDescription %}<p class="mt-1 text-sm text-muted-foreground">{{ newsletterDescription }}</p>{% endif %}<div class="mt-3">{% signup_form list=newsletterListSlug|default:"main" source="footer" submit=newsletterButtonText|default:"Subscribe" %}</div></div>{% endif %}{% endmacro %}
{% macro legalbar() %}<div class="mt-10 pt-6 border-t border-primary/30 flex flex-col sm:flex-row items-center justify-between gap-3 text-sm text-muted-foreground"><p class="cyberpunk-mono">{% if copyright %}&copy; {{ copyright }}{% else %}&copy; {{ companyName }}{% endif %}</p>{% if legal_items %}<nav class="flex flex-wrap items-center gap-x-4 gap-y-1">{% for l in legal_items %}<a href="{{ l.url|default:'#' }}"{% if l.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="hover:text-primary transition-colors">{{ l.label }}</a>{% endfor %}</nav>{% endif %}</div>{% endmacro %}
<footer class="w-full border-t border-primary/30 {{ fbg }} {{ class }}">
<div class="{% if fullWidth %}w-full px-4 sm:px-6 lg:px-8{% else %}max-w-7xl mx-auto px-4 sm:px-6 lg:px-8{% endif %} py-16">
{% if fl == "minimal" %}
<div class="flex flex-col sm:flex-row items-center justify-between gap-6">
<div class="flex items-center gap-3">{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-8 w-auto"/>{% elif companyName %}<span class="cyberpunk-display text-lg font-bold text-foreground">{{ companyName }}</span>{% endif %}{% if tagline %}<span class="text-sm text-muted-foreground hidden sm:inline">{{ tagline }}</span>{% endif %}</div>
{{ sociallinks("") }}
</div>
{{ legalbar() }}
{% elif fl == "centered" %}
<div class="flex flex-col items-center text-center gap-5">
{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-10 w-auto"/>{% elif companyName %}<span class="cyberpunk-display text-xl font-bold text-foreground">{{ companyName }}</span>{% endif %}
{% if tagline %}<p class="max-w-md text-sm text-muted-foreground">{{ tagline }}</p>{% endif %}
{% if columns %}<nav class="flex flex-wrap items-center justify-center gap-x-6 gap-y-2">{% for col in columns %}{% for item in col.items %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="cyberpunk-mono uppercase tracking-wide text-xs text-muted-foreground hover:text-primary transition-colors">{{ item.label }}</a>{% endfor %}{% endfor %}</nav>{% endif %}
{{ newsletter() }}
{{ sociallinks("justify-center") }}
</div>
{{ legalbar() }}
{% else %}
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-8">
<div class="col-span-2 lg:col-span-2">
{% if logo %}<img src="{{ logo }}" alt="{{ companyName }}" class="h-9 w-auto"/>{% elif companyName %}<span class="cyberpunk-display text-xl font-bold text-foreground">{{ companyName }}</span>{% endif %}
{% if tagline %}<p class="mt-3 max-w-xs text-sm text-muted-foreground">{{ tagline }}</p>{% endif %}
{% if address or email or phone %}<address class="mt-4 not-italic text-sm text-muted-foreground space-y-1">{% if address %}<div>{{ address }}</div>{% endif %}{% if email %}<div><a href="mailto:{{ email }}" class="hover:text-primary transition-colors">{{ email }}</a></div>{% endif %}{% if phone %}<div><a href="tel:{{ phone }}" class="hover:text-primary transition-colors">{{ phone }}</a></div>{% endif %}</address>{% endif %}
{{ sociallinks("mt-5") }}
</div>
{% for col in columns %}<div><h3 class="cyberpunk-mono uppercase tracking-widest text-xs font-semibold text-accent">{{ col.header }}</h3><ul class="mt-3 space-y-2">{% for item in col.items %}<li><a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="text-sm text-muted-foreground hover:text-primary transition-colors">{{ item.label }}</a></li>{% endfor %}</ul></div>{% endfor %}
{% if showNewsletter %}<div class="col-span-2 lg:col-span-1">{{ newsletter() }}</div>{% endif %}
</div>
{{ legalbar() }}
{% endif %}
</div>
</footer>

View File

@ -0,0 +1,52 @@
{# gallery cyberpunk override: neon-edged tiles. Lightbox script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
{% with layout=layout|default:"grid" gap=gap|default:"md" cols=columns|default:3 %}<div class="bn-gallery my-6{% if class %} {{ class }}{% endif %}" data-bn-gallery data-lightbox="{% if lightbox %}true{% else %}false{% endif %}"><div class="{% if layout == "carousel" %}flex snap-x snap-mandatory overflow-x-auto pb-2 {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% elif layout == "masonry" %}columns-2 {% if cols == 4 %}md:columns-4{% elif cols == 2 %}md:columns-2{% else %}md:columns-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% else %}grid grid-cols-2 {% if cols == 4 %}md:grid-cols-4{% elif cols == 2 %}md:grid-cols-2{% else %}md:grid-cols-3{% endif %} {% if gap == "sm" %}gap-2{% elif gap == "lg" %}gap-6{% else %}gap-4{% endif %}{% endif %}">{% for image in images %}<a href="{{ image.src|mediaURL }}" data-bn-gallery-item data-caption="{{ image.caption|default:"" }}" class="group relative block overflow-hidden rounded-md border border-primary/30 bg-muted neon-edge-magenta focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 {% if layout == "carousel" %}w-64 shrink-0 snap-start sm:w-80{% elif layout == "masonry" %}mb-4 w-full break-inside-avoid{% endif %}">{% if layout == "grid" %}{% img image.src alt=image.alt|default:"" class="aspect-square w-full object-cover" %}{% else %}{% img image.src alt=image.alt|default:"" class="h-auto w-full" %}{% endif %}{% if image.caption %}<span class="sr-only">{{ image.caption }}</span>{% endif %}</a>{% endfor %}</div></div>{% if lightbox %}<script>
(function(){
if(window.__bnLightbox)return;window.__bnLightbox=true;
var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var ov,imgEl,capEl,items=[],idx=0,lastFocus=null;
function build(){
ov=document.createElement('div');
ov.setAttribute('role','dialog');ov.setAttribute('aria-modal','true');ov.setAttribute('aria-label','Image viewer');ov.tabIndex=-1;
ov.style.cssText='position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,0.9);padding:2rem;';
var btn='position:absolute;width:2.75rem;height:2.75rem;display:inline-flex;align-items:center;justify-content:center;border-radius:9999px;border:none;background:hsl(var(--background));color:hsl(var(--foreground));cursor:pointer;';
ov.innerHTML=
'<button type="button" data-lb-close aria-label="Close" style="'+btn+'top:1rem;right:1rem;"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#x"/></svg></button>'+
'<button type="button" data-lb-prev aria-label="Previous image" style="'+btn+'left:1rem;top:50%;transform:translateY(-50%);"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#chevron-left"/></svg></button>'+
'<figure style="margin:0;max-width:92vw;max-height:92vh;display:flex;flex-direction:column;align-items:center;gap:0.75rem;"><img data-lb-img alt="" style="max-width:92vw;max-height:82vh;object-fit:contain;border-radius:0.5rem;'+(reduce?'':'transition:opacity .2s ease;')+'"><figcaption data-lb-cap style="color:hsl(var(--background));opacity:.85;font-size:.875rem;text-align:center;max-width:60ch;"></figcaption></figure>'+
'<button type="button" data-lb-next aria-label="Next image" style="'+btn+'right:1rem;top:50%;transform:translateY(-50%);"><svg style="width:1.25rem;height:1.25rem" aria-hidden="true"><use href="/icons/lucide.svg#chevron-right"/></svg></button>';
document.body.appendChild(ov);
imgEl=ov.querySelector('[data-lb-img]');capEl=ov.querySelector('[data-lb-cap]');
ov.addEventListener('click',function(e){if(e.target===ov||e.target.closest('[data-lb-close]'))close();});
ov.querySelector('[data-lb-prev]').addEventListener('click',function(e){e.stopPropagation();show(idx-1);});
ov.querySelector('[data-lb-next]').addEventListener('click',function(e){e.stopPropagation();show(idx+1);});
}
function show(i){
if(!items.length)return;
idx=(i+items.length)%items.length;
var a=items[idx];var im=a.querySelector('img');
imgEl.src=(im&&(im.currentSrc||im.src))||a.getAttribute('href');
imgEl.alt=im?im.alt:'';
var cap=a.getAttribute('data-caption')||'';capEl.textContent=cap;capEl.style.display=cap?'block':'none';
}
function open(g,link){
if(!ov)build();
items=Array.prototype.slice.call(g.querySelectorAll('[data-bn-gallery-item]'));
lastFocus=document.activeElement;ov.style.display='flex';
var multi=items.length>1;
ov.querySelector('[data-lb-prev]').style.display=multi?'inline-flex':'none';
ov.querySelector('[data-lb-next]').style.display=multi?'inline-flex':'none';
show(items.indexOf(link));ov.focus();
}
function close(){if(ov)ov.style.display='none';if(lastFocus&&lastFocus.focus)lastFocus.focus();}
document.addEventListener('click',function(e){
var link=e.target.closest('[data-bn-gallery][data-lightbox="true"] [data-bn-gallery-item]');
if(!link)return;e.preventDefault();open(link.closest('[data-bn-gallery]'),link);
});
document.addEventListener('keydown',function(e){
if(!ov||ov.style.display==='none')return;
if(e.key==='Escape'){e.preventDefault();close();}
else if(e.key==='ArrowLeft')show(idx-1);
else if(e.key==='ArrowRight')show(idx+1);
});
})();
</script>{% endif %}{% endwith %}

View File

@ -1 +1,7 @@
{% if level|integer == 1 %}<h1 class="cyberpunk-display glitch text-4xl md:text-5xl font-bold tracking-tight {{ textClass }}" data-text="{{ text }}" style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);">{{ text }}</h1>{% elif level|integer == 2 %}<h2 class="cyberpunk-display glitch text-3xl md:text-4xl font-semibold tracking-tight {{ textClass }}" data-text="{{ text }}" style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);">{{ text }}</h2>{% elif level|integer == 3 %}<h3 class="cyberpunk-display text-2xl font-semibold {{ textClass }}" style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);">{{ text }}</h3>{% elif level|integer == 4 %}<h4 class="cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest text-xl font-semibold {{ textClass }}" style="color: hsl(var(--accent));">{{ text }}</h4>{% elif level|integer == 5 %}<h5 class="cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest text-lg font-medium {{ textClass }}" style="color: hsl(var(--accent));">{{ text }}</h5>{% elif level|integer == 6 %}<h6 class="cyberpunk-eyebrow cyberpunk-mono uppercase tracking-widest text-base font-medium {{ textClass }}" style="color: hsl(var(--accent));">{{ text }}</h6>{% else %}<h2 class="cyberpunk-display glitch text-3xl md:text-4xl font-semibold tracking-tight {{ textClass }}" data-text="{{ text }}" style="color: hsl(var(--foreground)); font-family: var(--font-heading, 'Space Grotesk', system-ui, sans-serif);">{{ text }}</h2>{% endif %}
{# cyberpunk heading — cyberpunk-display everywhere; glitch on h1/h2 only #}
{% if level == 1 %}<h1 class="text-4xl font-bold tracking-tight cyberpunk-display glitch {{ textClass }} flex-1">{{ text }}</h1>
{% elif level == 3 %}<h3 class="text-2xl font-semibold cyberpunk-display {{ textClass }} flex-1">{{ text }}</h3>
{% elif level == 4 %}<h4 class="text-xl font-semibold cyberpunk-display {{ textClass }} flex-1">{{ text }}</h4>
{% elif level == 5 %}<h5 class="text-lg font-medium cyberpunk-display {{ textClass }} flex-1">{{ text }}</h5>
{% elif level == 6 %}<h6 class="text-base font-medium cyberpunk-display {{ textClass }} flex-1">{{ text }}</h6>
{% else %}<h2 class="text-3xl font-semibold tracking-tight cyberpunk-display glitch {{ textClass }} flex-1">{{ text }}</h2>{% endif %}

View File

@ -0,0 +1,95 @@
{# hero cyberpunk override (GO-BIG showpiece): canvas digital-rain background, glitch headline, scanline overlay. All builtin field reads + macro + branch structure preserved. The rain shows when there is no backgroundImage; it lazy-inits on view, honors prefers-reduced-motion, degrades to a static neon gradient with JS off, and is dependency-free. #}
{% set eyebrowText = eyebrow.text|default:eyebrow %}
{% set headlineText = headline.text|default:headline %}
{% set subText = subheadline.text|default:subheadline %}
{% set descText = description.text|default:description %}
{% set pText = ctaPrimary.text %}
{% set pUrl = ctaPrimary.url %}
{% set sText = ctaSecondary.text %}
{% set sUrl = ctaSecondary.url %}
{% set variant = template|default:"centered" %}
{% set hasImage = backgroundImage %}
{% set showOverlay = hasImage and overlayColor != "none" and overlayColor != "" %}
{% macro herocontent(align) %}
{% if eyebrowText %}<span class="cyberpunk-mono clip-cut inline-block mb-4 px-3 py-1 rounded-md border border-primary/50 bg-primary/10 text-primary text-xs font-semibold uppercase tracking-widest">{{ eyebrowText }} <span aria-hidden="true" class="caret-blink">_</span></span>{% endif %}
{% if headlineText %}<h1 class="cyberpunk-display glitch text-4xl sm:text-5xl lg:text-6xl font-bold leading-tight text-foreground" data-text="{{ headlineText }}">{{ headlineText }}</h1>{% endif %}
{% if subText %}<h2 class="mt-4 text-xl sm:text-2xl text-muted-foreground max-w-2xl{% if align == "center" %} mx-auto{% endif %}">{{ subText }}</h2>{% endif %}
{% if descText %}<p class="mt-4 max-w-xl leading-relaxed text-muted-foreground{% if align == "center" %} mx-auto{% endif %}">{{ descText }}</p>{% endif %}
{% if pText or sText %}<div class="mt-8 flex flex-col sm:flex-row gap-4{% if align == "center" %} justify-center{% endif %}">
{% if pText and pUrl %}<a href="{{ pUrl }}" 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">{{ pText }}</a>{% endif %}
{% if sText and sUrl %}<a href="{{ sUrl }}" 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">{{ sText }}</a>{% endif %}
</div>{% endif %}
{% if trustLogos %}<div class="mt-10">
<p class="cyberpunk-mono text-xs uppercase tracking-widest text-muted-foreground mb-3">Trusted by</p>
<div class="flex flex-wrap items-center gap-6{% if align == "center" %} justify-center{% endif %}">{% for logo in trustLogos %}{% if logo.url %}<a href="{{ logo.url }}" target="_blank" rel="noopener noreferrer" class="opacity-60 transition-opacity hover:opacity-100">{% img logo.image alt=logo.alt class="h-8 w-auto" %}</a>{% else %}{% img logo.image alt=logo.alt class="h-8 w-auto opacity-60" %}{% endif %}{% endfor %}</div>
</div>{% endif %}
{% endmacro %}
{% if variant == "split" %}
<section class="hero grid grid-cols-1 lg:grid-cols-2 overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="relative min-h-[320px] bg-muted {% if mediaPosition == "left" %}order-1 lg:order-1{% else %}order-1 lg:order-2{% endif %}">{% if hasImage %}{% img backgroundImage alt=headlineText class="absolute inset-0 h-full w-full object-cover" layout="hero" %}{% else %}<div class="cyberpunk-rain-wrap"><div class="cyberpunk-rain-fallback"></div><canvas class="cyberpunk-rain-canvas" aria-hidden="true"></canvas></div>{% endif %}<div aria-hidden="true" class="scanlines pointer-events-none absolute inset-0"></div></div>
<div class="flex flex-col justify-center px-6 py-16 lg:px-12 {% if mediaPosition == "left" %}order-2 lg:order-2{% else %}order-2 lg:order-1{% endif %}"><div class="max-w-lg text-left">{{ herocontent("left") }}</div></div>
</section>
{% else %}
{% set align = "center" %}
{% if variant == "left-aligned" %}{% set align = "left" %}{% endif %}
<section class="hero relative flex flex-col overflow-hidden bg-background text-foreground {% if minHeight == "screen" %}min-h-screen{% elif minHeight == "50vh" %}min-h-[50vh]{% elif minHeight == "auto" %}min-h-[400px]{% else %}min-h-[75vh]{% endif %} {% if verticalAlign == "top" %}justify-start pt-20{% elif verticalAlign == "bottom" %}justify-end pb-20{% else %}justify-center{% endif %}{% if class %} {{ class }}{% endif %}">
{% if hasImage %}<div class="absolute inset-0 z-0">{% img backgroundImage alt=headlineText class="h-full w-full object-cover" layout="hero" %}{% if showOverlay %}<div class="absolute inset-0 {% if overlayColor == "light" %}bg-background/60{% elif overlayColor == "primary" %}bg-primary/50{% elif overlayOpacity <= 30 %}bg-black/30{% elif overlayOpacity <= 50 %}bg-black/50{% elif overlayOpacity <= 70 %}bg-black/70{% else %}bg-black/80{% endif %}"></div>{% endif %}</div>{% else %}<div class="cyberpunk-rain-wrap z-0"><div class="cyberpunk-rain-fallback"></div><canvas class="cyberpunk-rain-canvas" aria-hidden="true"></canvas></div>{% endif %}
<div aria-hidden="true" class="scanlines pointer-events-none absolute inset-0 z-0"></div>
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 sm:px-6 lg:px-8 {% if variant == "left-aligned" %}text-left{% else %}text-center{% endif %}">{{ herocontent(align) }}</div>
</section>
{% endif %}
<script>
(function(){
if(window.__cyberpunkRain)return;window.__cyberpunkRain=1;
var reduce=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function initCanvas(cv){
if(cv.__init)return;cv.__init=1;
var ctx=cv.getContext&&cv.getContext('2d');if(!ctx)return;
var wrap=cv.parentElement;
var cs=getComputedStyle(document.documentElement);
function raw(n){return cs.getPropertyValue(n).trim();}
var fg=raw('--foreground');
var col1='hsl('+(raw('--primary')||fg)+')';
var col2='hsl('+(raw('--accent')||fg)+')';
var fade='hsl('+(raw('--background')||fg)+' / 0.14)';
var glyphs='01<>[]{}#$%&*+=/|=abcdefghijklmnopqrstuvwxyz'.split('');
var fontSize=14,cols=0,drops=[],W=0,H=0,dpr=1;
function resize(){
dpr=Math.min(window.devicePixelRatio||1,2);
W=wrap.clientWidth;H=wrap.clientHeight;
if(W===0||H===0)return;
cv.width=W*dpr;cv.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0);
cols=Math.max(1,Math.floor(W/fontSize));
drops=[];for(var i=0;i<cols;i++)drops[i]=Math.random()*-50;
}
resize();
var last=0,fps=1000/18;
function frame(t){
requestAnimationFrame(frame);
if(t-last<fps)return;last=t;
if(W===0||H===0){resize();return;}
ctx.fillStyle=fade;ctx.fillRect(0,0,W,H);
ctx.font=fontSize+'px monospace';
for(var i=0;i<cols;i++){
var ch=glyphs[Math.floor(Math.random()*glyphs.length)];
var x=i*fontSize,y=drops[i]*fontSize;
ctx.fillStyle=Math.random()<0.5?col1:col2;
ctx.fillText(ch,x,y);
if(y>H&&Math.random()>0.975)drops[i]=0;
drops[i]++;
}
}
if(window.ResizeObserver){var ro=new ResizeObserver(resize);ro.observe(wrap);}else{window.addEventListener('resize',resize);}
requestAnimationFrame(frame);
}
function boot(){
if(reduce)return;
var cvs=document.querySelectorAll('.cyberpunk-rain-canvas');
if(!cvs.length)return;
if(!('IntersectionObserver' in window)){cvs.forEach(initCanvas);return;}
var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){initCanvas(e.target);io.unobserve(e.target);}});},{threshold:0.05});
cvs.forEach(function(c){io.observe(c);});
}
if(document.readyState!='loading')boot();else document.addEventListener('DOMContentLoaded',boot);
})();
</script>

View File

@ -0,0 +1,31 @@
{# hours-location cyberpunk override: mono readout table, neon map panel. Map lazy-load button + inline handler preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-5xl px-4">
{% if heading %}<h2 class="cyberpunk-display glitch text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
<div class="mt-10 grid grid-cols-1 gap-10 md:grid-cols-2">
<div>
{% if hours %}
<table class="cyberpunk-mono w-full text-sm">
<tbody class="divide-y divide-primary/30">
{% for row in hours %}
<tr>
<th scope="row" class="py-2 pr-4 text-left font-medium uppercase tracking-wide">{{ row.day }}</th>
<td class="py-2 text-right {% if row.closed %}text-muted-foreground{% else %}text-primary{% endif %} tabular-nums">{% if row.closed %}Closed{% else %}{{ row.hours }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if specialHoursNote %}<p class="mt-4 rounded-md border border-primary/40 bg-muted px-3 py-2 text-sm text-muted-foreground">{{ specialHoursNote }}</p>{% endif %}
{% if address %}<div class="mt-6"><div class="cyberpunk-mono text-sm font-medium uppercase tracking-wide text-accent">Address</div><address class="mt-1 whitespace-pre-line not-italic">{{ address }}</address></div>{% endif %}
{% if phone %}<div class="mt-4"><div class="cyberpunk-mono text-sm font-medium uppercase tracking-wide text-accent">Phone</div><a href="tel:{{ phone }}" class="cyberpunk-mono mt-1 inline-block font-medium transition-colors hover:text-primary">{{ phone }}</a></div>{% endif %}
{% if directionsUrl %}<a href="{{ directionsUrl }}" target="_blank" rel="noopener noreferrer" class="cyberpunk-mono clip-cut mt-6 inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium uppercase tracking-wide text-primary-foreground transition-colors hover:bg-primary/90"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#navigation"></use></svg>Get directions</a>{% endif %}
</div>
{% if mapEmbedUrl %}
<div class="hud-frame aspect-square w-full overflow-hidden rounded-md border border-primary/40 bg-muted neon-edge-cyan md:aspect-auto">
<button type="button" data-map-src="{{ mapEmbedUrl }}" onclick="var f=document.createElement('iframe');f.src=this.getAttribute('data-map-src');f.setAttribute('class','h-full w-full border-0');f.setAttribute('loading','lazy');f.setAttribute('referrerpolicy','no-referrer-when-downgrade');f.setAttribute('title','Map');this.parentNode.replaceChild(f,this);" class="cyberpunk-mono flex h-full min-h-[16rem] w-full items-center justify-center gap-2 text-sm font-medium uppercase tracking-wide transition-colors hover:bg-accent/20 hover:text-foreground"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/lucide.svg#map-pin"></use></svg>{{ mapButtonLabel|default:"Load map" }}</button>
</div>
{% endif %}
</div>
</div>
</section>

View File

@ -0,0 +1,2 @@
{# cyberpunk image — cyan neon-edge frame, mono caption; all size/fit branches preserved #}
{% with size=size|default:"full" align=align|default:"center" aspectRatio=aspectRatio|default:"auto" objectFit=objectFit|default:"cover" rounded=rounded|default:"lg" shadow=shadow|default:"none" %}<figure class="flex-1 {% if class %}{{ class }} {% endif %}{% if align == "left" %}flex justify-start{% elif align == "right" %}flex justify-end{% else %}flex justify-center{% endif %}"><div class="relative overflow-hidden neon-edge-cyan {% if size == "xs" %}max-w-[200px] w-full{% elif size == "sm" %}max-w-[400px] w-full{% elif size == "md" %}max-w-[600px] w-full{% elif size == "lg" %}max-w-[800px] w-full{% else %}w-full{% endif %}{% if aspectRatio == "square" %} aspect-square{% elif aspectRatio == "video" %} aspect-video{% elif aspectRatio == "portrait" %} aspect-[3/4]{% elif aspectRatio == "wide" %} aspect-[21/9]{% endif %}{% if rounded == "sm" %} rounded-sm{% elif rounded == "md" %} rounded-md{% elif rounded == "lg" %} rounded-lg{% elif rounded == "xl" %} rounded-xl{% elif rounded == "full" %} rounded-full{% endif %}{% if shadow == "sm" %} shadow-sm{% elif shadow == "md" %} shadow-md{% elif shadow == "lg" %} shadow-lg{% elif shadow == "xl" %} shadow-xl{% endif %}">{% if objectFit == "contain" %}{% img src alt=alt class="w-full h-full object-contain" layout=size %}{% elif objectFit == "fill" %}{% img src alt=alt class="w-full h-full object-fill" layout=size %}{% elif objectFit == "none" %}{% img src alt=alt class="w-full h-full object-none" layout=size %}{% else %}{% img src alt=alt class="w-full h-full object-cover" layout=size %}{% endif %}</div>{% if caption %}<figcaption class="mt-2 text-sm text-muted-foreground text-center cyberpunk-mono">{{ caption }}</figcaption>{% endif %}</figure>{% endwith %}

View File

@ -0,0 +1,16 @@
{# logo_strip cyberpunk override: mono kicker. Marquee classes, {% img %}, and keyframes <style> preserved byte-for-byte. #}
<section class="py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if heading %}<p class="cyberpunk-mono text-center text-sm font-semibold uppercase tracking-widest text-accent">{{ heading }}</p>{% endif %}
<div class="mt-8 {% if marquee %}group relative overflow-hidden [mask-image:linear-gradient(to_right,transparent,black_8%,black_92%,transparent)]{% endif %}">
<div class="flex flex-wrap items-center justify-center gap-x-10 gap-y-6 {% if marquee %}flex-nowrap [animation:logo-marquee_30s_linear_infinite] motion-reduce:[animation:none] group-hover:[animation-play-state:paused]{% endif %}">
{% for logo in logos %}
{% if logo.url %}<a href="{{ logo.url }}" class="block shrink-0 opacity-80 transition hover:opacity-100">{% endif %}
<span class="inline-flex h-8 items-center{% if grayscale %} grayscale transition-[filter] hover:grayscale-0{% endif %}">{% img logo.image alt=logo.alt class="h-8 w-auto object-contain" %}</span>
{% if logo.url %}</a>{% endif %}
{% endfor %}
</div>
</div>
</div>
</section>
{% if marquee %}<style>@keyframes logo-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}</style>{% endif %}

View File

@ -0,0 +1,30 @@
{# menu-list cyberpunk override: mono price readouts, neon section rules. All field reads + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-5xl 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 max-w-2xl text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
<div class="mt-10 grid grid-cols-1 gap-x-12 gap-y-10 {% if columns == 2 %}md:grid-cols-2{% endif %}">
{% for section in sections %}
<div class="menu-section">
{% if section.title %}<h3 class="cyberpunk-display border-b border-primary/40 pb-2 text-xl font-semibold">{{ section.title }}</h3>{% endif %}
{% if section.note %}<p class="mt-2 text-sm text-muted-foreground">{{ section.note }}</p>{% endif %}
<ul class="mt-4 space-y-5">
{% for item in section.items %}
<li class="flex items-baseline gap-4">
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
<span class="font-medium">{{ item.name }}</span>
{% for badge in item.badges %}{% if badge.label %}<span class="cyberpunk-mono clip-cut inline-block rounded border border-accent/50 px-2 py-0.5 text-xs uppercase tracking-wide text-accent">{{ badge.label }}</span>{% endif %}{% endfor %}
</div>
{% if item.description %}<p class="mt-0.5 text-sm text-muted-foreground">{{ item.description }}</p>{% endif %}
</div>
{% if item.price %}<span class="cyberpunk-mono shrink-0 font-medium tabular-nums text-primary">{% if currency %}{{ currency }}{% endif %}{{ item.price }}</span>{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% if footnote %}<p class="cyberpunk-mono mt-10 text-xs text-muted-foreground">{{ footnote }}</p>{% endif %}
</div>
</section>

View File

@ -0,0 +1,104 @@
{# navbar (WO-TF-005): variants standard|centered-logo|split|transparent-over-hero; sticky none|sticky|shrink-on-scroll; dependency-free off-canvas drawer (pure-CSS baseline + progressive-JS focus-trap/Escape/scroll-lock); menu data from the `menus` provider. Cyberpunk override: mono nav links, neon dropdown panels, clip-cut CTA — drawer ids/style/script/data-bn-* preserved byte-for-byte. #}
{% set nv = variant %}
{% if not nv %}{% if template == "centered" %}{% set nv = "centered-logo" %}{% elif template == "transparent" %}{% set nv = "transparent-over-hero" %}{% else %}{% set nv = "standard" %}{% endif %}{% endif %}
{% set sb = stickyBehavior %}
{% if not sb %}{% if sticky %}{% set sb = "sticky" %}{% else %}{% set sb = "none" %}{% endif %}{% endif %}
{% set use_mega = megaMenu or template == "mega" %}
{% set is_transparent = nv == "transparent-over-hero" %}
{% set cta_text = cta.text|default:ctaLabel %}
{% set cta_url = cta.url|default:ctaUrl %}
{% set bg = backgroundColor|default:"bg-background" %}
{% macro navlink(item) %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} class="cyberpunk-mono uppercase tracking-wider px-3 py-2 text-xs font-medium text-foreground/80 hover:text-primary transition-colors {{ item.css_class }}">{{ item.label }}</a>{% endmacro %}
{% macro dropdown(item) %}<div class="relative group">
<button type="button" class="cyberpunk-mono uppercase tracking-wider px-3 py-2 text-xs font-medium text-foreground/80 hover:text-primary transition-colors inline-flex items-center gap-1">{{ item.label }}<svg class="h-4 w-4 transition-transform group-hover:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></button>
{% if use_mega %}<div class="absolute left-1/2 -translate-x-1/2 mt-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="p-6 bg-card border border-primary/40 neon-edge-magenta rounded-md shadow-xl grid gap-8" style="grid-template-columns:repeat({{ item.children|length|default:1 }},minmax(10rem,1fr));min-width:min(90vw,42rem)">{% for child in item.children %}<div class="space-y-1">{% if child.item_type == "header" %}<div class="cyberpunk-mono px-2 py-1 text-xs font-semibold text-accent uppercase tracking-widest border-b border-primary/40 mb-2">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 rounded hover:bg-accent transition-colors {{ child.css_class }}">{% if child.icon %}<span class="mr-1.5 text-primary">::{{ child.icon }}::</span>{% endif %}<span class="text-sm font-medium text-foreground">{{ child.label }}</span>{% if child.description %}<span class="block text-xs text-muted-foreground mt-0.5">{{ child.description }}</span>{% endif %}</a>{% endif %}</div>{% endfor %}</div></div>
{% else %}<div class="absolute left-0 mt-1 w-56 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50"><div class="py-2 bg-card border border-primary/40 neon-edge-cyan rounded-md shadow-lg">{% for child in item.children %}{% if child.item_type == "header" %}<div class="cyberpunk-mono px-4 py-2 text-xs font-semibold text-accent uppercase tracking-widest">{{ child.label }}</div>{% else %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-4 py-2 text-sm text-foreground hover:bg-accent transition-colors {{ child.css_class }}">{{ child.label }}</a>{% endif %}{% endfor %}</div></div>{% endif %}
</div>{% endmacro %}
{% macro brand(size) %}<a href="/" class="flex-shrink-0 flex items-center">{% if logoType == "text" and logoText.text %}<span class="cyberpunk-display text-xl font-bold text-foreground {{ logoText.color }}">{{ logoText.text }}</span>{% elif logo %}<img src="{{ logo }}" alt="{{ alt|default:companyName }}" class="{{ size }} w-auto"{% if logoMaxWidth or logoMaxHeight %} style="{% if logoMaxWidth %}max-width:{{ logoMaxWidth }};{% endif %}{% if logoMaxHeight %}max-height:{{ logoMaxHeight }};{% endif %}"{% endif %}/>{% elif companyName %}<span class="cyberpunk-display text-xl font-bold text-foreground">{{ companyName }}</span>{% endif %}</a>{% endmacro %}
{% macro ctabtn(extra) %}{% if cta_text %}<a href="{{ cta_url|default:'#' }}" class="cyberpunk-mono uppercase tracking-wider clip-cut rgb-split inline-flex items-center rounded-none bg-primary px-4 py-2 text-xs font-semibold text-primary-foreground hover:bg-primary/90 transition-colors {{ extra }}">{{ cta_text }}</a>{% endif %}{% endmacro %}
<nav data-bn-navbar data-bn-nav-variant="{{ nv }}" data-bn-sticky="{{ sb }}"{% if is_transparent %} data-bn-transparent="1"{% endif %} class="bn-navbar w-full {% if sb == "sticky" or sb == "shrink-on-scroll" %}sticky top-0 z-40 {% endif %}{% if is_transparent %}absolute top-0 left-0 z-40 text-white{% else %}{{ bg }}{% if borderBottom %} border-b border-primary/30{% endif %}{% endif %} {{ class }}">
{% if showUtilityBar %}<div class="cyberpunk-mono w-full text-xs {% if not showOnMobile %}hidden sm:block{% endif %} bg-muted/60 text-muted-foreground border-b border-primary/20"><div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between h-8"><div>{{ utilityBarText|safe }}</div><div class="flex items-center gap-4">{% if utilityBarPhone %}<a href="tel:{{ utilityBarPhone }}" class="hover:text-primary transition-colors">{{ utilityBarPhone }}</a>{% endif %}{% if utilityBarEmail %}<a href="mailto:{{ utilityBarEmail }}" class="hover:text-primary transition-colors">{{ utilityBarEmail }}</a>{% endif %}</div></div></div>{% endif %}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bn-navbar-row flex items-center justify-between h-16">
{% if nv == "centered-logo" %}
<div class="hidden md:flex items-center gap-1 flex-1">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}</div>
<div class="flex items-center justify-center">{{ brand("h-9") }}</div>
<div class="hidden md:flex items-center gap-3 flex-1 justify-end">{{ ctabtn("") }}</div>
{% elif nv == "split" %}
<div class="hidden md:flex items-center gap-1 flex-1">{% for item in menu.items %}{% if forloop.Counter0 * 2 < menu.items|length %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endif %}{% endfor %}</div>
<div class="flex items-center justify-center">{{ brand("h-9") }}</div>
<div class="hidden md:flex items-center gap-1 flex-1 justify-end">{% for item in menu.items %}{% if forloop.Counter0 * 2 >= menu.items|length %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}</div>
{% else %}
<div class="flex items-center">{{ brand("h-8") }}</div>
<div class="hidden md:flex items-center gap-1">{% for item in menu.items %}{% if item.children %}{{ dropdown(item) }}{% else %}{{ navlink(item) }}{% endif %}{% endfor %}{{ ctabtn("ml-3") }}</div>
{% endif %}
<div class="flex items-center md:hidden">
<label for="bn-navbar-toggle" data-bn-nav-open aria-controls="bn-navbar-drawer" aria-expanded="false" class="inline-flex items-center justify-center p-2 rounded-md cursor-pointer hover:bg-accent/50 transition-colors"><span class="sr-only">Open menu</span><svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg></label>
</div>
</div>
</div>
{# --- Mobile off-canvas drawer: pure-CSS checkbox baseline (works with no JS) --- #}
<input type="checkbox" id="bn-navbar-toggle" class="bn-navbar-toggle" aria-hidden="true"/>
<label for="bn-navbar-toggle" class="bn-navbar-overlay fixed inset-0 bg-black/50 z-40 md:hidden" aria-hidden="true"></label>
<div id="bn-navbar-drawer" class="bn-navbar-drawer fixed top-0 right-0 h-full w-80 max-w-[85vw] bg-background text-foreground z-50 shadow-xl md:hidden flex flex-col" role="dialog" aria-modal="true" aria-label="Site menu">
<div class="flex items-center justify-between p-4 border-b border-primary/30">{{ brand("h-8") }}<label for="bn-navbar-toggle" data-bn-nav-close class="p-2 rounded-md cursor-pointer text-muted-foreground hover:text-primary hover:bg-accent"><span class="sr-only">Close menu</span><svg class="h-6 w-6" 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></label></div>
<nav class="flex-1 overflow-y-auto py-4" aria-label="Mobile">{% for item in menu.items %}{% if item.children %}<details class="group/acc"><summary class="cyberpunk-mono uppercase tracking-wide flex items-center justify-between px-6 py-3 text-sm font-medium cursor-pointer hover:bg-accent transition-colors list-none">{{ item.label }}<svg class="h-4 w-4 transition-transform group-open/acc:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></summary><div class="pl-8 pb-2">{% for child in item.children %}{% if child.item_type != "header" %}<a href="{{ child.url|default:'#' }}"{% if child.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="block px-2 py-2 text-sm text-muted-foreground hover:text-primary transition-colors">{{ child.label }}</a>{% endif %}{% endfor %}</div></details>{% else %}<a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %} class="cyberpunk-mono uppercase tracking-wide block px-6 py-3 text-sm font-medium hover:bg-accent transition-colors">{{ item.label }}</a>{% endif %}{% endfor %}</nav>
{% if cta_text %}<div class="p-4 border-t border-primary/30"><a href="{{ cta_url|default:'#' }}" class="cyberpunk-mono uppercase tracking-wider clip-cut block w-full text-center rounded-none bg-primary px-4 py-3 text-base font-semibold text-primary-foreground hover:bg-primary/90 transition-colors">{{ cta_text }}</a></div>{% endif %}
</div>
</nav>
<style>
.bn-navbar-toggle{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;}
.bn-navbar-drawer{transform:translateX(100%);transition:transform .3s ease;}
.bn-navbar-overlay{opacity:0;pointer-events:none;transition:opacity .3s ease;}
.bn-navbar-toggle:checked ~ .bn-navbar-drawer{transform:translateX(0);}
.bn-navbar-toggle:checked ~ .bn-navbar-overlay{opacity:1;pointer-events:auto;}
.bn-navbar-drawer details > summary::-webkit-details-marker{display:none;}
[data-bn-sticky="shrink-on-scroll"].bn-navbar-shrunk .bn-navbar-row{height:3rem;}
[data-bn-sticky="shrink-on-scroll"] .bn-navbar-row{transition:height .2s ease;}
[data-bn-transparent].bn-navbar-solid{position:sticky;color:inherit;background:hsl(var(--background));border-bottom:1px solid hsl(var(--border));}
</style>
<script>
(function(){
if(window.__bnNavInit)return;window.__bnNavInit=1;
function ready(fn){if(document.readyState!='loading')fn();else document.addEventListener('DOMContentLoaded',fn);}
ready(function(){
document.querySelectorAll('[data-bn-navbar]').forEach(function(nav){
var toggle=nav.querySelector('.bn-navbar-toggle');
var drawer=nav.querySelector('.bn-navbar-drawer');
var opener=nav.querySelector('[data-bn-nav-open]');
if(toggle&&drawer){
function focusables(){return drawer.querySelectorAll('a[href],button,label[for],input,summary,[tabindex]:not([tabindex="-1"])');}
toggle.addEventListener('change',function(){
var open=toggle.checked;
document.documentElement.style.overflow=open?'hidden':'';
document.body.style.overflow=open?'hidden':'';
if(opener)opener.setAttribute('aria-expanded',open?'true':'false');
if(open){var f=focusables();if(f.length)f[0].focus();}
else if(opener&&opener.focus)opener.focus();
});
drawer.addEventListener('keydown',function(e){
if(e.key==='Escape'){toggle.checked=false;toggle.dispatchEvent(new Event('change'));return;}
if(e.key!=='Tab')return;
var f=focusables();if(!f.length)return;
var first=f[0],last=f[f.length-1];
if(e.shiftKey&&document.activeElement===first){e.preventDefault();last.focus();}
else if(!e.shiftKey&&document.activeElement===last){e.preventDefault();first.focus();}
});
drawer.querySelectorAll('a[href]').forEach(function(a){a.addEventListener('click',function(){toggle.checked=false;toggle.dispatchEvent(new Event('change'));});});
}
var sb=nav.getAttribute('data-bn-sticky');
var transparent=nav.getAttribute('data-bn-transparent')==='1';
if(sb==='shrink-on-scroll'||transparent){
var overHero=false;
if(transparent){var next=nav.nextElementSibling;overHero=!!next&&next.getBoundingClientRect().height>=240;if(!overHero)nav.classList.add('bn-navbar-solid');}
var onScroll=function(){
var y=window.scrollY||window.pageYOffset;
if(sb==='shrink-on-scroll')nav.classList.toggle('bn-navbar-shrunk',y>40);
if(transparent&&overHero)nav.classList.toggle('bn-navbar-solid',y>40);
};
window.addEventListener('scroll',onScroll,{passive:true});onScroll();
}
});
});
})();
</script>

View File

@ -0,0 +1,12 @@
{# newsletter-signup (cyberpunk override) — the {% signup_form %} tag carries the #}
{# untouchable subscribe wiring (/api/lists/subscribe HTMX post + honeypot); #}
{# this template supplies only the cyberpunk chrome around it. #}
<section class="py-12 bg-background text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-3xl px-4">
<div class="hud-frame clip-cut rounded-md {% if layout == "inline" %}bg-primary px-6 py-10 text-primary-foreground neon-edge-cyan md:px-12{% else %}border border-dashed border-primary/40 bg-card px-6 py-10 text-card-foreground neon-edge-magenta md:px-10{% endif %} text-center">
{% if title %}<h2 class="cyberpunk-display glitch text-2xl font-semibold tracking-tight md:text-3xl" data-text="{{ title }}">{{ title }}</h2>{% endif %}
{% if description %}<p class="cyberpunk-body mx-auto mt-3 max-w-xl {% if layout == "inline" %}text-primary-foreground/80{% else %}text-muted-foreground{% endif %}">{{ description }}</p>{% endif %}
<div class="mx-auto mt-6 max-w-md">{% signup_form list=listSlug source=source submit=submitText success_message=successMessage placeholder=placeholder %}</div>
</div>
</div>
</section>

View File

@ -0,0 +1,13 @@
{# page-suggestions cyberpunk override — themed 404. NOTE: the builtin builds its "did you mean" list from a Postgres full-text query (SearchSimilarPages), which a codeless template cannot run — so this override renders the on-theme 404 console (title / message / requested path / home CTA) without the dynamic suggestion list. Recorded as a known deviation in WO-TF-015. Content fields honored: title, emptyMessage. #}
<section class="relative overflow-hidden bg-background text-foreground py-20 md:py-28{% 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-2xl px-4 text-center">
<p class="cyberpunk-mono text-sm font-semibold uppercase tracking-[0.3em] text-accent">Error 404 <span aria-hidden="true" class="caret-blink">_</span></p>
<h1 class="cyberpunk-display glitch mt-3 text-4xl font-bold tracking-tight md:text-5xl" data-text="{{ title|default:'Signal lost' }}">{{ title|default:"Signal lost" }}</h1>
<p class="mt-4 text-lg text-muted-foreground">{{ emptyMessage|default:"That route returned nothing. The page moved, shipped, or never existed." }}</p>
{% if context.path %}<p class="cyberpunk-mono mt-3 text-sm text-muted-foreground">requested: <span class="text-primary">{{ context.path }}</span></p>{% endif %}
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
<a href="/" class="cyberpunk-mono clip-cut rgb-split inline-flex items-center gap-2 rounded-md bg-primary px-6 py-3 font-semibold uppercase tracking-wider text-primary-foreground transition-opacity hover:opacity-90"><svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg>Back to home</a>
</div>
</div>
</section>

View File

@ -0,0 +1,13 @@
{# password-reset-form cyberpunk override — reproduces the builtin's hardcoded wiring: hx-post /api/auth/request-password-reset (target #reset-message), back-to-login link /login. Only the chrome is themed. #}
<div class="password-reset-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="reset-message"></div>
<div id="request-reset-panel">
<h2 class="cyberpunk-display glitch text-2xl font-bold mb-2" data-text="Reset Password">Reset Password</h2>
<p class="text-muted-foreground mb-4">Enter your email address and we will send you a link to reset your password.</p>
<form hx-post="/api/auth/request-password-reset" hx-target="#reset-message" hx-swap="innerHTML">
<div class="mb-4"><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>
<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">Send Reset Link</button>
</form>
<p class="mt-4 text-center text-sm"><a href="/login" class="cyberpunk-mono uppercase tracking-wide text-primary hover:underline">Back to login</a></p>
</div>
</div>

View File

@ -0,0 +1,16 @@
{# popular-posts (cyberpunk override) — compact ranked list from the `posts` provider. #}
<section class="bg-background text-foreground{% if class %} {{ class }}{% endif %}">
{% if heading %}<h2 class="cyberpunk-eyebrow cyberpunk-mono mb-4 text-xs font-semibold uppercase tracking-widest text-accent">{{ heading }}</h2>{% endif %}
<ol class="space-y-4">
{% for post in posts %}
<li class="flex items-start gap-3">
{% if showRank %}<span class="cyberpunk-mono mt-0.5 text-lg font-bold tabular-nums text-primary" style="text-shadow: 0 0 8px hsl(var(--primary));">{{ forloop.Counter }}</span>{% endif %}
{% if showThumbnail and post.featured_image_url %}<a href="{{ post.url }}" class="h-12 w-12 flex-shrink-0 overflow-hidden rounded-md border border-primary/40 bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover" %}</a>{% endif %}
<div class="min-w-0">
<h3 class="text-sm font-medium leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
{% if post.published_at %}<p class="cyberpunk-mono mt-0.5 text-xs uppercase tracking-wide text-accent">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
</div>
</li>
{% endfor %}
</ol>
</section>

View File

@ -0,0 +1,23 @@
{# post-hero (cyberpunk override) — current post from the `post` provider. When the post #}
{# has a featured image it becomes an overlaid banner; otherwise a clean text hero. #}
{% set center = align != "left" %}
{% if post.featured_image_url %}
<section class="hero relative flex min-h-[50vh] flex-col justify-end overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="absolute inset-0 z-0">{% img post.featured_image_url alt=post.featured_image_alt class="h-full w-full object-cover" layout="hero" %}<div class="absolute inset-0 bg-black/60"></div></div>
<div class="scanlines pointer-events-none absolute inset-0 z-[1]"></div>
<div class="relative z-10 mx-auto w-full max-w-4xl px-4 py-12 sm:px-6 lg:px-8 {% if center %}text-center{% endif %}">
<h1 class="cyberpunk-display glitch text-3xl font-bold leading-tight text-white sm:text-4xl lg:text-5xl" data-text="{{ post.title }}">{{ post.title }}</h1>
{% if showExcerpt and post.excerpt %}<p class="cyberpunk-body mt-4 max-w-2xl text-lg text-white/80{% if center %} mx-auto{% endif %}">{{ post.excerpt }}</p>{% endif %}
{% if showMeta %}<div class="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 cyberpunk-mono text-sm uppercase tracking-wide text-white/80{% if center %} justify-center{% endif %}">{% if post.author_name %}<span class="font-medium text-white">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"F j, Y" }}</span>{% endif %}{% if post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}</div>{% endif %}
</div>
</section>
{% else %}
<section class="relative overflow-hidden bg-background py-14 text-foreground md:py-20{% if class %} {{ class }}{% endif %}">
<div class="scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto w-full max-w-3xl px-4 {% if center %}text-center{% endif %}">
<h1 class="cyberpunk-display glitch text-3xl font-bold leading-tight tracking-tight sm:text-4xl lg:text-5xl" data-text="{{ post.title }}">{{ post.title }}</h1>
{% if showExcerpt and post.excerpt %}<p class="cyberpunk-body mt-4 text-lg text-muted-foreground{% if center %} mx-auto max-w-2xl{% endif %}">{{ post.excerpt }}</p>{% endif %}
{% if showMeta %}<div class="mt-6 flex flex-wrap items-center gap-x-3 gap-y-1 cyberpunk-mono text-sm uppercase tracking-wide text-accent{% if center %} justify-center{% endif %}">{% if post.author_name %}<span class="font-medium text-foreground">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"F j, Y" }}</span>{% endif %}{% if post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}</div>{% endif %}
</div>
</section>
{% endif %}

View File

@ -0,0 +1,9 @@
{# post-metadata (cyberpunk override) — author/date/reading-time row from the `post` provider. #}
{% if post %}
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 cyberpunk-mono text-sm uppercase tracking-wide text-accent{% if class %} {{ class }}{% endif %}">
{% if showAuthor and post.author_name %}<span class="flex items-center gap-2">{% if showAvatar and post.author_avatar %}{% img post.author_avatar alt=post.author_name class="h-7 w-7 rounded-full border border-primary/40 object-cover" %}{% endif %}<span class="font-medium text-foreground">{{ post.author_name }}</span></span>{% endif %}
{% if showDate and post.published_at %}<time datetime="{{ post.published_at|date:"Y-m-d" }}">{{ post.published_at|date:"F j, Y" }}</time>{% endif %}
{% if showReadingTime and post.reading_time %}<span>{{ post.reading_time }} min read</span>{% endif %}
{% if post.categories %}<span class="flex flex-wrap gap-1.5">{% for cat in post.categories %}<a href="{{ cat.url|default:'#' }}" class="clip-cut border border-primary/40 bg-card px-2 py-0.5 text-xs text-card-foreground transition-colors hover:text-primary">{{ cat.name }}</a>{% endfor %}</span>{% endif %}
</div>
{% endif %}

View File

@ -0,0 +1,26 @@
{# pricing cyberpunk override: scanline overlay, glitch heading, HUD neon tier panels (magenta highlight / cyan standard), clip-cut CTAs. Field reads + control flow preserved. #}
<section class="relative overflow-hidden py-16 md:py-24 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-6xl px-4">
{% if heading %}<h2 class="cyberpunk-display glitch 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 md:grid-cols-2 lg:grid-cols-3">
{% for tier in tiers %}
<div class="hud-frame relative flex flex-col rounded-md border p-6 {% if tier.highlighted %}border-primary bg-card text-card-foreground neon-edge-magenta ring-1 ring-primary{% else %}border-primary/40 bg-card text-card-foreground neon-edge-cyan{% endif %}">
{% if tier.badge %}<span class="cyberpunk-mono clip-cut absolute -top-3 left-1/2 -translate-x-1/2 rounded-none bg-primary px-3 py-1 text-xs font-semibold uppercase tracking-widest text-primary-foreground">{{ tier.badge }}</span>{% endif %}
<h3 class="cyberpunk-display text-lg font-semibold">{{ tier.name }}</h3>
<p class="mt-4 flex items-baseline gap-1">
<span class="cyberpunk-display text-4xl font-bold tracking-tight" style="font-family:var(--font-heading)">{% if currency %}{{ currency }}{% else %}${% endif %}{{ tier.price }}</span>
{% if tier.period %}<span class="cyberpunk-mono text-sm text-muted-foreground">{{ tier.period }}</span>{% endif %}
</p>
{% if tier.description %}<p class="mt-3 text-sm text-muted-foreground">{{ tier.description }}</p>{% endif %}
{% if tier.features %}<ul class="mt-6 flex-1 space-y-3 text-sm">
{% for feature in tier.features %}<li class="flex items-start gap-2"><svg class="mt-0.5 h-4 w-4 shrink-0 text-primary" aria-hidden="true"><use href="/icons/lucide.svg#check"></use></svg><span>{{ feature }}</span></li>{% endfor %}
</ul>{% endif %}
{% if tier.ctaUrl and tier.ctaLabel %}<a href="{{ tier.ctaUrl }}" class="cyberpunk-mono uppercase tracking-wider clip-cut mt-8 inline-flex w-full items-center justify-center rounded-none px-5 py-3 font-semibold transition-opacity hover:opacity-90 {% if tier.highlighted %}rgb-split bg-primary text-primary-foreground{% else %}border border-primary/40 bg-background text-foreground hover:bg-muted{% endif %}">{{ tier.ctaLabel }}</a>{% endif %}
</div>
{% endfor %}
</div>
{% if billingNote %}<p class="cyberpunk-mono mt-8 text-center text-sm text-muted-foreground">{{ billingNote }}</p>{% endif %}
</div>
</section>

View File

@ -0,0 +1,2 @@
{# cyberpunk quote — dashed neon left border; cyberpunk-display for pull; mono figcaption #}
{% with style=style|default:"block" align=align|default:"left" %}<figure class="bn-quote my-6 {% if style == "inline-card" %}rounded-md border border-dashed border-primary/40 bg-card p-6 md:p-8 neon-edge-magenta{% elif style == "pull" %}border-l-4 border-dashed border-primary pl-6 md:pl-8 py-2{% else %}border-l-2 border-dashed border-primary/50 pl-5 py-1{% endif %}{% if align == "center" %} text-center{% endif %}{% if class %} {{ class }}{% endif %}"><blockquote{% if cite %} cite="{{ cite }}"{% endif %} class="{% if style == "pull" %}text-2xl md:text-3xl font-semibold leading-snug tracking-tight cyberpunk-display{% else %}text-lg md:text-xl leading-relaxed{% endif %} text-foreground">{{ quote|safe }}</blockquote>{% if attribution or role or cite %}<figcaption class="mt-4 text-sm text-muted-foreground cyberpunk-mono{% if align == "center" %} flex flex-col items-center{% endif %}">{% if attribution %}<span class="font-medium text-foreground">{{ attribution }}</span>{% endif %}{% if role %}<span class="{% if attribution %}block {% endif %}text-muted-foreground">{{ role }}</span>{% endif %}{% if cite %}<a href="{{ cite }}" class="{% if attribution or role %}mt-1 {% endif %}inline-block underline underline-offset-2 hover:text-foreground">Source</a>{% endif %}</figcaption>{% endif %}</figure>{% endwith %}

View File

@ -0,0 +1,17 @@
{# related-posts (cyberpunk override) — grid from the `posts` provider. #}
{% if posts %}
<section class="border-t border-primary/30 py-12 md:py-16 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
<h2 class="cyberpunk-display glitch mb-8 text-2xl font-semibold tracking-tight" data-text="{{ heading|default:'Related posts' }}">{{ heading|default:"Related posts" }}</h2>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% else %}lg:grid-cols-3{% endif %}">
{% 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-cyan transition-transform hover:-translate-y-0.5">
{% if post.featured_image_url %}<a href="{{ post.url }}" class="mb-3 block aspect-video overflow-hidden rounded-md bg-muted">{% img post.featured_image_url alt=post.title class="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
<h3 class="cyberpunk-display text-base font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h3>
{% if post.published_at %}<p class="cyberpunk-mono mt-1 text-xs uppercase tracking-wide text-accent">{{ post.published_at|date:"M j, Y" }}</p>{% endif %}
</article>
{% endfor %}
</div>
</div>
</section>
{% endif %}

View File

@ -0,0 +1,14 @@
{# rich_section cyberpunk override: mono eyebrow, glitch heading, HUD-framed media, clip-cut CTA. Field reads + {% img %} preserved. #}
<section class="py-16 md:py-24 {% if background == "muted" %}bg-muted text-foreground{% elif background == "card" %}bg-card text-card-foreground{% else %}bg-background text-foreground{% endif %}{% if class %} {{ class }}{% endif %}">
<div class="mx-auto grid max-w-6xl items-center gap-10 px-4 md:grid-cols-2 md:gap-16">
<div class="{% if mediaPosition == "left" %}md:order-2{% else %}md:order-1{% endif %}">
{% if eyebrow %}<p class="cyberpunk-mono text-xs font-semibold uppercase tracking-widest text-accent">{{ eyebrow }}</p>{% endif %}
{% if heading %}<h2 class="cyberpunk-display glitch mt-2 text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
{% if body %}<div class="mt-4 space-y-4 text-lg text-muted-foreground [&_a]:text-primary [&_a]:underline [&_strong]:text-foreground">{{ body|safe }}</div>{% endif %}
{% if ctaUrl and ctaLabel %}<a href="{{ ctaUrl }}" class="cyberpunk-mono uppercase tracking-wider clip-cut rgb-split mt-6 inline-flex items-center gap-2 rounded-none bg-primary px-5 py-3 font-semibold text-primary-foreground transition-opacity hover:opacity-90">{{ ctaLabel }}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#arrow-right"></use></svg></a>{% endif %}
</div>
<div class="{% if mediaPosition == "left" %}md:order-1{% else %}md:order-2{% endif %}">
{% if media %}<div class="hud-frame overflow-hidden rounded-md border border-dashed border-primary/50 neon-edge-cyan">{% img media alt=mediaAlt class="w-full h-auto object-cover" %}</div>{% endif %}
</div>
</div>
</section>

View File

@ -0,0 +1,2 @@
{# cyberpunk rich-text — body font via cyberpunk-prose #}
{% if html %}<article class="rich-text prose prose-lg max-w-none cyberpunk-prose cyberpunk-body">{{ html|safe }}</article>{% endif %}

View File

@ -0,0 +1,15 @@
{# sidebar-nav (cyberpunk override) — vertical menu from the `menus` provider. Variants #}
{# default | compact. highlight-current carried as a progressive-enhancement #}
{# script keyed on data-bn-sidebar-link (marks the anchor matching location.pathname). #}
{% set compact = template == "compact" %}
<nav class="flex-1 {% if compact %}w-56 py-4 pr-3{% else %}w-64 py-6 pr-4{% endif %}{% if showBorder %} border-r border-primary/30{% endif %}{% if class %} {{ class }}{% endif %}">
{% if title %}<h3 class="cyberpunk-eyebrow cyberpunk-mono {% if compact %}px-2 mb-3 text-xs uppercase tracking-widest text-accent{% else %}px-3 mb-4 text-sm uppercase tracking-widest text-accent{% endif %} font-semibold">{{ title }}</h3>{% endif %}
<ul class="{% if compact %}space-y-0.5{% else %}space-y-1{% endif %}">
{% for item in menu.items %}
<li><a href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} data-bn-sidebar-link class="flex items-center rounded-md cyberpunk-mono text-sm uppercase tracking-wide text-muted-foreground transition-colors hover:text-primary hover:bg-accent {% if compact %}px-2 py-1.5{% else %}px-3 py-2{% endif %} {{ item.css_class }}">{% if item.icon %}<svg class="mr-2 h-4 w-4" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"></use></svg>{% endif %}{{ item.label }}</a></li>
{% endfor %}
</ul>
</nav>
{% if highlightCurrent %}<script>
(function(){var p=window.location.pathname;document.querySelectorAll('[data-bn-sidebar-link]').forEach(function(a){if(a.getAttribute('href')===p){a.classList.remove('text-muted-foreground');a.classList.add('text-foreground','bg-accent','font-medium');}});})();
</script>{% endif %}

View File

@ -0,0 +1,14 @@
{# cyberpunk social-links — mono label, sharp neon-bordered chips; icon sprites preserved #}
<div class="social-links{% if class %} {{ class }}{% endif %}">
{% if heading %}<p class="mb-3 text-sm font-medium text-muted-foreground cyberpunk-mono uppercase tracking-widest">{{ heading }}</p>{% endif %}
<ul class="flex {% if style == "stack" %}flex-col gap-2{% else %}flex-wrap items-center gap-3{% endif %}">
{% for link in links %}{% if link.url %}
<li>
<a href="{{ link.url }}" target="_blank" rel="noopener noreferrer" aria-label="{{ link.label|default:link.network }}" class="inline-flex items-center gap-2 rounded-none border border-primary/40 text-foreground transition-colors hover:bg-accent hover:text-accent-foreground {% if style == "stack" %}w-full px-4 py-2{% else %}p-2.5{% endif %}">
{% if link.network %}<svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ link.network|split:":"|first }}.svg#{{ link.network|split:":"|last }}"></use></svg>{% endif %}
{% if style == "stack" and link.label %}<span class="text-sm font-medium cyberpunk-mono">{{ link.label }}</span>{% endif %}
</a>
</li>
{% endif %}{% endfor %}
</ul>
</div>

View File

@ -0,0 +1,50 @@
{# stats cyberpunk override: scanline overlay, glitch heading, HUD stat tiles, mono labels. data-stats + countUp script/attrs preserved byte-for-byte. #}
<section class="relative overflow-hidden py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}"{% if countUp %} data-stats{% endif %}>
<div aria-hidden="true" class="scanlines pointer-events-none absolute inset-0"></div>
<div class="relative z-10 mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="cyberpunk-display glitch 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 %}
<dl class="mt-12 grid grid-cols-2 gap-8 {% if columns == 2 %}md:grid-cols-2{% elif columns == 3 %}md:grid-cols-3{% else %}md:grid-cols-4{% endif %}">
{% for item in items %}
<div class="hud-frame text-center">
<dd class="cyberpunk-display text-4xl font-bold tracking-tight md:text-5xl" style="font-family:var(--font-heading)">{% if item.prefix %}{{ item.prefix }}{% endif %}<span{% if countUp %} data-countup-value="{{ item.value }}"{% endif %}>{{ item.value }}</span>{% if item.suffix %}{{ item.suffix }}{% endif %}</dd>
{% if item.label %}<dt class="cyberpunk-mono mt-2 text-sm font-medium uppercase tracking-widest text-muted-foreground">{{ item.label }}</dt>{% endif %}
</div>
{% endfor %}
</dl>
</div>
</section>
{% if countUp %}<script>
(function(){
var sections = document.querySelectorAll('[data-stats]');
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function animate(section){
var nodes = section.querySelectorAll('[data-countup-value]');
nodes.forEach(function(node){
var raw = node.getAttribute('data-countup-value') || '';
var target = parseFloat(raw.replace(/[^0-9.\-]/g, ''));
if (isNaN(target)) return;
var decimals = (raw.split('.')[1] || '').length;
var start = null, dur = 1200;
function step(ts){
if (start === null) start = ts;
var p = Math.min((ts - start) / dur, 1);
var eased = 1 - Math.pow(1 - p, 3);
node.textContent = (target * eased).toFixed(decimals);
if (p < 1) requestAnimationFrame(step);
else node.textContent = target.toFixed(decimals);
}
requestAnimationFrame(step);
});
}
sections.forEach(function(section){
if (reduce || !('IntersectionObserver' in window)) return;
var io = new IntersectionObserver(function(entries){
entries.forEach(function(e){
if (e.isIntersecting){ animate(section); io.disconnect(); }
});
}, { threshold: 0.3 });
io.observe(section);
});
})();
</script>{% endif %}

View File

@ -0,0 +1,22 @@
{# team cyberpunk override: glitch heading, dashed-neon HUD member panels (lime), mono roles. {% img %} + social icon svgs + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="cyberpunk-display glitch 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 {% if layout == "list" %}mx-auto max-w-3xl space-y-6{% elif layout == "single" %}mx-auto max-w-xl{% else %}grid grid-cols-1 gap-8 sm:grid-cols-2 {% if columns == 2 %}lg:grid-cols-2{% elif columns == 4 %}lg:grid-cols-4{% else %}lg:grid-cols-3{% endif %}{% endif %}">
{% for member in members %}
<div class="{% if layout == "list" %}hud-frame flex items-start gap-5 rounded-md border border-dashed border-primary/50 bg-card p-5 text-card-foreground neon-edge-lime{% else %}text-center{% endif %}">
{% if member.photo %}<span class="{% if layout == "list" %}h-20 w-20{% else %}mx-auto h-28 w-28{% endif %} block shrink-0 overflow-hidden rounded-full bg-muted">{% img member.photo alt=member.name class="h-full w-full object-cover" %}</span>{% endif %}
<div class="{% if layout != "list" %}mt-4{% endif %}">
{% if member.name %}<h3 class="cyberpunk-display text-lg font-semibold">{{ member.name }}</h3>{% endif %}
{% if member.role %}<p class="cyberpunk-mono uppercase tracking-wide text-sm font-medium text-primary">{{ member.role }}</p>{% endif %}
{% if member.bio %}<p class="mt-2 text-sm text-muted-foreground">{{ member.bio }}</p>{% endif %}
{% if member.socials %}<div class="mt-3 flex gap-3 {% if layout != "list" %}justify-center{% endif %}">
{% for social in member.socials %}{% if social.url %}<a href="{{ social.url }}" class="text-muted-foreground transition-colors hover:text-primary" aria-label="Social link"><svg class="h-5 w-5" aria-hidden="true"><use href="/icons/{{ social.icon|split:":"|first }}.svg#{{ social.icon|split:":"|last }}"></use></svg></a>{% endif %}{% endfor %}
</div>{% endif %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>

View File

@ -0,0 +1,27 @@
{# testimonial cyberpunk override: glitch heading, dashed-neon HUD quote cards. Rating svgs, {% img %}, and control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-6xl px-4">
{% if heading %}<h2 class="cyberpunk-display glitch text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
<div class="mt-12 {% if layout == "single" %}mx-auto max-w-3xl{% elif layout == "carousel" %}flex snap-x snap-mandatory gap-6 overflow-x-auto pb-4{% else %}grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3{% endif %}">
{% for q in quotes %}
<figure class="hud-frame flex flex-col rounded-md border border-dashed border-primary/50 bg-card p-6 text-card-foreground neon-edge-cyan{% if layout == "carousel" %} min-w-[18rem] max-w-sm shrink-0 snap-start{% endif %}">
{% if q.rating %}<div class="mb-3 flex gap-0.5" aria-label="Rated {{ q.rating }} out of 5">
<svg class="h-4 w-4 {% if q.rating >= 1 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
<svg class="h-4 w-4 {% if q.rating >= 2 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
<svg class="h-4 w-4 {% if q.rating >= 3 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
<svg class="h-4 w-4 {% if q.rating >= 4 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
<svg class="h-4 w-4 {% if q.rating >= 5 %}text-primary{% else %}text-muted-foreground/30{% endif %}" aria-hidden="true"><use href="/icons/lucide.svg#star"></use></svg>
</div>{% endif %}
<blockquote class="flex-1 text-lg leading-relaxed">{% if q.quote %}&ldquo;{{ q.quote }}&rdquo;{% endif %}</blockquote>
<figcaption class="mt-5 flex items-center gap-3">
{% if q.avatar %}<span class="h-10 w-10 shrink-0 overflow-hidden rounded-full bg-muted">{% img q.avatar alt=q.name class="h-full w-full object-cover" %}</span>{% endif %}
<span>
{% if q.name %}<span class="cyberpunk-display block font-semibold">{{ q.name }}</span>{% endif %}
{% if q.role %}<span class="cyberpunk-mono uppercase tracking-wide block text-sm text-muted-foreground">{{ q.role }}</span>{% endif %}
</span>
</figcaption>
</figure>
{% endfor %}
</div>
</div>
</section>

View File

@ -1 +1,2 @@
<div class="cyberpunk-prose prose prose-invert max-w-none leading-relaxed {{ class }}" style="color: hsl(var(--foreground)); font-family: var(--font-body, 'Inter', system-ui, sans-serif);">{{ text|safe }}</div>
{# cyberpunk text — body font via cyberpunk-prose #}
<div class="prose prose-gray max-w-none flex-1 cyberpunk-prose cyberpunk-body {{ proseClass }}">{{ text|safe }}</div>

View File

@ -0,0 +1,22 @@
{# timeline cyberpunk override: glitch heading, mono step labels, neon markers. All field reads + control flow preserved. #}
<section class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
<div class="mx-auto max-w-4xl px-4">
{% if heading %}<h2 class="cyberpunk-display glitch 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 %}
<ol class="mt-12 {% if orientation == "horizontal" %}grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-4{% else %}relative space-y-8 before:absolute before:left-4 before:top-2 before:h-[calc(100%-1rem)] before:w-px before:bg-primary/50{% endif %}">
{% for step in steps %}
<li class="{% if orientation == "horizontal" %}text-center{% else %}relative pl-14{% endif %}">
<span class="clip-cut flex h-8 w-8 items-center justify-center rounded-md bg-primary text-sm font-semibold text-primary-foreground cyberpunk-mono {% if orientation == "horizontal" %}mx-auto{% else %}absolute left-0 top-0 ring-4 ring-background{% endif %}">
{% if marker == "numbered" %}{{ forloop.counter }}{% elif marker == "dot" %}<span class="h-2 w-2 rounded-full bg-primary-foreground"></span>{% else %}<svg class="h-4 w-4" aria-hidden="true"><use href="/icons/lucide.svg#flag"></use></svg>{% endif %}
</span>
<div class="{% if orientation == "horizontal" %}mt-4{% else %}pt-1{% endif %}">
{% if step.label %}<p class="cyberpunk-mono text-xs font-semibold uppercase tracking-widest text-accent">{{ step.label }}</p>{% endif %}
{% if step.title %}<h3 class="cyberpunk-display mt-1 text-lg font-semibold">{{ step.title }}</h3>{% endif %}
{% if step.text %}<p class="mt-1 text-muted-foreground">{{ step.text }}</p>{% endif %}
{% if step.media %}<div class="mt-3 overflow-hidden rounded-md border border-primary/40">{% img step.media alt=step.title class="w-full h-auto object-cover" %}</div>{% endif %}
</div>
</li>
{% endfor %}
</ol>
</div>
</section>

View File

@ -0,0 +1,10 @@
{# utility_bar (WO-TF-006): thin top contact strip; left/right item lists {icon,label,link}; icons render through the sprite pipeline via the ::pack:name:: shorthand. Cyberpunk override: mono terminal readout, neon underline. #}
{% set ubcls = "bg-muted text-muted-foreground border-b border-primary/30" %}
{% if variant == "primary" %}{% set ubcls = "bg-primary text-primary-foreground" %}{% elif variant == "dark" %}{% set ubcls = "bg-foreground text-background" %}{% endif %}
{% macro ubitem(it) %}{% if it.link %}<a href="{{ it.link }}" class="inline-flex items-center gap-1.5 hover:opacity-80 transition-opacity">{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</a>{% else %}<span class="inline-flex items-center gap-1.5">{% if it.icon %}::{{ it.icon }}::{% endif %}{% if it.label %}<span>{{ it.label }}</span>{% endif %}</span>{% endif %}{% endmacro %}
<div class="cyberpunk-mono uppercase tracking-wide w-full text-xs {% if not showOnMobile %}hidden sm:block{% endif %} {{ ubcls }} {{ class }}">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-9 flex items-center justify-between gap-4">
<div class="flex items-center gap-4 flex-wrap">{% for it in leftItems %}{{ ubitem(it) }}{% endfor %}</div>
<div class="flex items-center gap-4 flex-wrap justify-end">{% for it in rightItems %}{{ ubitem(it) }}{% endfor %}</div>
</div>
</div>

View File

@ -0,0 +1,23 @@
{# video-embed cyberpunk override: neon HUD frame, clip-cut play button. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
{% with aspect=aspect|default:"16:9" %}<figure class="bn-video my-6{% if class %} {{ class }}{% endif %}"><div class="bn-video-frame hud-frame relative w-full overflow-hidden rounded-md border border-primary/40 bg-muted neon-edge-magenta {% if aspect == "4:3" %}aspect-[4/3]{% elif aspect == "1:1" %}aspect-square{% elif aspect == "21:9" %}aspect-[21/9]{% else %}aspect-video{% endif %}" data-bn-video data-video-url="{{ url|default:"" }}" data-video-media="{% if media %}{{ media|mediaURL }}{% endif %}"><button type="button" data-bn-video-play class="group absolute inset-0 flex h-full w-full items-center justify-center focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring" aria-label="Play video{% if title %}: {{ title }}{% endif %}">{% if poster %}{% img poster alt=title|default:"" class="absolute inset-0 h-full w-full object-cover" %}{% endif %}<span class="absolute inset-0 bg-primary/10 transition-colors group-hover:bg-primary/20" aria-hidden="true"></span><span class="clip-cut relative inline-flex h-16 w-16 items-center justify-center rounded-md border border-primary/60 bg-background/90 text-primary shadow-lg transition-transform group-hover:scale-105 motion-reduce:transform-none">::lucide:play:xl::</span></button></div>{% if caption %}<figcaption class="cyberpunk-mono mt-2 text-center text-sm text-muted-foreground">{{ caption }}</figcaption>{% endif %}</figure><script>
(function(){
if(window.__bnVideoFacade)return;window.__bnVideoFacade=true;
function yt(u){var m=u.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/)|youtu\.be\/)([\w-]{6,})/);return m?m[1]:null;}
function vim(u){var m=u.match(/vimeo\.com\/(?:video\/)?(\d+)/);return m?m[1]:null;}
document.addEventListener('click',function(e){
var btn=e.target.closest('[data-bn-video-play]');if(!btn)return;
var frame=btn.closest('[data-bn-video]');if(!frame)return;
e.preventDefault();
var url=frame.getAttribute('data-video-url')||'',media=frame.getAttribute('data-video-media')||'',node;
var vstyle='position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:rgba(0,0,0,1);';
if(media){node=document.createElement('video');node.src=media;node.controls=true;node.autoplay=true;node.setAttribute('playsinline','');node.style.cssText=vstyle;}
else{var y=yt(url),v=vim(url),embed;
if(y)embed='https://www.youtube-nocookie.com/embed/'+y+'?autoplay=1&rel=0';
else if(v)embed='https://player.vimeo.com/video/'+v+'?autoplay=1';
if(embed){node=document.createElement('iframe');node.src=embed;node.allow='autoplay; fullscreen; picture-in-picture';node.setAttribute('allowfullscreen','');node.setAttribute('title',btn.getAttribute('aria-label')||'Video');node.style.cssText='position:absolute;inset:0;width:100%;height:100%;border:0;';}
else if(url){node=document.createElement('video');node.src=url;node.controls=true;node.autoplay=true;node.setAttribute('playsinline','');node.style.cssText=vstyle;}
}
if(node){btn.remove();frame.appendChild(node);}
});
})();
</script>{% endwith %}