@oalacea/chaosui 0.4.0 → 0.5.1

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 (178) hide show
  1. package/buttons/cta-brutal/css/cta-brutal.module.css +175 -0
  2. package/buttons/cta-brutal/css/index.tsx +78 -0
  3. package/buttons/cta-brutal/tailwind/index.tsx +85 -0
  4. package/buttons/dead-button/css/dead-button.module.css +216 -0
  5. package/buttons/dead-button/css/index.tsx +47 -0
  6. package/buttons/dead-button/tailwind/index.tsx +90 -0
  7. package/buttons/deeper-button/css/deeper-button.module.css +141 -0
  8. package/buttons/deeper-button/css/index.tsx +62 -0
  9. package/buttons/deeper-button/tailwind/index.tsx +78 -0
  10. package/buttons/dual-choice/css/dual-choice.module.css +176 -0
  11. package/buttons/dual-choice/css/index.tsx +56 -0
  12. package/buttons/dual-choice/tailwind/index.tsx +89 -0
  13. package/buttons/tension-bar/css/index.tsx +96 -0
  14. package/buttons/tension-bar/css/tension-bar.module.css +204 -0
  15. package/buttons/tension-bar/tailwind/index.tsx +122 -0
  16. package/decorative/inscription/css/index.tsx +66 -0
  17. package/decorative/inscription/css/inscription.module.css +160 -0
  18. package/decorative/inscription/tailwind/index.tsx +91 -0
  19. package/decorative/ornaments/css/index.tsx +136 -0
  20. package/decorative/ornaments/css/ornaments.module.css +144 -0
  21. package/decorative/ornaments/tailwind/index.tsx +122 -0
  22. package/decorative/rune-symbols/css/index.tsx +116 -0
  23. package/decorative/rune-symbols/css/rune-symbols.module.css +116 -0
  24. package/decorative/rune-symbols/tailwind/index.tsx +108 -0
  25. package/decorative/sheet-music/css/index.tsx +144 -0
  26. package/decorative/sheet-music/css/sheet-music.module.css +152 -0
  27. package/decorative/sheet-music/tailwind/index.tsx +111 -0
  28. package/layout/horizontal-scroll/css/horizontal-scroll.module.css +93 -0
  29. package/layout/horizontal-scroll/css/index.tsx +114 -0
  30. package/layout/horizontal-scroll/tailwind/index.tsx +108 -0
  31. package/layout/spec-grid/css/index.tsx +103 -0
  32. package/layout/spec-grid/css/spec-grid.module.css +125 -0
  33. package/layout/spec-grid/tailwind/index.tsx +91 -0
  34. package/layout/tower-pricing/css/index.tsx +91 -0
  35. package/layout/tower-pricing/css/tower-pricing.module.css +177 -0
  36. package/layout/tower-pricing/tailwind/index.tsx +106 -0
  37. package/layout/tracklist/css/index.tsx +96 -0
  38. package/layout/tracklist/css/tracklist.module.css +141 -0
  39. package/layout/tracklist/tailwind/index.tsx +92 -0
  40. package/layout/void-frame/css/index.tsx +60 -0
  41. package/layout/void-frame/css/void-frame.module.css +141 -0
  42. package/layout/void-frame/tailwind/index.tsx +64 -0
  43. package/navigation/brutal-nav/css/brutal-nav.module.css +178 -0
  44. package/navigation/brutal-nav/css/index.tsx +80 -0
  45. package/navigation/brutal-nav/tailwind/index.tsx +95 -0
  46. package/navigation/progress-dots/css/index.tsx +60 -0
  47. package/navigation/progress-dots/css/progress-dots.module.css +152 -0
  48. package/navigation/progress-dots/tailwind/index.tsx +105 -0
  49. package/navigation/scattered-nav/css/index.tsx +59 -0
  50. package/navigation/scattered-nav/css/scattered-nav.module.css +121 -0
  51. package/navigation/scattered-nav/tailwind/index.tsx +77 -0
  52. package/navigation/scroll-indicator/css/index.tsx +72 -0
  53. package/navigation/scroll-indicator/css/scroll-indicator.module.css +129 -0
  54. package/navigation/scroll-indicator/tailwind/index.tsx +107 -0
  55. package/navigation/vertical-nav/css/index.tsx +69 -0
  56. package/navigation/vertical-nav/css/vertical-nav.module.css +117 -0
  57. package/navigation/vertical-nav/tailwind/index.tsx +91 -0
  58. package/package.json +8 -33
  59. package/text/ascii-art/css/ascii-art.module.css +173 -0
  60. package/text/ascii-art/css/index.tsx +116 -0
  61. package/text/ascii-art/tailwind/index.tsx +124 -0
  62. package/text/blood-drip/css/blood-drip.module.css +142 -0
  63. package/text/blood-drip/css/index.tsx +113 -0
  64. package/text/blood-drip/tailwind/index.tsx +133 -0
  65. package/text/char-glitch/css/char-glitch.module.css +124 -0
  66. package/text/char-glitch/css/index.tsx +153 -0
  67. package/text/char-glitch/tailwind/index.tsx +126 -0
  68. package/text/countdown-display/css/countdown-display.module.css +179 -0
  69. package/text/countdown-display/css/index.tsx +190 -0
  70. package/text/countdown-display/tailwind/index.tsx +155 -0
  71. package/text/giant-layers/css/giant-layers.module.css +156 -0
  72. package/text/giant-layers/css/index.tsx +97 -0
  73. package/text/giant-layers/tailwind/index.tsx +111 -0
  74. package/text/reveal-text/css/index.tsx +180 -0
  75. package/text/reveal-text/css/reveal-text.module.css +129 -0
  76. package/text/reveal-text/tailwind/index.tsx +135 -0
  77. package/text/rotate-text/css/index.tsx +139 -0
  78. package/text/rotate-text/css/rotate-text.module.css +162 -0
  79. package/text/rotate-text/tailwind/index.tsx +127 -0
  80. package/text/strike-reveal/css/index.tsx +124 -0
  81. package/text/strike-reveal/css/strike-reveal.module.css +139 -0
  82. package/text/strike-reveal/tailwind/index.tsx +138 -0
  83. package/text/terminal-output/css/index.tsx +179 -0
  84. package/text/terminal-output/css/terminal-output.module.css +203 -0
  85. package/text/terminal-output/tailwind/index.tsx +174 -0
  86. package/text/typing-text/css/index.tsx +115 -0
  87. package/text/typing-text/css/typing-text.module.css +84 -0
  88. package/text/typing-text/tailwind/index.tsx +126 -0
  89. package/bin/cli.js +0 -278
  90. package/components/backgrounds/light-beams/index.tsx +0 -80
  91. package/components/backgrounds/light-beams/light-beams.module.css +0 -27
  92. package/components/navigation/hexagon-menu/css/hexagon-menu.module.css +0 -55
  93. package/components/navigation/hexagon-menu/css/index.tsx +0 -35
  94. package/components/navigation/hexagon-menu/tailwind/index.tsx +0 -53
  95. /package/{components/backgrounds → backgrounds}/glow-orbs/glow-orbs.module.css +0 -0
  96. /package/{components/backgrounds → backgrounds}/glow-orbs/index.tsx +0 -0
  97. /package/{components/backgrounds → backgrounds}/noise-canvas/index.tsx +0 -0
  98. /package/{components/backgrounds → backgrounds}/noise-canvas/noise-canvas.module.css +0 -0
  99. /package/{components/backgrounds → backgrounds}/particle-field/index.tsx +0 -0
  100. /package/{components/backgrounds → backgrounds}/particle-field/particle-field.module.css +0 -0
  101. /package/{components/buttons → buttons}/chaos-button/chaos-button.module.css +0 -0
  102. /package/{components/buttons → buttons}/chaos-button/index.tsx +0 -0
  103. /package/{components/buttons → buttons}/glitch-button/glitch-button.module.css +0 -0
  104. /package/{components/buttons → buttons}/glitch-button/index.tsx +0 -0
  105. /package/{components/chaos-vars.css → chaos-vars.css} +0 -0
  106. /package/{components/cyber → cyber}/cyber-avatar/css/cyber-avatar.module.css +0 -0
  107. /package/{components/cyber → cyber}/cyber-avatar/css/index.tsx +0 -0
  108. /package/{components/cyber → cyber}/cyber-avatar/tailwind/index.tsx +0 -0
  109. /package/{components/cyber → cyber}/cyber-input/css/cyber-input.module.css +0 -0
  110. /package/{components/cyber → cyber}/cyber-input/css/index.tsx +0 -0
  111. /package/{components/cyber → cyber}/cyber-input/tailwind/index.tsx +0 -0
  112. /package/{components/cyber → cyber}/cyber-loader/css/cyber-loader.module.css +0 -0
  113. /package/{components/cyber → cyber}/cyber-loader/css/index.tsx +0 -0
  114. /package/{components/cyber → cyber}/cyber-loader/tailwind/index.tsx +0 -0
  115. /package/{components/cyber → cyber}/cyber-modal/css/cyber-modal.module.css +0 -0
  116. /package/{components/cyber → cyber}/cyber-modal/css/index.tsx +0 -0
  117. /package/{components/cyber → cyber}/cyber-modal/tailwind/index.tsx +0 -0
  118. /package/{components/cyber → cyber}/cyber-slider/css/cyber-slider.module.css +0 -0
  119. /package/{components/cyber → cyber}/cyber-slider/css/index.tsx +0 -0
  120. /package/{components/cyber → cyber}/cyber-slider/tailwind/index.tsx +0 -0
  121. /package/{components/cyber → cyber}/cyber-tooltip/css/cyber-tooltip.module.css +0 -0
  122. /package/{components/cyber → cyber}/cyber-tooltip/css/index.tsx +0 -0
  123. /package/{components/cyber → cyber}/cyber-tooltip/tailwind/index.tsx +0 -0
  124. /package/{components/effects → effects}/cursor-follower/cursor-follower.module.css +0 -0
  125. /package/{components/effects → effects}/cursor-follower/index.tsx +0 -0
  126. /package/{components/effects → effects}/glitch-image/css/glitch-image.module.css +0 -0
  127. /package/{components/effects → effects}/glitch-image/css/index.tsx +0 -0
  128. /package/{components/effects → effects}/glitch-image/tailwind/index.tsx +0 -0
  129. /package/{components/effects → effects}/glowing-border/css/glowing-border.module.css +0 -0
  130. /package/{components/effects → effects}/glowing-border/css/index.tsx +0 -0
  131. /package/{components/effects → effects}/glowing-border/tailwind/index.tsx +0 -0
  132. /package/{components/effects → effects}/screen-distortion/index.tsx +0 -0
  133. /package/{components/effects → effects}/screen-distortion/screen-distortion.module.css +0 -0
  134. /package/{components/effects → effects}/warning-tape/index.tsx +0 -0
  135. /package/{components/effects → effects}/warning-tape/warning-tape.module.css +0 -0
  136. /package/{components/layout → layout}/data-grid/css/data-grid.module.css +0 -0
  137. /package/{components/layout → layout}/data-grid/css/index.tsx +0 -0
  138. /package/{components/layout → layout}/data-grid/tailwind/index.tsx +0 -0
  139. /package/{components/layout → layout}/hologram-card/css/hologram-card.module.css +0 -0
  140. /package/{components/layout → layout}/hologram-card/css/index.tsx +0 -0
  141. /package/{components/layout → layout}/hologram-card/tailwind/index.tsx +0 -0
  142. /package/{components/neon → neon}/neon-alert/css/index.tsx +0 -0
  143. /package/{components/neon → neon}/neon-alert/css/neon-alert.module.css +0 -0
  144. /package/{components/neon → neon}/neon-alert/tailwind/index.tsx +0 -0
  145. /package/{components/neon → neon}/neon-badge/css/index.tsx +0 -0
  146. /package/{components/neon → neon}/neon-badge/css/neon-badge.module.css +0 -0
  147. /package/{components/neon → neon}/neon-badge/tailwind/index.tsx +0 -0
  148. /package/{components/neon → neon}/neon-button/css/index.tsx +0 -0
  149. /package/{components/neon → neon}/neon-button/css/neon-button.module.css +0 -0
  150. /package/{components/neon → neon}/neon-button/tailwind/index.tsx +0 -0
  151. /package/{components/neon → neon}/neon-divider/css/index.tsx +0 -0
  152. /package/{components/neon → neon}/neon-divider/css/neon-divider.module.css +0 -0
  153. /package/{components/neon → neon}/neon-divider/tailwind/index.tsx +0 -0
  154. /package/{components/neon → neon}/neon-progress/css/index.tsx +0 -0
  155. /package/{components/neon → neon}/neon-progress/css/neon-progress.module.css +0 -0
  156. /package/{components/neon → neon}/neon-progress/tailwind/index.tsx +0 -0
  157. /package/{components/neon → neon}/neon-tabs/css/index.tsx +0 -0
  158. /package/{components/neon → neon}/neon-tabs/css/neon-tabs.module.css +0 -0
  159. /package/{components/neon → neon}/neon-tabs/tailwind/index.tsx +0 -0
  160. /package/{components/neon → neon}/neon-toggle/css/index.tsx +0 -0
  161. /package/{components/neon → neon}/neon-toggle/css/neon-toggle.module.css +0 -0
  162. /package/{components/neon → neon}/neon-toggle/tailwind/index.tsx +0 -0
  163. /package/{components/overlays → overlays}/noise-overlay/index.tsx +0 -0
  164. /package/{components/overlays → overlays}/noise-overlay/noise-overlay.module.css +0 -0
  165. /package/{components/overlays → overlays}/scanlines/index.tsx +0 -0
  166. /package/{components/overlays → overlays}/scanlines/scanlines.module.css +0 -0
  167. /package/{components/overlays → overlays}/static-flicker/index.tsx +0 -0
  168. /package/{components/overlays → overlays}/static-flicker/static-flicker.module.css +0 -0
  169. /package/{components/overlays → overlays}/vignette/index.tsx +0 -0
  170. /package/{components/overlays → overlays}/vignette/vignette.module.css +0 -0
  171. /package/{components/text → text}/distortion-text/distortion-text.module.css +0 -0
  172. /package/{components/text → text}/distortion-text/index.tsx +0 -0
  173. /package/{components/text → text}/falling-text/falling-text.module.css +0 -0
  174. /package/{components/text → text}/falling-text/index.tsx +0 -0
  175. /package/{components/text → text}/flicker-text/flicker-text.module.css +0 -0
  176. /package/{components/text → text}/flicker-text/index.tsx +0 -0
  177. /package/{components/text → text}/glitch-text/glitch-text.module.css +0 -0
  178. /package/{components/text → text}/glitch-text/index.tsx +0 -0
@@ -0,0 +1,162 @@
1
+ .container {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 0.5em;
5
+ overflow: hidden;
6
+ }
7
+
8
+ .static {
9
+ display: inline;
10
+ }
11
+
12
+ .rotator {
13
+ display: inline-block;
14
+ position: relative;
15
+ height: 1.2em;
16
+ overflow: hidden;
17
+ }
18
+
19
+ .word {
20
+ display: block;
21
+ position: absolute;
22
+ top: 0;
23
+ left: 0;
24
+ width: 100%;
25
+ transform: translateY(100%);
26
+ opacity: 0;
27
+ transition: transform 0.5s cubic-bezier(0.19, 1, 0.22, 1), opacity 0.5s ease;
28
+ }
29
+
30
+ .word.active {
31
+ transform: translateY(0);
32
+ opacity: 1;
33
+ }
34
+
35
+ .word.exit {
36
+ transform: translateY(-100%);
37
+ opacity: 0;
38
+ }
39
+
40
+ /* Direction variants */
41
+ .up .word { transform: translateY(100%); }
42
+ .up .word.active { transform: translateY(0); }
43
+ .up .word.exit { transform: translateY(-100%); }
44
+
45
+ .down .word { transform: translateY(-100%); }
46
+ .down .word.active { transform: translateY(0); }
47
+ .down .word.exit { transform: translateY(100%); }
48
+
49
+ .left .word { transform: translateX(100%); }
50
+ .left .word.active { transform: translateX(0); }
51
+ .left .word.exit { transform: translateX(-100%); }
52
+
53
+ .right .word { transform: translateX(-100%); }
54
+ .right .word.active { transform: translateX(0); }
55
+ .right .word.exit { transform: translateX(100%); }
56
+
57
+ /* Flip variant */
58
+ .flip .word {
59
+ transform: rotateX(90deg);
60
+ transform-origin: bottom;
61
+ }
62
+
63
+ .flip .word.active {
64
+ transform: rotateX(0deg);
65
+ }
66
+
67
+ .flip .word.exit {
68
+ transform: rotateX(-90deg);
69
+ transform-origin: top;
70
+ }
71
+
72
+ /* Fade variant */
73
+ .fade .word {
74
+ transform: none;
75
+ opacity: 0;
76
+ }
77
+
78
+ .fade .word.active {
79
+ opacity: 1;
80
+ }
81
+
82
+ .fade .word.exit {
83
+ opacity: 0;
84
+ }
85
+
86
+ /* Zoom variant */
87
+ .zoom .word {
88
+ transform: scale(0);
89
+ opacity: 0;
90
+ }
91
+
92
+ .zoom .word.active {
93
+ transform: scale(1);
94
+ opacity: 1;
95
+ }
96
+
97
+ .zoom .word.exit {
98
+ transform: scale(2);
99
+ opacity: 0;
100
+ }
101
+
102
+ /* Blur variant */
103
+ .blur .word {
104
+ transform: translateY(50%);
105
+ opacity: 0;
106
+ filter: blur(10px);
107
+ }
108
+
109
+ .blur .word.active {
110
+ transform: translateY(0);
111
+ opacity: 1;
112
+ filter: blur(0);
113
+ }
114
+
115
+ .blur .word.exit {
116
+ transform: translateY(-50%);
117
+ opacity: 0;
118
+ filter: blur(10px);
119
+ }
120
+
121
+ /* Color variants */
122
+ .highlight .word.active {
123
+ color: var(--highlight-color, #ff0040);
124
+ text-shadow: 0 0 10px var(--highlight-color, #ff0040);
125
+ }
126
+
127
+ /* Styling variants */
128
+ .underline .word.active {
129
+ text-decoration: underline;
130
+ text-decoration-color: var(--highlight-color, #ff0040);
131
+ text-underline-offset: 4px;
132
+ }
133
+
134
+ .bracket::before,
135
+ .bracket::after {
136
+ color: var(--bracket-color, #666);
137
+ }
138
+
139
+ .bracket::before { content: '['; margin-right: 0.25em; }
140
+ .bracket::after { content: ']'; margin-left: 0.25em; }
141
+
142
+ /* Speed variants */
143
+ .fast .word { transition-duration: 0.3s; }
144
+ .slow .word { transition-duration: 0.8s; }
145
+
146
+ /* Hover pause */
147
+ .pauseOnHover:hover .word {
148
+ animation-play-state: paused;
149
+ }
150
+
151
+ /* Cursor indicator */
152
+ .cursor::after {
153
+ content: '|';
154
+ display: inline-block;
155
+ margin-left: 2px;
156
+ animation: cursorBlink 1s step-end infinite;
157
+ }
158
+
159
+ @keyframes cursorBlink {
160
+ 0%, 100% { opacity: 1; }
161
+ 50% { opacity: 0; }
162
+ }
@@ -0,0 +1,127 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useEffect, useState, useRef, useCallback } from 'react';
4
+
5
+ export interface RotateTextProps extends HTMLAttributes<HTMLSpanElement> {
6
+ prefix?: string;
7
+ suffix?: string;
8
+ words: string[];
9
+ animation?: 'up' | 'down' | 'left' | 'right' | 'flip' | 'fade' | 'zoom' | 'blur';
10
+ duration?: number;
11
+ speed?: 'fast' | 'normal' | 'slow';
12
+ highlight?: boolean;
13
+ highlightColor?: string;
14
+ underline?: boolean;
15
+ bracket?: boolean;
16
+ bracketColor?: string;
17
+ pauseOnHover?: boolean;
18
+ cursor?: boolean;
19
+ onChange?: (word: string, index: number) => void;
20
+ }
21
+
22
+ const speedDurations = { fast: 'duration-300', normal: 'duration-500', slow: 'duration-700' };
23
+
24
+ const getTransform = (animation: string, state: 'hidden' | 'active' | 'exit') => {
25
+ const transforms: Record<string, Record<string, string>> = {
26
+ up: { hidden: 'translate-y-full opacity-0', active: 'translate-y-0 opacity-100', exit: '-translate-y-full opacity-0' },
27
+ down: { hidden: '-translate-y-full opacity-0', active: 'translate-y-0 opacity-100', exit: 'translate-y-full opacity-0' },
28
+ left: { hidden: 'translate-x-full opacity-0', active: 'translate-x-0 opacity-100', exit: '-translate-x-full opacity-0' },
29
+ right: { hidden: '-translate-x-full opacity-0', active: 'translate-x-0 opacity-100', exit: 'translate-x-full opacity-0' },
30
+ fade: { hidden: 'opacity-0', active: 'opacity-100', exit: 'opacity-0' },
31
+ zoom: { hidden: 'scale-0 opacity-0', active: 'scale-100 opacity-100', exit: 'scale-150 opacity-0' },
32
+ blur: { hidden: 'translate-y-1/2 opacity-0 blur-sm', active: 'translate-y-0 opacity-100 blur-0', exit: '-translate-y-1/2 opacity-0 blur-sm' },
33
+ flip: { hidden: 'rotateX-90 opacity-0', active: 'rotateX-0 opacity-100', exit: '-rotateX-90 opacity-0' },
34
+ };
35
+ return transforms[animation]?.[state] || '';
36
+ };
37
+
38
+ export const RotateText = forwardRef<HTMLSpanElement, RotateTextProps>(
39
+ (
40
+ {
41
+ prefix,
42
+ suffix,
43
+ words,
44
+ animation = 'up',
45
+ duration = 2000,
46
+ speed = 'normal',
47
+ highlight = false,
48
+ highlightColor = '#ff0040',
49
+ underline = false,
50
+ bracket = false,
51
+ bracketColor = '#666',
52
+ pauseOnHover = false,
53
+ cursor = false,
54
+ onChange,
55
+ className = '',
56
+ ...props
57
+ },
58
+ ref
59
+ ) => {
60
+ const [currentIndex, setCurrentIndex] = useState(0);
61
+ const [exitIndex, setExitIndex] = useState<number | null>(null);
62
+ const intervalRef = useRef<NodeJS.Timeout>();
63
+ const isPaused = useRef(false);
64
+
65
+ const maxWidth = Math.max(...words.map(w => w.length));
66
+
67
+ const rotate = useCallback(() => {
68
+ if (isPaused.current) return;
69
+
70
+ setExitIndex(currentIndex);
71
+ const nextIndex = (currentIndex + 1) % words.length;
72
+ setCurrentIndex(nextIndex);
73
+ onChange?.(words[nextIndex], nextIndex);
74
+
75
+ setTimeout(() => setExitIndex(null), 500);
76
+ }, [currentIndex, words, onChange]);
77
+
78
+ useEffect(() => {
79
+ intervalRef.current = setInterval(rotate, duration);
80
+ return () => { if (intervalRef.current) clearInterval(intervalRef.current); };
81
+ }, [rotate, duration]);
82
+
83
+ const handleMouseEnter = () => { if (pauseOnHover) isPaused.current = true; };
84
+ const handleMouseLeave = () => { if (pauseOnHover) isPaused.current = false; };
85
+
86
+ return (
87
+ <span
88
+ ref={ref}
89
+ className={`inline-flex items-center gap-2 ${className}`}
90
+ onMouseEnter={handleMouseEnter}
91
+ onMouseLeave={handleMouseLeave}
92
+ {...props}
93
+ >
94
+ {prefix && <span>{prefix}</span>}
95
+ {bracket && <span style={{ color: bracketColor }}>[</span>}
96
+ <span
97
+ className="relative inline-block overflow-hidden"
98
+ style={{ width: `${maxWidth}ch`, height: '1.2em' }}
99
+ >
100
+ {words.map((word, i) => {
101
+ const isActive = i === currentIndex;
102
+ const isExit = i === exitIndex;
103
+ const state = isActive ? 'active' : isExit ? 'exit' : 'hidden';
104
+
105
+ return (
106
+ <span
107
+ key={i}
108
+ className={`absolute top-0 left-0 w-full transition-all ease-out ${speedDurations[speed]} ${getTransform(animation, state)} ${
109
+ isActive && highlight ? 'drop-shadow-lg' : ''
110
+ } ${isActive && underline ? 'underline underline-offset-4' : ''}`}
111
+ style={isActive && highlight ? { color: highlightColor, textShadow: `0 0 10px ${highlightColor}` } : undefined}
112
+ >
113
+ {word}
114
+ </span>
115
+ );
116
+ })}
117
+ </span>
118
+ {bracket && <span style={{ color: bracketColor }}>]</span>}
119
+ {suffix && <span>{suffix}</span>}
120
+ {cursor && <span className="animate-pulse">|</span>}
121
+ </span>
122
+ );
123
+ }
124
+ );
125
+
126
+ RotateText.displayName = 'RotateText';
127
+ export default RotateText;
@@ -0,0 +1,124 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useEffect, useState, useRef } from 'react';
4
+ import styles from './strike-reveal.module.css';
5
+
6
+ export interface StrikeRevealProps extends HTMLAttributes<HTMLSpanElement> {
7
+ /** Original text to strike through */
8
+ children: string;
9
+ /** Text to reveal after strike */
10
+ revealText?: string;
11
+ /** Visual variant */
12
+ variant?: 'permanent' | 'crossout' | 'redacted' | 'censored' | 'glitch';
13
+ /** Color variant */
14
+ color?: 'default' | 'blood' | 'cyber' | 'acid' | 'void';
15
+ /** Trigger mode */
16
+ trigger?: 'auto' | 'hover' | 'scroll' | 'click';
17
+ /** Delay before animation (ms) */
18
+ delay?: number;
19
+ /** Double strike line */
20
+ double?: boolean;
21
+ /** Custom strike color */
22
+ strikeColor?: string;
23
+ /** Callback when reveal completes */
24
+ onReveal?: () => void;
25
+ }
26
+
27
+ export const StrikeReveal = forwardRef<HTMLSpanElement, StrikeRevealProps>(
28
+ (
29
+ {
30
+ children,
31
+ revealText,
32
+ variant = 'permanent',
33
+ color = 'default',
34
+ trigger = 'auto',
35
+ delay = 0,
36
+ double = false,
37
+ strikeColor,
38
+ onReveal,
39
+ className,
40
+ style,
41
+ ...props
42
+ },
43
+ ref
44
+ ) => {
45
+ const [isActive, setIsActive] = useState(false);
46
+ const containerRef = useRef<HTMLSpanElement>(null);
47
+
48
+ useEffect(() => {
49
+ if (trigger === 'auto') {
50
+ const timeout = setTimeout(() => {
51
+ setIsActive(true);
52
+ setTimeout(() => onReveal?.(), 700);
53
+ }, delay);
54
+ return () => clearTimeout(timeout);
55
+ }
56
+
57
+ if (trigger === 'scroll') {
58
+ const observer = new IntersectionObserver(
59
+ ([entry]) => {
60
+ if (entry.isIntersecting) {
61
+ setTimeout(() => {
62
+ setIsActive(true);
63
+ setTimeout(() => onReveal?.(), 700);
64
+ }, delay);
65
+ }
66
+ },
67
+ { threshold: 0.5 }
68
+ );
69
+
70
+ if (containerRef.current) {
71
+ observer.observe(containerRef.current);
72
+ }
73
+
74
+ return () => observer.disconnect();
75
+ }
76
+ }, [trigger, delay, onReveal]);
77
+
78
+ const handleClick = () => {
79
+ if (trigger === 'click') {
80
+ setIsActive(true);
81
+ setTimeout(() => onReveal?.(), 700);
82
+ }
83
+ };
84
+
85
+ const containerClasses = [
86
+ styles.container,
87
+ styles[variant],
88
+ color !== 'default' && styles[color],
89
+ double && styles.double,
90
+ trigger === 'hover' && styles.hover,
91
+ isActive && styles.active,
92
+ className
93
+ ].filter(Boolean).join(' ');
94
+
95
+ return (
96
+ <span
97
+ ref={(node) => {
98
+ (containerRef as React.MutableRefObject<HTMLSpanElement | null>).current = node;
99
+ if (typeof ref === 'function') ref(node);
100
+ else if (ref) ref.current = node;
101
+ }}
102
+ className={containerClasses}
103
+ style={{
104
+ '--strike-color': strikeColor,
105
+ '--delay': `${delay}ms`,
106
+ ...style
107
+ } as React.CSSProperties}
108
+ onClick={handleClick}
109
+ {...props}
110
+ >
111
+ <span className={styles.text}>
112
+ {children}
113
+ <span className={styles.strike} />
114
+ </span>
115
+ {revealText && (
116
+ <span className={styles.revealed}>{revealText}</span>
117
+ )}
118
+ </span>
119
+ );
120
+ }
121
+ );
122
+
123
+ StrikeReveal.displayName = 'StrikeReveal';
124
+ export default StrikeReveal;
@@ -0,0 +1,139 @@
1
+ .container {
2
+ position: relative;
3
+ display: inline-block;
4
+ }
5
+
6
+ .text {
7
+ position: relative;
8
+ display: inline;
9
+ }
10
+
11
+ /* Strike line */
12
+ .strike {
13
+ position: absolute;
14
+ left: 0;
15
+ top: 50%;
16
+ width: 0;
17
+ height: 2px;
18
+ background: currentColor;
19
+ transform: translateY(-50%);
20
+ transition: width 0.4s cubic-bezier(0.19, 1, 0.22, 1);
21
+ }
22
+
23
+ .container.active .strike {
24
+ width: 100%;
25
+ }
26
+
27
+ /* Hidden text that reveals */
28
+ .revealed {
29
+ display: block;
30
+ opacity: 0;
31
+ transform: translateY(10px);
32
+ transition: opacity 0.4s ease, transform 0.4s ease;
33
+ transition-delay: 0.3s;
34
+ margin-top: 0.5em;
35
+ }
36
+
37
+ .container.active .revealed {
38
+ opacity: 1;
39
+ transform: translateY(0);
40
+ }
41
+
42
+ /* Variant: strikethrough stays */
43
+ .permanent .strike {
44
+ background: var(--strike-color, #ff0040);
45
+ }
46
+
47
+ .permanent.active .text {
48
+ opacity: 0.5;
49
+ }
50
+
51
+ /* Variant: text fades then reveals */
52
+ .crossout .text {
53
+ transition: opacity 0.3s ease;
54
+ }
55
+
56
+ .crossout.active .text {
57
+ opacity: 0.3;
58
+ }
59
+
60
+ /* Variant: redacted style */
61
+ .redacted .strike {
62
+ height: 1em;
63
+ background: var(--strike-color, #0a0a0a);
64
+ top: 50%;
65
+ transform: translateY(-50%);
66
+ }
67
+
68
+ .redacted.active .text {
69
+ color: transparent;
70
+ }
71
+
72
+ /* Variant: censored with bars */
73
+ .censored {
74
+ position: relative;
75
+ }
76
+
77
+ .censored .strike {
78
+ height: 100%;
79
+ background: repeating-linear-gradient(
80
+ 90deg,
81
+ var(--strike-color, #0a0a0a) 0px,
82
+ var(--strike-color, #0a0a0a) 8px,
83
+ transparent 8px,
84
+ transparent 12px
85
+ );
86
+ top: 0;
87
+ transform: none;
88
+ }
89
+
90
+ /* Variant: glitch strike */
91
+ .glitch .strike {
92
+ background: var(--strike-color, #ff0040);
93
+ animation: none;
94
+ }
95
+
96
+ .glitch.active .strike {
97
+ animation: glitchStrike 0.5s ease forwards;
98
+ }
99
+
100
+ @keyframes glitchStrike {
101
+ 0% { width: 0; transform: translateY(-50%); }
102
+ 30% { width: 110%; transform: translateY(-50%) translateX(-5%); }
103
+ 50% { width: 95%; transform: translateY(-50%) translateX(3%); }
104
+ 70% { width: 102%; transform: translateY(-50%) translateX(-2%); }
105
+ 100% { width: 100%; transform: translateY(-50%); }
106
+ }
107
+
108
+ /* Colors */
109
+ .blood .strike { background: #ff0040; }
110
+ .cyber .strike { background: #00ffff; }
111
+ .acid .strike { background: #aaff00; }
112
+ .void .strike { background: #0a0a0a; }
113
+
114
+ /* Hover trigger */
115
+ .hover:hover .strike {
116
+ width: 100%;
117
+ }
118
+
119
+ .hover:hover .revealed {
120
+ opacity: 1;
121
+ transform: translateY(0);
122
+ }
123
+
124
+ /* Animation on scroll */
125
+ .scroll .strike {
126
+ transition-delay: var(--delay, 0ms);
127
+ }
128
+
129
+ /* Double strike */
130
+ .double .strike::after {
131
+ content: '';
132
+ position: absolute;
133
+ left: 0;
134
+ top: 4px;
135
+ width: 100%;
136
+ height: 2px;
137
+ background: inherit;
138
+ opacity: 0.5;
139
+ }
@@ -0,0 +1,138 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useEffect, useState, useRef } from 'react';
4
+
5
+ export interface StrikeRevealProps extends HTMLAttributes<HTMLSpanElement> {
6
+ children: string;
7
+ revealText?: string;
8
+ variant?: 'permanent' | 'crossout' | 'redacted' | 'censored' | 'glitch';
9
+ color?: 'default' | 'blood' | 'cyber' | 'acid' | 'void';
10
+ trigger?: 'auto' | 'hover' | 'scroll' | 'click';
11
+ delay?: number;
12
+ double?: boolean;
13
+ strikeColor?: string;
14
+ onReveal?: () => void;
15
+ }
16
+
17
+ const colorStyles = {
18
+ default: 'bg-current',
19
+ blood: 'bg-rose-500',
20
+ cyber: 'bg-cyan-400',
21
+ acid: 'bg-lime-400',
22
+ void: 'bg-[#0a0a0a]',
23
+ };
24
+
25
+ export const StrikeReveal = forwardRef<HTMLSpanElement, StrikeRevealProps>(
26
+ (
27
+ {
28
+ children,
29
+ revealText,
30
+ variant = 'permanent',
31
+ color = 'default',
32
+ trigger = 'auto',
33
+ delay = 0,
34
+ double = false,
35
+ strikeColor,
36
+ onReveal,
37
+ className = '',
38
+ style,
39
+ ...props
40
+ },
41
+ ref
42
+ ) => {
43
+ const [isActive, setIsActive] = useState(false);
44
+ const containerRef = useRef<HTMLSpanElement>(null);
45
+
46
+ useEffect(() => {
47
+ if (trigger === 'auto') {
48
+ const timeout = setTimeout(() => {
49
+ setIsActive(true);
50
+ setTimeout(() => onReveal?.(), 700);
51
+ }, delay);
52
+ return () => clearTimeout(timeout);
53
+ }
54
+
55
+ if (trigger === 'scroll') {
56
+ const observer = new IntersectionObserver(
57
+ ([entry]) => {
58
+ if (entry.isIntersecting) {
59
+ setTimeout(() => {
60
+ setIsActive(true);
61
+ setTimeout(() => onReveal?.(), 700);
62
+ }, delay);
63
+ }
64
+ },
65
+ { threshold: 0.5 }
66
+ );
67
+
68
+ if (containerRef.current) observer.observe(containerRef.current);
69
+ return () => observer.disconnect();
70
+ }
71
+ }, [trigger, delay, onReveal]);
72
+
73
+ const handleClick = () => {
74
+ if (trigger === 'click') {
75
+ setIsActive(true);
76
+ setTimeout(() => onReveal?.(), 700);
77
+ }
78
+ };
79
+
80
+ const handleMouseEnter = () => {
81
+ if (trigger === 'hover') setIsActive(true);
82
+ };
83
+
84
+ const handleMouseLeave = () => {
85
+ if (trigger === 'hover') setIsActive(false);
86
+ };
87
+
88
+ const strikeHeight = variant === 'redacted' ? 'h-[1em]' : variant === 'censored' ? 'h-full' : 'h-0.5';
89
+ const bgStyle = strikeColor ? { backgroundColor: strikeColor } : undefined;
90
+
91
+ return (
92
+ <span
93
+ ref={(node) => {
94
+ (containerRef as React.MutableRefObject<HTMLSpanElement | null>).current = node;
95
+ if (typeof ref === 'function') ref(node);
96
+ else if (ref) ref.current = node;
97
+ }}
98
+ className={`relative inline-block ${className}`}
99
+ style={style}
100
+ onClick={handleClick}
101
+ onMouseEnter={handleMouseEnter}
102
+ onMouseLeave={handleMouseLeave}
103
+ {...props}
104
+ >
105
+ <span className={`relative inline ${isActive && variant === 'crossout' ? 'opacity-30' : ''} ${isActive && variant === 'redacted' ? 'text-transparent' : ''} transition-opacity duration-300`}>
106
+ {children}
107
+ <span
108
+ className={`absolute left-0 top-1/2 -translate-y-1/2 ${strikeHeight} ${colorStyles[color]} transition-all duration-400 ease-out ${
109
+ isActive ? 'w-full' : 'w-0'
110
+ }`}
111
+ style={bgStyle}
112
+ />
113
+ {double && (
114
+ <span
115
+ className={`absolute left-0 top-1/2 translate-y-0.5 h-0.5 ${colorStyles[color]} opacity-50 transition-all duration-400 ease-out ${
116
+ isActive ? 'w-full' : 'w-0'
117
+ }`}
118
+ style={bgStyle}
119
+ />
120
+ )}
121
+ </span>
122
+ {revealText && (
123
+ <span
124
+ className={`block mt-2 transition-all duration-400 ease-out ${
125
+ isActive ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'
126
+ }`}
127
+ style={{ transitionDelay: '300ms' }}
128
+ >
129
+ {revealText}
130
+ </span>
131
+ )}
132
+ </span>
133
+ );
134
+ }
135
+ );
136
+
137
+ StrikeReveal.displayName = 'StrikeReveal';
138
+ export default StrikeReveal;