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:
parent
8ea1477d37
commit
02a84ab31e
@ -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
24
RECOMMENDED_ICONS.md
Normal file
@ -0,0 +1,24 @@
|
||||
# Recommended icons — Cyberpunk
|
||||
|
||||
Icons render through the CMS sprite pipeline: `<svg><use href="/icons/<pack>.svg#<name>"/></svg>`
|
||||
(or the `::pack:name:size::` shorthand). The theme's overrides reference two bundled packs.
|
||||
|
||||
## Required packs
|
||||
|
||||
Declared in `plugin.mod` as `required_icon_packs = ["lucide", "simple-icons"]`
|
||||
(forward-declared — the loader does not yet auto-install; install manually until it does).
|
||||
|
||||
| Pack | Used for | Install |
|
||||
|---|---|---|
|
||||
| **lucide** | UI glyphs the builtin/override templates use: `arrow-right`, `check`, `chevron-down`, `chevron-left`, `chevron-right`, `x`, `star`, `flag`, `navigation`, `map-pin`, `download`, `play`, `asterisk`, `globe` | Ships bundled with the CMS — no action needed. |
|
||||
| **simple-icons** | Author/social brand glyphs: `x` (Twitter), `linkedin`, `github` | Ships bundled with the CMS — no action needed. |
|
||||
|
||||
Both packs ship bundled with the CMS, so a fresh instance renders every override icon with
|
||||
no manual step. If you author blocks that reference other packs (e.g. `tabler`, `phosphor`),
|
||||
install them from `/admin/icons` (Browse -> install -> enable) before activation.
|
||||
|
||||
## Note
|
||||
|
||||
Icon values in block content are stored as `"pack:name"` strings and split with
|
||||
`|split:":"|first` / `|last`. Never inline raw SVG path data for content icons — that
|
||||
bypasses the sprite system and the admin pack manager.
|
||||
21
assets/fonts/LICENSES.md
Normal file
21
assets/fonts/LICENSES.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Bundled font licenses — Cyberpunk theme
|
||||
|
||||
All three families are bundled as latin-subset `.woff2` and are licensed under the
|
||||
SIL Open Font License 1.1 (OFL). Redistribution inside this theme artifact is permitted.
|
||||
|
||||
| File | Family | License | Source |
|
||||
|---|---|---|---|
|
||||
| `orbitron-latin.woff2` | Orbitron (variable, wght 500–900) | OFL 1.1 | Google Fonts (fonts.gstatic.com, v35), latin subset |
|
||||
| `share-tech-mono-latin.woff2` | Share Tech Mono (400) | OFL 1.1 | Google Fonts (fonts.gstatic.com, v16), latin subset |
|
||||
| `inter-latin.woff2` | Inter (variable, wght 400–700) | OFL 1.1 | Google Fonts (fonts.gstatic.com, v20), latin subset |
|
||||
|
||||
OFL 1.1 full text: https://openfontlicense.org
|
||||
|
||||
- Orbitron © The Orbitron Project Authors (Matt McInerney).
|
||||
- Share Tech Mono © Carrois Apostrophe.
|
||||
- Inter © The Inter Project Authors (Rasmus Andersson).
|
||||
|
||||
These are the Google-hosted latin subsets (Latin + Latin-1 punctuation), chosen to keep
|
||||
the bundle small (~73 KB total). They cover English content; sites needing extended Latin,
|
||||
Cyrillic, Greek, or Vietnamese glyphs should assign the full family from the admin Google
|
||||
Fonts picker instead.
|
||||
BIN
assets/fonts/inter-latin.woff2
Normal file
BIN
assets/fonts/inter-latin.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/orbitron-latin.woff2
Normal file
BIN
assets/fonts/orbitron-latin.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/share-tech-mono-latin.woff2
Normal file
BIN
assets/fonts/share-tech-mono-latin.woff2
Normal file
Binary file not shown.
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
5
blocks/code_neon.sample.json
Normal file
5
blocks/code_neon.sample.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"language": "bash",
|
||||
"code": "npm install @neon-forge/sdk\nnpx neon-forge init my-game\nnpx neon-forge ship --stage prod",
|
||||
"copy": "on"
|
||||
}
|
||||
@ -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>
|
||||
@ -1,34 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Terminal CTA",
|
||||
"description": "Faux $ prompt strip with blinking caret and optional copy-to-clipboard.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"title": "Prompt",
|
||||
"description": "The command text following the $ glyph (e.g. npm i your-thing)",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"button": {
|
||||
"type": "string",
|
||||
"title": "Button label",
|
||||
"description": "Label for the action button (e.g. Copy)",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"href": {
|
||||
"type": "string",
|
||||
"title": "Button URL",
|
||||
"description": "Optional URL the button points at when copy is off",
|
||||
"x-editor": "link"
|
||||
},
|
||||
"copyable": {
|
||||
"type": "string",
|
||||
"title": "Copy to clipboard",
|
||||
"description": "When 'on' the button copies the prompt text instead of navigating",
|
||||
"x-editor": "select",
|
||||
"enum": ["on", "off"],
|
||||
"default": "on"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +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>
|
||||
@ -1,34 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Neon Feature Card",
|
||||
"description": "Single feature card with icon, title, body and per-card neon edge glow.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"icon": {
|
||||
"type": "string",
|
||||
"title": "Icon",
|
||||
"description": "Media reference for the feature icon",
|
||||
"x-editor": "media"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"title": "Title",
|
||||
"description": "Card heading",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"title": "Body",
|
||||
"description": "Supporting rich text",
|
||||
"x-editor": "richtext"
|
||||
},
|
||||
"accent": {
|
||||
"type": "string",
|
||||
"title": "Accent",
|
||||
"description": "Neon edge colour: magenta, cyan or lime",
|
||||
"x-editor": "select",
|
||||
"enum": ["magenta", "cyan", "lime"],
|
||||
"default": "magenta"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +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>
|
||||
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
blocks/glitch_hero.ninjatpl
Normal file
19
blocks/glitch_hero.ninjatpl
Normal file
@ -0,0 +1,19 @@
|
||||
{# cyberpunk:glitch_hero — persona boot-console hero. Terminal boot lines, glitch wordmark, neon CTAs. No canvas (the builtin hero override owns the digital-rain showpiece). #}
|
||||
<section data-block="cyberpunk:glitch_hero" class="relative overflow-hidden bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div aria-hidden="true" class="scanlines pointer-events-none absolute inset-0"></div>
|
||||
<div class="relative z-10 mx-auto max-w-4xl px-4 py-20 md:py-28">
|
||||
<div class="hud-frame rounded-md border border-primary/40 bg-card/60 p-6 md:p-10 neon-edge-magenta">
|
||||
{% if bootLines %}<div class="cyberpunk-mono mb-6 space-y-1 text-sm text-muted-foreground">
|
||||
{% for line in bootLines %}<div><span class="text-accent">$</span> {{ line.text }}</div>{% endfor %}
|
||||
<div><span class="text-accent">$</span> <span class="caret-blink text-primary">_</span></div>
|
||||
</div>{% endif %}
|
||||
{% if eyebrow %}<p class="cyberpunk-eyebrow mb-3">{{ eyebrow }}</p>{% endif %}
|
||||
{% if headline %}<h1 class="cyberpunk-display glitch text-4xl md:text-6xl font-bold tracking-tight" data-text="{{ headline }}">{{ headline }}</h1>{% endif %}
|
||||
{% if subhead %}<p class="mt-4 max-w-2xl text-lg text-muted-foreground">{{ subhead }}</p>{% endif %}
|
||||
{% if primaryLabel or secondaryLabel %}<div class="mt-8 flex flex-col sm:flex-row gap-4">
|
||||
{% if primaryLabel and primaryUrl %}<a href="{{ primaryUrl }}" class="cyberpunk-mono clip-cut rgb-split inline-flex items-center justify-center gap-2 rounded-md bg-primary px-6 py-3 font-semibold uppercase tracking-wider text-primary-foreground transition-opacity hover:opacity-90">{{ primaryLabel }}</a>{% endif %}
|
||||
{% if secondaryLabel and secondaryUrl %}<a href="{{ secondaryUrl }}" class="cyberpunk-mono inline-flex items-center justify-center rounded-md border border-accent/60 px-6 py-3 font-semibold uppercase tracking-wider text-accent transition-colors hover:bg-accent/10">{{ secondaryLabel }}</a>{% endif %}
|
||||
</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
14
blocks/glitch_hero.sample.json
Normal file
14
blocks/glitch_hero.sample.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"eyebrow": "Build 2.049.0",
|
||||
"headline": "Ship the impossible",
|
||||
"subhead": "A game studio and the tools that ship it. Engine, live ops, and a build farm that never sleeps.",
|
||||
"primaryLabel": "Get the SDK",
|
||||
"primaryUrl": "/contact",
|
||||
"secondaryLabel": "Read the docs",
|
||||
"secondaryUrl": "/blog",
|
||||
"bootLines": [
|
||||
{ "text": "init neon-forge core ... ok" },
|
||||
{ "text": "mount render pipeline ... ok" },
|
||||
{ "text": "link shader cache ... ok" }
|
||||
]
|
||||
}
|
||||
29
blocks/glitch_hero.schema.json
Normal file
29
blocks/glitch_hero.schema.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Glitch Boot Hero",
|
||||
"description": "Terminal boot-console hero with a glitch wordmark and neon CTAs.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" },
|
||||
"headline": { "type": "string", "title": "Headline", "x-editor": "text" },
|
||||
"subhead": { "type": "string", "title": "Subheadline", "x-editor": "textarea" },
|
||||
"primaryLabel": { "type": "string", "title": "Primary button label", "x-editor": "text" },
|
||||
"primaryUrl": { "type": "string", "title": "Primary button URL", "x-editor": "link" },
|
||||
"secondaryLabel": { "type": "string", "title": "Secondary button label", "x-editor": "text" },
|
||||
"secondaryUrl": { "type": "string", "title": "Secondary button URL", "x-editor": "link" },
|
||||
"bootLines": {
|
||||
"type": "array",
|
||||
"title": "Boot lines",
|
||||
"description": "Faux terminal boot lines shown above the headline",
|
||||
"default": [],
|
||||
"x-editor": "collection",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": { "type": "string", "title": "Line", "x-editor": "text" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"class": { "type": "string", "title": "CSS Classes", "x-editor": "text" }
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -1,54 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Glitch Hero",
|
||||
"description": "Cyberpunk hero section with RGB-split H1, eyebrow, sub, primary + secondary CTAs and an optional scanline overlay.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"eyebrow": {
|
||||
"type": "string",
|
||||
"title": "Eyebrow",
|
||||
"description": "Short mono caps label above the headline (e.g. NEW)",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"headline": {
|
||||
"type": "string",
|
||||
"title": "Headline",
|
||||
"description": "Main H1; rendered with RGB-split glitch text-shadow",
|
||||
"x-editor": "text"
|
||||
},
|
||||
"sub": {
|
||||
"type": "string",
|
||||
"title": "Sub",
|
||||
"description": "Supporting paragraph beneath the headline",
|
||||
"x-editor": "textarea"
|
||||
},
|
||||
"cta": {
|
||||
"type": "object",
|
||||
"title": "Primary CTA",
|
||||
"description": "Primary call to action {label, href}",
|
||||
"x-editor": "link",
|
||||
"properties": {
|
||||
"label": { "type": "string", "title": "Label", "x-editor": "text" },
|
||||
"href": { "type": "string", "title": "Href", "x-editor": "text" }
|
||||
}
|
||||
},
|
||||
"secondaryCta": {
|
||||
"type": "object",
|
||||
"title": "Secondary CTA",
|
||||
"description": "Optional secondary call to action {label, href}",
|
||||
"x-editor": "link",
|
||||
"properties": {
|
||||
"label": { "type": "string", "title": "Label", "x-editor": "text" },
|
||||
"href": { "type": "string", "title": "Href", "x-editor": "text" }
|
||||
}
|
||||
},
|
||||
"scanlines": {
|
||||
"type": "string",
|
||||
"title": "Scanlines",
|
||||
"description": "Toggle the 4% scanline overlay inside the hero section",
|
||||
"x-editor": "select",
|
||||
"enum": ["on", "off"],
|
||||
"default": "on"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +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>
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
blocks/patch_notes.ninjatpl
Normal file
21
blocks/patch_notes.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
||||
{# cyberpunk:patch_notes — changelog feed. Versioned entries with tag chip + mono item list. #}
|
||||
<section data-block="cyberpunk:patch_notes" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-3xl px-4">
|
||||
{% if heading %}<h2 class="cyberpunk-display glitch text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mt-3 text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||
<ol class="mt-10 space-y-6">
|
||||
{% for entry in entries %}
|
||||
<li class="hud-frame rounded-md border border-primary/40 bg-card p-5 text-card-foreground neon-edge-magenta">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
{% if entry.version %}<span class="cyberpunk-mono text-lg font-bold text-primary">{{ entry.version }}</span>{% endif %}
|
||||
{% if entry.tag %}<span class="cyberpunk-mono clip-cut rounded border border-accent/50 px-2 py-0.5 text-xs uppercase tracking-wide text-accent">{{ entry.tag }}</span>{% endif %}
|
||||
{% if entry.date %}<span class="cyberpunk-mono ml-auto text-xs text-muted-foreground">{{ entry.date }}</span>{% endif %}
|
||||
</div>
|
||||
{% if entry.items %}<ul class="mt-3 space-y-1.5">
|
||||
{% for item in entry.items %}<li class="cyberpunk-mono flex gap-2 text-sm text-muted-foreground"><span class="text-primary">+</span><span>{{ item.text }}</span></li>{% endfor %}
|
||||
</ul>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
32
blocks/patch_notes.sample.json
Normal file
32
blocks/patch_notes.sample.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"heading": "Patch notes",
|
||||
"intro": "What changed, build by build.",
|
||||
"entries": [
|
||||
{
|
||||
"version": "v2.049.0",
|
||||
"date": "2049.04.01",
|
||||
"tag": "FEATURE",
|
||||
"items": [
|
||||
{ "text": "New render path: volumetric neon fog on all night maps." },
|
||||
{ "text": "Build farm now caches shaders across branches." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "v2.048.3",
|
||||
"date": "2049.03.18",
|
||||
"tag": "FIX",
|
||||
"items": [
|
||||
{ "text": "Fixed a stall when hot-reloading materials." },
|
||||
{ "text": "Netcode: reduced desync on 200ms links." }
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "v2.048.0",
|
||||
"date": "2049.03.02",
|
||||
"tag": "BALANCE",
|
||||
"items": [
|
||||
{ "text": "Tuned dash cooldown from 4s to 3.2s." }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
37
blocks/patch_notes.schema.json
Normal file
37
blocks/patch_notes.schema.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Patch Notes",
|
||||
"description": "Changelog feed of versioned entries with tag chips.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"heading": { "type": "string", "title": "Heading", "x-editor": "text" },
|
||||
"intro": { "type": "string", "title": "Intro", "x-editor": "textarea" },
|
||||
"entries": {
|
||||
"type": "array",
|
||||
"title": "Entries",
|
||||
"default": [],
|
||||
"x-editor": "collection",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"version": { "type": "string", "title": "Version", "description": "e.g. v2.049.0", "x-editor": "text" },
|
||||
"date": { "type": "string", "title": "Date", "x-editor": "text" },
|
||||
"tag": { "type": "string", "title": "Tag", "description": "e.g. FEATURE / FIX / BALANCE", "x-editor": "text" },
|
||||
"items": {
|
||||
"type": "array",
|
||||
"title": "Items",
|
||||
"default": [],
|
||||
"x-editor": "collection",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": { "type": "string", "title": "Line", "x-editor": "text" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"class": { "type": "string", "title": "CSS Classes", "x-editor": "text" }
|
||||
}
|
||||
}
|
||||
23
blocks/roster_grid.ninjatpl
Normal file
23
blocks/roster_grid.ninjatpl
Normal file
@ -0,0 +1,23 @@
|
||||
{# cyberpunk:roster_grid — crew / studio roster. Neon HUD cards with handle, role, status dot. #}
|
||||
<section data-block="cyberpunk:roster_grid" class="py-16 md:py-24 bg-background text-foreground{% if class %} {{ class }}{% endif %}">
|
||||
<div class="mx-auto max-w-6xl px-4">
|
||||
{% if eyebrow %}<p class="cyberpunk-eyebrow text-center">{{ eyebrow }}</p>{% endif %}
|
||||
{% if heading %}<h2 class="cyberpunk-display glitch mt-2 text-center text-3xl font-semibold tracking-tight md:text-4xl" data-text="{{ heading }}">{{ heading }}</h2>{% endif %}
|
||||
{% if intro %}<p class="mx-auto mt-4 max-w-2xl text-center text-lg text-muted-foreground">{{ intro }}</p>{% endif %}
|
||||
<div class="mt-12 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{% for member in members %}
|
||||
<div class="hud-frame flex items-center gap-4 rounded-md border border-primary/40 bg-card p-5 text-card-foreground neon-edge-magenta">
|
||||
{% if member.avatar %}<span class="clip-cut h-14 w-14 shrink-0 overflow-hidden rounded-md border border-primary/40 bg-muted">{% img member.avatar alt=member.name class="h-full w-full object-cover" %}</span>{% else %}<span class="clip-cut flex h-14 w-14 shrink-0 items-center justify-center rounded-md border border-primary/40 bg-primary/10 text-primary"><svg class="h-6 w-6" aria-hidden="true"><use href="/icons/lucide.svg#user"></use></svg></span>{% endif %}
|
||||
<div class="min-w-0">
|
||||
{% if member.name %}<h3 class="cyberpunk-display text-base font-semibold leading-tight">{{ member.name }}</h3>{% endif %}
|
||||
{% if member.handle %}<p class="cyberpunk-mono text-sm text-primary">{{ member.handle }}</p>{% endif %}
|
||||
<p class="mt-1 flex items-center gap-2 text-xs text-muted-foreground">
|
||||
{% if member.status %}<span class="cyberpunk-status-dot inline-block h-2 w-2 rounded-full bg-accent"></span>{% endif %}
|
||||
{% if member.role %}<span class="cyberpunk-mono uppercase tracking-wide">{{ member.role }}</span>{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
13
blocks/roster_grid.sample.json
Normal file
13
blocks/roster_grid.sample.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"eyebrow": "The crew",
|
||||
"heading": "Who ships this",
|
||||
"intro": "A small team that builds the engine, the game, and the pipeline in between.",
|
||||
"members": [
|
||||
{ "name": "Nova Reyes", "handle": "@nova", "role": "Engine lead", "status": true },
|
||||
{ "name": "Kai Okafor", "handle": "@kaisdf", "role": "Graphics", "status": true },
|
||||
{ "name": "Sable Lin", "handle": "@sable", "role": "Live ops", "status": false },
|
||||
{ "name": "Juno Vale", "handle": "@juno", "role": "Gameplay", "status": true },
|
||||
{ "name": "Rin Castellano", "handle": "@rin", "role": "Tools", "status": true },
|
||||
{ "name": "Ash Mori", "handle": "@ash", "role": "Netcode", "status": false }
|
||||
]
|
||||
}
|
||||
28
blocks/roster_grid.schema.json
Normal file
28
blocks/roster_grid.schema.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Roster Grid",
|
||||
"description": "Crew / studio roster of neon HUD cards.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"eyebrow": { "type": "string", "title": "Eyebrow", "x-editor": "text" },
|
||||
"heading": { "type": "string", "title": "Heading", "x-editor": "text" },
|
||||
"intro": { "type": "string", "title": "Intro", "x-editor": "textarea" },
|
||||
"members": {
|
||||
"type": "array",
|
||||
"title": "Members",
|
||||
"default": [],
|
||||
"x-editor": "collection",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string", "title": "Name", "x-editor": "text" },
|
||||
"handle": { "type": "string", "title": "Handle", "description": "e.g. @nova", "x-editor": "text" },
|
||||
"role": { "type": "string", "title": "Role", "x-editor": "text" },
|
||||
"avatar": { "type": "string", "title": "Avatar", "x-editor": "media" },
|
||||
"status": { "type": "boolean", "title": "Show online dot", "default": true, "x-editor": "checkbox" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"class": { "type": "string", "title": "CSS Classes", "x-editor": "text" }
|
||||
}
|
||||
}
|
||||
@ -1 +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>
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
fonts.json
29
fonts.json
@ -1 +1,28 @@
|
||||
[]
|
||||
[
|
||||
{
|
||||
"name": "Orbitron",
|
||||
"family": "Orbitron",
|
||||
"variants": [
|
||||
{ "weight": "500", "style": "normal", "file": "fonts/orbitron-latin.woff2" },
|
||||
{ "weight": "700", "style": "normal", "file": "fonts/orbitron-latin.woff2" },
|
||||
{ "weight": "900", "style": "normal", "file": "fonts/orbitron-latin.woff2" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Share Tech Mono",
|
||||
"family": "Share Tech Mono",
|
||||
"variants": [
|
||||
{ "weight": "400", "style": "normal", "file": "fonts/share-tech-mono-latin.woff2" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Inter",
|
||||
"family": "Inter",
|
||||
"variants": [
|
||||
{ "weight": "400", "style": "normal", "file": "fonts/inter-latin.woff2" },
|
||||
{ "weight": "500", "style": "normal", "file": "fonts/inter-latin.woff2" },
|
||||
{ "weight": "600", "style": "normal", "file": "fonts/inter-latin.woff2" },
|
||||
{ "weight": "700", "style": "normal", "file": "fonts/inter-latin.woff2" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
336
manifest.yaml
336
manifest.yaml
@ -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; }
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@ -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"]
|
||||
|
||||
100
presets.json
100
presets.json
@ -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
219
seed/seed.json
Normal file
@ -0,0 +1,219 @@
|
||||
{
|
||||
"demo": true,
|
||||
"settings": {
|
||||
"merge": {
|
||||
"companyName": "Neon Forge",
|
||||
"tagline": "We build games and the tools that ship them."
|
||||
}
|
||||
},
|
||||
"data_tables": [
|
||||
{
|
||||
"key": "contact_submissions",
|
||||
"name": "Contact Enquiries",
|
||||
"description": "Seeded contact-form submissions for the Neon Forge demo. A submission becomes a row here and emails the site admins.",
|
||||
"columns": [
|
||||
{ "key": "name", "label": "Name", "type": "text", "required": true },
|
||||
{ "key": "email", "label": "Email", "type": "email", "required": true },
|
||||
{ "key": "studio", "label": "Studio", "type": "text", "required": false },
|
||||
{ "key": "message", "label": "Message", "type": "text", "required": true }
|
||||
]
|
||||
}
|
||||
],
|
||||
"pages": [
|
||||
{
|
||||
"slug": "/",
|
||||
"title": "Neon Forge",
|
||||
"template_key": "landing",
|
||||
"blocks": [
|
||||
{
|
||||
"block_key": "hero",
|
||||
"title": "Hero",
|
||||
"slot": "main",
|
||||
"sort_order": 1,
|
||||
"content": {
|
||||
"template": "centered",
|
||||
"minHeight": "screen",
|
||||
"eyebrow": "Build 2.049",
|
||||
"headline": "Ship the impossible",
|
||||
"subheadline": "A game studio and the tools that ship it.",
|
||||
"description": "We build a rendering engine, live ops, and a build farm that never sleeps. Then we make games on top of it.",
|
||||
"ctaPrimary": { "text": "Get the SDK", "url": "/contact" },
|
||||
"ctaSecondary": { "text": "Read the devlog", "url": "/blog" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "feature-grid",
|
||||
"title": "What we ship",
|
||||
"slot": "main",
|
||||
"sort_order": 2,
|
||||
"content": {
|
||||
"eyebrow": "The stack",
|
||||
"heading": "One pipeline, end to end",
|
||||
"intro": "Engine, tooling, and live service. Built in house, shipped together.",
|
||||
"layout": "card",
|
||||
"columns": 3,
|
||||
"items": [
|
||||
{ "icon": "lucide:cpu", "title": "Render engine", "text": "Neon lighting, volumetric fog, and a deferred path tuned for night maps." },
|
||||
{ "icon": "lucide:terminal", "title": "Build farm", "text": "Shader caching across branches. Green builds in minutes, not hours." },
|
||||
{ "icon": "lucide:radio", "title": "Live ops", "text": "Ship events, patch balance, and roll back without a redeploy." }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "cyberpunk:roster_grid",
|
||||
"title": "The crew",
|
||||
"slot": "main",
|
||||
"sort_order": 3,
|
||||
"content": {
|
||||
"eyebrow": "The crew",
|
||||
"heading": "Who ships this",
|
||||
"intro": "A small team that builds the engine, the game, and the pipeline in between.",
|
||||
"members": [
|
||||
{ "name": "Nova Reyes", "handle": "@nova", "role": "Engine lead", "status": true },
|
||||
{ "name": "Kai Okafor", "handle": "@kaisdf", "role": "Graphics", "status": true },
|
||||
{ "name": "Sable Lin", "handle": "@sable", "role": "Live ops", "status": false },
|
||||
{ "name": "Juno Vale", "handle": "@juno", "role": "Gameplay", "status": true },
|
||||
{ "name": "Rin Castellano", "handle": "@rin", "role": "Tools", "status": true },
|
||||
{ "name": "Ash Mori", "handle": "@ash", "role": "Netcode", "status": false }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "cyberpunk:patch_notes",
|
||||
"title": "Patch notes",
|
||||
"slot": "main",
|
||||
"sort_order": 4,
|
||||
"content": {
|
||||
"heading": "Patch notes",
|
||||
"intro": "What changed, build by build.",
|
||||
"entries": [
|
||||
{ "version": "v2.049.0", "date": "2049.04.01", "tag": "FEATURE", "items": [ { "text": "Volumetric neon fog on all night maps." }, { "text": "Build farm caches shaders across branches." } ] },
|
||||
{ "version": "v2.048.3", "date": "2049.03.18", "tag": "FIX", "items": [ { "text": "Fixed a stall when hot-reloading materials." }, { "text": "Reduced desync on high-latency links." } ] },
|
||||
{ "version": "v2.048.0", "date": "2049.03.02", "tag": "BALANCE", "items": [ { "text": "Tuned dash cooldown from 4s to 3.2s." } ] }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"block_key": "cta",
|
||||
"title": "CTA",
|
||||
"slot": "main",
|
||||
"sort_order": 5,
|
||||
"content": {
|
||||
"background": "band",
|
||||
"heading": "Start building tonight",
|
||||
"text": "Grab the SDK and ship your first build before the coffee runs out.",
|
||||
"primaryLabel": "Get the SDK",
|
||||
"primaryUrl": "/contact"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"slug": "/about",
|
||||
"title": "The studio",
|
||||
"template_key": "default",
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "The studio", "level": 1 } },
|
||||
{ "block_key": "rich-text", "title": "Story", "slot": "main", "sort_order": 2, "content": { "html": "<p>Neon Forge started as a build tool for a game that shipped 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
19
seed/workflows.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"workflows": [
|
||||
{
|
||||
"key": "contact_notify_admins",
|
||||
"name": "Contact enquiry to admins",
|
||||
"description": "On a contact-form submission, email the site admins.",
|
||||
"enabled": true,
|
||||
"trigger": { "type": "row_inserted", "table": "contact_submissions" },
|
||||
"steps": [
|
||||
{
|
||||
"type": "email",
|
||||
"name": "Email admins",
|
||||
"template": "Form Submission Notification",
|
||||
"recipients": { "mode": "admins", "scope": "all" }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,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, "Inter", system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
|
||||
<header class="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>
|
||||
|
||||
14
templates/cyberpunk/auth.ninjatpl
Normal file
14
templates/cyberpunk/auth.ninjatpl
Normal file
@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Inter", system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
|
||||
<header class="cyberpunk-chrome w-full" style="border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
|
||||
<main class="flex-grow w-full flex items-center justify-center px-4 py-16">
|
||||
<div class="w-full max-w-md">{% if slots.main %}{{ slots.main|safe }}{% else %}<p class="cyberpunk-mono text-center uppercase tracking-widest" style="color: hsl(var(--muted-foreground));">// auth console</p>{% endif %}</div>
|
||||
</main>
|
||||
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
12
templates/cyberpunk/blog-index.ninjatpl
Normal file
12
templates/cyberpunk/blog-index.ninjatpl
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Inter", system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
|
||||
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
|
||||
<main class="flex-grow w-full max-w-6xl mx-auto px-4 py-12">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center cyberpunk-mono uppercase tracking-widest" style="color: hsl(var(--muted-foreground));"><p>// no posts yet</p></div>{% endif %}</main>
|
||||
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
12
templates/cyberpunk/contact.ninjatpl
Normal file
12
templates/cyberpunk/contact.ninjatpl
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="{% if theme_mode == "dark" %}dark{% endif %}">
|
||||
{{ head_html|safe }}
|
||||
<body class="cyberpunk-body min-h-screen flex flex-col antialiased" style="background-color: hsl(var(--background)); color: hsl(var(--foreground)); font-family: var(--font-body, "Inter", system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
|
||||
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
|
||||
<main class="flex-grow w-full max-w-5xl mx-auto px-4 py-12">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center cyberpunk-mono uppercase tracking-widest" style="color: hsl(var(--muted-foreground));"><p>// contact console offline</p></div>{% endif %}</main>
|
||||
<footer class="w-full mt-auto" style="border-top: 1px solid hsl(var(--border));">{{ slots.footer|safe }}</footer>
|
||||
{{ body_end_html|safe }}
|
||||
</body>
|
||||
</html>
|
||||
@ -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, "Inter", system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
|
||||
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
|
||||
<main class="flex-grow w-full max-w-6xl mx-auto px-4 py-12">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center cyberpunk-empty" style="color: hsl(var(--muted-foreground));"><p class="cyberpunk-mono">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>
|
||||
|
||||
@ -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, "Inter", system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
|
||||
<header class="cyberpunk-chrome w-full sticky top-0 z-40" style="background-color: hsl(var(--background) / 0.85); border-bottom: 1px solid hsl(var(--border));">{{ slots.header|safe }}</header>
|
||||
<main class="flex-grow w-full">{% if slots.main %}{{ slots.main|safe }}{% else %}<div class="py-20 text-center cyberpunk-mono" 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>
|
||||
|
||||
@ -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, "Inter", system-ui, sans-serif);">
|
||||
{{ admin_banner_html|safe }}
|
||||
<div aria-hidden="true" class="scanlines pointer-events-none fixed inset-0 z-50" data-cyberpunk-scanlines></div>
|
||||
<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>
|
||||
|
||||
@ -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;"> </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>
|
||||
|
||||
21
templates/overrides/cyberpunk/announcement-bar.ninjatpl
Normal file
21
templates/overrides/cyberpunk/announcement-bar.ninjatpl
Normal file
@ -0,0 +1,21 @@
|
||||
{# announcement_bar (WO-TF-006): dismissable banner; dismissal persists in localStorage keyed by a content hash (variant + slugified message) so it reappears when the message changes; no-JS fallback is a static non-dismissable strip. Cyberpunk override: mono broadcast readout — data-* + script preserved. #}
|
||||
{% set anncls = "bg-primary/10 text-foreground" %}
|
||||
{% if variant == "promo" %}{% set anncls = "bg-primary text-primary-foreground" %}{% elif variant == "alert" %}{% set anncls = "bg-destructive text-destructive-foreground" %}{% endif %}
|
||||
<div data-bn-announcement data-bn-annkey="{{ variant|default:'info' }}:{{ message|slugify }}" class="bn-announcement w-full {{ anncls }} {{ class }}">
|
||||
<div class="max-w-7xl mx-auto px-10 py-2 relative flex items-center justify-center gap-2 text-sm text-center">
|
||||
<p class="cyberpunk-mono uppercase tracking-wide font-medium">{{ message }}{% if linkText and linkUrl %} <a href="{{ linkUrl }}" class="underline underline-offset-2 hover:opacity-80">{{ linkText }}</a>{% endif %}</p>
|
||||
{% if dismissable %}<button type="button" data-bn-ann-dismiss aria-label="Dismiss announcement" class="absolute right-3 top-1/2 -translate-y-1/2 inline-flex items-center justify-center p-1 rounded hover:bg-black/10 transition-colors"><svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg></button>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if dismissable %}<script>
|
||||
(function(){
|
||||
var bars=document.querySelectorAll('[data-bn-announcement]:not([data-bn-ann-wired])');
|
||||
bars.forEach(function(bar){
|
||||
bar.setAttribute('data-bn-ann-wired','1');
|
||||
var key='bnann:'+bar.getAttribute('data-bn-annkey');
|
||||
try{if(localStorage.getItem(key)==='1'){bar.style.display='none';return;}}catch(e){}
|
||||
var btn=bar.querySelector('[data-bn-ann-dismiss]');
|
||||
if(btn)btn.addEventListener('click',function(){bar.style.display='none';try{localStorage.setItem(key,'1');}catch(e){}});
|
||||
});
|
||||
})();
|
||||
</script>{% endif %}
|
||||
16
templates/overrides/cyberpunk/audio-embed.ninjatpl
Normal file
16
templates/overrides/cyberpunk/audio-embed.ninjatpl
Normal file
@ -0,0 +1,16 @@
|
||||
{# audio-embed cyberpunk override: neon panel, mono meta. Facade script + data-bn-* attributes + {% img %} preserved byte-for-byte. #}
|
||||
<figure class="bn-audio my-6 overflow-hidden rounded-md border border-primary/40 bg-card text-card-foreground neon-edge-magenta{% if class %} {{ class }}{% endif %}" data-bn-audio data-audio-url="{{ url|default:"" }}" data-audio-media="{% if media %}{{ media|mediaURL }}{% endif %}"><div class="flex items-center gap-4 p-4">{% if artwork %}<div class="h-16 w-16 shrink-0 overflow-hidden rounded-md border border-primary/30 bg-muted">{% img artwork alt=title|default:"" class="h-full w-full object-cover" %}</div>{% endif %}<div class="min-w-0 flex-1">{% if title %}<p class="cyberpunk-display truncate font-medium text-foreground">{{ title }}</p>{% endif %}{% if artist %}<p class="cyberpunk-mono truncate text-sm text-muted-foreground">{{ artist }}</p>{% endif %}<div class="mt-2 flex items-center gap-3" data-bn-audio-slot><button type="button" data-bn-audio-play class="cyberpunk-mono clip-cut inline-flex items-center gap-2 rounded-md bg-primary px-4 py-1.5 text-sm font-medium uppercase tracking-wide text-primary-foreground transition-colors hover:bg-primary/90 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring" aria-label="Play{% if title %}: {{ title }}{% endif %}">::lucide:play:sm:: Play</button>{% if download %}<a href="{% if media %}{{ media|mediaURL }}{% else %}{{ url }}{% endif %}" download class="cyberpunk-mono inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground">::lucide:download:sm:: Download</a>{% endif %}</div></div></div></figure><script>
|
||||
(function(){
|
||||
if(window.__bnAudioFacade)return;window.__bnAudioFacade=true;
|
||||
document.addEventListener('click',function(e){
|
||||
var btn=e.target.closest('[data-bn-audio-play]');if(!btn)return;
|
||||
var fig=btn.closest('[data-bn-audio]');if(!fig)return;
|
||||
e.preventDefault();
|
||||
var src=fig.getAttribute('data-audio-media')||fig.getAttribute('data-audio-url')||'';
|
||||
if(!src)return;
|
||||
var slot=btn.closest('[data-bn-audio-slot]');
|
||||
var a=document.createElement('audio');a.src=src;a.controls=true;a.autoplay=true;a.preload='none';a.style.cssText='width:100%;min-width:12rem;height:2.25rem;';
|
||||
btn.remove();slot.insertBefore(a,slot.firstChild);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
77
templates/overrides/cyberpunk/auth-form.ninjatpl
Normal file
77
templates/overrides/cyberpunk/auth-form.ninjatpl
Normal file
@ -0,0 +1,77 @@
|
||||
{# auth-form cyberpunk override — reproduces the builtin's security-critical wiring: hardcoded HTMX endpoints /api/auth/login + /api/auth/register (target #auth-message), /forgot-password link, OAuth start URLs gated on context.site.*, and the exact Cap widget markup when captchaEnabled. Only the chrome is themed. #}
|
||||
{% if context.isPublicLoggedIn %}
|
||||
<div class="auth-form-logged-in mx-auto max-w-md text-center py-6">
|
||||
<p class="cyberpunk-mono text-sm uppercase tracking-wide text-muted-foreground">Session active as <strong class="text-primary">{{ context.publicUsername }}</strong></p>
|
||||
<a href="/account" class="cyberpunk-mono clip-cut mt-3 inline-flex items-center rounded-md bg-primary px-4 py-2 text-sm font-medium uppercase tracking-wide text-primary-foreground hover:bg-primary/90">My Account</a>
|
||||
</div>
|
||||
{% else %}
|
||||
{% set loginActive = "border-primary text-primary" %}
|
||||
{% set registerActive = "border-transparent text-muted-foreground" %}
|
||||
{% set loginHidden = "" %}
|
||||
{% set registerHidden = "hidden" %}
|
||||
{% if default_tab == "register" %}{% set loginActive = "border-transparent text-muted-foreground" %}{% set registerActive = "border-primary text-primary" %}{% set loginHidden = "hidden" %}{% set registerHidden = "" %}{% endif %}
|
||||
{% set tabbase = "flex-1 py-2 text-center cyberpunk-mono text-sm font-medium uppercase tracking-wider border-b-2 transition-colors" %}
|
||||
{% macro captcha() %}<script>
|
||||
window.CAP_CUSTOM_WASM_URL = "/assets/captcha/cap_wasm_bg.wasm";
|
||||
window.CAP_PAKO_URL = "/assets/captcha/pako_inflate.min.js";
|
||||
</script>
|
||||
<cap-widget data-cap-api-endpoint="/api/captcha/" data-cap-worker-count="2"></cap-widget>
|
||||
<script src="/assets/captcha/cap.min.js" defer></script>
|
||||
<script>
|
||||
(function(){
|
||||
var s = document.currentScript;
|
||||
var form = s && s.closest ? s.closest('form') : null;
|
||||
if (!form) return;
|
||||
form.addEventListener('htmx:afterRequest', function(){
|
||||
var w = form.querySelector('cap-widget');
|
||||
if (w && typeof w.reset === 'function') w.reset();
|
||||
});
|
||||
})();
|
||||
</script>{% endmacro %}
|
||||
{% macro social() %}{% if show_social != false and (context.site.google_oauth_client_id or context.site.facebook_app_id) %}<div class="auth-social my-4">
|
||||
<div class="relative my-4"><hr class="border-border"/><span class="cyberpunk-mono absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-background px-3 text-xs uppercase tracking-widest text-muted-foreground">or</span></div>
|
||||
{% if context.site.google_oauth_client_id %}<a href="/api/auth/google/start" class="cyberpunk-mono mb-2 flex w-full items-center justify-center gap-3 rounded-md border border-border px-4 py-2.5 uppercase tracking-wide transition-colors hover:bg-accent/10"><svg 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 %}
|
||||
36
templates/overrides/cyberpunk/auth-status.ninjatpl
Normal file
36
templates/overrides/cyberpunk/auth-status.ninjatpl
Normal file
@ -0,0 +1,36 @@
|
||||
{# auth-status cyberpunk override — reproduces the builtin's hardcoded wiring: /login + /login?tab=register when logged out; /account, /account/reviews, and hx-post /api/auth/logout dropdown when logged in; hx-post /api/auth/resend-verification banner when unverified. State from context.*. #}
|
||||
{% if not context.isPublicLoggedIn %}
|
||||
<div class="auth-status flex items-center gap-2">
|
||||
<a href="/login" class="cyberpunk-mono rounded-md px-3 py-1.5 text-sm font-medium uppercase tracking-wide opacity-80 transition-colors hover:bg-primary/10 hover:opacity-100">Login</a>
|
||||
<a href="/login?tab=register" class="cyberpunk-mono clip-cut rounded-md bg-accent px-3 py-1.5 text-sm font-medium uppercase tracking-wide text-accent-foreground hover:bg-accent/90">Sign Up</a>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if not context.publicEmailVerified %}<div class="fixed top-16 inset-x-0 z-40 border-y border-destructive/50 bg-destructive/15 px-4 py-2 text-center text-sm text-destructive">
|
||||
<span class="cyberpunk-mono uppercase tracking-wide">Verify your email address.</span> <button hx-post="/api/auth/resend-verification" hx-swap="outerHTML" class="underline font-medium">Resend verification email</button>
|
||||
</div>{% endif %}
|
||||
<div class="auth-status relative">
|
||||
<button type="button" onclick="this.nextElementSibling.classList.toggle('hidden')" class="flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium opacity-90 transition-colors hover:bg-primary/10 hover:opacity-100">
|
||||
<span class="clip-cut flex h-7 w-7 items-center justify-center rounded-md bg-accent text-accent-foreground"><svg 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 %}
|
||||
22
templates/overrides/cyberpunk/author-bio-hero.ninjatpl
Normal file
22
templates/overrides/cyberpunk/author-bio-hero.ninjatpl
Normal file
@ -0,0 +1,22 @@
|
||||
{# author-bio-hero (cyberpunk override) — author-page header from the `authors` provider. #}
|
||||
{% if author %}
|
||||
<section class="relative overflow-hidden border-b border-primary/30 bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||
<div class="scanlines pointer-events-none absolute inset-0"></div>
|
||||
<div class="relative z-10 mx-auto flex max-w-3xl flex-col items-center px-4 text-center">
|
||||
{% if showAvatar and author.avatar_url %}<div class="mb-5 h-24 w-24 overflow-hidden rounded-full bg-muted ring-2 ring-primary neon-edge-magenta">{% img author.avatar_url alt=author.display_name class="h-full w-full object-cover" %}</div>{% endif %}
|
||||
<h1 class="cyberpunk-display glitch text-3xl font-bold tracking-tight md:text-4xl" data-text="{{ author.display_name }}">{{ author.display_name }}</h1>
|
||||
{% if author.job_title or author.company %}<p class="cyberpunk-mono mt-1 text-sm uppercase tracking-wide text-accent">{{ author.job_title }}{% if author.job_title and author.company %} · {% endif %}{{ author.company }}</p>{% endif %}
|
||||
{% if author.bio %}<p class="cyberpunk-body mt-4 max-w-2xl leading-relaxed text-muted-foreground">{{ author.bio }}</p>{% endif %}
|
||||
<div class="mt-5 flex flex-wrap items-center justify-center gap-4 cyberpunk-mono text-sm uppercase tracking-wide text-accent">
|
||||
{% if showPostCount %}<span>{{ author.post_count|default:0 }} post{% if author.post_count != 1 %}s{% endif %}</span>{% endif %}
|
||||
{% if author.location %}<span>{{ author.location }}</span>{% endif %}
|
||||
</div>
|
||||
{% if showSocial %}<div class="mt-5 flex items-center justify-center gap-3">
|
||||
{% if author.website_url %}<a href="{{ author.website_url }}" target="_blank" rel="noopener noreferrer" class="text-muted-foreground transition-colors hover:text-primary" title="Website"><svg 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 %}
|
||||
13
templates/overrides/cyberpunk/author-bio.ninjatpl
Normal file
13
templates/overrides/cyberpunk/author-bio.ninjatpl
Normal file
@ -0,0 +1,13 @@
|
||||
{# author-bio (cyberpunk override) — byline card sourced from the `post` provider's author_* fields. #}
|
||||
{% if post.author_name %}
|
||||
<aside class="hud-frame rounded-md border border-dashed border-primary/40 bg-card p-6 text-card-foreground neon-edge-magenta{% if class %} {{ class }}{% endif %}">
|
||||
{% if heading %}<p class="cyberpunk-eyebrow cyberpunk-mono mb-4 text-xs font-semibold uppercase tracking-widest text-accent">{{ heading }}</p>{% endif %}
|
||||
<div class="flex items-start gap-4">
|
||||
{% if showAvatar and post.author_avatar %}<div class="h-14 w-14 flex-shrink-0 overflow-hidden rounded-full border border-primary/40 bg-muted">{% img post.author_avatar alt=post.author_name class="h-full w-full object-cover" %}</div>{% endif %}
|
||||
<div class="min-w-0">
|
||||
<h3 class="cyberpunk-display text-base font-semibold">{% if post.author_slug %}<a href="/author/{{ post.author_slug }}" class="transition-colors hover:text-primary">{{ post.author_name }}</a>{% else %}{{ post.author_name }}{% endif %}</h3>
|
||||
{% if post.author_bio %}<p class="cyberpunk-body mt-1 text-sm text-muted-foreground">{{ post.author_bio }}</p>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
{% endif %}
|
||||
24
templates/overrides/cyberpunk/blog-hero.ninjatpl
Normal file
24
templates/overrides/cyberpunk/blog-hero.ninjatpl
Normal file
@ -0,0 +1,24 @@
|
||||
{# blog-hero (cyberpunk override) — blog-index header + featured posts from the `posts` provider. #}
|
||||
<section class="relative overflow-hidden bg-background py-12 text-foreground md:py-16{% if class %} {{ class }}{% endif %}">
|
||||
<div class="scanlines pointer-events-none absolute inset-0"></div>
|
||||
<div class="relative z-10 mx-auto max-w-6xl px-4">
|
||||
<div class="max-w-2xl">
|
||||
{% if title %}<h1 class="cyberpunk-display glitch text-4xl font-bold tracking-tight md:text-5xl" data-text="{{ title }}">{{ title }}</h1>{% endif %}
|
||||
{% if subtitle %}<p class="cyberpunk-body mt-3 text-lg text-muted-foreground">{{ subtitle }}</p>{% endif %}
|
||||
</div>
|
||||
{% if posts %}
|
||||
<div class="mt-10 grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
{% for post in posts %}
|
||||
<article class="hud-frame group flex flex-col rounded-md border border-dashed border-primary/40 bg-card p-4 text-card-foreground neon-edge-magenta{% if forloop.First %} md:col-span-3 md:flex-row md:items-stretch md:gap-6{% endif %}">
|
||||
{% if post.featured_image_url %}<a href="{{ post.url }}" class="mb-3 block overflow-hidden rounded-md bg-muted{% if forloop.First %} md:mb-0 md:w-1/2{% endif %}">{% img post.featured_image_url alt=post.title class="aspect-video h-full w-full object-cover transition-transform duration-300 group-hover:scale-105" %}</a>{% endif %}
|
||||
<div class="flex flex-col justify-center{% if forloop.First %} md:w-1/2{% endif %}">
|
||||
<h2 class="cyberpunk-display {% if forloop.First %}text-2xl{% else %}text-lg{% endif %} font-semibold leading-snug"><a href="{{ post.url }}" class="transition-colors hover:text-primary">{{ post.title }}</a></h2>
|
||||
{% if showExcerpt and post.excerpt %}<p class="cyberpunk-body mt-2 text-sm text-muted-foreground">{{ post.excerpt }}</p>{% endif %}
|
||||
<div class="mt-3 flex flex-wrap items-center gap-x-3 cyberpunk-mono text-xs uppercase tracking-wide text-accent">{% if post.author_name %}<span class="font-medium text-foreground">{{ post.author_name }}</span>{% endif %}{% if post.published_at %}<span>{{ post.published_at|date:"M j, Y" }}</span>{% endif %}</div>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
15
templates/overrides/cyberpunk/breadcrumbs.ninjatpl
Normal file
15
templates/overrides/cyberpunk/breadcrumbs.ninjatpl
Normal file
@ -0,0 +1,15 @@
|
||||
{# breadcrumbs (cyberpunk override) — trail from the `breadcrumbs` provider (page ancestry). #}
|
||||
{# showHome prepends a Home link; separator is text ("/") for default or a chevron #}
|
||||
{# for the arrows template. The trailing is_current item renders as plain text. #}
|
||||
{% set arrows = template == "arrows" %}
|
||||
<nav aria-label="Breadcrumb" class="py-3{% if class %} {{ class }}{% endif %}">
|
||||
<ol class="flex flex-wrap items-center gap-1 cyberpunk-mono text-sm uppercase tracking-wide">
|
||||
{% if showHome %}<li class="flex items-center"><a href="/" class="text-accent transition-colors hover:text-primary">{{ homeLabel|default:"Home" }}</a></li>{% endif %}
|
||||
{% for item in breadcrumbs %}
|
||||
<li class="flex items-center">
|
||||
{% if showHome or not forloop.First %}{% if arrows %}<svg 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>
|
||||
@ -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 %}
|
||||
|
||||
@ -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 %}
|
||||
|
||||
18
templates/overrides/cyberpunk/category-list.ninjatpl
Normal file
18
templates/overrides/cyberpunk/category-list.ninjatpl
Normal 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>
|
||||
18
templates/overrides/cyberpunk/contact-card.ninjatpl
Normal file
18
templates/overrides/cyberpunk/contact-card.ninjatpl
Normal 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>
|
||||
40
templates/overrides/cyberpunk/contact-form.ninjatpl
Normal file
40
templates/overrides/cyberpunk/contact-form.ninjatpl
Normal 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 %}
|
||||
50
templates/overrides/cyberpunk/countdown.ninjatpl
Normal file
50
templates/overrides/cyberpunk/countdown.ninjatpl
Normal 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>
|
||||
16
templates/overrides/cyberpunk/cta.ninjatpl
Normal file
16
templates/overrides/cyberpunk/cta.ninjatpl
Normal 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>
|
||||
2
templates/overrides/cyberpunk/divider.ninjatpl
Normal file
2
templates/overrides/cyberpunk/divider.ninjatpl
Normal 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 %}
|
||||
41
templates/overrides/cyberpunk/event-list.ninjatpl
Normal file
41
templates/overrides/cyberpunk/event-list.ninjatpl
Normal 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 %} – {{ 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>
|
||||
18
templates/overrides/cyberpunk/faq.ninjatpl
Normal file
18
templates/overrides/cyberpunk/faq.ninjatpl
Normal 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>
|
||||
19
templates/overrides/cyberpunk/feature-grid.ninjatpl
Normal file
19
templates/overrides/cyberpunk/feature-grid.ninjatpl
Normal 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>
|
||||
23
templates/overrides/cyberpunk/featured-posts.ninjatpl
Normal file
23
templates/overrides/cyberpunk/featured-posts.ninjatpl
Normal 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>
|
||||
39
templates/overrides/cyberpunk/footer.ninjatpl
Normal file
39
templates/overrides/cyberpunk/footer.ninjatpl
Normal 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 %}© {{ copyright }}{% else %}© {{ 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>
|
||||
52
templates/overrides/cyberpunk/gallery.ninjatpl
Normal file
52
templates/overrides/cyberpunk/gallery.ninjatpl
Normal 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 %}
|
||||
@ -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 %}
|
||||
|
||||
95
templates/overrides/cyberpunk/hero.ninjatpl
Normal file
95
templates/overrides/cyberpunk/hero.ninjatpl
Normal 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>
|
||||
31
templates/overrides/cyberpunk/hours-location.ninjatpl
Normal file
31
templates/overrides/cyberpunk/hours-location.ninjatpl
Normal 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>
|
||||
2
templates/overrides/cyberpunk/image.ninjatpl
Normal file
2
templates/overrides/cyberpunk/image.ninjatpl
Normal 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 %}
|
||||
16
templates/overrides/cyberpunk/logo-strip.ninjatpl
Normal file
16
templates/overrides/cyberpunk/logo-strip.ninjatpl
Normal 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 %}
|
||||
30
templates/overrides/cyberpunk/menu-list.ninjatpl
Normal file
30
templates/overrides/cyberpunk/menu-list.ninjatpl
Normal 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>
|
||||
104
templates/overrides/cyberpunk/navbar.ninjatpl
Normal file
104
templates/overrides/cyberpunk/navbar.ninjatpl
Normal 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>
|
||||
12
templates/overrides/cyberpunk/newsletter-signup.ninjatpl
Normal file
12
templates/overrides/cyberpunk/newsletter-signup.ninjatpl
Normal 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>
|
||||
13
templates/overrides/cyberpunk/page-suggestions.ninjatpl
Normal file
13
templates/overrides/cyberpunk/page-suggestions.ninjatpl
Normal 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>
|
||||
13
templates/overrides/cyberpunk/password-reset-form.ninjatpl
Normal file
13
templates/overrides/cyberpunk/password-reset-form.ninjatpl
Normal 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>
|
||||
16
templates/overrides/cyberpunk/popular-posts.ninjatpl
Normal file
16
templates/overrides/cyberpunk/popular-posts.ninjatpl
Normal 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>
|
||||
23
templates/overrides/cyberpunk/post-hero.ninjatpl
Normal file
23
templates/overrides/cyberpunk/post-hero.ninjatpl
Normal 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 %}
|
||||
9
templates/overrides/cyberpunk/post-metadata.ninjatpl
Normal file
9
templates/overrides/cyberpunk/post-metadata.ninjatpl
Normal 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 %}
|
||||
26
templates/overrides/cyberpunk/pricing.ninjatpl
Normal file
26
templates/overrides/cyberpunk/pricing.ninjatpl
Normal 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>
|
||||
2
templates/overrides/cyberpunk/quote.ninjatpl
Normal file
2
templates/overrides/cyberpunk/quote.ninjatpl
Normal 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 %}
|
||||
17
templates/overrides/cyberpunk/related-posts.ninjatpl
Normal file
17
templates/overrides/cyberpunk/related-posts.ninjatpl
Normal 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 %}
|
||||
14
templates/overrides/cyberpunk/rich-section.ninjatpl
Normal file
14
templates/overrides/cyberpunk/rich-section.ninjatpl
Normal 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>
|
||||
2
templates/overrides/cyberpunk/rich-text.ninjatpl
Normal file
2
templates/overrides/cyberpunk/rich-text.ninjatpl
Normal 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 %}
|
||||
15
templates/overrides/cyberpunk/sidebar-nav.ninjatpl
Normal file
15
templates/overrides/cyberpunk/sidebar-nav.ninjatpl
Normal 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 %}
|
||||
14
templates/overrides/cyberpunk/social-links.ninjatpl
Normal file
14
templates/overrides/cyberpunk/social-links.ninjatpl
Normal 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>
|
||||
50
templates/overrides/cyberpunk/stats.ninjatpl
Normal file
50
templates/overrides/cyberpunk/stats.ninjatpl
Normal 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 %}
|
||||
22
templates/overrides/cyberpunk/team.ninjatpl
Normal file
22
templates/overrides/cyberpunk/team.ninjatpl
Normal 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>
|
||||
27
templates/overrides/cyberpunk/testimonial.ninjatpl
Normal file
27
templates/overrides/cyberpunk/testimonial.ninjatpl
Normal 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 %}“{{ q.quote }}”{% 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>
|
||||
@ -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>
|
||||
|
||||
22
templates/overrides/cyberpunk/timeline.ninjatpl
Normal file
22
templates/overrides/cyberpunk/timeline.ninjatpl
Normal 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>
|
||||
10
templates/overrides/cyberpunk/utility-bar.ninjatpl
Normal file
10
templates/overrides/cyberpunk/utility-bar.ninjatpl
Normal 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>
|
||||
23
templates/overrides/cyberpunk/video-embed.ninjatpl
Normal file
23
templates/overrides/cyberpunk/video-embed.ninjatpl
Normal 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 %}
|
||||
Loading…
x
Reference in New Issue
Block a user