@oalacea/chaosui 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/bin/cli.js +32 -2
  2. package/components/buttons/cta-brutal/cta-brutal.module.css +81 -0
  3. package/components/buttons/cta-brutal/index.tsx +56 -0
  4. package/components/buttons/dead-button/dead-button.module.css +111 -0
  5. package/components/buttons/dead-button/index.tsx +47 -0
  6. package/components/buttons/deeper-button/deeper-button.module.css +76 -0
  7. package/components/buttons/deeper-button/index.tsx +51 -0
  8. package/components/buttons/dual-choice/dual-choice.module.css +90 -0
  9. package/components/buttons/dual-choice/index.tsx +54 -0
  10. package/components/buttons/tension-bar/index.tsx +79 -0
  11. package/components/buttons/tension-bar/tension-bar.module.css +105 -0
  12. package/components/decorative/coffee-stain/coffee-stain.module.css +24 -0
  13. package/components/decorative/coffee-stain/index.tsx +55 -0
  14. package/components/decorative/ornaments/index.tsx +51 -0
  15. package/components/decorative/ornaments/ornaments.module.css +33 -0
  16. package/components/decorative/rune-symbols/index.tsx +55 -0
  17. package/components/decorative/rune-symbols/rune-symbols.module.css +22 -0
  18. package/components/layout/horizontal-scroll/horizontal-scroll.module.css +30 -0
  19. package/components/layout/horizontal-scroll/index.tsx +78 -0
  20. package/components/layout/spec-grid/index.tsx +56 -0
  21. package/components/layout/spec-grid/spec-grid.module.css +21 -0
  22. package/components/layout/tower-pricing/index.tsx +56 -0
  23. package/components/layout/tower-pricing/tower-pricing.module.css +27 -0
  24. package/components/layout/tracklist/index.tsx +45 -0
  25. package/components/layout/tracklist/tracklist.module.css +24 -0
  26. package/components/layout/void-frame/index.tsx +32 -0
  27. package/components/layout/void-frame/void-frame.module.css +38 -0
  28. package/components/navigation/brutal-nav/brutal-nav.module.css +85 -0
  29. package/components/navigation/brutal-nav/index.tsx +71 -0
  30. package/components/navigation/progress-dots/index.tsx +55 -0
  31. package/components/navigation/progress-dots/progress-dots.module.css +91 -0
  32. package/components/navigation/scattered-nav/index.tsx +59 -0
  33. package/components/navigation/scattered-nav/scattered-nav.module.css +113 -0
  34. package/components/navigation/scroll-indicator/index.tsx +58 -0
  35. package/components/navigation/scroll-indicator/scroll-indicator.module.css +82 -0
  36. package/components/navigation/vertical-nav/index.tsx +59 -0
  37. package/components/navigation/vertical-nav/vertical-nav.module.css +98 -0
  38. package/components/text/ascii-art/css/ascii-art.module.css +173 -0
  39. package/components/text/ascii-art/css/index.tsx +116 -0
  40. package/components/text/ascii-art/tailwind/index.tsx +124 -0
  41. package/components/text/blood-drip/css/blood-drip.module.css +142 -0
  42. package/components/text/blood-drip/css/index.tsx +113 -0
  43. package/components/text/blood-drip/tailwind/index.tsx +133 -0
  44. package/components/text/char-glitch/css/char-glitch.module.css +124 -0
  45. package/components/text/char-glitch/css/index.tsx +153 -0
  46. package/components/text/char-glitch/tailwind/index.tsx +126 -0
  47. package/components/text/countdown-display/css/countdown-display.module.css +179 -0
  48. package/components/text/countdown-display/css/index.tsx +190 -0
  49. package/components/text/countdown-display/tailwind/index.tsx +155 -0
  50. package/components/text/giant-layers/css/giant-layers.module.css +156 -0
  51. package/components/text/giant-layers/css/index.tsx +97 -0
  52. package/components/text/giant-layers/tailwind/index.tsx +111 -0
  53. package/components/text/reveal-text/css/index.tsx +180 -0
  54. package/components/text/reveal-text/css/reveal-text.module.css +129 -0
  55. package/components/text/reveal-text/tailwind/index.tsx +135 -0
  56. package/components/text/rotate-text/css/index.tsx +139 -0
  57. package/components/text/rotate-text/css/rotate-text.module.css +162 -0
  58. package/components/text/rotate-text/tailwind/index.tsx +127 -0
  59. package/components/text/strike-reveal/css/index.tsx +124 -0
  60. package/components/text/strike-reveal/css/strike-reveal.module.css +139 -0
  61. package/components/text/strike-reveal/tailwind/index.tsx +138 -0
  62. package/components/text/terminal-output/css/index.tsx +179 -0
  63. package/components/text/terminal-output/css/terminal-output.module.css +203 -0
  64. package/components/text/terminal-output/tailwind/index.tsx +174 -0
  65. package/components/text/typing-text/css/index.tsx +115 -0
  66. package/components/text/typing-text/css/typing-text.module.css +84 -0
  67. package/components/text/typing-text/tailwind/index.tsx +126 -0
  68. package/package.json +1 -1
@@ -0,0 +1,124 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useEffect, useState } from 'react';
4
+
5
+ export interface AsciiArtProps extends HTMLAttributes<HTMLPreElement> {
6
+ children: string;
7
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
8
+ variant?: 'default' | 'blood' | 'cyber' | 'matrix' | 'amber' | 'ghost' | 'gradient';
9
+ animation?: 'none' | 'typing' | 'reveal' | 'glitch' | 'flicker' | 'pulse';
10
+ scanlines?: boolean;
11
+ bordered?: boolean;
12
+ title?: string;
13
+ revealDelay?: number;
14
+ color?: string;
15
+ }
16
+
17
+ const sizeClasses = {
18
+ xs: 'text-[0.5rem]',
19
+ sm: 'text-[0.65rem]',
20
+ md: 'text-[0.8rem]',
21
+ lg: 'text-base',
22
+ xl: 'text-xl',
23
+ };
24
+
25
+ const variantClasses = {
26
+ default: '',
27
+ blood: 'text-rose-500 drop-shadow-[0_0_5px_#ff004080]',
28
+ cyber: 'text-cyan-400 drop-shadow-[0_0_5px_#00ffff80]',
29
+ matrix: 'text-green-400 drop-shadow-[0_0_5px_#00ff0080]',
30
+ amber: 'text-amber-500 drop-shadow-[0_0_5px_#ffaa0080]',
31
+ ghost: 'text-gray-500 opacity-70',
32
+ gradient: 'bg-gradient-to-b from-rose-500 via-cyan-400 to-green-400 bg-clip-text text-transparent',
33
+ };
34
+
35
+ const animationClasses = {
36
+ none: '',
37
+ typing: 'overflow-hidden whitespace-nowrap animate-[typeIn_2s_steps(40,end)_forwards]',
38
+ reveal: '',
39
+ glitch: 'relative',
40
+ flicker: 'animate-pulse',
41
+ pulse: 'animate-[pulse_2s_ease-in-out_infinite]',
42
+ };
43
+
44
+ export const AsciiArt = forwardRef<HTMLPreElement, AsciiArtProps>(
45
+ (
46
+ {
47
+ children,
48
+ size = 'md',
49
+ variant = 'default',
50
+ animation = 'none',
51
+ scanlines = false,
52
+ bordered = false,
53
+ title,
54
+ revealDelay = 100,
55
+ color,
56
+ className = '',
57
+ style,
58
+ ...props
59
+ },
60
+ ref
61
+ ) => {
62
+ const [revealedLines, setRevealedLines] = useState<number>(0);
63
+ const lines = children.split('\n');
64
+
65
+ useEffect(() => {
66
+ if (animation === 'reveal') {
67
+ setRevealedLines(0);
68
+ let lineIndex = 0;
69
+
70
+ const interval = setInterval(() => {
71
+ if (lineIndex < lines.length) {
72
+ setRevealedLines(lineIndex + 1);
73
+ lineIndex++;
74
+ } else {
75
+ clearInterval(interval);
76
+ }
77
+ }, revealDelay);
78
+
79
+ return () => clearInterval(interval);
80
+ }
81
+ }, [animation, lines.length, revealDelay, children]);
82
+
83
+ const renderContent = () => {
84
+ if (animation === 'reveal') {
85
+ return lines.map((line, i) => (
86
+ <span
87
+ key={i}
88
+ className="block transition-all duration-300 ease-out"
89
+ style={{
90
+ opacity: i < revealedLines ? 1 : 0,
91
+ transform: i < revealedLines ? 'translateX(0)' : 'translateX(-10px)',
92
+ }}
93
+ >
94
+ {line}
95
+ {'\n'}
96
+ </span>
97
+ ));
98
+ }
99
+ return children;
100
+ };
101
+
102
+ return (
103
+ <pre
104
+ ref={ref}
105
+ className={`font-mono whitespace-pre leading-tight overflow-x-auto ${sizeClasses[size]} ${variantClasses[variant]} ${animationClasses[animation]} ${
106
+ bordered ? 'border border-current p-4 relative' : ''
107
+ } ${scanlines ? 'relative after:absolute after:inset-0 after:bg-[repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(0,0,0,0.1)_2px,rgba(0,0,0,0.1)_4px)] after:pointer-events-none' : ''
108
+ } ${className}`}
109
+ style={{ color, ...style }}
110
+ {...props}
111
+ >
112
+ {bordered && title && (
113
+ <span className="absolute -top-2.5 left-4 bg-inherit px-2 text-[0.8em]">
114
+ ┌{title}┐
115
+ </span>
116
+ )}
117
+ {renderContent()}
118
+ </pre>
119
+ );
120
+ }
121
+ );
122
+
123
+ AsciiArt.displayName = 'AsciiArt';
124
+ export default AsciiArt;
@@ -0,0 +1,142 @@
1
+ .container {
2
+ position: relative;
3
+ display: inline-block;
4
+ --drip-color: #ff0040;
5
+ }
6
+
7
+ .char {
8
+ display: inline-block;
9
+ position: relative;
10
+ }
11
+
12
+ /* Drip elements */
13
+ .drip {
14
+ position: absolute;
15
+ left: 50%;
16
+ top: 100%;
17
+ transform: translateX(-50%);
18
+ width: 4px;
19
+ height: 0;
20
+ background: var(--drip-color);
21
+ border-radius: 0 0 50% 50%;
22
+ animation: drip var(--drip-duration, 2s) ease-in infinite;
23
+ animation-delay: var(--drip-delay, 0s);
24
+ }
25
+
26
+ .drip::after {
27
+ content: '';
28
+ position: absolute;
29
+ bottom: 0;
30
+ left: 50%;
31
+ transform: translateX(-50%);
32
+ width: 8px;
33
+ height: 8px;
34
+ background: var(--drip-color);
35
+ border-radius: 50%;
36
+ opacity: 0;
37
+ animation: droplet var(--drip-duration, 2s) ease-in infinite;
38
+ animation-delay: var(--drip-delay, 0s);
39
+ }
40
+
41
+ @keyframes drip {
42
+ 0% { height: 0; opacity: 1; }
43
+ 20% { height: 20px; opacity: 1; }
44
+ 80% { height: 60px; opacity: 1; }
45
+ 100% { height: 80px; opacity: 0; }
46
+ }
47
+
48
+ @keyframes droplet {
49
+ 0%, 70% { opacity: 0; transform: translateX(-50%) scale(1); }
50
+ 75% { opacity: 1; transform: translateX(-50%) scale(1); }
51
+ 100% { opacity: 0; transform: translateX(-50%) scale(0) translateY(20px); }
52
+ }
53
+
54
+ /* Multiple drips per character */
55
+ .drip.secondary {
56
+ left: 30%;
57
+ animation-delay: calc(var(--drip-delay, 0s) + 0.5s);
58
+ }
59
+
60
+ .drip.tertiary {
61
+ left: 70%;
62
+ animation-delay: calc(var(--drip-delay, 0s) + 1s);
63
+ }
64
+
65
+ /* Intensity levels */
66
+ .light .drip {
67
+ width: 2px;
68
+ }
69
+
70
+ .light .drip::after {
71
+ width: 4px;
72
+ height: 4px;
73
+ }
74
+
75
+ .heavy .drip {
76
+ width: 6px;
77
+ }
78
+
79
+ .heavy .drip::after {
80
+ width: 12px;
81
+ height: 12px;
82
+ }
83
+
84
+ /* Color variants */
85
+ .blood { --drip-color: #ff0040; }
86
+ .acid { --drip-color: #aaff00; }
87
+ .cyber { --drip-color: #00ffff; }
88
+ .void { --drip-color: #0a0a0a; }
89
+ .chrome { --drip-color: linear-gradient(180deg, #888 0%, #fff 50%, #888 100%); }
90
+
91
+ /* Text glow matching drip */
92
+ .glowing .char {
93
+ text-shadow: 0 0 10px var(--drip-color), 0 0 20px var(--drip-color);
94
+ }
95
+
96
+ /* Melting effect */
97
+ .melting .char {
98
+ animation: melt 3s ease-in-out infinite;
99
+ animation-delay: var(--char-delay, 0s);
100
+ }
101
+
102
+ @keyframes melt {
103
+ 0%, 100% { transform: scaleY(1); transform-origin: top; }
104
+ 50% { transform: scaleY(1.1); transform-origin: top; }
105
+ }
106
+
107
+ /* Pooling effect at bottom */
108
+ .pool {
109
+ position: absolute;
110
+ bottom: -20px;
111
+ left: 0;
112
+ right: 0;
113
+ height: 10px;
114
+ background: var(--drip-color);
115
+ border-radius: 50%;
116
+ opacity: 0.5;
117
+ filter: blur(5px);
118
+ animation: poolGrow 3s ease-out forwards;
119
+ }
120
+
121
+ @keyframes poolGrow {
122
+ 0% { transform: scaleX(0); opacity: 0; }
123
+ 100% { transform: scaleX(1); opacity: 0.5; }
124
+ }
125
+
126
+ /* Hover pause */
127
+ .pauseOnHover:hover .drip {
128
+ animation-play-state: paused;
129
+ }
130
+
131
+ /* Static drip (no animation) */
132
+ .static .drip {
133
+ animation: none;
134
+ height: 40px;
135
+ opacity: 1;
136
+ }
137
+
138
+ .static .drip::after {
139
+ animation: none;
140
+ opacity: 1;
141
+ bottom: -4px;
142
+ }
@@ -0,0 +1,113 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useMemo } from 'react';
4
+ import styles from './blood-drip.module.css';
5
+
6
+ export interface BloodDripProps extends HTMLAttributes<HTMLSpanElement> {
7
+ /** Text to display */
8
+ children: string;
9
+ /** Drip color variant */
10
+ variant?: 'blood' | 'acid' | 'cyber' | 'void' | 'chrome';
11
+ /** Drip intensity */
12
+ intensity?: 'light' | 'medium' | 'heavy';
13
+ /** Number of drips per character (1-3) */
14
+ dripsPerChar?: 1 | 2 | 3;
15
+ /** Base drip duration in seconds */
16
+ duration?: number;
17
+ /** Add text glow effect */
18
+ glowing?: boolean;
19
+ /** Add melting animation */
20
+ melting?: boolean;
21
+ /** Show pool at bottom */
22
+ showPool?: boolean;
23
+ /** Pause animation on hover */
24
+ pauseOnHover?: boolean;
25
+ /** Static drips (no animation) */
26
+ static?: boolean;
27
+ /** Custom drip color */
28
+ dripColor?: string;
29
+ /** Probability of drip per character (0-1) */
30
+ dripProbability?: number;
31
+ }
32
+
33
+ export const BloodDrip = forwardRef<HTMLSpanElement, BloodDripProps>(
34
+ (
35
+ {
36
+ children,
37
+ variant = 'blood',
38
+ intensity = 'medium',
39
+ dripsPerChar = 1,
40
+ duration = 2,
41
+ glowing = false,
42
+ melting = false,
43
+ showPool = false,
44
+ pauseOnHover = false,
45
+ static: isStatic = false,
46
+ dripColor,
47
+ dripProbability = 0.7,
48
+ className,
49
+ style,
50
+ ...props
51
+ },
52
+ ref
53
+ ) => {
54
+ const chars = children.split('');
55
+
56
+ const dripMap = useMemo(() => {
57
+ return chars.map(() => Math.random() < dripProbability);
58
+ }, [chars.length, dripProbability]);
59
+
60
+ const containerClasses = [
61
+ styles.container,
62
+ styles[variant],
63
+ styles[intensity],
64
+ glowing && styles.glowing,
65
+ melting && styles.melting,
66
+ pauseOnHover && styles.pauseOnHover,
67
+ isStatic && styles.static,
68
+ className
69
+ ].filter(Boolean).join(' ');
70
+
71
+ return (
72
+ <span
73
+ ref={ref}
74
+ className={containerClasses}
75
+ style={{
76
+ '--drip-color': dripColor,
77
+ ...style
78
+ } as React.CSSProperties}
79
+ {...props}
80
+ >
81
+ {chars.map((char, i) => {
82
+ const hasDrip = dripMap[i] && char !== ' ';
83
+ const delay = Math.random() * 2;
84
+
85
+ return (
86
+ <span
87
+ key={i}
88
+ className={styles.char}
89
+ style={{
90
+ '--char-delay': `${i * 0.1}s`,
91
+ '--drip-delay': `${delay}s`,
92
+ '--drip-duration': `${duration}s`
93
+ } as React.CSSProperties}
94
+ >
95
+ {char === ' ' ? '\u00A0' : char}
96
+ {hasDrip && (
97
+ <>
98
+ <span className={styles.drip} />
99
+ {dripsPerChar >= 2 && <span className={`${styles.drip} ${styles.secondary}`} />}
100
+ {dripsPerChar >= 3 && <span className={`${styles.drip} ${styles.tertiary}`} />}
101
+ </>
102
+ )}
103
+ </span>
104
+ );
105
+ })}
106
+ {showPool && <span className={styles.pool} />}
107
+ </span>
108
+ );
109
+ }
110
+ );
111
+
112
+ BloodDrip.displayName = 'BloodDrip';
113
+ export default BloodDrip;
@@ -0,0 +1,133 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useMemo } from 'react';
4
+
5
+ export interface BloodDripProps extends HTMLAttributes<HTMLSpanElement> {
6
+ children: string;
7
+ variant?: 'blood' | 'acid' | 'cyber' | 'void' | 'chrome';
8
+ intensity?: 'light' | 'medium' | 'heavy';
9
+ dripsPerChar?: 1 | 2 | 3;
10
+ duration?: number;
11
+ glowing?: boolean;
12
+ melting?: boolean;
13
+ showPool?: boolean;
14
+ pauseOnHover?: boolean;
15
+ static?: boolean;
16
+ dripColor?: string;
17
+ dripProbability?: number;
18
+ }
19
+
20
+ const variantColors = {
21
+ blood: '#ff0040',
22
+ acid: '#aaff00',
23
+ cyber: '#00ffff',
24
+ void: '#0a0a0a',
25
+ chrome: '#888888',
26
+ };
27
+
28
+ const dripWidths = { light: 2, medium: 4, heavy: 6 };
29
+ const dropletSizes = { light: 4, medium: 8, heavy: 12 };
30
+
31
+ export const BloodDrip = forwardRef<HTMLSpanElement, BloodDripProps>(
32
+ (
33
+ {
34
+ children,
35
+ variant = 'blood',
36
+ intensity = 'medium',
37
+ dripsPerChar = 1,
38
+ duration = 2,
39
+ glowing = false,
40
+ melting = false,
41
+ showPool = false,
42
+ pauseOnHover = false,
43
+ static: isStatic = false,
44
+ dripColor,
45
+ dripProbability = 0.7,
46
+ className = '',
47
+ style,
48
+ ...props
49
+ },
50
+ ref
51
+ ) => {
52
+ const chars = children.split('');
53
+ const color = dripColor || variantColors[variant];
54
+ const dripWidth = dripWidths[intensity];
55
+ const dropletSize = dropletSizes[intensity];
56
+
57
+ const dripMap = useMemo(() => {
58
+ return chars.map(() => Math.random() < dripProbability);
59
+ }, [chars.length, dripProbability]);
60
+
61
+ const glowStyle = glowing ? { textShadow: `0 0 10px ${color}, 0 0 20px ${color}` } : {};
62
+
63
+ return (
64
+ <span
65
+ ref={ref}
66
+ className={`relative inline-block ${pauseOnHover ? 'hover:[&_.drip]:pause' : ''} ${className}`}
67
+ style={{ ...glowStyle, ...style }}
68
+ {...props}
69
+ >
70
+ {chars.map((char, i) => {
71
+ const hasDrip = dripMap[i] && char !== ' ';
72
+ const delay = Math.random() * 2;
73
+
74
+ return (
75
+ <span
76
+ key={i}
77
+ className={`inline-block relative ${melting ? 'animate-pulse' : ''}`}
78
+ >
79
+ {char === ' ' ? '\u00A0' : char}
80
+ {hasDrip && (
81
+ <span
82
+ className="drip absolute left-1/2 top-full -translate-x-1/2"
83
+ style={{
84
+ width: dripWidth,
85
+ height: isStatic ? 40 : 0,
86
+ background: color,
87
+ borderRadius: '0 0 50% 50%',
88
+ animation: isStatic ? 'none' : `drip ${duration}s ease-in infinite`,
89
+ animationDelay: `${delay}s`,
90
+ }}
91
+ >
92
+ <span
93
+ className="absolute bottom-0 left-1/2 -translate-x-1/2 rounded-full"
94
+ style={{
95
+ width: dropletSize,
96
+ height: dropletSize,
97
+ background: color,
98
+ opacity: isStatic ? 1 : 0,
99
+ animation: isStatic ? 'none' : `droplet ${duration}s ease-in infinite`,
100
+ animationDelay: `${delay}s`,
101
+ }}
102
+ />
103
+ </span>
104
+ )}
105
+ </span>
106
+ );
107
+ })}
108
+ {showPool && (
109
+ <span
110
+ className="absolute -bottom-5 left-0 right-0 h-2.5 rounded-full opacity-50 blur-sm"
111
+ style={{ background: color }}
112
+ />
113
+ )}
114
+ <style>{`
115
+ @keyframes drip {
116
+ 0% { height: 0; opacity: 1; }
117
+ 20% { height: 20px; opacity: 1; }
118
+ 80% { height: 60px; opacity: 1; }
119
+ 100% { height: 80px; opacity: 0; }
120
+ }
121
+ @keyframes droplet {
122
+ 0%, 70% { opacity: 0; transform: translateX(-50%) scale(1); }
123
+ 75% { opacity: 1; transform: translateX(-50%) scale(1); }
124
+ 100% { opacity: 0; transform: translateX(-50%) scale(0) translateY(20px); }
125
+ }
126
+ `}</style>
127
+ </span>
128
+ );
129
+ }
130
+ );
131
+
132
+ BloodDrip.displayName = 'BloodDrip';
133
+ export default BloodDrip;
@@ -0,0 +1,124 @@
1
+ .container {
2
+ display: inline-block;
3
+ position: relative;
4
+ }
5
+
6
+ .char {
7
+ display: inline-block;
8
+ position: relative;
9
+ transition: transform 0.1s ease-out;
10
+ }
11
+
12
+ .char.glitching {
13
+ animation: charGlitch 0.15s ease-in-out;
14
+ }
15
+
16
+ .char::before,
17
+ .char::after {
18
+ content: attr(data-char);
19
+ position: absolute;
20
+ top: 0;
21
+ left: 0;
22
+ opacity: 0;
23
+ pointer-events: none;
24
+ }
25
+
26
+ .char::before {
27
+ color: #ff0040;
28
+ z-index: -1;
29
+ }
30
+
31
+ .char::after {
32
+ color: #00ffff;
33
+ z-index: -2;
34
+ }
35
+
36
+ .char.glitching::before {
37
+ animation: charLayer1 0.15s ease-in-out;
38
+ opacity: 0.8;
39
+ }
40
+
41
+ .char.glitching::after {
42
+ animation: charLayer2 0.15s ease-in-out;
43
+ opacity: 0.8;
44
+ }
45
+
46
+ /* Intensity levels */
47
+ .subtle .char.glitching {
48
+ animation-duration: 0.3s;
49
+ }
50
+
51
+ .intense .char.glitching {
52
+ animation-duration: 0.08s;
53
+ }
54
+
55
+ /* Variants */
56
+ .blood .char::before { color: #ff0040; }
57
+ .blood .char::after { color: #880020; }
58
+
59
+ .cyber .char::before { color: #00ffff; }
60
+ .cyber .char::after { color: #ff00ff; }
61
+
62
+ .matrix .char::before { color: #00ff00; }
63
+ .matrix .char::after { color: #008800; }
64
+
65
+ .corrupt .char::before { color: #ffffff; }
66
+ .corrupt .char::after { color: #000000; }
67
+
68
+ /* Hover mode */
69
+ .hover .char:hover {
70
+ animation: charGlitch 0.15s ease-in-out infinite;
71
+ }
72
+
73
+ .hover .char:hover::before,
74
+ .hover .char:hover::after {
75
+ opacity: 0.8;
76
+ }
77
+
78
+ .hover .char:hover::before {
79
+ animation: charLayer1 0.15s ease-in-out infinite;
80
+ }
81
+
82
+ .hover .char:hover::after {
83
+ animation: charLayer2 0.15s ease-in-out infinite;
84
+ }
85
+
86
+ @keyframes charGlitch {
87
+ 0%, 100% { transform: translate(0); }
88
+ 20% { transform: translate(-2px, 1px); }
89
+ 40% { transform: translate(2px, -1px); }
90
+ 60% { transform: translate(-1px, -1px); }
91
+ 80% { transform: translate(1px, 1px); }
92
+ }
93
+
94
+ @keyframes charLayer1 {
95
+ 0%, 100% { transform: translate(0); opacity: 0; }
96
+ 20% { transform: translate(-3px, 0); opacity: 0.8; }
97
+ 40% { transform: translate(3px, 0); opacity: 0.6; }
98
+ 60% { transform: translate(-2px, 0); opacity: 0.8; }
99
+ 80% { transform: translate(2px, 0); opacity: 0.6; }
100
+ }
101
+
102
+ @keyframes charLayer2 {
103
+ 0%, 100% { transform: translate(0); opacity: 0; }
104
+ 20% { transform: translate(3px, 0); opacity: 0.6; }
105
+ 40% { transform: translate(-3px, 0); opacity: 0.8; }
106
+ 60% { transform: translate(2px, 0); opacity: 0.6; }
107
+ 80% { transform: translate(-2px, 0); opacity: 0.8; }
108
+ }
109
+
110
+ /* Scramble effect */
111
+ .scramble .char {
112
+ font-family: monospace;
113
+ }
114
+
115
+ .scramble .char.scrambling {
116
+ color: transparent;
117
+ }
118
+
119
+ .scramble .char.scrambling::before {
120
+ color: inherit;
121
+ opacity: 1;
122
+ animation: none;
123
+ transform: none;
124
+ }