@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,190 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useEffect, useState, useRef } from 'react';
4
+ import styles from './countdown-display.module.css';
5
+
6
+ export interface CountdownDisplayProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
7
+ /** Target date/time or duration in seconds */
8
+ target: Date | number;
9
+ /** Display format */
10
+ format?: 'full' | 'hms' | 'ms' | 'dhms';
11
+ /** Size variant */
12
+ size?: 'sm' | 'md' | 'lg';
13
+ /** Visual variant */
14
+ variant?: 'default' | 'minimal' | 'neon' | 'brutal' | 'glitch';
15
+ /** Accent color */
16
+ accentColor?: string;
17
+ /** Show labels under numbers */
18
+ showLabels?: boolean;
19
+ /** Compact mode (less spacing) */
20
+ compact?: boolean;
21
+ /** Enable flip animation */
22
+ flip?: boolean;
23
+ /** Urgent mode threshold (seconds) - when to show urgency effect */
24
+ urgentThreshold?: number;
25
+ /** Callback when countdown reaches zero */
26
+ onComplete?: () => void;
27
+ /** Labels customization */
28
+ labels?: {
29
+ days?: string;
30
+ hours?: string;
31
+ minutes?: string;
32
+ seconds?: string;
33
+ };
34
+ }
35
+
36
+ interface TimeLeft {
37
+ days: number;
38
+ hours: number;
39
+ minutes: number;
40
+ seconds: number;
41
+ }
42
+
43
+ export const CountdownDisplay = forwardRef<HTMLDivElement, CountdownDisplayProps>(
44
+ (
45
+ {
46
+ target,
47
+ format = 'hms',
48
+ size = 'md',
49
+ variant = 'default',
50
+ accentColor = '#ff0040',
51
+ showLabels = true,
52
+ compact = false,
53
+ flip = false,
54
+ urgentThreshold = 60,
55
+ onComplete,
56
+ labels = {},
57
+ className,
58
+ style,
59
+ ...props
60
+ },
61
+ ref
62
+ ) => {
63
+ const [timeLeft, setTimeLeft] = useState<TimeLeft>({ days: 0, hours: 0, minutes: 0, seconds: 0 });
64
+ const [isUrgent, setIsUrgent] = useState(false);
65
+ const [changing, setChanging] = useState<string[]>([]);
66
+ const prevValues = useRef<TimeLeft>({ days: 0, hours: 0, minutes: 0, seconds: 0 });
67
+ const completedRef = useRef(false);
68
+
69
+ const defaultLabels = {
70
+ days: labels.days || 'DAYS',
71
+ hours: labels.hours || 'HOURS',
72
+ minutes: labels.minutes || 'MIN',
73
+ seconds: labels.seconds || 'SEC',
74
+ };
75
+
76
+ useEffect(() => {
77
+ const calculateTimeLeft = (): TimeLeft => {
78
+ let totalSeconds: number;
79
+
80
+ if (target instanceof Date) {
81
+ totalSeconds = Math.max(0, Math.floor((target.getTime() - Date.now()) / 1000));
82
+ } else {
83
+ totalSeconds = Math.max(0, target);
84
+ }
85
+
86
+ return {
87
+ days: Math.floor(totalSeconds / (24 * 60 * 60)),
88
+ hours: Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60)),
89
+ minutes: Math.floor((totalSeconds % (60 * 60)) / 60),
90
+ seconds: totalSeconds % 60,
91
+ };
92
+ };
93
+
94
+ const tick = () => {
95
+ const newTime = calculateTimeLeft();
96
+ const totalSeconds = newTime.days * 86400 + newTime.hours * 3600 + newTime.minutes * 60 + newTime.seconds;
97
+
98
+ // Detect changes for flip animation
99
+ if (flip) {
100
+ const changed: string[] = [];
101
+ if (newTime.days !== prevValues.current.days) changed.push('days');
102
+ if (newTime.hours !== prevValues.current.hours) changed.push('hours');
103
+ if (newTime.minutes !== prevValues.current.minutes) changed.push('minutes');
104
+ if (newTime.seconds !== prevValues.current.seconds) changed.push('seconds');
105
+ setChanging(changed);
106
+ setTimeout(() => setChanging([]), 300);
107
+ }
108
+
109
+ prevValues.current = newTime;
110
+ setTimeLeft(newTime);
111
+ setIsUrgent(totalSeconds <= urgentThreshold && totalSeconds > 0);
112
+
113
+ if (totalSeconds === 0 && !completedRef.current) {
114
+ completedRef.current = true;
115
+ onComplete?.();
116
+ }
117
+ };
118
+
119
+ tick();
120
+ const interval = setInterval(tick, 1000);
121
+ return () => clearInterval(interval);
122
+ }, [target, urgentThreshold, flip, onComplete]);
123
+
124
+ const pad = (num: number) => String(num).padStart(2, '0');
125
+
126
+ const containerClasses = [
127
+ styles.container,
128
+ styles[size],
129
+ styles[variant],
130
+ flip && styles.flip,
131
+ isUrgent && styles.urgent,
132
+ !showLabels && styles.hideLabels,
133
+ compact && styles.compact,
134
+ className
135
+ ].filter(Boolean).join(' ');
136
+
137
+ const renderBlock = (value: number, label: string, key: string) => (
138
+ <div className={styles.block} key={key}>
139
+ <span
140
+ className={`${styles.value} ${changing.includes(key) ? styles.changing : ''}`}
141
+ data-value={pad(value)}
142
+ >
143
+ {pad(value)}
144
+ </span>
145
+ {showLabels && <span className={styles.label}>{label}</span>}
146
+ </div>
147
+ );
148
+
149
+ const renderSeparator = (key: number) => (
150
+ <span className={styles.separator} key={`sep-${key}`}>:</span>
151
+ );
152
+
153
+ const renderBlocks = () => {
154
+ const blocks = [];
155
+
156
+ if (format === 'dhms' || format === 'full') {
157
+ blocks.push(renderBlock(timeLeft.days, defaultLabels.days, 'days'));
158
+ blocks.push(renderSeparator(1));
159
+ }
160
+
161
+ if (format !== 'ms') {
162
+ blocks.push(renderBlock(timeLeft.hours, defaultLabels.hours, 'hours'));
163
+ blocks.push(renderSeparator(2));
164
+ }
165
+
166
+ blocks.push(renderBlock(timeLeft.minutes, defaultLabels.minutes, 'minutes'));
167
+ blocks.push(renderSeparator(3));
168
+ blocks.push(renderBlock(timeLeft.seconds, defaultLabels.seconds, 'seconds'));
169
+
170
+ return blocks;
171
+ };
172
+
173
+ return (
174
+ <div
175
+ ref={ref}
176
+ className={containerClasses}
177
+ style={{
178
+ '--accent-color': accentColor,
179
+ ...style
180
+ } as React.CSSProperties}
181
+ {...props}
182
+ >
183
+ {renderBlocks()}
184
+ </div>
185
+ );
186
+ }
187
+ );
188
+
189
+ CountdownDisplay.displayName = 'CountdownDisplay';
190
+ export default CountdownDisplay;
@@ -0,0 +1,155 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useEffect, useState, useRef } from 'react';
4
+
5
+ export interface CountdownDisplayProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
6
+ target: Date | number;
7
+ format?: 'full' | 'hms' | 'ms' | 'dhms';
8
+ size?: 'sm' | 'md' | 'lg';
9
+ variant?: 'default' | 'minimal' | 'neon' | 'brutal' | 'glitch';
10
+ accentColor?: string;
11
+ showLabels?: boolean;
12
+ compact?: boolean;
13
+ flip?: boolean;
14
+ urgentThreshold?: number;
15
+ onComplete?: () => void;
16
+ labels?: { days?: string; hours?: string; minutes?: string; seconds?: string; };
17
+ }
18
+
19
+ interface TimeLeft { days: number; hours: number; minutes: number; seconds: number; }
20
+
21
+ const sizeClasses = {
22
+ sm: { value: 'text-3xl md:text-5xl', separator: 'text-xl md:text-3xl', label: 'text-[0.5rem]' },
23
+ md: { value: 'text-5xl md:text-7xl', separator: 'text-3xl md:text-5xl', label: 'text-[0.6rem]' },
24
+ lg: { value: 'text-7xl md:text-9xl', separator: 'text-5xl md:text-7xl', label: 'text-[0.7rem]' },
25
+ };
26
+
27
+ export const CountdownDisplay = forwardRef<HTMLDivElement, CountdownDisplayProps>(
28
+ (
29
+ {
30
+ target,
31
+ format = 'hms',
32
+ size = 'md',
33
+ variant = 'default',
34
+ accentColor = '#ff0040',
35
+ showLabels = true,
36
+ compact = false,
37
+ flip = false,
38
+ urgentThreshold = 60,
39
+ onComplete,
40
+ labels = {},
41
+ className = '',
42
+ ...props
43
+ },
44
+ ref
45
+ ) => {
46
+ const [timeLeft, setTimeLeft] = useState<TimeLeft>({ days: 0, hours: 0, minutes: 0, seconds: 0 });
47
+ const [isUrgent, setIsUrgent] = useState(false);
48
+ const completedRef = useRef(false);
49
+
50
+ const defaultLabels = {
51
+ days: labels.days || 'DAYS', hours: labels.hours || 'HOURS',
52
+ minutes: labels.minutes || 'MIN', seconds: labels.seconds || 'SEC',
53
+ };
54
+
55
+ useEffect(() => {
56
+ const calculateTimeLeft = (): TimeLeft => {
57
+ let totalSeconds: number;
58
+ if (target instanceof Date) {
59
+ totalSeconds = Math.max(0, Math.floor((target.getTime() - Date.now()) / 1000));
60
+ } else {
61
+ totalSeconds = Math.max(0, target);
62
+ }
63
+ return {
64
+ days: Math.floor(totalSeconds / 86400),
65
+ hours: Math.floor((totalSeconds % 86400) / 3600),
66
+ minutes: Math.floor((totalSeconds % 3600) / 60),
67
+ seconds: totalSeconds % 60,
68
+ };
69
+ };
70
+
71
+ const tick = () => {
72
+ const newTime = calculateTimeLeft();
73
+ const totalSeconds = newTime.days * 86400 + newTime.hours * 3600 + newTime.minutes * 60 + newTime.seconds;
74
+ setTimeLeft(newTime);
75
+ setIsUrgent(totalSeconds <= urgentThreshold && totalSeconds > 0);
76
+ if (totalSeconds === 0 && !completedRef.current) {
77
+ completedRef.current = true;
78
+ onComplete?.();
79
+ }
80
+ };
81
+
82
+ tick();
83
+ const interval = setInterval(tick, 1000);
84
+ return () => clearInterval(interval);
85
+ }, [target, urgentThreshold, onComplete]);
86
+
87
+ const pad = (num: number) => String(num).padStart(2, '0');
88
+ const { value: valueClass, separator: sepClass, label: labelClass } = sizeClasses[size];
89
+
90
+ const getValueStyle = () => {
91
+ switch (variant) {
92
+ case 'neon':
93
+ return { color: accentColor, textShadow: `0 0 10px ${accentColor}, 0 0 20px ${accentColor}, 0 0 40px ${accentColor}` };
94
+ case 'brutal':
95
+ return { background: accentColor, color: '#0a0a0a', padding: '0 0.25em', WebkitTextFillColor: '#0a0a0a' };
96
+ case 'minimal':
97
+ return { color: '#fafafa' };
98
+ default:
99
+ return { background: 'linear-gradient(180deg, #fafafa 0%, #888 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' };
100
+ }
101
+ };
102
+
103
+ const renderBlock = (value: number, label: string) => (
104
+ <div className="flex flex-col items-center relative">
105
+ <span
106
+ className={`${valueClass} font-extrabold leading-none tracking-tight ${isUrgent ? 'animate-pulse' : ''}`}
107
+ style={getValueStyle()}
108
+ >
109
+ {pad(value)}
110
+ </span>
111
+ {showLabels && (
112
+ <span className={`${labelClass} tracking-[0.3em] text-gray-600 mt-2 uppercase font-normal`}>
113
+ {label}
114
+ </span>
115
+ )}
116
+ </div>
117
+ );
118
+
119
+ const renderSeparator = (key: number) => (
120
+ <span
121
+ key={`sep-${key}`}
122
+ className={`${sepClass} font-extrabold text-gray-700 animate-pulse self-start mt-2`}
123
+ style={variant === 'neon' ? { color: accentColor, textShadow: `0 0 10px ${accentColor}` } : undefined}
124
+ >
125
+ :
126
+ </span>
127
+ );
128
+
129
+ const blocks = [];
130
+ if (format === 'dhms' || format === 'full') {
131
+ blocks.push(<div key="days">{renderBlock(timeLeft.days, defaultLabels.days)}</div>);
132
+ blocks.push(renderSeparator(1));
133
+ }
134
+ if (format !== 'ms') {
135
+ blocks.push(<div key="hours">{renderBlock(timeLeft.hours, defaultLabels.hours)}</div>);
136
+ blocks.push(renderSeparator(2));
137
+ }
138
+ blocks.push(<div key="minutes">{renderBlock(timeLeft.minutes, defaultLabels.minutes)}</div>);
139
+ blocks.push(renderSeparator(3));
140
+ blocks.push(<div key="seconds">{renderBlock(timeLeft.seconds, defaultLabels.seconds)}</div>);
141
+
142
+ return (
143
+ <div
144
+ ref={ref}
145
+ className={`flex items-center justify-center ${compact ? 'gap-2' : 'gap-4 md:gap-8'} font-sans ${className}`}
146
+ {...props}
147
+ >
148
+ {blocks}
149
+ </div>
150
+ );
151
+ }
152
+ );
153
+
154
+ CountdownDisplay.displayName = 'CountdownDisplay';
155
+ export default CountdownDisplay;
@@ -0,0 +1,156 @@
1
+ .container {
2
+ position: relative;
3
+ display: inline-block;
4
+ font-weight: 800;
5
+ line-height: 1;
6
+ letter-spacing: -0.02em;
7
+ }
8
+
9
+ .layer {
10
+ display: block;
11
+ position: relative;
12
+ }
13
+
14
+ .layer:not(:first-child) {
15
+ position: absolute;
16
+ top: 0;
17
+ left: 0;
18
+ pointer-events: none;
19
+ }
20
+
21
+ /* Base layer styling */
22
+ .base {
23
+ background: linear-gradient(180deg, #fafafa 0%, #888 100%);
24
+ -webkit-background-clip: text;
25
+ -webkit-text-fill-color: transparent;
26
+ background-clip: text;
27
+ }
28
+
29
+ /* Shadow layers */
30
+ .shadow1 {
31
+ transform: translate(4px, 4px);
32
+ z-index: -1;
33
+ opacity: 0.5;
34
+ background: linear-gradient(180deg, #ff0040 0%, #ff004080 100%);
35
+ -webkit-background-clip: text;
36
+ -webkit-text-fill-color: transparent;
37
+ background-clip: text;
38
+ }
39
+
40
+ .shadow2 {
41
+ transform: translate(8px, 8px);
42
+ z-index: -2;
43
+ opacity: 0.3;
44
+ background: linear-gradient(180deg, #ff0040 0%, #ff004040 100%);
45
+ -webkit-background-clip: text;
46
+ -webkit-text-fill-color: transparent;
47
+ background-clip: text;
48
+ }
49
+
50
+ .shadow3 {
51
+ transform: translate(12px, 12px);
52
+ z-index: -3;
53
+ opacity: 0.15;
54
+ background: linear-gradient(180deg, #ff0040 0%, #ff004020 100%);
55
+ -webkit-background-clip: text;
56
+ -webkit-text-fill-color: transparent;
57
+ background-clip: text;
58
+ }
59
+
60
+ /* Variants */
61
+ .blood .shadow1,
62
+ .blood .shadow2,
63
+ .blood .shadow3 {
64
+ background: linear-gradient(180deg, #ff0040 0%, transparent 100%);
65
+ -webkit-background-clip: text;
66
+ background-clip: text;
67
+ }
68
+
69
+ .cyber .shadow1 { background: linear-gradient(180deg, #00ffff 0%, transparent 100%); -webkit-background-clip: text; background-clip: text; }
70
+ .cyber .shadow2 { background: linear-gradient(180deg, #ff00ff 0%, transparent 100%); -webkit-background-clip: text; background-clip: text; }
71
+ .cyber .shadow3 { background: linear-gradient(180deg, #00ff00 0%, transparent 100%); -webkit-background-clip: text; background-clip: text; }
72
+
73
+ .mono .base,
74
+ .mono .shadow1,
75
+ .mono .shadow2,
76
+ .mono .shadow3 {
77
+ background: none;
78
+ -webkit-text-fill-color: currentColor;
79
+ }
80
+ .mono .shadow1 { color: #666; }
81
+ .mono .shadow2 { color: #444; }
82
+ .mono .shadow3 { color: #222; }
83
+
84
+ .neon .base {
85
+ color: #fff;
86
+ -webkit-text-fill-color: #fff;
87
+ text-shadow: 0 0 10px #ff0040, 0 0 20px #ff0040, 0 0 40px #ff0040;
88
+ }
89
+ .neon .shadow1,
90
+ .neon .shadow2,
91
+ .neon .shadow3 {
92
+ opacity: 0;
93
+ }
94
+
95
+ /* Animated variant */
96
+ .animated .shadow1 {
97
+ animation: layerFloat1 3s ease-in-out infinite;
98
+ }
99
+
100
+ .animated .shadow2 {
101
+ animation: layerFloat2 4s ease-in-out infinite;
102
+ }
103
+
104
+ .animated .shadow3 {
105
+ animation: layerFloat3 5s ease-in-out infinite;
106
+ }
107
+
108
+ @keyframes layerFloat1 {
109
+ 0%, 100% { transform: translate(4px, 4px); }
110
+ 50% { transform: translate(6px, 6px); }
111
+ }
112
+
113
+ @keyframes layerFloat2 {
114
+ 0%, 100% { transform: translate(8px, 8px); }
115
+ 50% { transform: translate(12px, 10px); }
116
+ }
117
+
118
+ @keyframes layerFloat3 {
119
+ 0%, 100% { transform: translate(12px, 12px); }
120
+ 50% { transform: translate(16px, 14px); }
121
+ }
122
+
123
+ /* Hover effect */
124
+ .hover:hover .shadow1 {
125
+ transform: translate(8px, 8px);
126
+ transition: transform 0.3s ease;
127
+ }
128
+
129
+ .hover:hover .shadow2 {
130
+ transform: translate(16px, 16px);
131
+ transition: transform 0.3s ease;
132
+ }
133
+
134
+ .hover:hover .shadow3 {
135
+ transform: translate(24px, 24px);
136
+ transition: transform 0.3s ease;
137
+ }
138
+
139
+ /* Sizes */
140
+ .sm { font-size: clamp(2rem, 8vw, 4rem); }
141
+ .md { font-size: clamp(4rem, 15vw, 8rem); }
142
+ .lg { font-size: clamp(6rem, 20vw, 12rem); }
143
+ .xl { font-size: clamp(8rem, 25vw, 18rem); }
144
+
145
+ /* Offset directions */
146
+ .diagonal .shadow1 { transform: translate(4px, 4px); }
147
+ .diagonal .shadow2 { transform: translate(8px, 8px); }
148
+ .diagonal .shadow3 { transform: translate(12px, 12px); }
149
+
150
+ .horizontal .shadow1 { transform: translateX(4px); }
151
+ .horizontal .shadow2 { transform: translateX(8px); }
152
+ .horizontal .shadow3 { transform: translateX(12px); }
153
+
154
+ .vertical .shadow1 { transform: translateY(4px); }
155
+ .vertical .shadow2 { transform: translateY(8px); }
156
+ .vertical .shadow3 { transform: translateY(12px); }
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes } from 'react';
4
+ import styles from './giant-layers.module.css';
5
+
6
+ export interface GiantLayersProps extends HTMLAttributes<HTMLSpanElement> {
7
+ /** Text to display */
8
+ children: string;
9
+ /** Number of shadow layers (1-3) */
10
+ layers?: 1 | 2 | 3;
11
+ /** Size preset */
12
+ size?: 'sm' | 'md' | 'lg' | 'xl';
13
+ /** Visual variant */
14
+ variant?: 'blood' | 'cyber' | 'mono' | 'neon';
15
+ /** Shadow offset direction */
16
+ direction?: 'diagonal' | 'horizontal' | 'vertical';
17
+ /** Animate layers */
18
+ animated?: boolean;
19
+ /** Expand on hover */
20
+ hover?: boolean;
21
+ /** Custom layer colors */
22
+ layerColors?: [string, string?, string?];
23
+ }
24
+
25
+ export const GiantLayers = forwardRef<HTMLSpanElement, GiantLayersProps>(
26
+ (
27
+ {
28
+ children,
29
+ layers = 3,
30
+ size = 'lg',
31
+ variant = 'blood',
32
+ direction = 'diagonal',
33
+ animated = false,
34
+ hover = false,
35
+ layerColors,
36
+ className,
37
+ style,
38
+ ...props
39
+ },
40
+ ref
41
+ ) => {
42
+ const containerClasses = [
43
+ styles.container,
44
+ styles[size],
45
+ styles[variant],
46
+ styles[direction],
47
+ animated && styles.animated,
48
+ hover && styles.hover,
49
+ className
50
+ ].filter(Boolean).join(' ');
51
+
52
+ const getLayerStyle = (index: number) => {
53
+ if (layerColors && layerColors[index]) {
54
+ return {
55
+ background: `linear-gradient(180deg, ${layerColors[index]} 0%, transparent 100%)`,
56
+ WebkitBackgroundClip: 'text',
57
+ WebkitTextFillColor: 'transparent',
58
+ backgroundClip: 'text',
59
+ };
60
+ }
61
+ return undefined;
62
+ };
63
+
64
+ return (
65
+ <span
66
+ ref={ref}
67
+ className={containerClasses}
68
+ style={style}
69
+ {...props}
70
+ >
71
+ {/* Shadow layers */}
72
+ {layers >= 3 && (
73
+ <span className={`${styles.layer} ${styles.shadow3}`} style={getLayerStyle(2)} aria-hidden>
74
+ {children}
75
+ </span>
76
+ )}
77
+ {layers >= 2 && (
78
+ <span className={`${styles.layer} ${styles.shadow2}`} style={getLayerStyle(1)} aria-hidden>
79
+ {children}
80
+ </span>
81
+ )}
82
+ {layers >= 1 && (
83
+ <span className={`${styles.layer} ${styles.shadow1}`} style={getLayerStyle(0)} aria-hidden>
84
+ {children}
85
+ </span>
86
+ )}
87
+ {/* Base layer */}
88
+ <span className={`${styles.layer} ${styles.base}`}>
89
+ {children}
90
+ </span>
91
+ </span>
92
+ );
93
+ }
94
+ );
95
+
96
+ GiantLayers.displayName = 'GiantLayers';
97
+ export default GiantLayers;
@@ -0,0 +1,111 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes } from 'react';
4
+
5
+ export interface GiantLayersProps extends HTMLAttributes<HTMLSpanElement> {
6
+ children: string;
7
+ layers?: 1 | 2 | 3;
8
+ size?: 'sm' | 'md' | 'lg' | 'xl';
9
+ variant?: 'blood' | 'cyber' | 'mono' | 'neon';
10
+ direction?: 'diagonal' | 'horizontal' | 'vertical';
11
+ animated?: boolean;
12
+ hover?: boolean;
13
+ layerColors?: [string, string?, string?];
14
+ }
15
+
16
+ const sizeClasses = {
17
+ sm: 'text-4xl md:text-6xl',
18
+ md: 'text-6xl md:text-8xl',
19
+ lg: 'text-7xl md:text-9xl',
20
+ xl: 'text-8xl md:text-[12rem]',
21
+ };
22
+
23
+ const variantStyles = {
24
+ blood: {
25
+ base: 'bg-gradient-to-b from-white to-gray-500 bg-clip-text text-transparent',
26
+ shadow: 'text-rose-500',
27
+ },
28
+ cyber: {
29
+ base: 'bg-gradient-to-b from-white to-gray-500 bg-clip-text text-transparent',
30
+ shadow: 'text-cyan-400',
31
+ },
32
+ mono: {
33
+ base: 'text-white',
34
+ shadow: 'text-gray-600',
35
+ },
36
+ neon: {
37
+ base: 'text-white drop-shadow-[0_0_10px_#ff0040] drop-shadow-[0_0_20px_#ff0040]',
38
+ shadow: 'text-transparent',
39
+ },
40
+ };
41
+
42
+ const getOffset = (direction: string, layer: number) => {
43
+ const px = (layer + 1) * 4;
44
+ switch (direction) {
45
+ case 'horizontal': return { transform: `translateX(${px}px)` };
46
+ case 'vertical': return { transform: `translateY(${px}px)` };
47
+ default: return { transform: `translate(${px}px, ${px}px)` };
48
+ }
49
+ };
50
+
51
+ export const GiantLayers = forwardRef<HTMLSpanElement, GiantLayersProps>(
52
+ (
53
+ {
54
+ children,
55
+ layers = 3,
56
+ size = 'lg',
57
+ variant = 'blood',
58
+ direction = 'diagonal',
59
+ animated = false,
60
+ hover = false,
61
+ layerColors,
62
+ className = '',
63
+ ...props
64
+ },
65
+ ref
66
+ ) => {
67
+ const { base, shadow } = variantStyles[variant];
68
+ const opacities = [0.5, 0.3, 0.15];
69
+
70
+ return (
71
+ <span
72
+ ref={ref}
73
+ className={`relative inline-block font-extrabold leading-none tracking-tight ${sizeClasses[size]} ${
74
+ hover ? 'group' : ''
75
+ } ${className}`}
76
+ {...props}
77
+ >
78
+ {/* Shadow layers */}
79
+ {[...Array(layers)].map((_, i) => {
80
+ const layerIndex = layers - 1 - i;
81
+ const colorStyle = layerColors?.[layerIndex]
82
+ ? { color: layerColors[layerIndex] }
83
+ : undefined;
84
+
85
+ return (
86
+ <span
87
+ key={i}
88
+ className={`absolute top-0 left-0 pointer-events-none ${shadow} ${
89
+ animated ? 'animate-pulse' : ''
90
+ } ${hover ? 'transition-transform duration-300 group-hover:translate-x-6 group-hover:translate-y-6' : ''}`}
91
+ style={{
92
+ ...getOffset(direction, layerIndex),
93
+ opacity: opacities[layerIndex],
94
+ zIndex: -(layerIndex + 1),
95
+ ...colorStyle,
96
+ }}
97
+ aria-hidden
98
+ >
99
+ {children}
100
+ </span>
101
+ );
102
+ })}
103
+ {/* Base layer */}
104
+ <span className={`relative ${base}`}>{children}</span>
105
+ </span>
106
+ );
107
+ }
108
+ );
109
+
110
+ GiantLayers.displayName = 'GiantLayers';
111
+ export default GiantLayers;