@oalacea/chaosui 0.5.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 (197) hide show
  1. package/buttons/cta-brutal/css/cta-brutal.module.css +175 -0
  2. package/{components/buttons/cta-brutal → buttons/cta-brutal/css}/index.tsx +25 -3
  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/tailwind/index.tsx +90 -0
  6. package/buttons/deeper-button/css/deeper-button.module.css +141 -0
  7. package/{components/buttons/deeper-button → buttons/deeper-button/css}/index.tsx +14 -3
  8. package/buttons/deeper-button/tailwind/index.tsx +78 -0
  9. package/buttons/dual-choice/css/dual-choice.module.css +176 -0
  10. package/{components/buttons/dual-choice → buttons/dual-choice/css}/index.tsx +2 -0
  11. package/buttons/dual-choice/tailwind/index.tsx +89 -0
  12. package/{components/buttons/tension-bar → buttons/tension-bar/css}/index.tsx +22 -5
  13. package/buttons/tension-bar/css/tension-bar.module.css +204 -0
  14. package/buttons/tension-bar/tailwind/index.tsx +122 -0
  15. package/decorative/inscription/css/index.tsx +66 -0
  16. package/decorative/inscription/css/inscription.module.css +160 -0
  17. package/decorative/inscription/tailwind/index.tsx +91 -0
  18. package/decorative/ornaments/css/index.tsx +136 -0
  19. package/decorative/ornaments/css/ornaments.module.css +144 -0
  20. package/decorative/ornaments/tailwind/index.tsx +122 -0
  21. package/decorative/rune-symbols/css/index.tsx +116 -0
  22. package/decorative/rune-symbols/css/rune-symbols.module.css +116 -0
  23. package/decorative/rune-symbols/tailwind/index.tsx +108 -0
  24. package/decorative/sheet-music/css/index.tsx +144 -0
  25. package/decorative/sheet-music/css/sheet-music.module.css +152 -0
  26. package/decorative/sheet-music/tailwind/index.tsx +111 -0
  27. package/layout/horizontal-scroll/css/horizontal-scroll.module.css +93 -0
  28. package/{components/layout/horizontal-scroll → layout/horizontal-scroll/css}/index.tsx +41 -5
  29. package/layout/horizontal-scroll/tailwind/index.tsx +108 -0
  30. package/layout/spec-grid/css/index.tsx +103 -0
  31. package/layout/spec-grid/css/spec-grid.module.css +125 -0
  32. package/layout/spec-grid/tailwind/index.tsx +91 -0
  33. package/{components/layout/tower-pricing → layout/tower-pricing/css}/index.tsx +43 -8
  34. package/layout/tower-pricing/css/tower-pricing.module.css +177 -0
  35. package/layout/tower-pricing/tailwind/index.tsx +106 -0
  36. package/layout/tracklist/css/index.tsx +96 -0
  37. package/layout/tracklist/css/tracklist.module.css +141 -0
  38. package/layout/tracklist/tailwind/index.tsx +92 -0
  39. package/{components/layout/void-frame → layout/void-frame/css}/index.tsx +31 -3
  40. package/layout/void-frame/css/void-frame.module.css +141 -0
  41. package/layout/void-frame/tailwind/index.tsx +64 -0
  42. package/navigation/brutal-nav/css/brutal-nav.module.css +178 -0
  43. package/{components/navigation/brutal-nav → navigation/brutal-nav/css}/index.tsx +12 -3
  44. package/navigation/brutal-nav/tailwind/index.tsx +95 -0
  45. package/{components/navigation/progress-dots → navigation/progress-dots/css}/index.tsx +7 -2
  46. package/{components/navigation/progress-dots → navigation/progress-dots/css}/progress-dots.module.css +74 -13
  47. package/navigation/progress-dots/tailwind/index.tsx +105 -0
  48. package/{components/navigation/scattered-nav → navigation/scattered-nav/css}/scattered-nav.module.css +9 -1
  49. package/navigation/scattered-nav/tailwind/index.tsx +77 -0
  50. package/{components/navigation/scroll-indicator → navigation/scroll-indicator/css}/index.tsx +18 -4
  51. package/{components/navigation/scroll-indicator → navigation/scroll-indicator/css}/scroll-indicator.module.css +57 -10
  52. package/navigation/scroll-indicator/tailwind/index.tsx +107 -0
  53. package/{components/navigation/vertical-nav → navigation/vertical-nav/css}/index.tsx +13 -3
  54. package/{components/navigation/vertical-nav → navigation/vertical-nav/css}/vertical-nav.module.css +24 -5
  55. package/navigation/vertical-nav/tailwind/index.tsx +91 -0
  56. package/package.json +8 -33
  57. package/bin/cli.js +0 -308
  58. package/components/backgrounds/light-beams/index.tsx +0 -80
  59. package/components/backgrounds/light-beams/light-beams.module.css +0 -27
  60. package/components/buttons/cta-brutal/cta-brutal.module.css +0 -81
  61. package/components/buttons/dead-button/dead-button.module.css +0 -111
  62. package/components/buttons/deeper-button/deeper-button.module.css +0 -76
  63. package/components/buttons/dual-choice/dual-choice.module.css +0 -90
  64. package/components/buttons/tension-bar/tension-bar.module.css +0 -105
  65. package/components/decorative/coffee-stain/coffee-stain.module.css +0 -24
  66. package/components/decorative/coffee-stain/index.tsx +0 -55
  67. package/components/decorative/ornaments/index.tsx +0 -51
  68. package/components/decorative/ornaments/ornaments.module.css +0 -33
  69. package/components/decorative/rune-symbols/index.tsx +0 -55
  70. package/components/decorative/rune-symbols/rune-symbols.module.css +0 -22
  71. package/components/layout/horizontal-scroll/horizontal-scroll.module.css +0 -30
  72. package/components/layout/spec-grid/index.tsx +0 -56
  73. package/components/layout/spec-grid/spec-grid.module.css +0 -21
  74. package/components/layout/tower-pricing/tower-pricing.module.css +0 -27
  75. package/components/layout/tracklist/index.tsx +0 -45
  76. package/components/layout/tracklist/tracklist.module.css +0 -24
  77. package/components/layout/void-frame/void-frame.module.css +0 -38
  78. package/components/navigation/brutal-nav/brutal-nav.module.css +0 -85
  79. package/components/navigation/hexagon-menu/css/hexagon-menu.module.css +0 -55
  80. package/components/navigation/hexagon-menu/css/index.tsx +0 -35
  81. package/components/navigation/hexagon-menu/tailwind/index.tsx +0 -53
  82. /package/{components/backgrounds → backgrounds}/glow-orbs/glow-orbs.module.css +0 -0
  83. /package/{components/backgrounds → backgrounds}/glow-orbs/index.tsx +0 -0
  84. /package/{components/backgrounds → backgrounds}/noise-canvas/index.tsx +0 -0
  85. /package/{components/backgrounds → backgrounds}/noise-canvas/noise-canvas.module.css +0 -0
  86. /package/{components/backgrounds → backgrounds}/particle-field/index.tsx +0 -0
  87. /package/{components/backgrounds → backgrounds}/particle-field/particle-field.module.css +0 -0
  88. /package/{components/buttons → buttons}/chaos-button/chaos-button.module.css +0 -0
  89. /package/{components/buttons → buttons}/chaos-button/index.tsx +0 -0
  90. /package/{components/buttons/dead-button → buttons/dead-button/css}/index.tsx +0 -0
  91. /package/{components/buttons → buttons}/glitch-button/glitch-button.module.css +0 -0
  92. /package/{components/buttons → buttons}/glitch-button/index.tsx +0 -0
  93. /package/{components/chaos-vars.css → chaos-vars.css} +0 -0
  94. /package/{components/cyber → cyber}/cyber-avatar/css/cyber-avatar.module.css +0 -0
  95. /package/{components/cyber → cyber}/cyber-avatar/css/index.tsx +0 -0
  96. /package/{components/cyber → cyber}/cyber-avatar/tailwind/index.tsx +0 -0
  97. /package/{components/cyber → cyber}/cyber-input/css/cyber-input.module.css +0 -0
  98. /package/{components/cyber → cyber}/cyber-input/css/index.tsx +0 -0
  99. /package/{components/cyber → cyber}/cyber-input/tailwind/index.tsx +0 -0
  100. /package/{components/cyber → cyber}/cyber-loader/css/cyber-loader.module.css +0 -0
  101. /package/{components/cyber → cyber}/cyber-loader/css/index.tsx +0 -0
  102. /package/{components/cyber → cyber}/cyber-loader/tailwind/index.tsx +0 -0
  103. /package/{components/cyber → cyber}/cyber-modal/css/cyber-modal.module.css +0 -0
  104. /package/{components/cyber → cyber}/cyber-modal/css/index.tsx +0 -0
  105. /package/{components/cyber → cyber}/cyber-modal/tailwind/index.tsx +0 -0
  106. /package/{components/cyber → cyber}/cyber-slider/css/cyber-slider.module.css +0 -0
  107. /package/{components/cyber → cyber}/cyber-slider/css/index.tsx +0 -0
  108. /package/{components/cyber → cyber}/cyber-slider/tailwind/index.tsx +0 -0
  109. /package/{components/cyber → cyber}/cyber-tooltip/css/cyber-tooltip.module.css +0 -0
  110. /package/{components/cyber → cyber}/cyber-tooltip/css/index.tsx +0 -0
  111. /package/{components/cyber → cyber}/cyber-tooltip/tailwind/index.tsx +0 -0
  112. /package/{components/effects → effects}/cursor-follower/cursor-follower.module.css +0 -0
  113. /package/{components/effects → effects}/cursor-follower/index.tsx +0 -0
  114. /package/{components/effects → effects}/glitch-image/css/glitch-image.module.css +0 -0
  115. /package/{components/effects → effects}/glitch-image/css/index.tsx +0 -0
  116. /package/{components/effects → effects}/glitch-image/tailwind/index.tsx +0 -0
  117. /package/{components/effects → effects}/glowing-border/css/glowing-border.module.css +0 -0
  118. /package/{components/effects → effects}/glowing-border/css/index.tsx +0 -0
  119. /package/{components/effects → effects}/glowing-border/tailwind/index.tsx +0 -0
  120. /package/{components/effects → effects}/screen-distortion/index.tsx +0 -0
  121. /package/{components/effects → effects}/screen-distortion/screen-distortion.module.css +0 -0
  122. /package/{components/effects → effects}/warning-tape/index.tsx +0 -0
  123. /package/{components/effects → effects}/warning-tape/warning-tape.module.css +0 -0
  124. /package/{components/layout → layout}/data-grid/css/data-grid.module.css +0 -0
  125. /package/{components/layout → layout}/data-grid/css/index.tsx +0 -0
  126. /package/{components/layout → layout}/data-grid/tailwind/index.tsx +0 -0
  127. /package/{components/layout → layout}/hologram-card/css/hologram-card.module.css +0 -0
  128. /package/{components/layout → layout}/hologram-card/css/index.tsx +0 -0
  129. /package/{components/layout → layout}/hologram-card/tailwind/index.tsx +0 -0
  130. /package/{components/navigation/scattered-nav → navigation/scattered-nav/css}/index.tsx +0 -0
  131. /package/{components/neon → neon}/neon-alert/css/index.tsx +0 -0
  132. /package/{components/neon → neon}/neon-alert/css/neon-alert.module.css +0 -0
  133. /package/{components/neon → neon}/neon-alert/tailwind/index.tsx +0 -0
  134. /package/{components/neon → neon}/neon-badge/css/index.tsx +0 -0
  135. /package/{components/neon → neon}/neon-badge/css/neon-badge.module.css +0 -0
  136. /package/{components/neon → neon}/neon-badge/tailwind/index.tsx +0 -0
  137. /package/{components/neon → neon}/neon-button/css/index.tsx +0 -0
  138. /package/{components/neon → neon}/neon-button/css/neon-button.module.css +0 -0
  139. /package/{components/neon → neon}/neon-button/tailwind/index.tsx +0 -0
  140. /package/{components/neon → neon}/neon-divider/css/index.tsx +0 -0
  141. /package/{components/neon → neon}/neon-divider/css/neon-divider.module.css +0 -0
  142. /package/{components/neon → neon}/neon-divider/tailwind/index.tsx +0 -0
  143. /package/{components/neon → neon}/neon-progress/css/index.tsx +0 -0
  144. /package/{components/neon → neon}/neon-progress/css/neon-progress.module.css +0 -0
  145. /package/{components/neon → neon}/neon-progress/tailwind/index.tsx +0 -0
  146. /package/{components/neon → neon}/neon-tabs/css/index.tsx +0 -0
  147. /package/{components/neon → neon}/neon-tabs/css/neon-tabs.module.css +0 -0
  148. /package/{components/neon → neon}/neon-tabs/tailwind/index.tsx +0 -0
  149. /package/{components/neon → neon}/neon-toggle/css/index.tsx +0 -0
  150. /package/{components/neon → neon}/neon-toggle/css/neon-toggle.module.css +0 -0
  151. /package/{components/neon → neon}/neon-toggle/tailwind/index.tsx +0 -0
  152. /package/{components/overlays → overlays}/noise-overlay/index.tsx +0 -0
  153. /package/{components/overlays → overlays}/noise-overlay/noise-overlay.module.css +0 -0
  154. /package/{components/overlays → overlays}/scanlines/index.tsx +0 -0
  155. /package/{components/overlays → overlays}/scanlines/scanlines.module.css +0 -0
  156. /package/{components/overlays → overlays}/static-flicker/index.tsx +0 -0
  157. /package/{components/overlays → overlays}/static-flicker/static-flicker.module.css +0 -0
  158. /package/{components/overlays → overlays}/vignette/index.tsx +0 -0
  159. /package/{components/overlays → overlays}/vignette/vignette.module.css +0 -0
  160. /package/{components/text → text}/ascii-art/css/ascii-art.module.css +0 -0
  161. /package/{components/text → text}/ascii-art/css/index.tsx +0 -0
  162. /package/{components/text → text}/ascii-art/tailwind/index.tsx +0 -0
  163. /package/{components/text → text}/blood-drip/css/blood-drip.module.css +0 -0
  164. /package/{components/text → text}/blood-drip/css/index.tsx +0 -0
  165. /package/{components/text → text}/blood-drip/tailwind/index.tsx +0 -0
  166. /package/{components/text → text}/char-glitch/css/char-glitch.module.css +0 -0
  167. /package/{components/text → text}/char-glitch/css/index.tsx +0 -0
  168. /package/{components/text → text}/char-glitch/tailwind/index.tsx +0 -0
  169. /package/{components/text → text}/countdown-display/css/countdown-display.module.css +0 -0
  170. /package/{components/text → text}/countdown-display/css/index.tsx +0 -0
  171. /package/{components/text → text}/countdown-display/tailwind/index.tsx +0 -0
  172. /package/{components/text → text}/distortion-text/distortion-text.module.css +0 -0
  173. /package/{components/text → text}/distortion-text/index.tsx +0 -0
  174. /package/{components/text → text}/falling-text/falling-text.module.css +0 -0
  175. /package/{components/text → text}/falling-text/index.tsx +0 -0
  176. /package/{components/text → text}/flicker-text/flicker-text.module.css +0 -0
  177. /package/{components/text → text}/flicker-text/index.tsx +0 -0
  178. /package/{components/text → text}/giant-layers/css/giant-layers.module.css +0 -0
  179. /package/{components/text → text}/giant-layers/css/index.tsx +0 -0
  180. /package/{components/text → text}/giant-layers/tailwind/index.tsx +0 -0
  181. /package/{components/text → text}/glitch-text/glitch-text.module.css +0 -0
  182. /package/{components/text → text}/glitch-text/index.tsx +0 -0
  183. /package/{components/text → text}/reveal-text/css/index.tsx +0 -0
  184. /package/{components/text → text}/reveal-text/css/reveal-text.module.css +0 -0
  185. /package/{components/text → text}/reveal-text/tailwind/index.tsx +0 -0
  186. /package/{components/text → text}/rotate-text/css/index.tsx +0 -0
  187. /package/{components/text → text}/rotate-text/css/rotate-text.module.css +0 -0
  188. /package/{components/text → text}/rotate-text/tailwind/index.tsx +0 -0
  189. /package/{components/text → text}/strike-reveal/css/index.tsx +0 -0
  190. /package/{components/text → text}/strike-reveal/css/strike-reveal.module.css +0 -0
  191. /package/{components/text → text}/strike-reveal/tailwind/index.tsx +0 -0
  192. /package/{components/text → text}/terminal-output/css/index.tsx +0 -0
  193. /package/{components/text → text}/terminal-output/css/terminal-output.module.css +0 -0
  194. /package/{components/text → text}/terminal-output/tailwind/index.tsx +0 -0
  195. /package/{components/text → text}/typing-text/css/index.tsx +0 -0
  196. /package/{components/text → text}/typing-text/css/typing-text.module.css +0 -0
  197. /package/{components/text → text}/typing-text/tailwind/index.tsx +0 -0
@@ -0,0 +1,108 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes } from 'react';
4
+
5
+ export const RUNES = {
6
+ fehu: 'ᚠ', uruz: 'ᚢ', thurisaz: 'ᚦ', ansuz: 'ᚨ', raidho: 'ᚱ', kenaz: 'ᚲ',
7
+ gebo: 'ᚷ', wunjo: 'ᚹ', hagalaz: 'ᚺ', nauthiz: 'ᚾ', isaz: 'ᛁ', jera: 'ᛃ',
8
+ eihwaz: 'ᛇ', perthro: 'ᛈ', algiz: 'ᛉ', sowilo: 'ᛊ', tiwaz: 'ᛏ', berkano: 'ᛒ',
9
+ ehwaz: 'ᛖ', mannaz: 'ᛗ', laguz: 'ᛚ', ingwaz: 'ᛝ', dagaz: 'ᛞ', othala: 'ᛟ',
10
+ };
11
+
12
+ export interface RuneSymbolsProps extends HTMLAttributes<HTMLDivElement> {
13
+ runes?: (keyof typeof RUNES | string)[];
14
+ count?: number;
15
+ variant?: 'gold' | 'blood' | 'bone' | 'iron' | 'cyan';
16
+ animation?: 'glow' | 'floating' | 'pulsing' | 'flickering' | 'none';
17
+ direction?: 'horizontal' | 'vertical';
18
+ size?: 'sm' | 'md' | 'lg' | 'xl';
19
+ scattered?: boolean;
20
+ }
21
+
22
+ const variantStyles = {
23
+ gold: 'text-amber-600',
24
+ blood: 'text-red-800',
25
+ bone: 'text-stone-400',
26
+ iron: 'text-zinc-500',
27
+ cyan: 'text-cyan-400',
28
+ };
29
+
30
+ const sizeClasses = {
31
+ sm: 'text-base gap-3',
32
+ md: 'text-3xl gap-6',
33
+ lg: 'text-5xl gap-8',
34
+ xl: 'text-6xl gap-12',
35
+ };
36
+
37
+ const getRandomRunes = (count: number): string[] => {
38
+ const runeValues = Object.values(RUNES);
39
+ return Array.from({ length: count }, () => runeValues[Math.floor(Math.random() * runeValues.length)]);
40
+ };
41
+
42
+ export const RuneSymbols = forwardRef<HTMLDivElement, RuneSymbolsProps>(
43
+ ({ runes, count = 6, variant = 'gold', animation = 'glow', direction = 'horizontal', size = 'md', scattered = false, className = '', style, ...props }, ref) => {
44
+ const resolvedRunes = runes ? runes.map(r => RUNES[r as keyof typeof RUNES] || r) : getRandomRunes(count);
45
+
46
+ const animationClass = {
47
+ glow: 'animate-[runeGlow_4s_ease-in-out_infinite]',
48
+ floating: 'animate-[runeFloat_6s_ease-in-out_infinite]',
49
+ pulsing: 'animate-[runePulse_2s_ease-in-out_infinite]',
50
+ flickering: 'animate-[runeFlicker_0.5s_steps(2)_infinite]',
51
+ none: '',
52
+ }[animation];
53
+
54
+ return (
55
+ <div
56
+ ref={ref}
57
+ className={`
58
+ flex ${sizeClasses[size]}
59
+ ${direction === 'vertical' ? 'flex-col' : ''}
60
+ ${scattered ? 'relative w-full h-full' : ''}
61
+ ${className}
62
+ `}
63
+ style={style}
64
+ {...props}
65
+ >
66
+ <style>{`
67
+ @keyframes runeGlow {
68
+ 0%, 100% { opacity: 0.4; text-shadow: none; }
69
+ 50% { opacity: 0.8; text-shadow: 0 0 15px currentColor; }
70
+ }
71
+ @keyframes runeFloat {
72
+ 0%, 100% { transform: translateY(0) rotate(0deg); opacity: 0.4; }
73
+ 25% { transform: translateY(-10px) rotate(5deg); opacity: 0.7; }
74
+ 50% { transform: translateY(0) rotate(0deg); opacity: 0.5; }
75
+ 75% { transform: translateY(10px) rotate(-5deg); opacity: 0.7; }
76
+ }
77
+ @keyframes runePulse {
78
+ 0%, 100% { transform: scale(1); opacity: 0.5; }
79
+ 50% { transform: scale(1.15); opacity: 1; text-shadow: 0 0 25px currentColor; }
80
+ }
81
+ @keyframes runeFlicker {
82
+ 0% { opacity: 0.3; } 25% { opacity: 0.8; } 50% { opacity: 0.5; } 75% { opacity: 0.9; } 100% { opacity: 0.3; }
83
+ }
84
+ `}</style>
85
+ {resolvedRunes.map((rune, i) => (
86
+ <span
87
+ key={i}
88
+ className={`
89
+ ${variantStyles[variant]} opacity-60 transition-all duration-400
90
+ hover:opacity-100 hover:scale-110 hover:[text-shadow:0_0_20px_currentColor]
91
+ ${animationClass}
92
+ ${scattered ? 'absolute' : ''}
93
+ `}
94
+ style={{
95
+ animationDelay: `${i * 0.5}s`,
96
+ ...(scattered ? { left: `${Math.random() * 80 + 10}%`, top: `${Math.random() * 80 + 10}%` } : {}),
97
+ }}
98
+ >
99
+ {rune}
100
+ </span>
101
+ ))}
102
+ </div>
103
+ );
104
+ }
105
+ );
106
+
107
+ RuneSymbols.displayName = 'RuneSymbols';
108
+ export default RuneSymbols;
@@ -0,0 +1,144 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useMemo } from 'react';
4
+ import styles from './sheet-music.module.css';
5
+
6
+ export const MUSIC_SYMBOLS = {
7
+ quarterNote: '♩',
8
+ eighthNote: '♪',
9
+ beamedNotes: '♫',
10
+ sixteenthNotes: '♬',
11
+ trebleClef: '𝄞',
12
+ bassClef: '𝄢',
13
+ sharp: '♯',
14
+ flat: '♭',
15
+ natural: '♮',
16
+ fermata: '𝄐',
17
+ coda: '𝄌',
18
+ segno: '𝄋',
19
+ };
20
+
21
+ export interface NoteConfig {
22
+ symbol: keyof typeof MUSIC_SYMBOLS | string;
23
+ position: { top: string; left: string };
24
+ size?: 'sm' | 'md' | 'lg' | 'xl';
25
+ delay?: number;
26
+ }
27
+
28
+ export interface SheetMusicProps extends HTMLAttributes<HTMLDivElement> {
29
+ /** Positioning mode */
30
+ mode?: 'overlay' | 'inline';
31
+ /** Animation style */
32
+ animation?: 'drift' | 'falling' | 'swirling' | 'rising' | 'none';
33
+ /** Color variant */
34
+ variant?: 'ash' | 'silver' | 'gold' | 'blood' | 'ivory';
35
+ /** Note density */
36
+ density?: 'sparse' | 'normal' | 'dense';
37
+ /** Number of random notes */
38
+ count?: number;
39
+ /** Custom note configurations */
40
+ notes?: NoteConfig[];
41
+ /** Show decorative staff lines */
42
+ showStaff?: boolean;
43
+ /** Symbols to use for random generation */
44
+ symbols?: (keyof typeof MUSIC_SYMBOLS)[];
45
+ }
46
+
47
+ const generateRandomNotes = (
48
+ count: number,
49
+ symbols: (keyof typeof MUSIC_SYMBOLS)[]
50
+ ): NoteConfig[] => {
51
+ const sizes: ('sm' | 'md' | 'lg' | 'xl')[] = ['sm', 'md', 'lg', 'xl'];
52
+ return Array.from({ length: count }, (_, i) => ({
53
+ symbol: symbols[Math.floor(Math.random() * symbols.length)],
54
+ position: {
55
+ top: `${Math.random() * 80 + 10}%`,
56
+ left: `${Math.random() * 90 + 5}%`,
57
+ },
58
+ size: sizes[Math.floor(Math.random() * sizes.length)],
59
+ delay: i * (Math.random() * 3 + 1),
60
+ }));
61
+ };
62
+
63
+ const sizeClasses = {
64
+ sm: styles.note4,
65
+ md: styles.note1,
66
+ lg: styles.note2,
67
+ xl: styles.note5,
68
+ };
69
+
70
+ export const SheetMusic = forwardRef<HTMLDivElement, SheetMusicProps>(
71
+ (
72
+ {
73
+ mode = 'overlay',
74
+ animation = 'drift',
75
+ variant = 'ash',
76
+ density = 'normal',
77
+ count = 8,
78
+ notes: customNotes,
79
+ showStaff = false,
80
+ symbols = ['quarterNote', 'eighthNote', 'beamedNotes', 'sixteenthNotes', 'trebleClef', 'bassClef'],
81
+ className,
82
+ ...props
83
+ },
84
+ ref
85
+ ) => {
86
+ const notes = useMemo(() =>
87
+ customNotes || generateRandomNotes(count, symbols),
88
+ [customNotes, count, symbols]
89
+ );
90
+
91
+ const animationClass = {
92
+ drift: '',
93
+ falling: styles.falling,
94
+ swirling: styles.swirling,
95
+ rising: styles.rising,
96
+ none: '',
97
+ }[animation];
98
+
99
+ return (
100
+ <div
101
+ ref={ref}
102
+ className={`
103
+ ${styles.container}
104
+ ${mode === 'inline' ? styles.inline : ''}
105
+ ${styles[variant]}
106
+ ${styles[density]}
107
+ ${animationClass}
108
+ ${className || ''}
109
+ `}
110
+ {...props}
111
+ >
112
+ {showStaff && (
113
+ <>
114
+ <div className={`${styles.staff} ${styles.staffTop}`}>
115
+ {[...Array(5)].map((_, i) => <div key={i} className={styles.staffLine} />)}
116
+ </div>
117
+ <div className={`${styles.staff} ${styles.staffBottom}`}>
118
+ {[...Array(5)].map((_, i) => <div key={i} className={styles.staffLine} />)}
119
+ </div>
120
+ </>
121
+ )}
122
+
123
+ {notes.map((note, i) => {
124
+ const symbol = MUSIC_SYMBOLS[note.symbol as keyof typeof MUSIC_SYMBOLS] || note.symbol;
125
+ return (
126
+ <span
127
+ key={i}
128
+ className={`${styles.note} ${sizeClasses[note.size || 'md']}`}
129
+ style={{
130
+ ...note.position,
131
+ animationDelay: `${note.delay || i * 2}s`,
132
+ }}
133
+ >
134
+ {symbol}
135
+ </span>
136
+ );
137
+ })}
138
+ </div>
139
+ );
140
+ }
141
+ );
142
+
143
+ SheetMusic.displayName = 'SheetMusic';
144
+ export default SheetMusic;
@@ -0,0 +1,152 @@
1
+ .container {
2
+ --note-color: #2a2a2a;
3
+
4
+ position: fixed;
5
+ inset: 0;
6
+ pointer-events: none;
7
+ overflow: hidden;
8
+ z-index: 1;
9
+ }
10
+
11
+ .inline {
12
+ position: relative;
13
+ width: 100%;
14
+ height: 100%;
15
+ }
16
+
17
+ .note {
18
+ position: absolute;
19
+ color: var(--note-color);
20
+ opacity: 0.3;
21
+ animation: noteDrift 20s linear infinite;
22
+ }
23
+
24
+ /* Note variations */
25
+ .note1 { font-size: 1.5rem; }
26
+ .note2 { font-size: 2rem; }
27
+ .note3 { font-size: 1.8rem; }
28
+ .note4 { font-size: 1.2rem; }
29
+ .note5 { font-size: 3rem; }
30
+ .note6 { font-size: 2.5rem; }
31
+
32
+ @keyframes noteDrift {
33
+ 0%, 100% {
34
+ transform: translateY(0) rotate(0deg);
35
+ opacity: 0.3;
36
+ }
37
+ 25% {
38
+ transform: translateY(-20px) rotate(5deg);
39
+ opacity: 0.5;
40
+ }
41
+ 50% {
42
+ transform: translateY(0) rotate(0deg);
43
+ opacity: 0.3;
44
+ }
45
+ 75% {
46
+ transform: translateY(20px) rotate(-5deg);
47
+ opacity: 0.5;
48
+ }
49
+ }
50
+
51
+ /* Falling animation */
52
+ .falling .note {
53
+ animation: noteFall 15s linear infinite;
54
+ }
55
+
56
+ @keyframes noteFall {
57
+ 0% {
58
+ transform: translateY(-100%) rotate(0deg);
59
+ opacity: 0;
60
+ }
61
+ 10% {
62
+ opacity: 0.4;
63
+ }
64
+ 90% {
65
+ opacity: 0.4;
66
+ }
67
+ 100% {
68
+ transform: translateY(100vh) rotate(360deg);
69
+ opacity: 0;
70
+ }
71
+ }
72
+
73
+ /* Swirling animation */
74
+ .swirling .note {
75
+ animation: noteSwirl 25s ease-in-out infinite;
76
+ }
77
+
78
+ @keyframes noteSwirl {
79
+ 0%, 100% {
80
+ transform: translate(0, 0) rotate(0deg) scale(1);
81
+ opacity: 0.2;
82
+ }
83
+ 25% {
84
+ transform: translate(30px, -40px) rotate(90deg) scale(1.1);
85
+ opacity: 0.5;
86
+ }
87
+ 50% {
88
+ transform: translate(0, 0) rotate(180deg) scale(1);
89
+ opacity: 0.3;
90
+ }
91
+ 75% {
92
+ transform: translate(-30px, 40px) rotate(270deg) scale(0.9);
93
+ opacity: 0.5;
94
+ }
95
+ }
96
+
97
+ /* Rising animation */
98
+ .rising .note {
99
+ animation: noteRise 20s ease-in-out infinite;
100
+ }
101
+
102
+ @keyframes noteRise {
103
+ 0% {
104
+ transform: translateY(100vh) scale(0.5);
105
+ opacity: 0;
106
+ }
107
+ 20% {
108
+ opacity: 0.4;
109
+ }
110
+ 80% {
111
+ opacity: 0.4;
112
+ }
113
+ 100% {
114
+ transform: translateY(-100%) scale(1.5);
115
+ opacity: 0;
116
+ }
117
+ }
118
+
119
+ /* Staff lines */
120
+ .staff {
121
+ position: absolute;
122
+ width: 100%;
123
+ display: flex;
124
+ flex-direction: column;
125
+ gap: 8px;
126
+ opacity: 0.1;
127
+ }
128
+
129
+ .staffLine {
130
+ height: 1px;
131
+ background: var(--note-color);
132
+ }
133
+
134
+ .staffTop {
135
+ top: 15%;
136
+ }
137
+
138
+ .staffBottom {
139
+ bottom: 15%;
140
+ }
141
+
142
+ /* Variants */
143
+ .ash { --note-color: #2a2a2a; }
144
+ .silver { --note-color: #888888; }
145
+ .gold { --note-color: #c9a227; }
146
+ .blood { --note-color: #6b1010; }
147
+ .ivory { --note-color: #e8e8e8; }
148
+
149
+ /* Density */
150
+ .sparse .note { animation-duration: 30s; }
151
+ .normal .note { animation-duration: 20s; }
152
+ .dense .note { animation-duration: 12s; }
@@ -0,0 +1,111 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useMemo } from 'react';
4
+
5
+ export const MUSIC_SYMBOLS = {
6
+ quarterNote: '♩', eighthNote: '♪', beamedNotes: '♫', sixteenthNotes: '♬',
7
+ trebleClef: '𝄞', bassClef: '𝄢', sharp: '♯', flat: '♭', natural: '♮',
8
+ fermata: '𝄐', coda: '𝄌', segno: '𝄋',
9
+ };
10
+
11
+ export interface NoteConfig {
12
+ symbol: keyof typeof MUSIC_SYMBOLS | string;
13
+ position: { top: string; left: string };
14
+ size?: 'sm' | 'md' | 'lg' | 'xl';
15
+ delay?: number;
16
+ }
17
+
18
+ export interface SheetMusicProps extends HTMLAttributes<HTMLDivElement> {
19
+ mode?: 'overlay' | 'inline';
20
+ animation?: 'drift' | 'falling' | 'swirling' | 'rising' | 'none';
21
+ variant?: 'ash' | 'silver' | 'gold' | 'blood' | 'ivory';
22
+ density?: 'sparse' | 'normal' | 'dense';
23
+ count?: number;
24
+ notes?: NoteConfig[];
25
+ showStaff?: boolean;
26
+ symbols?: (keyof typeof MUSIC_SYMBOLS)[];
27
+ }
28
+
29
+ const variantColors = {
30
+ ash: 'text-zinc-800',
31
+ silver: 'text-gray-400',
32
+ gold: 'text-amber-600',
33
+ blood: 'text-red-900',
34
+ ivory: 'text-stone-200',
35
+ };
36
+
37
+ const sizeClasses = { sm: 'text-lg', md: 'text-2xl', lg: 'text-3xl', xl: 'text-5xl' };
38
+ const densityDurations = { sparse: '30s', normal: '20s', dense: '12s' };
39
+
40
+ const generateRandomNotes = (count: number, symbols: (keyof typeof MUSIC_SYMBOLS)[]): NoteConfig[] => {
41
+ const sizes: ('sm' | 'md' | 'lg' | 'xl')[] = ['sm', 'md', 'lg', 'xl'];
42
+ return Array.from({ length: count }, (_, i) => ({
43
+ symbol: symbols[Math.floor(Math.random() * symbols.length)],
44
+ position: { top: `${Math.random() * 80 + 10}%`, left: `${Math.random() * 90 + 5}%` },
45
+ size: sizes[Math.floor(Math.random() * sizes.length)],
46
+ delay: i * (Math.random() * 3 + 1),
47
+ }));
48
+ };
49
+
50
+ export const SheetMusic = forwardRef<HTMLDivElement, SheetMusicProps>(
51
+ ({ mode = 'overlay', animation = 'drift', variant = 'ash', density = 'normal', count = 8, notes: customNotes, showStaff = false, symbols = ['quarterNote', 'eighthNote', 'beamedNotes', 'sixteenthNotes', 'trebleClef', 'bassClef'], className = '', ...props }, ref) => {
52
+ const notes = useMemo(() => customNotes || generateRandomNotes(count, symbols), [customNotes, count, symbols]);
53
+ const duration = densityDurations[density];
54
+
55
+ const animationKeyframes = {
56
+ drift: `@keyframes noteDrift { 0%, 100% { transform: translateY(0) rotate(0deg); opacity: 0.3; } 25% { transform: translateY(-20px) rotate(5deg); opacity: 0.5; } 50% { transform: translateY(0) rotate(0deg); opacity: 0.3; } 75% { transform: translateY(20px) rotate(-5deg); opacity: 0.5; } }`,
57
+ falling: `@keyframes noteFall { 0% { transform: translateY(-100%) rotate(0deg); opacity: 0; } 10%, 90% { opacity: 0.4; } 100% { transform: translateY(100vh) rotate(360deg); opacity: 0; } }`,
58
+ swirling: `@keyframes noteSwirl { 0%, 100% { transform: translate(0, 0) rotate(0deg) scale(1); opacity: 0.2; } 25% { transform: translate(30px, -40px) rotate(90deg) scale(1.1); opacity: 0.5; } 50% { transform: translate(0, 0) rotate(180deg) scale(1); opacity: 0.3; } 75% { transform: translate(-30px, 40px) rotate(270deg) scale(0.9); opacity: 0.5; } }`,
59
+ rising: `@keyframes noteRise { 0% { transform: translateY(100vh) scale(0.5); opacity: 0; } 20%, 80% { opacity: 0.4; } 100% { transform: translateY(-100%) scale(1.5); opacity: 0; } }`,
60
+ none: '',
61
+ };
62
+
63
+ const animationNames = { drift: 'noteDrift', falling: 'noteFall', swirling: 'noteSwirl', rising: 'noteRise', none: '' };
64
+
65
+ return (
66
+ <div
67
+ ref={ref}
68
+ className={`
69
+ pointer-events-none overflow-hidden z-[1]
70
+ ${mode === 'overlay' ? 'fixed inset-0' : 'relative w-full h-full'}
71
+ ${variantColors[variant]}
72
+ ${className}
73
+ `}
74
+ {...props}
75
+ >
76
+ <style>{animationKeyframes[animation]}</style>
77
+
78
+ {showStaff && (
79
+ <>
80
+ <div className="absolute w-full top-[15%] flex flex-col gap-2 opacity-10">
81
+ {[...Array(5)].map((_, i) => <div key={i} className="h-px bg-current" />)}
82
+ </div>
83
+ <div className="absolute w-full bottom-[15%] flex flex-col gap-2 opacity-10">
84
+ {[...Array(5)].map((_, i) => <div key={i} className="h-px bg-current" />)}
85
+ </div>
86
+ </>
87
+ )}
88
+
89
+ {notes.map((note, i) => {
90
+ const symbol = MUSIC_SYMBOLS[note.symbol as keyof typeof MUSIC_SYMBOLS] || note.symbol;
91
+ return (
92
+ <span
93
+ key={i}
94
+ className={`absolute opacity-30 ${sizeClasses[note.size || 'md']}`}
95
+ style={{
96
+ ...note.position,
97
+ animation: animation !== 'none' ? `${animationNames[animation]} ${duration} linear infinite` : undefined,
98
+ animationDelay: `${note.delay || i * 2}s`,
99
+ }}
100
+ >
101
+ {symbol}
102
+ </span>
103
+ );
104
+ })}
105
+ </div>
106
+ );
107
+ }
108
+ );
109
+
110
+ SheetMusic.displayName = 'SheetMusic';
111
+ export default SheetMusic;
@@ -0,0 +1,93 @@
1
+ .container {
2
+ --scroll-accent: #00f0ff;
3
+ --scroll-bg: rgba(0, 0, 0, 0.3);
4
+ --panel-gap: 2rem;
5
+
6
+ width: 100%;
7
+ overflow-x: auto;
8
+ overflow-y: hidden;
9
+ scroll-snap-type: x mandatory;
10
+ scroll-behavior: smooth;
11
+ -webkit-overflow-scrolling: touch;
12
+ scrollbar-width: thin;
13
+ scrollbar-color: var(--scroll-accent) var(--scroll-bg);
14
+ }
15
+
16
+ .container::-webkit-scrollbar {
17
+ height: 4px;
18
+ }
19
+
20
+ .container::-webkit-scrollbar-track {
21
+ background: var(--scroll-bg);
22
+ }
23
+
24
+ .container::-webkit-scrollbar-thumb {
25
+ background: var(--scroll-accent);
26
+ border-radius: 2px;
27
+ }
28
+
29
+ .track {
30
+ display: flex;
31
+ gap: var(--panel-gap);
32
+ padding: 1rem 0;
33
+ width: max-content;
34
+ }
35
+
36
+ .panel {
37
+ flex: 0 0 auto;
38
+ scroll-snap-align: start;
39
+ scroll-snap-stop: normal;
40
+ }
41
+
42
+ /* Variants */
43
+ .cyan { --scroll-accent: #00f0ff; }
44
+ .green { --scroll-accent: #00ff88; }
45
+ .amber { --scroll-accent: #ffaa00; }
46
+ .blood { --scroll-accent: #8b1a1a; }
47
+
48
+ /* Panel sizes */
49
+ .panelFull {
50
+ width: 100vw;
51
+ }
52
+
53
+ .panelLarge {
54
+ width: 80vw;
55
+ }
56
+
57
+ .panelMedium {
58
+ width: 60vw;
59
+ }
60
+
61
+ .panelSmall {
62
+ width: 40vw;
63
+ }
64
+
65
+ /* Fade edges */
66
+ .fadeEdges {
67
+ mask-image: linear-gradient(90deg, transparent 0%, black 5%, black 95%, transparent 100%);
68
+ -webkit-mask-image: linear-gradient(90deg, transparent 0%, black 5%, black 95%, transparent 100%);
69
+ }
70
+
71
+ /* Indicators */
72
+ .indicators {
73
+ display: flex;
74
+ justify-content: center;
75
+ gap: 0.5rem;
76
+ margin-top: 1rem;
77
+ }
78
+
79
+ .indicator {
80
+ width: 8px;
81
+ height: 8px;
82
+ border-radius: 50%;
83
+ background: var(--scroll-bg);
84
+ border: 1px solid var(--scroll-accent);
85
+ cursor: pointer;
86
+ transition: all 0.3s ease;
87
+ }
88
+
89
+ .indicator:hover,
90
+ .indicatorActive {
91
+ background: var(--scroll-accent);
92
+ box-shadow: 0 0 10px var(--scroll-accent);
93
+ }
@@ -4,16 +4,35 @@ import { forwardRef, HTMLAttributes, ReactNode, useRef, useState, useEffect } fr
4
4
  import styles from './horizontal-scroll.module.css';
5
5
 
6
6
  export interface HorizontalScrollProps extends HTMLAttributes<HTMLDivElement> {
7
+ /** Panel contents */
7
8
  children: ReactNode;
9
+ /** Color variant */
8
10
  variant?: 'cyan' | 'green' | 'amber' | 'blood';
11
+ /** Panel size */
9
12
  panelSize?: 'full' | 'large' | 'medium' | 'small';
13
+ /** Show fade on edges */
10
14
  fadeEdges?: boolean;
15
+ /** Show navigation indicators */
11
16
  showIndicators?: boolean;
17
+ /** Gap between panels in rem */
12
18
  gap?: number;
13
19
  }
14
20
 
15
21
  export const HorizontalScroll = forwardRef<HTMLDivElement, HorizontalScrollProps>(
16
- ({ children, variant = 'cyan', panelSize = 'large', fadeEdges = false, showIndicators = false, gap = 2, className, style, ...props }, ref) => {
22
+ (
23
+ {
24
+ children,
25
+ variant = 'cyan',
26
+ panelSize = 'large',
27
+ fadeEdges = false,
28
+ showIndicators = false,
29
+ gap = 2,
30
+ className,
31
+ style,
32
+ ...props
33
+ },
34
+ ref
35
+ ) => {
17
36
  const containerRef = useRef<HTMLDivElement>(null);
18
37
  const [activeIndex, setActiveIndex] = useState(0);
19
38
  const [panelCount, setPanelCount] = useState(0);
@@ -21,13 +40,15 @@ export const HorizontalScroll = forwardRef<HTMLDivElement, HorizontalScrollProps
21
40
  useEffect(() => {
22
41
  const container = containerRef.current;
23
42
  if (!container) return;
43
+
24
44
  const panels = container.querySelectorAll(`.${styles.panel}`);
25
45
  setPanelCount(panels.length);
26
46
 
27
47
  const handleScroll = () => {
28
48
  if (!container) return;
49
+ const scrollLeft = container.scrollLeft;
29
50
  const panelWidth = container.scrollWidth / panels.length;
30
- setActiveIndex(Math.round(container.scrollLeft / panelWidth));
51
+ setActiveIndex(Math.round(scrollLeft / panelWidth));
31
52
  };
32
53
 
33
54
  container.addEventListener('scroll', handleScroll, { passive: true });
@@ -41,7 +62,12 @@ export const HorizontalScroll = forwardRef<HTMLDivElement, HorizontalScrollProps
41
62
  container.scrollTo({ left: panelWidth * index, behavior: 'smooth' });
42
63
  };
43
64
 
44
- const panelSizeClass = { full: styles.panelFull, large: styles.panelLarge, medium: styles.panelMedium, small: styles.panelSmall }[panelSize];
65
+ const panelSizeClass = {
66
+ full: styles.panelFull,
67
+ large: styles.panelLarge,
68
+ medium: styles.panelMedium,
69
+ small: styles.panelSmall,
70
+ }[panelSize];
45
71
 
46
72
  return (
47
73
  <div className={className}>
@@ -57,15 +83,25 @@ export const HorizontalScroll = forwardRef<HTMLDivElement, HorizontalScrollProps
57
83
  >
58
84
  <div className={styles.track}>
59
85
  {Array.isArray(children)
60
- ? children.map((child, i) => <div key={i} className={`${styles.panel} ${panelSizeClass}`}>{child}</div>)
86
+ ? children.map((child, i) => (
87
+ <div key={i} className={`${styles.panel} ${panelSizeClass}`}>
88
+ {child}
89
+ </div>
90
+ ))
61
91
  : <div className={`${styles.panel} ${panelSizeClass}`}>{children}</div>
62
92
  }
63
93
  </div>
64
94
  </div>
95
+
65
96
  {showIndicators && panelCount > 1 && (
66
97
  <div className={styles.indicators}>
67
98
  {Array.from({ length: panelCount }).map((_, i) => (
68
- <button key={i} className={`${styles.indicator} ${i === activeIndex ? styles.indicatorActive : ''}`} onClick={() => scrollToPanel(i)} aria-label={`Go to panel ${i + 1}`} />
99
+ <button
100
+ key={i}
101
+ className={`${styles.indicator} ${i === activeIndex ? styles.indicatorActive : ''}`}
102
+ onClick={() => scrollToPanel(i)}
103
+ aria-label={`Go to panel ${i + 1}`}
104
+ />
69
105
  ))}
70
106
  </div>
71
107
  )}