Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously an unversioned directory inside ~/src/blockninja-themes/cyberpunk. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
234 lines
5.5 KiB
CSS
234 lines
5.5 KiB
CSS
/* ============================================================
|
|
Cyberpunk — Neon-on-black BlockNinja theme
|
|
Scanlines / glitch / RGB-split / caret-blink / neon-edge utilities.
|
|
All colours consume the host shadcn HSL tokens via hsl(var(--token)).
|
|
No hardcoded hex / rgb / named colours.
|
|
============================================================ */
|
|
|
|
/* --- Typography fallbacks --------------------------------- */
|
|
|
|
.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);
|
|
}
|
|
|
|
.cyberpunk-display {
|
|
font-family: var(--font-heading, "Space Grotesk", 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;
|
|
}
|
|
|
|
.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-size: 0.875em;
|
|
}
|
|
|
|
/* --- Scanlines -------------------------------------------- */
|
|
|
|
.scanlines {
|
|
opacity: 0.04;
|
|
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 H1/H2 -------------- */
|
|
|
|
.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);
|
|
}
|
|
|
|
/* 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));
|
|
}
|
|
|
|
/* :focus-visible ring uses the ring token, not browser default */
|
|
.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) ---------- */
|
|
|
|
.neon-edge-magenta {
|
|
box-shadow:
|
|
0 0 0 1px hsl(var(--primary) / 0.4),
|
|
0 0 18px hsl(var(--primary) / 0.25);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
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);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* --- Card override dashed border --------------------------- */
|
|
|
|
.cyberpunk-card {
|
|
position: relative;
|
|
}
|
|
|
|
/* --- Status pill / dot animation -------------------------- */
|
|
|
|
.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 ------ */
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.glitch,
|
|
.caret-blink,
|
|
.scanlines,
|
|
.cyberpunk-status-dot {
|
|
animation: none !important;
|
|
animation-duration: 0.01ms !important;
|
|
}
|
|
.glitch {
|
|
text-shadow: none;
|
|
}
|
|
.scanlines {
|
|
opacity: 0;
|
|
}
|
|
.rgb-split:hover,
|
|
.rgb-split:focus-visible {
|
|
transform: none;
|
|
}
|
|
}
|
|
|
|
/* --- High-contrast: kill scanlines ------------------------ */
|
|
|
|
@media (prefers-contrast: more) {
|
|
.scanlines {
|
|
display: none;
|
|
}
|
|
}
|