themes-cyberpunk/assets/css/cyberpunk.css
Alex Dunmow 313ebaf296 initial: theme plugin cyberpunk
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>
2026-06-06 14:11:25 +08:00

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;
}
}