Hero blank-hero bug: eyebrow/headline/subheadline/description read
field.text|default:field, which hard-errors in the ninjatpl engine on
bare-string seed values (attribute access on a string), blanking the whole
hero. Switch to field|get:"text"|default:field — the process-global `get`
filter degrades gracefully on both {text,...} objects and bare strings.
Responsive layout back to Tailwind (supersedes the prior CSS-class approach
in 6787a01): the host Tailwind build now scans theme .ninjatpl, so the
cp-grid / cp-cols / cp-span / cp-split / cp-article-grid classes were exact
reimplementations of stock utilities. Restore plain Tailwind in the
templates (grid grid-cols-1 sm:/md:/lg:grid-cols-*, gap-*, columns-*,
col-span-*, before:* timeline connector) across feature-grid, pricing,
stats, team, testimonial, contact-card, featured/related-posts, blog-hero,
menu-list, event-list, hours-location, timeline, footer, gallery,
rich-section, roster_grid, article + hero split section, and delete the
duplicate CSS from manifest css.input_css_append.
Kept as genuinely bespoke design CSS: cp-hero-* bounded clamp() min-heights
(the anti-balloon whitespace fix — not a stock min-h-* utility; restoring
min-h-screen would reintroduce the empty neon void) and the reduced-motion /
high-contrast blocks.
Scroll-reveal: cyberpunk ships no opacity-hidden .reveal content — its two
IntersectionObservers (hero digital-rain, stats countUp) fire on the IO
initial callback when already in view and both have static, visible no-JS /
reduced-motion fallbacks, so content is never hidden behind JS.
make (codeless .bnp) OK; check-safety 32 checks 19 ok 13 skip -> OK.
No version bump. No push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
391 lines
13 KiB
YAML
391 lines
13 KiB
YAML
# Cyberpunk theme — codeless .bnp manifest (WO-TF-015, Wave A).
|
|
# Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm).
|
|
theme_presets: presets.json
|
|
bundled_fonts: fonts.json
|
|
master_pages: master_pages.json
|
|
required_icon_packs: ["lucide", "simple-icons"]
|
|
|
|
system_templates:
|
|
- key: cyberpunk
|
|
title: Cyberpunk
|
|
description: "Neon-on-black game-studio / hacker-collective theme — RGB-split glitch, CRT scanlines, HUD brackets, angled clip cuts, and a canvas digital-rain hero."
|
|
|
|
page_templates:
|
|
- system: cyberpunk
|
|
key: default
|
|
title: Default
|
|
description: "Standard dark page with scanline overlay and a sticky terminal-style header"
|
|
slots: [header, main, footer]
|
|
- system: cyberpunk
|
|
key: full-width
|
|
title: Full Width
|
|
description: "Edge-to-edge dashboard / showcase layout, no max-width"
|
|
slots: [header, main, footer]
|
|
- system: cyberpunk
|
|
key: landing
|
|
title: Landing
|
|
description: "Canvas digital-rain hero, neon feature grid, glowing CTA, footer"
|
|
slots: [header, main, footer]
|
|
- system: cyberpunk
|
|
key: article
|
|
title: Article
|
|
description: "Mono-metadata header, prose column, HUD-framed aside"
|
|
slots: [header, main, aside, footer]
|
|
- system: cyberpunk
|
|
key: blog-index
|
|
title: Blog Index
|
|
description: "Patch-notes feed layout with neon post cards and sidebar"
|
|
slots: [header, main, footer]
|
|
- system: cyberpunk
|
|
key: contact
|
|
title: Contact
|
|
description: "Terminal contact console — form panel plus HUD contact card"
|
|
slots: [header, main, footer]
|
|
- system: cyberpunk
|
|
key: auth
|
|
title: Auth
|
|
description: "Centered neon auth console for login / register / reset"
|
|
slots: [header, main, footer]
|
|
|
|
# All 49 mandatory-tier builtin overrides (OVERRIDE-CONTRACT §1), dashed keys.
|
|
template_overrides:
|
|
# Chrome (4)
|
|
- { template: cyberpunk, block: navbar }
|
|
- { template: cyberpunk, block: footer }
|
|
- { template: cyberpunk, block: utility-bar }
|
|
- { template: cyberpunk, block: announcement-bar }
|
|
# Marketing (12)
|
|
- { template: cyberpunk, block: hero }
|
|
- { template: cyberpunk, block: feature-grid }
|
|
- { template: cyberpunk, block: rich-section }
|
|
- { template: cyberpunk, block: stats }
|
|
- { template: cyberpunk, block: testimonial }
|
|
- { template: cyberpunk, block: logo-strip }
|
|
- { template: cyberpunk, block: pricing }
|
|
- { template: cyberpunk, block: team }
|
|
- { template: cyberpunk, block: faq }
|
|
- { template: cyberpunk, block: timeline }
|
|
- { template: cyberpunk, block: cta }
|
|
- { template: cyberpunk, block: countdown }
|
|
# Primitives (11)
|
|
- { template: cyberpunk, block: heading }
|
|
- { template: cyberpunk, block: text }
|
|
- { template: cyberpunk, block: rich-text }
|
|
- { template: cyberpunk, block: image }
|
|
- { template: cyberpunk, block: quote }
|
|
- { template: cyberpunk, block: divider }
|
|
- { template: cyberpunk, block: button }
|
|
- { template: cyberpunk, block: card }
|
|
- { template: cyberpunk, block: gallery }
|
|
- { template: cyberpunk, block: video-embed }
|
|
- { template: cyberpunk, block: audio-embed }
|
|
# Commerce / local (6)
|
|
- { template: cyberpunk, block: menu-list }
|
|
- { template: cyberpunk, block: event-list }
|
|
- { template: cyberpunk, block: contact-form }
|
|
- { template: cyberpunk, block: contact-card }
|
|
- { template: cyberpunk, block: hours-location }
|
|
- { template: cyberpunk, block: social-links }
|
|
# Blog family (12)
|
|
- { template: cyberpunk, block: blog-hero }
|
|
- { template: cyberpunk, block: featured-posts }
|
|
- { template: cyberpunk, block: related-posts }
|
|
- { template: cyberpunk, block: popular-posts }
|
|
- { template: cyberpunk, block: category-list }
|
|
- { template: cyberpunk, block: post-hero }
|
|
- { template: cyberpunk, block: post-metadata }
|
|
- { template: cyberpunk, block: author-bio }
|
|
- { template: cyberpunk, block: author-bio-hero }
|
|
- { template: cyberpunk, block: newsletter-signup }
|
|
- { template: cyberpunk, block: breadcrumbs }
|
|
- { template: cyberpunk, block: sidebar-nav }
|
|
# Auth surface (3)
|
|
- { template: cyberpunk, block: auth-form }
|
|
- { template: cyberpunk, block: password-reset-form }
|
|
- { template: cyberpunk, block: auth-status }
|
|
# System (1)
|
|
- { template: cyberpunk, block: page-suggestions }
|
|
|
|
email_wrappers:
|
|
- cyberpunk
|
|
|
|
css:
|
|
input_css_append: |
|
|
/* ============================================================
|
|
Cyberpunk — Neon-on-black game-studio / hacker-collective theme
|
|
(WO-TF-015). Scanlines / glitch / RGB-split / caret-blink /
|
|
neon-edge / HUD-bracket / clip-cut / digital-rain utilities.
|
|
All colours consume the host shadcn HSL tokens via hsl(var(--token)).
|
|
No hardcoded hex / rgb / named colours.
|
|
============================================================ */
|
|
|
|
/* --- Typography ------------------------------------------- */
|
|
/* Families flow through the admin-bound CSS variables; the fallback
|
|
leads with the bundled OFL family so the intended look survives
|
|
before the admin assigns a slot. @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 code,
|
|
pre.cyberpunk-mono,
|
|
code.cyberpunk-mono {
|
|
font-family: var(--font-mono, "Share Tech Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace);
|
|
}
|
|
|
|
.cyberpunk-display {
|
|
font-family: var(--font-heading, "Orbitron", system-ui, -apple-system, "Segoe UI", sans-serif);
|
|
letter-spacing: 0.01em;
|
|
}
|
|
|
|
.cyberpunk-prose,
|
|
.cyberpunk-prose p,
|
|
.cyberpunk-prose li {
|
|
font-family: var(--font-body, "Inter", system-ui, sans-serif);
|
|
line-height: 1.75;
|
|
}
|
|
|
|
/* Eyebrow / kicker: mono, uppercase, wide-tracked accent readout. Carries
|
|
enough on its own that it is never a silent no-op when used alone. */
|
|
.cyberpunk-eyebrow {
|
|
font-family: var(--font-mono, "Share Tech Mono", ui-monospace, monospace);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.2em;
|
|
font-size: 0.75rem;
|
|
color: hsl(var(--accent));
|
|
}
|
|
|
|
.cyberpunk-prose code {
|
|
color: hsl(var(--accent));
|
|
background-color: hsl(var(--muted));
|
|
padding: 0.125rem 0.375rem;
|
|
border-radius: 0.25rem;
|
|
font-family: var(--font-mono, "Share Tech Mono", ui-monospace, monospace);
|
|
font-size: 0.875em;
|
|
}
|
|
|
|
/* --- Scanlines -------------------------------------------- */
|
|
|
|
.scanlines {
|
|
opacity: 0.05;
|
|
background-image: linear-gradient(
|
|
to bottom,
|
|
hsl(var(--foreground)) 0px,
|
|
hsl(var(--foreground)) 1px,
|
|
transparent 1px,
|
|
transparent 3px
|
|
);
|
|
background-size: 100% 3px;
|
|
animation: scanline-drift 6s linear infinite;
|
|
}
|
|
|
|
@keyframes scanline-drift {
|
|
0% { background-position: 0 0; }
|
|
100% { background-position: 0 3px; }
|
|
}
|
|
|
|
/* --- Glitch (RGB-split text-shadow) on big headings ------- */
|
|
|
|
.glitch {
|
|
position: relative;
|
|
text-shadow:
|
|
-2px 0 hsl(var(--primary)),
|
|
2px 0 hsl(var(--accent));
|
|
animation: glitch-x 3.6s steps(1, end) infinite;
|
|
}
|
|
|
|
@keyframes glitch-x {
|
|
0%, 92%, 100% {
|
|
text-shadow: -2px 0 hsl(var(--primary)), 2px 0 hsl(var(--accent));
|
|
}
|
|
93% {
|
|
text-shadow: -3px 0 hsl(var(--primary)), 3px 0 hsl(var(--accent)), 0 1px hsl(var(--accent));
|
|
}
|
|
95% {
|
|
text-shadow: -1px 0 hsl(var(--accent)), 4px 0 hsl(var(--primary));
|
|
}
|
|
97% {
|
|
text-shadow: -2px 0 hsl(var(--primary)), 2px 0 hsl(var(--accent));
|
|
}
|
|
}
|
|
|
|
/* --- RGB-split hover/focus/active on primary buttons ------ */
|
|
|
|
.rgb-split {
|
|
transition: transform 120ms ease, box-shadow 120ms ease;
|
|
}
|
|
.rgb-split:hover,
|
|
.rgb-split:focus-visible {
|
|
box-shadow: -2px 0 0 hsl(var(--primary)), 2px 0 0 hsl(var(--accent));
|
|
transform: translateY(-1px);
|
|
}
|
|
.cyberpunk-btn:active,
|
|
.rgb-split:active,
|
|
button.cyberpunk-btn:active {
|
|
box-shadow: -2px 0 0 hsl(var(--primary)), 2px 0 0 hsl(var(--accent));
|
|
}
|
|
.cyberpunk-btn:focus-visible,
|
|
.cyberpunk-btn-ghost:focus-visible,
|
|
button:focus-visible,
|
|
a:focus-visible {
|
|
outline: 2px solid hsl(var(--ring));
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* --- Caret blink ------------------------------------------ */
|
|
|
|
.caret-blink {
|
|
display: inline-block;
|
|
animation: caret 1.05s steps(2, end) infinite;
|
|
}
|
|
@keyframes caret {
|
|
0%, 50% { opacity: 1; }
|
|
51%, 100% { opacity: 0; }
|
|
}
|
|
|
|
/* --- Neon edge utilities (magenta / cyan / lime channels) - */
|
|
/* --primary is the lead accent, --accent the secondary, --ring
|
|
the third neon channel (present in every preset). */
|
|
|
|
.neon-edge-magenta {
|
|
box-shadow: 0 0 0 1px hsl(var(--primary) / 0.4), 0 0 18px hsl(var(--primary) / 0.22);
|
|
transition: box-shadow 180ms ease, transform 180ms ease;
|
|
}
|
|
.neon-edge-magenta:hover,
|
|
.neon-edge-magenta:focus-within {
|
|
box-shadow: 0 0 0 1px hsl(var(--primary) / 0.85), 0 0 28px hsl(var(--primary) / 0.42);
|
|
transform: translateY(-1px);
|
|
}
|
|
.neon-edge-cyan {
|
|
box-shadow: 0 0 0 1px hsl(var(--accent) / 0.4), 0 0 18px hsl(var(--accent) / 0.22);
|
|
transition: box-shadow 180ms ease, transform 180ms ease;
|
|
}
|
|
.neon-edge-cyan:hover,
|
|
.neon-edge-cyan:focus-within {
|
|
box-shadow: 0 0 0 1px hsl(var(--accent) / 0.85), 0 0 28px hsl(var(--accent) / 0.42);
|
|
transform: translateY(-1px);
|
|
}
|
|
.neon-edge-lime {
|
|
box-shadow: 0 0 0 1px hsl(var(--ring) / 0.4), 0 0 18px hsl(var(--ring) / 0.22);
|
|
transition: box-shadow 180ms ease, transform 180ms ease;
|
|
}
|
|
.neon-edge-lime:hover,
|
|
.neon-edge-lime:focus-within {
|
|
box-shadow: 0 0 0 1px hsl(var(--ring) / 0.85), 0 0 28px hsl(var(--ring) / 0.42);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* --- HUD corner brackets ---------------------------------- */
|
|
/* Draw two L-brackets (top-left, bottom-right) around any panel. */
|
|
|
|
.hud-frame {
|
|
position: relative;
|
|
}
|
|
.hud-frame::before,
|
|
.hud-frame::after {
|
|
content: "";
|
|
position: absolute;
|
|
width: 14px;
|
|
height: 14px;
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
.hud-frame::before {
|
|
top: 0;
|
|
left: 0;
|
|
border-top: 2px solid hsl(var(--primary));
|
|
border-left: 2px solid hsl(var(--primary));
|
|
}
|
|
.hud-frame::after {
|
|
bottom: 0;
|
|
right: 0;
|
|
border-bottom: 2px solid hsl(var(--accent));
|
|
border-right: 2px solid hsl(var(--accent));
|
|
}
|
|
|
|
/* --- Angled clip-path cut (arcade panel silhouette) ------- */
|
|
|
|
.clip-cut {
|
|
clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 10px 100%, 0 calc(100% - 10px));
|
|
}
|
|
|
|
/* --- Status pill / dot pulse ------------------------------ */
|
|
|
|
.cyberpunk-status-dot {
|
|
animation: cyberpunk-pulse 2.2s ease-in-out infinite;
|
|
}
|
|
@keyframes cyberpunk-pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.4; }
|
|
}
|
|
|
|
/* --- Digital-rain canvas showpiece (hero override only) --- */
|
|
/* The <canvas> lazy-inits from the hero's inline script. This block
|
|
styles the container + the static no-JS neon fallback layer. */
|
|
|
|
.cyberpunk-rain-wrap {
|
|
position: absolute;
|
|
inset: 0;
|
|
overflow: hidden;
|
|
pointer-events: none;
|
|
}
|
|
.cyberpunk-rain-canvas {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
opacity: 0.5;
|
|
}
|
|
/* Static fallback: a neon vertical-gradient wash shown until (or instead
|
|
of) the canvas. Tokens only. */
|
|
.cyberpunk-rain-fallback {
|
|
position: absolute;
|
|
inset: 0;
|
|
background:
|
|
radial-gradient(120% 80% at 50% 0%, hsl(var(--primary) / 0.18), transparent 60%),
|
|
radial-gradient(120% 80% at 100% 100%, hsl(var(--accent) / 0.16), transparent 55%),
|
|
linear-gradient(to bottom, transparent, hsl(var(--background) / 0.6));
|
|
}
|
|
|
|
/* --- Hero heights (bespoke bounded clamp — anti-balloon) -------------- */
|
|
/* Responsive grids/columns/spans/splits are plain Tailwind utilities in the
|
|
templates (grid-cols-*, columns-*, col-span-*, gap-*) — the host build
|
|
scans theme .ninjatpl, so no CSS reimplementation of those.
|
|
These hero min-heights stay CSS: a bespoke clamp() that caps a sparse
|
|
hero so it never balloons into an empty neon void while a populated hero
|
|
still reads full-bleed — not a stock min-h-* utility. */
|
|
.cp-hero-tall { min-height: clamp(32rem, 82vh, 50rem); }
|
|
.cp-hero-mid { min-height: clamp(28rem, 70vh, 44rem); }
|
|
.cp-hero-short { min-height: clamp(20rem, 50vh, 30rem); }
|
|
.cp-hero-auto { min-height: 22rem; }
|
|
.cp-hero-split { min-height: clamp(20rem, 46vh, 28rem); }
|
|
|
|
/* --- Reduced motion: kill all motion --------------------- */
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.glitch,
|
|
.caret-blink,
|
|
.scanlines,
|
|
.cyberpunk-status-dot,
|
|
.cyberpunk-rain-canvas {
|
|
animation: none !important;
|
|
animation-duration: 0.01ms !important;
|
|
}
|
|
.glitch { text-shadow: none; }
|
|
.scanlines { opacity: 0; }
|
|
.cyberpunk-rain-canvas { display: none; }
|
|
.rgb-split:hover,
|
|
.rgb-split:focus-visible { transform: none; }
|
|
}
|
|
|
|
/* --- High-contrast: kill scanlines + rain ---------------- */
|
|
|
|
@media (prefers-contrast: more) {
|
|
.scanlines,
|
|
.cyberpunk-rain-canvas { display: none; }
|
|
}
|