From 7d294dc700480c892bca66eb597ca6c9a9bea4a6 Mon Sep 17 00:00:00 2001 From: Aidan Bleser Date: Mon, 10 Nov 2025 06:43:52 -0600 Subject: [PATCH] alternative method --- jsrepo-build-config.json | 37 - jsrepo.config.ts | 6302 +++++++++ package-lock.json | 1900 +-- package.json | 11 +- public/r/ASCIIText-JS-CSS.json | 31 +- public/r/ASCIIText-JS-TW.json | 31 +- public/r/ASCIIText-TS-CSS.json | 31 +- public/r/ASCIIText-TS-TW.json | 31 +- public/r/AnimatedContent-JS-CSS.json | 31 +- public/r/AnimatedContent-JS-TW.json | 31 +- public/r/AnimatedContent-TS-CSS.json | 31 +- public/r/AnimatedContent-TS-TW.json | 31 +- public/r/AnimatedList-JS-CSS.json | 41 +- public/r/AnimatedList-JS-TW.json | 31 +- public/r/AnimatedList-TS-CSS.json | 41 +- public/r/AnimatedList-TS-TW.json | 31 +- public/r/Aurora-JS-CSS.json | 41 +- public/r/Aurora-JS-TW.json | 31 +- public/r/Aurora-TS-CSS.json | 41 +- public/r/Aurora-TS-TW.json | 31 +- public/r/Balatro-JS-CSS.json | 41 +- public/r/Balatro-JS-TW.json | 31 +- public/r/Balatro-TS-CSS.json | 41 +- public/r/Balatro-TS-TW.json | 31 +- public/r/Ballpit-JS-CSS.json | 31 +- public/r/Ballpit-JS-TW.json | 31 +- public/r/Ballpit-TS-CSS.json | 33 +- public/r/Ballpit-TS-TW.json | 33 +- public/r/Beams-JS-CSS.json | 45 +- public/r/Beams-JS-TW.json | 35 +- public/r/Beams-TS-CSS.json | 45 +- public/r/Beams-TS-TW.json | 35 +- public/r/BlobCursor-JS-CSS.json | 41 +- public/r/BlobCursor-JS-TW.json | 31 +- public/r/BlobCursor-TS-CSS.json | 41 +- public/r/BlobCursor-TS-TW.json | 31 +- public/r/BlurText-JS-CSS.json | 31 +- public/r/BlurText-JS-TW.json | 31 +- public/r/BlurText-TS-CSS.json | 31 +- public/r/BlurText-TS-TW.json | 31 +- public/r/BounceCards-JS-CSS.json | 41 +- public/r/BounceCards-JS-TW.json | 31 +- public/r/BounceCards-TS-CSS.json | 41 +- public/r/BounceCards-TS-TW.json | 31 +- public/r/BubbleMenu-JS-CSS.json | 41 +- public/r/BubbleMenu-JS-TW.json | 31 +- public/r/BubbleMenu-TS-CSS.json | 41 +- public/r/BubbleMenu-TS-TW.json | 31 +- public/r/CardNav-JS-CSS.json | 42 +- public/r/CardNav-JS-TW.json | 32 +- public/r/CardNav-TS-CSS.json | 42 +- public/r/CardNav-TS-TW.json | 32 +- public/r/CardSwap-JS-CSS.json | 41 +- public/r/CardSwap-JS-TW.json | 31 +- public/r/CardSwap-TS-CSS.json | 41 +- public/r/CardSwap-TS-TW.json | 31 +- public/r/Carousel-JS-CSS.json | 42 +- public/r/Carousel-JS-TW.json | 32 +- public/r/Carousel-TS-CSS.json | 42 +- public/r/Carousel-TS-TW.json | 32 +- public/r/ChromaGrid-JS-CSS.json | 41 +- public/r/ChromaGrid-JS-TW.json | 31 +- public/r/ChromaGrid-TS-CSS.json | 41 +- public/r/ChromaGrid-TS-TW.json | 31 +- public/r/CircularGallery-JS-CSS.json | 41 +- public/r/CircularGallery-JS-TW.json | 31 +- public/r/CircularGallery-TS-CSS.json | 41 +- public/r/CircularGallery-TS-TW.json | 31 +- public/r/CircularText-JS-CSS.json | 41 +- public/r/CircularText-JS-TW.json | 31 +- public/r/CircularText-TS-CSS.json | 41 +- public/r/CircularText-TS-TW.json | 31 +- public/r/ClickSpark-JS-CSS.json | 26 +- public/r/ClickSpark-JS-TW.json | 26 +- public/r/ClickSpark-TS-CSS.json | 26 +- public/r/ClickSpark-TS-TW.json | 26 +- public/r/ColorBends-JS-CSS.json | 41 +- public/r/ColorBends-JS-TW.json | 31 +- public/r/ColorBends-TS-CSS.json | 41 +- public/r/ColorBends-TS-TW.json | 31 +- public/r/CountUp-JS-CSS.json | 31 +- public/r/CountUp-JS-TW.json | 31 +- public/r/CountUp-TS-CSS.json | 31 +- public/r/CountUp-TS-TW.json | 31 +- public/r/Counter-JS-CSS.json | 41 +- public/r/Counter-JS-TW.json | 31 +- public/r/Counter-TS-CSS.json | 41 +- public/r/Counter-TS-TW.json | 31 +- public/r/Crosshair-JS-CSS.json | 31 +- public/r/Crosshair-JS-TW.json | 31 +- public/r/Crosshair-TS-CSS.json | 31 +- public/r/Crosshair-TS-TW.json | 31 +- public/r/Cubes-JS-CSS.json | 41 +- public/r/Cubes-JS-TW.json | 31 +- public/r/Cubes-TS-CSS.json | 41 +- public/r/Cubes-TS-TW.json | 31 +- public/r/CurvedLoop-JS-CSS.json | 36 +- public/r/CurvedLoop-JS-TW.json | 26 +- public/r/CurvedLoop-TS-CSS.json | 36 +- public/r/CurvedLoop-TS-TW.json | 26 +- public/r/DarkVeil-JS-CSS.json | 41 +- public/r/DarkVeil-JS-TW.json | 31 +- public/r/DarkVeil-TS-CSS.json | 41 +- public/r/DarkVeil-TS-TW.json | 31 +- public/r/DecayCard-JS-CSS.json | 41 +- public/r/DecayCard-JS-TW.json | 31 +- public/r/DecayCard-TS-CSS.json | 41 +- public/r/DecayCard-TS-TW.json | 31 +- public/r/DecryptedText-JS-CSS.json | 31 +- public/r/DecryptedText-JS-TW.json | 31 +- public/r/DecryptedText-TS-CSS.json | 31 +- public/r/DecryptedText-TS-TW.json | 31 +- public/r/Dither-JS-CSS.json | 47 +- public/r/Dither-JS-TW.json | 37 +- public/r/Dither-TS-CSS.json | 47 +- public/r/Dither-TS-TW.json | 37 +- public/r/Dock-JS-CSS.json | 41 +- public/r/Dock-JS-TW.json | 31 +- public/r/Dock-TS-CSS.json | 41 +- public/r/Dock-TS-TW.json | 31 +- public/r/DomeGallery-JS-CSS.json | 41 +- public/r/DomeGallery-JS-TW.json | 31 +- public/r/DomeGallery-TS-CSS.json | 41 +- public/r/DomeGallery-TS-TW.json | 31 +- public/r/DotGrid-JS-CSS.json | 41 +- public/r/DotGrid-JS-TW.json | 31 +- public/r/DotGrid-TS-CSS.json | 41 +- public/r/DotGrid-TS-TW.json | 31 +- public/r/ElasticSlider-JS-CSS.json | 44 +- public/r/ElasticSlider-JS-TW.json | 31 +- public/r/ElasticSlider-TS-CSS.json | 44 +- public/r/ElasticSlider-TS-TW.json | 31 +- public/r/ElectricBorder-JS-CSS.json | 36 +- public/r/ElectricBorder-JS-TW.json | 26 +- public/r/ElectricBorder-TS-CSS.json | 36 +- public/r/ElectricBorder-TS-TW.json | 26 +- public/r/FadeContent-JS-CSS.json | 26 +- public/r/FadeContent-JS-TW.json | 26 +- public/r/FadeContent-TS-CSS.json | 26 +- public/r/FadeContent-TS-TW.json | 26 +- public/r/FallingText-JS-CSS.json | 41 +- public/r/FallingText-JS-TW.json | 31 +- public/r/FallingText-TS-CSS.json | 41 +- public/r/FallingText-TS-TW.json | 31 +- public/r/FaultyTerminal-JS-CSS.json | 41 +- public/r/FaultyTerminal-JS-TW.json | 31 +- public/r/FaultyTerminal-TS-CSS.json | 41 +- public/r/FaultyTerminal-TS-TW.json | 31 +- public/r/FlowingMenu-JS-CSS.json | 41 +- public/r/FlowingMenu-JS-TW.json | 32 +- public/r/FlowingMenu-TS-CSS.json | 41 +- public/r/FlowingMenu-TS-TW.json | 32 +- public/r/FluidGlass-JS-CSS.json | 37 +- public/r/FluidGlass-JS-TW.json | 37 +- public/r/FluidGlass-TS-CSS.json | 37 +- public/r/FluidGlass-TS-TW.json | 37 +- public/r/FlyingPosters-JS-CSS.json | 41 +- public/r/FlyingPosters-JS-TW.json | 31 +- public/r/FlyingPosters-TS-CSS.json | 41 +- public/r/FlyingPosters-TS-TW.json | 31 +- public/r/Folder-JS-CSS.json | 36 +- public/r/Folder-JS-TW.json | 26 +- public/r/Folder-TS-CSS.json | 36 +- public/r/Folder-TS-TW.json | 26 +- public/r/FuzzyText-JS-CSS.json | 26 +- public/r/FuzzyText-JS-TW.json | 26 +- public/r/FuzzyText-TS-CSS.json | 26 +- public/r/FuzzyText-TS-TW.json | 26 +- public/r/Galaxy-JS-CSS.json | 41 +- public/r/Galaxy-JS-TW.json | 31 +- public/r/Galaxy-TS-CSS.json | 41 +- public/r/Galaxy-TS-TW.json | 31 +- public/r/GhostCursor-JS-CSS.json | 41 +- public/r/GhostCursor-JS-TW.json | 31 +- public/r/GhostCursor-TS-CSS.json | 41 +- public/r/GhostCursor-TS-TW.json | 31 +- public/r/GlareHover-JS-CSS.json | 36 +- public/r/GlareHover-JS-TW.json | 26 +- public/r/GlareHover-TS-CSS.json | 36 +- public/r/GlareHover-TS-TW.json | 26 +- public/r/GlassIcons-JS-CSS.json | 36 +- public/r/GlassIcons-JS-TW.json | 26 +- public/r/GlassIcons-TS-CSS.json | 36 +- public/r/GlassIcons-TS-TW.json | 26 +- public/r/GlassSurface-JS-CSS.json | 36 +- public/r/GlassSurface-JS-TW.json | 26 +- public/r/GlassSurface-TS-CSS.json | 36 +- public/r/GlassSurface-TS-TW.json | 26 +- public/r/GlitchText-JS-CSS.json | 36 +- public/r/GlitchText-JS-TW.json | 26 +- public/r/GlitchText-TS-CSS.json | 36 +- public/r/GlitchText-TS-TW.json | 26 +- public/r/GooeyNav-JS-CSS.json | 36 +- public/r/GooeyNav-JS-TW.json | 26 +- public/r/GooeyNav-TS-CSS.json | 36 +- public/r/GooeyNav-TS-TW.json | 26 +- public/r/GradientBlinds-JS-CSS.json | 41 +- public/r/GradientBlinds-JS-TW.json | 31 +- public/r/GradientBlinds-TS-CSS.json | 41 +- public/r/GradientBlinds-TS-TW.json | 31 +- public/r/GradientText-JS-CSS.json | 36 +- public/r/GradientText-JS-TW.json | 26 +- public/r/GradientText-TS-CSS.json | 36 +- public/r/GradientText-TS-TW.json | 26 +- public/r/GradualBlur-JS-CSS.json | 41 +- public/r/GradualBlur-JS-TW.json | 31 +- public/r/GradualBlur-TS-CSS.json | 41 +- public/r/GradualBlur-TS-TW.json | 31 +- public/r/GridDistortion-JS-CSS.json | 41 +- public/r/GridDistortion-JS-TW.json | 31 +- public/r/GridDistortion-TS-CSS.json | 41 +- public/r/GridDistortion-TS-TW.json | 31 +- public/r/GridMotion-JS-CSS.json | 41 +- public/r/GridMotion-JS-TW.json | 31 +- public/r/GridMotion-TS-CSS.json | 41 +- public/r/GridMotion-TS-TW.json | 31 +- public/r/GridScan-JS-CSS.json | 45 +- public/r/GridScan-JS-TW.json | 35 +- public/r/GridScan-TS-CSS.json | 45 +- public/r/GridScan-TS-TW.json | 35 +- public/r/Hyperspeed-JS-CSS.json | 43 +- public/r/Hyperspeed-JS-TW.json | 33 +- public/r/Hyperspeed-TS-CSS.json | 43 +- public/r/Hyperspeed-TS-TW.json | 33 +- public/r/ImageTrail-JS-CSS.json | 41 +- public/r/ImageTrail-JS-TW.json | 31 +- public/r/ImageTrail-TS-CSS.json | 41 +- public/r/ImageTrail-TS-TW.json | 31 +- public/r/InfiniteMenu-JS-CSS.json | 41 +- public/r/InfiniteMenu-JS-TW.json | 31 +- public/r/InfiniteMenu-TS-CSS.json | 41 +- public/r/InfiniteMenu-TS-TW.json | 31 +- public/r/InfiniteScroll-JS-CSS.json | 22 - public/r/InfiniteScroll-JS-TW.json | 17 - public/r/InfiniteScroll-TS-CSS.json | 22 - public/r/InfiniteScroll-TS-TW.json | 17 - public/r/Iridescence-JS-CSS.json | 41 +- public/r/Iridescence-JS-TW.json | 31 +- public/r/Iridescence-TS-CSS.json | 41 +- public/r/Iridescence-TS-TW.json | 31 +- public/r/Lanyard-JS-CSS.json | 49 +- public/r/Lanyard-JS-TW.json | 39 +- public/r/Lanyard-TS-CSS.json | 49 +- public/r/Lanyard-TS-TW.json | 39 +- public/r/LaserFlow-JS-CSS.json | 41 +- public/r/LaserFlow-JS-TW.json | 31 +- public/r/LaserFlow-TS-CSS.json | 41 +- public/r/LaserFlow-TS-TW.json | 31 +- public/r/LetterGlitch-JS-CSS.json | 26 +- public/r/LetterGlitch-JS-TW.json | 26 +- public/r/LetterGlitch-TS-CSS.json | 26 +- public/r/LetterGlitch-TS-TW.json | 26 +- public/r/LightRays-JS-CSS.json | 41 +- public/r/LightRays-JS-TW.json | 31 +- public/r/LightRays-TS-CSS.json | 41 +- public/r/LightRays-TS-TW.json | 31 +- public/r/Lightning-JS-CSS.json | 36 +- public/r/Lightning-JS-TW.json | 26 +- public/r/Lightning-TS-CSS.json | 36 +- public/r/Lightning-TS-TW.json | 26 +- public/r/LiquidChrome-JS-CSS.json | 41 +- public/r/LiquidChrome-JS-TW.json | 31 +- public/r/LiquidChrome-TS-CSS.json | 41 +- public/r/LiquidChrome-TS-TW.json | 31 +- public/r/LiquidEther-JS-CSS.json | 41 +- public/r/LiquidEther-JS-TW.json | 31 +- public/r/LiquidEther-TS-CSS.json | 41 +- public/r/LiquidEther-TS-TW.json | 31 +- public/r/LogoLoop-JS-CSS.json | 36 +- public/r/LogoLoop-JS-TW.json | 26 +- public/r/LogoLoop-TS-CSS.json | 36 +- public/r/LogoLoop-TS-TW.json | 26 +- public/r/MagicBento-JS-CSS.json | 41 +- public/r/MagicBento-JS-TW.json | 31 +- public/r/MagicBento-TS-CSS.json | 41 +- public/r/MagicBento-TS-TW.json | 31 +- public/r/Magnet-JS-CSS.json | 26 +- public/r/Magnet-JS-TW.json | 26 +- public/r/Magnet-TS-CSS.json | 26 +- public/r/Magnet-TS-TW.json | 26 +- public/r/MagnetLines-JS-CSS.json | 36 +- public/r/MagnetLines-JS-TW.json | 26 +- public/r/MagnetLines-TS-CSS.json | 36 +- public/r/MagnetLines-TS-TW.json | 26 +- public/r/Masonry-JS-CSS.json | 41 +- public/r/Masonry-JS-TW.json | 31 +- public/r/Masonry-TS-CSS.json | 41 +- public/r/Masonry-TS-TW.json | 31 +- public/r/MetaBalls-JS-CSS.json | 41 +- public/r/MetaBalls-JS-TW.json | 31 +- public/r/MetaBalls-TS-CSS.json | 41 +- public/r/MetaBalls-TS-TW.json | 31 +- public/r/MetallicPaint-JS-CSS.json | 36 +- public/r/MetallicPaint-JS-TW.json | 26 +- public/r/MetallicPaint-TS-CSS.json | 36 +- public/r/MetallicPaint-TS-TW.json | 26 +- public/r/ModelViewer-JS-CSS.json | 35 +- public/r/ModelViewer-JS-TW.json | 35 +- public/r/ModelViewer-TS-CSS.json | 35 +- public/r/ModelViewer-TS-TW.json | 35 +- public/r/Noise-JS-CSS.json | 36 +- public/r/Noise-JS-TW.json | 26 +- public/r/Noise-TS-CSS.json | 36 +- public/r/Noise-TS-TW.json | 26 +- public/r/Orb-JS-CSS.json | 41 +- public/r/Orb-JS-TW.json | 31 +- public/r/Orb-TS-CSS.json | 41 +- public/r/Orb-TS-TW.json | 31 +- public/r/Particles-JS-CSS.json | 41 +- public/r/Particles-JS-TW.json | 31 +- public/r/Particles-TS-CSS.json | 41 +- public/r/Particles-TS-TW.json | 31 +- public/r/PillNav-JS-CSS.json | 43 +- public/r/PillNav-JS-TW.json | 33 +- public/r/PillNav-TS-CSS.json | 43 +- public/r/PillNav-TS-TW.json | 33 +- public/r/PixelBlast-JS-CSS.json | 43 +- public/r/PixelBlast-JS-TW.json | 33 +- public/r/PixelBlast-TS-CSS.json | 43 +- public/r/PixelBlast-TS-TW.json | 33 +- public/r/PixelCard-JS-CSS.json | 36 +- public/r/PixelCard-JS-TW.json | 26 +- public/r/PixelCard-TS-CSS.json | 36 +- public/r/PixelCard-TS-TW.json | 26 +- public/r/PixelTrail-JS-CSS.json | 45 +- public/r/PixelTrail-JS-TW.json | 35 +- public/r/PixelTrail-TS-CSS.json | 45 +- public/r/PixelTrail-TS-TW.json | 35 +- public/r/PixelTransition-JS-CSS.json | 41 +- public/r/PixelTransition-JS-TW.json | 31 +- public/r/PixelTransition-TS-CSS.json | 41 +- public/r/PixelTransition-TS-TW.json | 31 +- public/r/Plasma-JS-CSS.json | 41 +- public/r/Plasma-JS-TW.json | 31 +- public/r/Plasma-TS-CSS.json | 41 +- public/r/Plasma-TS-TW.json | 31 +- public/r/Prism-JS-CSS.json | 41 +- public/r/Prism-JS-TW.json | 31 +- public/r/Prism-TS-CSS.json | 41 +- public/r/Prism-TS-TW.json | 31 +- public/r/PrismaticBurst-JS-CSS.json | 41 +- public/r/PrismaticBurst-JS-TW.json | 31 +- public/r/PrismaticBurst-TS-CSS.json | 41 +- public/r/PrismaticBurst-TS-TW.json | 31 +- public/r/ProfileCard-JS-CSS.json | 36 +- public/r/ProfileCard-TS-CSS.json | 36 +- public/r/Ribbons-JS-CSS.json | 41 +- public/r/Ribbons-JS-TW.json | 31 +- public/r/Ribbons-TS-CSS.json | 41 +- public/r/Ribbons-TS-TW.json | 31 +- public/r/RippleGrid-JS-CSS.json | 41 +- public/r/RippleGrid-JS-TW.json | 31 +- public/r/RippleGrid-TS-CSS.json | 41 +- public/r/RippleGrid-TS-TW.json | 31 +- public/r/RollingGallery-JS-CSS.json | 22 - public/r/RollingGallery-JS-TW.json | 17 - public/r/RollingGallery-TS-CSS.json | 22 - public/r/RollingGallery-TS-TW.json | 17 - public/r/RotatingText-JS-CSS.json | 41 +- public/r/RotatingText-JS-TW.json | 31 +- public/r/RotatingText-TS-CSS.json | 41 +- public/r/RotatingText-TS-TW.json | 31 +- public/r/ScrambledText-JS-CSS.json | 41 +- public/r/ScrambledText-JS-TW.json | 31 +- public/r/ScrambledText-TS-CSS.json | 41 +- public/r/ScrambledText-TS-TW.json | 31 +- public/r/ScrollFloat-JS-CSS.json | 41 +- public/r/ScrollFloat-JS-TW.json | 31 +- public/r/ScrollFloat-TS-CSS.json | 41 +- public/r/ScrollFloat-TS-TW.json | 31 +- public/r/ScrollReveal-JS-CSS.json | 41 +- public/r/ScrollReveal-JS-TW.json | 31 +- public/r/ScrollReveal-TS-CSS.json | 41 +- public/r/ScrollReveal-TS-TW.json | 31 +- public/r/ScrollStack-JS-CSS.json | 41 +- public/r/ScrollStack-JS-TW.json | 31 +- public/r/ScrollStack-TS-CSS.json | 41 +- public/r/ScrollStack-TS-TW.json | 31 +- public/r/ScrollVelocity-JS-CSS.json | 41 +- public/r/ScrollVelocity-JS-TW.json | 31 +- public/r/ScrollVelocity-TS-CSS.json | 41 +- public/r/ScrollVelocity-TS-TW.json | 31 +- public/r/ShapeBlur-JS-CSS.json | 31 +- public/r/ShapeBlur-JS-TW.json | 31 +- public/r/ShapeBlur-TS-CSS.json | 31 +- public/r/ShapeBlur-TS-TW.json | 31 +- public/r/ShinyText-JS-CSS.json | 36 +- public/r/ShinyText-JS-TW.json | 26 +- public/r/ShinyText-TS-CSS.json | 36 +- public/r/ShinyText-TS-TW.json | 26 +- public/r/Shuffle-JS-CSS.json | 43 +- public/r/Shuffle-JS-TW.json | 33 +- public/r/Shuffle-TS-CSS.json | 43 +- public/r/Shuffle-TS-TW.json | 33 +- public/r/Silk-JS-CSS.json | 33 +- public/r/Silk-JS-TW.json | 33 +- public/r/Silk-TS-CSS.json | 33 +- public/r/Silk-TS-TW.json | 33 +- public/r/SplashCursor-JS-CSS.json | 26 +- public/r/SplashCursor-JS-TW.json | 26 +- public/r/SplashCursor-TS-CSS.json | 26 +- public/r/SplashCursor-TS-TW.json | 26 +- public/r/SplitText-JS-CSS.json | 33 +- public/r/SplitText-JS-TW.json | 33 +- public/r/SplitText-TS-CSS.json | 33 +- public/r/SplitText-TS-TW.json | 33 +- public/r/SpotlightCard-JS-CSS.json | 36 +- public/r/SpotlightCard-JS-TW.json | 26 +- public/r/SpotlightCard-TS-CSS.json | 36 +- public/r/SpotlightCard-TS-TW.json | 26 +- public/r/Squares-JS-CSS.json | 36 +- public/r/Squares-JS-TW.json | 26 +- public/r/Squares-TS-CSS.json | 36 +- public/r/Squares-TS-TW.json | 26 +- public/r/Stack-JS-CSS.json | 41 +- public/r/Stack-JS-TW.json | 31 +- public/r/Stack-TS-CSS.json | 41 +- public/r/Stack-TS-TW.json | 31 +- public/r/StaggeredMenu-JS-CSS.json | 41 +- public/r/StaggeredMenu-JS-TW.json | 31 +- public/r/StaggeredMenu-TS-CSS.json | 41 +- public/r/StaggeredMenu-TS-TW.json | 31 +- public/r/StarBorder-JS-CSS.json | 36 +- public/r/StarBorder-JS-TW.json | 26 +- public/r/StarBorder-TS-CSS.json | 36 +- public/r/StarBorder-TS-TW.json | 26 +- public/r/Stepper-JS-CSS.json | 41 +- public/r/Stepper-JS-TW.json | 31 +- public/r/Stepper-TS-CSS.json | 41 +- public/r/Stepper-TS-TW.json | 31 +- public/r/StickerPeel-JS-CSS.json | 41 +- public/r/StickerPeel-JS-TW.json | 31 +- public/r/StickerPeel-TS-CSS.json | 41 +- public/r/StickerPeel-TS-TW.json | 31 +- public/r/TargetCursor-JS-CSS.json | 41 +- public/r/TargetCursor-JS-TW.json | 31 +- public/r/TargetCursor-TS-CSS.json | 41 +- public/r/TargetCursor-TS-TW.json | 31 +- public/r/TextCursor-JS-CSS.json | 41 +- public/r/TextCursor-JS-TW.json | 31 +- public/r/TextCursor-TS-CSS.json | 41 +- public/r/TextCursor-TS-TW.json | 31 +- public/r/TextPressure-JS-CSS.json | 26 +- public/r/TextPressure-JS-TW.json | 26 +- public/r/TextPressure-TS-CSS.json | 26 +- public/r/TextPressure-TS-TW.json | 26 +- public/r/TextTrail-JS-CSS.json | 22 - public/r/TextTrail-JS-TW.json | 17 - public/r/TextTrail-TS-CSS.json | 22 - public/r/TextTrail-TS-TW.json | 17 - public/r/TextType-JS-CSS.json | 41 +- public/r/TextType-JS-TW.json | 31 +- public/r/TextType-TS-CSS.json | 41 +- public/r/TextType-TS-TW.json | 31 +- public/r/Threads-JS-CSS.json | 41 +- public/r/Threads-JS-TW.json | 31 +- public/r/Threads-TS-CSS.json | 41 +- public/r/Threads-TS-TW.json | 31 +- public/r/TiltedCard-JS-CSS.json | 41 +- public/r/TiltedCard-JS-TW.json | 31 +- public/r/TiltedCard-TS-CSS.json | 41 +- public/r/TiltedCard-TS-TW.json | 31 +- public/r/TrueFocus-JS-CSS.json | 41 +- public/r/TrueFocus-JS-TW.json | 31 +- public/r/TrueFocus-TS-CSS.json | 41 +- public/r/TrueFocus-TS-TW.json | 31 +- public/r/VariableProximity-JS-CSS.json | 41 +- public/r/VariableProximity-JS-TW.json | 31 +- public/r/VariableProximity-TS-CSS.json | 41 +- public/r/VariableProximity-TS-TW.json | 31 +- public/r/Waves-JS-CSS.json | 36 +- public/r/Waves-JS-TW.json | 26 +- public/r/Waves-TS-CSS.json | 36 +- public/r/Waves-TS-TW.json | 26 +- public/r/registry.json | 15408 ++++++++++++----------- registry.json | 7417 ----------- 476 files changed, 22785 insertions(+), 24304 deletions(-) delete mode 100644 jsrepo-build-config.json create mode 100644 jsrepo.config.ts delete mode 100644 public/r/InfiniteScroll-JS-CSS.json delete mode 100644 public/r/InfiniteScroll-JS-TW.json delete mode 100644 public/r/InfiniteScroll-TS-CSS.json delete mode 100644 public/r/InfiniteScroll-TS-TW.json delete mode 100644 public/r/RollingGallery-JS-CSS.json delete mode 100644 public/r/RollingGallery-JS-TW.json delete mode 100644 public/r/RollingGallery-TS-CSS.json delete mode 100644 public/r/RollingGallery-TS-TW.json delete mode 100644 public/r/TextTrail-JS-CSS.json delete mode 100644 public/r/TextTrail-JS-TW.json delete mode 100644 public/r/TextTrail-TS-CSS.json delete mode 100644 public/r/TextTrail-TS-TW.json delete mode 100644 registry.json diff --git a/jsrepo-build-config.json b/jsrepo-build-config.json deleted file mode 100644 index 57e1c62d..00000000 --- a/jsrepo-build-config.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "$schema": "https://unpkg.com/jsrepo@1.30.1/schemas/registry-config.json", - "meta": { - "authors": ["David Haz"], - "description": "An open source collection of animated, interactive & fully customizable React components for building stunning, memorable user interfaces.", - "bugs": "https://github.com/DavidHDev/react-bits/issues", - "homepage": "https://reactbits.dev", - "repository": "https://github.com/DavidHDev/react-bits", - "tags": [ - "react", - "javascript", - "components", - "web", - "reactjs", - "css-animations", - "component-library", - "ui-components", - "3d", - "ui-library", - "tailwind", - "tailwindcss", - "components", - "components-library" - ] - }, - "dirs": [], - "doNotListBlocks": [], - "doNotListCategories": [], - "listBlocks": [], - "listCategories": [], - "excludeDeps": ["react"], - "includeBlocks": [], - "includeCategories": [], - "excludeBlocks": [], - "excludeCategories": [], - "preview": true -} diff --git a/jsrepo.config.ts b/jsrepo.config.ts new file mode 100644 index 00000000..d54bfc99 --- /dev/null +++ b/jsrepo.config.ts @@ -0,0 +1,6302 @@ +import { defineConfig } from 'jsrepo'; +import { output as shadcn } from '@jsrepo/shadcn'; + +export default defineConfig({ + registry: { + name: 'react-bits', + authors: ['David Haz'], + description: + 'An open source collection of animated, interactive & fully customizable React components for building stunning, memorable user interfaces.', + bugs: 'https://github.com/DavidHDev/react-bits/issues', + homepage: 'https://reactbits.dev', + repository: 'https://github.com/DavidHDev/react-bits', + tags: [ + 'react', + 'javascript', + 'components', + 'web', + 'reactjs', + 'css-animations', + 'component-library', + 'ui-components', + '3d', + 'ui-library', + 'tailwind', + 'tailwindcss', + 'components', + 'components-library' + ], + excludeDeps: ['react'], + outputs: [shadcn({ dir: 'public/r', format: true })], + items: [ + { + name: 'AnimatedContent-JS-CSS', + type: 'registry:block', + title: 'AnimatedContent', + description: + 'Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.', + files: [ + { + path: 'src/content/Animations/AnimatedContent/AnimatedContent.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'AnimatedContent-JS-TW', + type: 'registry:block', + title: 'AnimatedContent', + description: + 'Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.', + files: [ + { + path: 'src/tailwind/Animations/AnimatedContent/AnimatedContent.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'AnimatedContent-TS-CSS', + type: 'registry:block', + title: 'AnimatedContent', + description: + 'Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.', + files: [ + { + path: 'src/ts-default/Animations/AnimatedContent/AnimatedContent.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'AnimatedContent-TS-TW', + type: 'registry:block', + title: 'AnimatedContent', + description: + 'Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.', + files: [ + { + path: 'src/ts-tailwind/Animations/AnimatedContent/AnimatedContent.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'AnimatedList-JS-CSS', + type: 'registry:block', + title: 'AnimatedList', + description: 'List items enter with staggered motion variants for polished reveals.', + files: [ + { + path: 'src/content/Components/AnimatedList/AnimatedList.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/AnimatedList/AnimatedList.css', + type: 'registry:item' + } + ] + }, + { + name: 'AnimatedList-JS-TW', + type: 'registry:block', + title: 'AnimatedList', + description: 'List items enter with staggered motion variants for polished reveals.', + files: [ + { + path: 'src/tailwind/Components/AnimatedList/AnimatedList.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'AnimatedList-TS-CSS', + type: 'registry:block', + title: 'AnimatedList', + description: 'List items enter with staggered motion variants for polished reveals.', + files: [ + { + path: 'src/ts-default/Components/AnimatedList/AnimatedList.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/AnimatedList/AnimatedList.css', + type: 'registry:item' + } + ] + }, + { + name: 'AnimatedList-TS-TW', + type: 'registry:block', + title: 'AnimatedList', + description: 'List items enter with staggered motion variants for polished reveals.', + files: [ + { + path: 'src/ts-tailwind/Components/AnimatedList/AnimatedList.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ASCIIText-JS-CSS', + type: 'registry:block', + title: 'ASCIIText', + description: 'Renders text with an animated ASCII background for a retro feel.', + files: [ + { + path: 'src/content/TextAnimations/ASCIIText/ASCIIText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ASCIIText-JS-TW', + type: 'registry:block', + title: 'ASCIIText', + description: 'Renders text with an animated ASCII background for a retro feel.', + files: [ + { + path: 'src/tailwind/TextAnimations/ASCIIText/ASCIIText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ASCIIText-TS-CSS', + type: 'registry:block', + title: 'ASCIIText', + description: 'Renders text with an animated ASCII background for a retro feel.', + files: [ + { + path: 'src/ts-default/TextAnimations/ASCIIText/ASCIIText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ASCIIText-TS-TW', + type: 'registry:block', + title: 'ASCIIText', + description: 'Renders text with an animated ASCII background for a retro feel.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/ASCIIText/ASCIIText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Aurora-JS-CSS', + type: 'registry:block', + title: 'Aurora', + description: 'Flowing aurora gradient background.', + files: [ + { + path: 'src/content/Backgrounds/Aurora/Aurora.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Aurora/Aurora.css', + type: 'registry:item' + } + ] + }, + { + name: 'Aurora-JS-TW', + type: 'registry:block', + title: 'Aurora', + description: 'Flowing aurora gradient background.', + files: [ + { + path: 'src/tailwind/Backgrounds/Aurora/Aurora.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Aurora-TS-CSS', + type: 'registry:block', + title: 'Aurora', + description: 'Flowing aurora gradient background.', + files: [ + { + path: 'src/ts-default/Backgrounds/Aurora/Aurora.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Aurora/Aurora.css', + type: 'registry:item' + } + ] + }, + { + name: 'Aurora-TS-TW', + type: 'registry:block', + title: 'Aurora', + description: 'Flowing aurora gradient background.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Aurora/Aurora.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Balatro-JS-CSS', + type: 'registry:block', + title: 'Balatro', + description: 'The balatro shader, fully customizalbe and interactive.', + files: [ + { + path: 'src/content/Backgrounds/Balatro/Balatro.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Balatro/Balatro.css', + type: 'registry:item' + } + ] + }, + { + name: 'Balatro-JS-TW', + type: 'registry:block', + title: 'Balatro', + description: 'The balatro shader, fully customizalbe and interactive.', + files: [ + { + path: 'src/tailwind/Backgrounds/Balatro/Balatro.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Balatro-TS-CSS', + type: 'registry:block', + title: 'Balatro', + description: 'The balatro shader, fully customizalbe and interactive.', + files: [ + { + path: 'src/ts-default/Backgrounds/Balatro/Balatro.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Balatro/Balatro.css', + type: 'registry:item' + } + ] + }, + { + name: 'Balatro-TS-TW', + type: 'registry:block', + title: 'Balatro', + description: 'The balatro shader, fully customizalbe and interactive.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Balatro/Balatro.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Ballpit-JS-CSS', + type: 'registry:block', + title: 'Ballpit', + description: 'Physics ball pit simulation with bouncing colorful spheres.', + files: [ + { + path: 'src/content/Backgrounds/Ballpit/Ballpit.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Ballpit-JS-TW', + type: 'registry:block', + title: 'Ballpit', + description: 'Physics ball pit simulation with bouncing colorful spheres.', + files: [ + { + path: 'src/tailwind/Backgrounds/Ballpit/Ballpit.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Ballpit-TS-CSS', + type: 'registry:block', + title: 'Ballpit', + description: 'Physics ball pit simulation with bouncing colorful spheres.', + files: [ + { + path: 'src/ts-default/Backgrounds/Ballpit/Ballpit.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Ballpit-TS-TW', + type: 'registry:block', + title: 'Ballpit', + description: 'Physics ball pit simulation with bouncing colorful spheres.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Ballpit/Ballpit.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Beams-JS-CSS', + type: 'registry:block', + title: 'Beams', + description: 'Crossing animated ribbons with customizable properties.', + files: [ + { + path: 'src/content/Backgrounds/Beams/Beams.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Beams/Beams.css', + type: 'registry:item' + } + ] + }, + { + name: 'Beams-JS-TW', + type: 'registry:block', + title: 'Beams', + description: 'Crossing animated ribbons with customizable properties.', + files: [ + { + path: 'src/tailwind/Backgrounds/Beams/Beams.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Beams-TS-CSS', + type: 'registry:block', + title: 'Beams', + description: 'Crossing animated ribbons with customizable properties.', + files: [ + { + path: 'src/ts-default/Backgrounds/Beams/Beams.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Beams/Beams.css', + type: 'registry:item' + } + ] + }, + { + name: 'Beams-TS-TW', + type: 'registry:block', + title: 'Beams', + description: 'Crossing animated ribbons with customizable properties.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Beams/Beams.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'BlobCursor-JS-CSS', + type: 'registry:block', + title: 'BlobCursor', + description: 'Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.', + files: [ + { + path: 'src/content/Animations/BlobCursor/BlobCursor.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/BlobCursor/BlobCursor.css', + type: 'registry:item' + } + ] + }, + { + name: 'BlobCursor-JS-TW', + type: 'registry:block', + title: 'BlobCursor', + description: 'Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.', + files: [ + { + path: 'src/tailwind/Animations/BlobCursor/BlobCursor.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'BlobCursor-TS-CSS', + type: 'registry:block', + title: 'BlobCursor', + description: 'Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.', + files: [ + { + path: 'src/ts-default/Animations/BlobCursor/BlobCursor.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/BlobCursor/BlobCursor.css', + type: 'registry:item' + } + ] + }, + { + name: 'BlobCursor-TS-TW', + type: 'registry:block', + title: 'BlobCursor', + description: 'Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.', + files: [ + { + path: 'src/ts-tailwind/Animations/BlobCursor/BlobCursor.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'BlurText-JS-CSS', + type: 'registry:block', + title: 'BlurText', + description: 'Text starts blurred then crisply resolves for a soft-focus reveal effect.', + files: [ + { + path: 'src/content/TextAnimations/BlurText/BlurText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'BlurText-JS-TW', + type: 'registry:block', + title: 'BlurText', + description: 'Text starts blurred then crisply resolves for a soft-focus reveal effect.', + files: [ + { + path: 'src/tailwind/TextAnimations/BlurText/BlurText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'BlurText-TS-CSS', + type: 'registry:block', + title: 'BlurText', + description: 'Text starts blurred then crisply resolves for a soft-focus reveal effect.', + files: [ + { + path: 'src/ts-default/TextAnimations/BlurText/BlurText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'BlurText-TS-TW', + type: 'registry:block', + title: 'BlurText', + description: 'Text starts blurred then crisply resolves for a soft-focus reveal effect.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/BlurText/BlurText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'BounceCards-JS-CSS', + type: 'registry:block', + title: 'BounceCards', + description: 'Cards bounce that bounce in on mount.', + files: [ + { + path: 'src/content/Components/BounceCards/BounceCards.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/BounceCards/BounceCards.css', + type: 'registry:item' + } + ] + }, + { + name: 'BounceCards-JS-TW', + type: 'registry:block', + title: 'BounceCards', + description: 'Cards bounce that bounce in on mount.', + files: [ + { + path: 'src/tailwind/Components/BounceCards/BounceCards.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'BounceCards-TS-CSS', + type: 'registry:block', + title: 'BounceCards', + description: 'Cards bounce that bounce in on mount.', + files: [ + { + path: 'src/ts-default/Components/BounceCards/BounceCards.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/BounceCards/BounceCards.css', + type: 'registry:item' + } + ] + }, + { + name: 'BounceCards-TS-TW', + type: 'registry:block', + title: 'BounceCards', + description: 'Cards bounce that bounce in on mount.', + files: [ + { + path: 'src/ts-tailwind/Components/BounceCards/BounceCards.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'BubbleMenu-JS-CSS', + type: 'registry:block', + title: 'BubbleMenu', + description: 'Floating circular expanding menu with staggered item reveal.', + files: [ + { + path: 'src/content/Components/BubbleMenu/BubbleMenu.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/BubbleMenu/BubbleMenu.css', + type: 'registry:item' + } + ] + }, + { + name: 'BubbleMenu-JS-TW', + type: 'registry:block', + title: 'BubbleMenu', + description: 'Floating circular expanding menu with staggered item reveal.', + files: [ + { + path: 'src/tailwind/Components/BubbleMenu/BubbleMenu.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'BubbleMenu-TS-CSS', + type: 'registry:block', + title: 'BubbleMenu', + description: 'Floating circular expanding menu with staggered item reveal.', + files: [ + { + path: 'src/ts-default/Components/BubbleMenu/BubbleMenu.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/BubbleMenu/BubbleMenu.css', + type: 'registry:item' + } + ] + }, + { + name: 'BubbleMenu-TS-TW', + type: 'registry:block', + title: 'BubbleMenu', + description: 'Floating circular expanding menu with staggered item reveal.', + files: [ + { + path: 'src/ts-tailwind/Components/BubbleMenu/BubbleMenu.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'CardNav-JS-CSS', + type: 'registry:block', + title: 'CardNav', + description: 'Expandable navigation bar with card panels revealing nested links.', + files: [ + { + path: 'src/content/Components/CardNav/CardNav.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/CardNav/CardNav.css', + type: 'registry:item' + } + ] + }, + { + name: 'CardNav-JS-TW', + type: 'registry:block', + title: 'CardNav', + description: 'Expandable navigation bar with card panels revealing nested links.', + files: [ + { + path: 'src/tailwind/Components/CardNav/CardNav.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'CardNav-TS-CSS', + type: 'registry:block', + title: 'CardNav', + description: 'Expandable navigation bar with card panels revealing nested links.', + files: [ + { + path: 'src/ts-default/Components/CardNav/CardNav.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/CardNav/CardNav.css', + type: 'registry:item' + } + ] + }, + { + name: 'CardNav-TS-TW', + type: 'registry:block', + title: 'CardNav', + description: 'Expandable navigation bar with card panels revealing nested links.', + files: [ + { + path: 'src/ts-tailwind/Components/CardNav/CardNav.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'CardSwap-JS-CSS', + type: 'registry:block', + title: 'CardSwap', + description: 'Cards animate position swapping with smooth layout transitions.', + files: [ + { + path: 'src/content/Components/CardSwap/CardSwap.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/CardSwap/CardSwap.css', + type: 'registry:item' + } + ] + }, + { + name: 'CardSwap-JS-TW', + type: 'registry:block', + title: 'CardSwap', + description: 'Cards animate position swapping with smooth layout transitions.', + files: [ + { + path: 'src/tailwind/Components/CardSwap/CardSwap.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'CardSwap-TS-CSS', + type: 'registry:block', + title: 'CardSwap', + description: 'Cards animate position swapping with smooth layout transitions.', + files: [ + { + path: 'src/ts-default/Components/CardSwap/CardSwap.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/CardSwap/CardSwap.css', + type: 'registry:item' + } + ] + }, + { + name: 'CardSwap-TS-TW', + type: 'registry:block', + title: 'CardSwap', + description: 'Cards animate position swapping with smooth layout transitions.', + files: [ + { + path: 'src/ts-tailwind/Components/CardSwap/CardSwap.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Carousel-JS-CSS', + type: 'registry:block', + title: 'Carousel', + description: 'Responsive carousel with touch gestures, looping and transitions.', + files: [ + { + path: 'src/content/Components/Carousel/Carousel.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/Carousel/Carousel.css', + type: 'registry:item' + } + ] + }, + { + name: 'Carousel-JS-TW', + type: 'registry:block', + title: 'Carousel', + description: 'Responsive carousel with touch gestures, looping and transitions.', + files: [ + { + path: 'src/tailwind/Components/Carousel/Carousel.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Carousel-TS-CSS', + type: 'registry:block', + title: 'Carousel', + description: 'Responsive carousel with touch gestures, looping and transitions.', + files: [ + { + path: 'src/ts-default/Components/Carousel/Carousel.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/Carousel/Carousel.css', + type: 'registry:item' + } + ] + }, + { + name: 'Carousel-TS-TW', + type: 'registry:block', + title: 'Carousel', + description: 'Responsive carousel with touch gestures, looping and transitions.', + files: [ + { + path: 'src/ts-tailwind/Components/Carousel/Carousel.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ChromaGrid-JS-CSS', + type: 'registry:block', + title: 'ChromaGrid', + description: 'A responsive grid of grayscale tiles. Hovering the grid reaveals their colors.', + files: [ + { + path: 'src/content/Components/ChromaGrid/ChromaGrid.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/ChromaGrid/ChromaGrid.css', + type: 'registry:item' + } + ] + }, + { + name: 'ChromaGrid-JS-TW', + type: 'registry:block', + title: 'ChromaGrid', + description: 'A responsive grid of grayscale tiles. Hovering the grid reaveals their colors.', + files: [ + { + path: 'src/tailwind/Components/ChromaGrid/ChromaGrid.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ChromaGrid-TS-CSS', + type: 'registry:block', + title: 'ChromaGrid', + description: 'A responsive grid of grayscale tiles. Hovering the grid reaveals their colors.', + files: [ + { + path: 'src/ts-default/Components/ChromaGrid/ChromaGrid.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/ChromaGrid/ChromaGrid.css', + type: 'registry:item' + } + ] + }, + { + name: 'ChromaGrid-TS-TW', + type: 'registry:block', + title: 'ChromaGrid', + description: 'A responsive grid of grayscale tiles. Hovering the grid reaveals their colors.', + files: [ + { + path: 'src/ts-tailwind/Components/ChromaGrid/ChromaGrid.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'CircularGallery-JS-CSS', + type: 'registry:block', + title: 'CircularGallery', + description: 'Circular orbit gallery rotating images.', + files: [ + { + path: 'src/content/Components/CircularGallery/CircularGallery.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/CircularGallery/CircularGallery.css', + type: 'registry:item' + } + ] + }, + { + name: 'CircularGallery-JS-TW', + type: 'registry:block', + title: 'CircularGallery', + description: 'Circular orbit gallery rotating images.', + files: [ + { + path: 'src/tailwind/Components/CircularGallery/CircularGallery.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'CircularGallery-TS-CSS', + type: 'registry:block', + title: 'CircularGallery', + description: 'Circular orbit gallery rotating images.', + files: [ + { + path: 'src/ts-default/Components/CircularGallery/CircularGallery.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/CircularGallery/CircularGallery.css', + type: 'registry:item' + } + ] + }, + { + name: 'CircularGallery-TS-TW', + type: 'registry:block', + title: 'CircularGallery', + description: 'Circular orbit gallery rotating images.', + files: [ + { + path: 'src/ts-tailwind/Components/CircularGallery/CircularGallery.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'CircularText-JS-CSS', + type: 'registry:block', + title: 'CircularText', + description: 'Layouts characters around a circle with optional rotation animation.', + files: [ + { + path: 'src/content/TextAnimations/CircularText/CircularText.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/CircularText/CircularText.css', + type: 'registry:item' + } + ] + }, + { + name: 'CircularText-JS-TW', + type: 'registry:block', + title: 'CircularText', + description: 'Layouts characters around a circle with optional rotation animation.', + files: [ + { + path: 'src/tailwind/TextAnimations/CircularText/CircularText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'CircularText-TS-CSS', + type: 'registry:block', + title: 'CircularText', + description: 'Layouts characters around a circle with optional rotation animation.', + files: [ + { + path: 'src/ts-default/TextAnimations/CircularText/CircularText.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/CircularText/CircularText.css', + type: 'registry:item' + } + ] + }, + { + name: 'CircularText-TS-TW', + type: 'registry:block', + title: 'CircularText', + description: 'Layouts characters around a circle with optional rotation animation.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/CircularText/CircularText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ClickSpark-JS-CSS', + type: 'registry:block', + title: 'ClickSpark', + description: 'Creates particle spark bursts at click position.', + files: [ + { + path: 'src/content/Animations/ClickSpark/ClickSpark.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ClickSpark-JS-TW', + type: 'registry:block', + title: 'ClickSpark', + description: 'Creates particle spark bursts at click position.', + files: [ + { + path: 'src/tailwind/Animations/ClickSpark/ClickSpark.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ClickSpark-TS-CSS', + type: 'registry:block', + title: 'ClickSpark', + description: 'Creates particle spark bursts at click position.', + files: [ + { + path: 'src/ts-default/Animations/ClickSpark/ClickSpark.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ClickSpark-TS-TW', + type: 'registry:block', + title: 'ClickSpark', + description: 'Creates particle spark bursts at click position.', + files: [ + { + path: 'src/ts-tailwind/Animations/ClickSpark/ClickSpark.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ColorBends-JS-CSS', + type: 'registry:block', + title: 'ColorBends', + description: 'Vibrant color bends with smooth flowing animation.', + files: [ + { + path: 'src/content/Backgrounds/ColorBends/ColorBends.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/ColorBends/ColorBends.css', + type: 'registry:item' + } + ] + }, + { + name: 'ColorBends-JS-TW', + type: 'registry:block', + title: 'ColorBends', + description: 'Vibrant color bends with smooth flowing animation.', + files: [ + { + path: 'src/tailwind/Backgrounds/ColorBends/ColorBends.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ColorBends-TS-CSS', + type: 'registry:block', + title: 'ColorBends', + description: 'Vibrant color bends with smooth flowing animation.', + files: [ + { + path: 'src/ts-default/Backgrounds/ColorBends/ColorBends.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/ColorBends/ColorBends.css', + type: 'registry:item' + } + ] + }, + { + name: 'ColorBends-TS-TW', + type: 'registry:block', + title: 'ColorBends', + description: 'Vibrant color bends with smooth flowing animation.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/ColorBends/ColorBends.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Counter-JS-CSS', + type: 'registry:block', + title: 'Counter', + description: 'Flexible animated counter supporting increments + easing.', + files: [ + { + path: 'src/content/Components/Counter/Counter.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/Counter/Counter.css', + type: 'registry:item' + } + ] + }, + { + name: 'Counter-JS-TW', + type: 'registry:block', + title: 'Counter', + description: 'Flexible animated counter supporting increments + easing.', + files: [ + { + path: 'src/tailwind/Components/Counter/Counter.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Counter-TS-CSS', + type: 'registry:block', + title: 'Counter', + description: 'Flexible animated counter supporting increments + easing.', + files: [ + { + path: 'src/ts-default/Components/Counter/Counter.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/Counter/Counter.css', + type: 'registry:item' + } + ] + }, + { + name: 'Counter-TS-TW', + type: 'registry:block', + title: 'Counter', + description: 'Flexible animated counter supporting increments + easing.', + files: [ + { + path: 'src/ts-tailwind/Components/Counter/Counter.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'CountUp-JS-CSS', + type: 'registry:block', + title: 'CountUp', + description: 'Animated number counter supporting formatting and decimals.', + files: [ + { + path: 'src/content/TextAnimations/CountUp/CountUp.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'CountUp-JS-TW', + type: 'registry:block', + title: 'CountUp', + description: 'Animated number counter supporting formatting and decimals.', + files: [ + { + path: 'src/tailwind/TextAnimations/CountUp/CountUp.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'CountUp-TS-CSS', + type: 'registry:block', + title: 'CountUp', + description: 'Animated number counter supporting formatting and decimals.', + files: [ + { + path: 'src/ts-default/TextAnimations/CountUp/CountUp.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'CountUp-TS-TW', + type: 'registry:block', + title: 'CountUp', + description: 'Animated number counter supporting formatting and decimals.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/CountUp/CountUp.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Crosshair-JS-CSS', + type: 'registry:block', + title: 'Crosshair', + description: 'Custom crosshair cursor with tracking, and link hover effects.', + files: [ + { + path: 'src/content/Animations/Crosshair/Crosshair.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Crosshair-JS-TW', + type: 'registry:block', + title: 'Crosshair', + description: 'Custom crosshair cursor with tracking, and link hover effects.', + files: [ + { + path: 'src/tailwind/Animations/Crosshair/Crosshair.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Crosshair-TS-CSS', + type: 'registry:block', + title: 'Crosshair', + description: 'Custom crosshair cursor with tracking, and link hover effects.', + files: [ + { + path: 'src/ts-default/Animations/Crosshair/Crosshair.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Crosshair-TS-TW', + type: 'registry:block', + title: 'Crosshair', + description: 'Custom crosshair cursor with tracking, and link hover effects.', + files: [ + { + path: 'src/ts-tailwind/Animations/Crosshair/Crosshair.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Cubes-JS-CSS', + type: 'registry:block', + title: 'Cubes', + description: '3D rotating cube cluster. Supports auto-rotation or hover interaction.', + files: [ + { + path: 'src/content/Animations/Cubes/Cubes.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/Cubes/Cubes.css', + type: 'registry:item' + } + ] + }, + { + name: 'Cubes-JS-TW', + type: 'registry:block', + title: 'Cubes', + description: '3D rotating cube cluster. Supports auto-rotation or hover interaction.', + files: [ + { + path: 'src/tailwind/Animations/Cubes/Cubes.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Cubes-TS-CSS', + type: 'registry:block', + title: 'Cubes', + description: '3D rotating cube cluster. Supports auto-rotation or hover interaction.', + files: [ + { + path: 'src/ts-default/Animations/Cubes/Cubes.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/Cubes/Cubes.css', + type: 'registry:item' + } + ] + }, + { + name: 'Cubes-TS-TW', + type: 'registry:block', + title: 'Cubes', + description: '3D rotating cube cluster. Supports auto-rotation or hover interaction.', + files: [ + { + path: 'src/ts-tailwind/Animations/Cubes/Cubes.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'CurvedLoop-JS-CSS', + type: 'registry:block', + title: 'CurvedLoop', + description: 'Flowing looping text path along a customizable curve with drag interaction.', + files: [ + { + path: 'src/content/TextAnimations/CurvedLoop/CurvedLoop.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/CurvedLoop/CurvedLoop.css', + type: 'registry:item' + } + ] + }, + { + name: 'CurvedLoop-JS-TW', + type: 'registry:block', + title: 'CurvedLoop', + description: 'Flowing looping text path along a customizable curve with drag interaction.', + files: [ + { + path: 'src/tailwind/TextAnimations/CurvedLoop/CurvedLoop.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'CurvedLoop-TS-CSS', + type: 'registry:block', + title: 'CurvedLoop', + description: 'Flowing looping text path along a customizable curve with drag interaction.', + files: [ + { + path: 'src/ts-default/TextAnimations/CurvedLoop/CurvedLoop.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/CurvedLoop/CurvedLoop.css', + type: 'registry:item' + } + ] + }, + { + name: 'CurvedLoop-TS-TW', + type: 'registry:block', + title: 'CurvedLoop', + description: 'Flowing looping text path along a customizable curve with drag interaction.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/CurvedLoop/CurvedLoop.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'DarkVeil-JS-CSS', + type: 'registry:block', + title: 'DarkVeil', + description: 'Subtle dark background with a smooth animation and postprocessing.', + files: [ + { + path: 'src/content/Backgrounds/DarkVeil/DarkVeil.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/DarkVeil/DarkVeil.css', + type: 'registry:item' + } + ] + }, + { + name: 'DarkVeil-JS-TW', + type: 'registry:block', + title: 'DarkVeil', + description: 'Subtle dark background with a smooth animation and postprocessing.', + files: [ + { + path: 'src/tailwind/Backgrounds/DarkVeil/DarkVeil.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'DarkVeil-TS-CSS', + type: 'registry:block', + title: 'DarkVeil', + description: 'Subtle dark background with a smooth animation and postprocessing.', + files: [ + { + path: 'src/ts-default/Backgrounds/DarkVeil/DarkVeil.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/DarkVeil/DarkVeil.css', + type: 'registry:item' + } + ] + }, + { + name: 'DarkVeil-TS-TW', + type: 'registry:block', + title: 'DarkVeil', + description: 'Subtle dark background with a smooth animation and postprocessing.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/DarkVeil/DarkVeil.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'DecayCard-JS-CSS', + type: 'registry:block', + title: 'DecayCard', + description: 'Hover parallax effect that disintegrates the content of a card.', + files: [ + { + path: 'src/content/Components/DecayCard/DecayCard.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/DecayCard/DecayCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'DecayCard-JS-TW', + type: 'registry:block', + title: 'DecayCard', + description: 'Hover parallax effect that disintegrates the content of a card.', + files: [ + { + path: 'src/tailwind/Components/DecayCard/DecayCard.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'DecayCard-TS-CSS', + type: 'registry:block', + title: 'DecayCard', + description: 'Hover parallax effect that disintegrates the content of a card.', + files: [ + { + path: 'src/ts-default/Components/DecayCard/DecayCard.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/DecayCard/DecayCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'DecayCard-TS-TW', + type: 'registry:block', + title: 'DecayCard', + description: 'Hover parallax effect that disintegrates the content of a card.', + files: [ + { + path: 'src/ts-tailwind/Components/DecayCard/DecayCard.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'DecryptedText-JS-CSS', + type: 'registry:block', + title: 'DecryptedText', + description: 'Hacker-style decryption cycling random glyphs until resolving to real text.', + files: [ + { + path: 'src/content/TextAnimations/DecryptedText/DecryptedText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'DecryptedText-JS-TW', + type: 'registry:block', + title: 'DecryptedText', + description: 'Hacker-style decryption cycling random glyphs until resolving to real text.', + files: [ + { + path: 'src/tailwind/TextAnimations/DecryptedText/DecryptedText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'DecryptedText-TS-CSS', + type: 'registry:block', + title: 'DecryptedText', + description: 'Hacker-style decryption cycling random glyphs until resolving to real text.', + files: [ + { + path: 'src/ts-default/TextAnimations/DecryptedText/DecryptedText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'DecryptedText-TS-TW', + type: 'registry:block', + title: 'DecryptedText', + description: 'Hacker-style decryption cycling random glyphs until resolving to real text.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/DecryptedText/DecryptedText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Dither-JS-CSS', + type: 'registry:block', + title: 'Dither', + description: 'Retro dithered noise shader background.', + files: [ + { + path: 'src/content/Backgrounds/Dither/Dither.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Dither/Dither.css', + type: 'registry:item' + } + ] + }, + { + name: 'Dither-JS-TW', + type: 'registry:block', + title: 'Dither', + description: 'Retro dithered noise shader background.', + files: [ + { + path: 'src/tailwind/Backgrounds/Dither/Dither.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Dither-TS-CSS', + type: 'registry:block', + title: 'Dither', + description: 'Retro dithered noise shader background.', + files: [ + { + path: 'src/ts-default/Backgrounds/Dither/Dither.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Dither/Dither.css', + type: 'registry:item' + } + ] + }, + { + name: 'Dither-TS-TW', + type: 'registry:block', + title: 'Dither', + description: 'Retro dithered noise shader background.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Dither/Dither.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Dock-JS-CSS', + type: 'registry:block', + title: 'Dock', + description: 'macOS style magnifying dock with proximity scaling of icons.', + files: [ + { + path: 'src/content/Components/Dock/Dock.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/Dock/Dock.css', + type: 'registry:item' + } + ] + }, + { + name: 'Dock-JS-TW', + type: 'registry:block', + title: 'Dock', + description: 'macOS style magnifying dock with proximity scaling of icons.', + files: [ + { + path: 'src/tailwind/Components/Dock/Dock.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Dock-TS-CSS', + type: 'registry:block', + title: 'Dock', + description: 'macOS style magnifying dock with proximity scaling of icons.', + files: [ + { + path: 'src/ts-default/Components/Dock/Dock.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/Dock/Dock.css', + type: 'registry:item' + } + ] + }, + { + name: 'Dock-TS-TW', + type: 'registry:block', + title: 'Dock', + description: 'macOS style magnifying dock with proximity scaling of icons.', + files: [ + { + path: 'src/ts-tailwind/Components/Dock/Dock.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'DomeGallery-JS-CSS', + type: 'registry:block', + title: 'DomeGallery', + description: 'Immersive 3D dome gallery projecting images on a hemispheric surface.', + files: [ + { + path: 'src/content/Components/DomeGallery/DomeGallery.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/DomeGallery/DomeGallery.css', + type: 'registry:item' + } + ] + }, + { + name: 'DomeGallery-JS-TW', + type: 'registry:block', + title: 'DomeGallery', + description: 'Immersive 3D dome gallery projecting images on a hemispheric surface.', + files: [ + { + path: 'src/tailwind/Components/DomeGallery/DomeGallery.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'DomeGallery-TS-CSS', + type: 'registry:block', + title: 'DomeGallery', + description: 'Immersive 3D dome gallery projecting images on a hemispheric surface.', + files: [ + { + path: 'src/ts-default/Components/DomeGallery/DomeGallery.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/DomeGallery/DomeGallery.css', + type: 'registry:item' + } + ] + }, + { + name: 'DomeGallery-TS-TW', + type: 'registry:block', + title: 'DomeGallery', + description: 'Immersive 3D dome gallery projecting images on a hemispheric surface.', + files: [ + { + path: 'src/ts-tailwind/Components/DomeGallery/DomeGallery.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'DotGrid-JS-CSS', + type: 'registry:block', + title: 'DotGrid', + description: 'Animated dot grid with cursor interactions.', + files: [ + { + path: 'src/content/Backgrounds/DotGrid/DotGrid.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/DotGrid/DotGrid.css', + type: 'registry:item' + } + ] + }, + { + name: 'DotGrid-JS-TW', + type: 'registry:block', + title: 'DotGrid', + description: 'Animated dot grid with cursor interactions.', + files: [ + { + path: 'src/tailwind/Backgrounds/DotGrid/DotGrid.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'DotGrid-TS-CSS', + type: 'registry:block', + title: 'DotGrid', + description: 'Animated dot grid with cursor interactions.', + files: [ + { + path: 'src/ts-default/Backgrounds/DotGrid/DotGrid.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/DotGrid/DotGrid.css', + type: 'registry:item' + } + ] + }, + { + name: 'DotGrid-TS-TW', + type: 'registry:block', + title: 'DotGrid', + description: 'Animated dot grid with cursor interactions.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/DotGrid/DotGrid.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ElasticSlider-JS-CSS', + type: 'registry:block', + title: 'ElasticSlider', + description: 'Slider handle stretches elastically then snaps with spring physics.', + files: [ + { + path: 'src/content/Components/ElasticSlider/ElasticSlider.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/ElasticSlider/ElasticSlider.css', + type: 'registry:item' + } + ] + }, + { + name: 'ElasticSlider-JS-TW', + type: 'registry:block', + title: 'ElasticSlider', + description: 'Slider handle stretches elastically then snaps with spring physics.', + files: [ + { + path: 'src/tailwind/Components/ElasticSlider/ElasticSlider.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ElasticSlider-TS-CSS', + type: 'registry:block', + title: 'ElasticSlider', + description: 'Slider handle stretches elastically then snaps with spring physics.', + files: [ + { + path: 'src/ts-default/Components/ElasticSlider/ElasticSlider.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/ElasticSlider/ElasticSlider.css', + type: 'registry:item' + } + ] + }, + { + name: 'ElasticSlider-TS-TW', + type: 'registry:block', + title: 'ElasticSlider', + description: 'Slider handle stretches elastically then snaps with spring physics.', + files: [ + { + path: 'src/ts-tailwind/Components/ElasticSlider/ElasticSlider.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ElectricBorder-JS-CSS', + type: 'registry:block', + title: 'ElectricBorder', + description: 'Jittery electric energy border with animated arcs, glow and adjustable intensity.', + files: [ + { + path: 'src/content/Animations/ElectricBorder/ElectricBorder.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/ElectricBorder/ElectricBorder.css', + type: 'registry:item' + } + ] + }, + { + name: 'ElectricBorder-JS-TW', + type: 'registry:block', + title: 'ElectricBorder', + description: 'Jittery electric energy border with animated arcs, glow and adjustable intensity.', + files: [ + { + path: 'src/tailwind/Animations/ElectricBorder/ElectricBorder.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ElectricBorder-TS-CSS', + type: 'registry:block', + title: 'ElectricBorder', + description: 'Jittery electric energy border with animated arcs, glow and adjustable intensity.', + files: [ + { + path: 'src/ts-default/Animations/ElectricBorder/ElectricBorder.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/ElectricBorder/ElectricBorder.css', + type: 'registry:item' + } + ] + }, + { + name: 'ElectricBorder-TS-TW', + type: 'registry:block', + title: 'ElectricBorder', + description: 'Jittery electric energy border with animated arcs, glow and adjustable intensity.', + files: [ + { + path: 'src/ts-tailwind/Animations/ElectricBorder/ElectricBorder.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FadeContent-JS-CSS', + type: 'registry:block', + title: 'FadeContent', + description: 'Simple directional fade / slide entrance wrapper with threshold-based activation.', + files: [ + { + path: 'src/content/Animations/FadeContent/FadeContent.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FadeContent-JS-TW', + type: 'registry:block', + title: 'FadeContent', + description: 'Simple directional fade / slide entrance wrapper with threshold-based activation.', + files: [ + { + path: 'src/tailwind/Animations/FadeContent/FadeContent.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FadeContent-TS-CSS', + type: 'registry:block', + title: 'FadeContent', + description: 'Simple directional fade / slide entrance wrapper with threshold-based activation.', + files: [ + { + path: 'src/ts-default/Animations/FadeContent/FadeContent.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FadeContent-TS-TW', + type: 'registry:block', + title: 'FadeContent', + description: 'Simple directional fade / slide entrance wrapper with threshold-based activation.', + files: [ + { + path: 'src/ts-tailwind/Animations/FadeContent/FadeContent.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FallingText-JS-CSS', + type: 'registry:block', + title: 'FallingText', + description: 'Characters fall with gravity + bounce creating a playful entrance.', + files: [ + { + path: 'src/content/TextAnimations/FallingText/FallingText.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/FallingText/FallingText.css', + type: 'registry:item' + } + ] + }, + { + name: 'FallingText-JS-TW', + type: 'registry:block', + title: 'FallingText', + description: 'Characters fall with gravity + bounce creating a playful entrance.', + files: [ + { + path: 'src/tailwind/TextAnimations/FallingText/FallingText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FallingText-TS-CSS', + type: 'registry:block', + title: 'FallingText', + description: 'Characters fall with gravity + bounce creating a playful entrance.', + files: [ + { + path: 'src/ts-default/TextAnimations/FallingText/FallingText.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/FallingText/FallingText.css', + type: 'registry:item' + } + ] + }, + { + name: 'FallingText-TS-TW', + type: 'registry:block', + title: 'FallingText', + description: 'Characters fall with gravity + bounce creating a playful entrance.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/FallingText/FallingText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FaultyTerminal-JS-CSS', + type: 'registry:block', + title: 'FaultyTerminal', + description: 'Terminal CRT scanline squares effect with flicker + noise.', + files: [ + { + path: 'src/content/Backgrounds/FaultyTerminal/FaultyTerminal.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/FaultyTerminal/FaultyTerminal.css', + type: 'registry:item' + } + ] + }, + { + name: 'FaultyTerminal-JS-TW', + type: 'registry:block', + title: 'FaultyTerminal', + description: 'Terminal CRT scanline squares effect with flicker + noise.', + files: [ + { + path: 'src/tailwind/Backgrounds/FaultyTerminal/FaultyTerminal.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FaultyTerminal-TS-CSS', + type: 'registry:block', + title: 'FaultyTerminal', + description: 'Terminal CRT scanline squares effect with flicker + noise.', + files: [ + { + path: 'src/ts-default/Backgrounds/FaultyTerminal/FaultyTerminal.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/FaultyTerminal/FaultyTerminal.css', + type: 'registry:item' + } + ] + }, + { + name: 'FaultyTerminal-TS-TW', + type: 'registry:block', + title: 'FaultyTerminal', + description: 'Terminal CRT scanline squares effect with flicker + noise.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/FaultyTerminal/FaultyTerminal.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FlowingMenu-JS-CSS', + type: 'registry:block', + title: 'FlowingMenu', + description: 'Liquid flowing active indicator glides between menu items.', + files: [ + { + path: 'src/content/Components/FlowingMenu/FlowingMenu.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/FlowingMenu/FlowingMenu.css', + type: 'registry:item' + } + ] + }, + { + name: 'FlowingMenu-JS-TW', + type: 'registry:block', + title: 'FlowingMenu', + description: 'Liquid flowing active indicator glides between menu items.', + files: [ + { + path: 'src/tailwind/Components/FlowingMenu/FlowingMenu.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FlowingMenu-TS-CSS', + type: 'registry:block', + title: 'FlowingMenu', + description: 'Liquid flowing active indicator glides between menu items.', + files: [ + { + path: 'src/ts-default/Components/FlowingMenu/FlowingMenu.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/FlowingMenu/FlowingMenu.css', + type: 'registry:item' + } + ] + }, + { + name: 'FlowingMenu-TS-TW', + type: 'registry:block', + title: 'FlowingMenu', + description: 'Liquid flowing active indicator glides between menu items.', + files: [ + { + path: 'src/ts-tailwind/Components/FlowingMenu/FlowingMenu.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FluidGlass-JS-CSS', + type: 'registry:block', + title: 'FluidGlass', + description: 'Glassmorphism container with animated liquid distortion refraction.', + files: [ + { + path: 'src/content/Components/FluidGlass/FluidGlass.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FluidGlass-JS-TW', + type: 'registry:block', + title: 'FluidGlass', + description: 'Glassmorphism container with animated liquid distortion refraction.', + files: [ + { + path: 'src/tailwind/Components/FluidGlass/FluidGlass.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FluidGlass-TS-CSS', + type: 'registry:block', + title: 'FluidGlass', + description: 'Glassmorphism container with animated liquid distortion refraction.', + files: [ + { + path: 'src/ts-default/Components/FluidGlass/FluidGlass.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FluidGlass-TS-TW', + type: 'registry:block', + title: 'FluidGlass', + description: 'Glassmorphism container with animated liquid distortion refraction.', + files: [ + { + path: 'src/ts-tailwind/Components/FluidGlass/FluidGlass.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FlyingPosters-JS-CSS', + type: 'registry:block', + title: 'FlyingPosters', + description: '3D posters rotate on scroll infinitely.', + files: [ + { + path: 'src/content/Components/FlyingPosters/FlyingPosters.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/FlyingPosters/FlyingPosters.css', + type: 'registry:item' + } + ] + }, + { + name: 'FlyingPosters-JS-TW', + type: 'registry:block', + title: 'FlyingPosters', + description: '3D posters rotate on scroll infinitely.', + files: [ + { + path: 'src/tailwind/Components/FlyingPosters/FlyingPosters.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FlyingPosters-TS-CSS', + type: 'registry:block', + title: 'FlyingPosters', + description: '3D posters rotate on scroll infinitely.', + files: [ + { + path: 'src/ts-default/Components/FlyingPosters/FlyingPosters.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/FlyingPosters/FlyingPosters.css', + type: 'registry:item' + } + ] + }, + { + name: 'FlyingPosters-TS-TW', + type: 'registry:block', + title: 'FlyingPosters', + description: '3D posters rotate on scroll infinitely.', + files: [ + { + path: 'src/ts-tailwind/Components/FlyingPosters/FlyingPosters.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Folder-JS-CSS', + type: 'registry:block', + title: 'Folder', + description: 'Interactive folder opens to reveal nested content smooth motion.', + files: [ + { + path: 'src/content/Components/Folder/Folder.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/Folder/Folder.css', + type: 'registry:item' + } + ] + }, + { + name: 'Folder-JS-TW', + type: 'registry:block', + title: 'Folder', + description: 'Interactive folder opens to reveal nested content smooth motion.', + files: [ + { + path: 'src/tailwind/Components/Folder/Folder.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Folder-TS-CSS', + type: 'registry:block', + title: 'Folder', + description: 'Interactive folder opens to reveal nested content smooth motion.', + files: [ + { + path: 'src/ts-default/Components/Folder/Folder.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/Folder/Folder.css', + type: 'registry:item' + } + ] + }, + { + name: 'Folder-TS-TW', + type: 'registry:block', + title: 'Folder', + description: 'Interactive folder opens to reveal nested content smooth motion.', + files: [ + { + path: 'src/ts-tailwind/Components/Folder/Folder.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FuzzyText-JS-CSS', + type: 'registry:block', + title: 'FuzzyText', + description: 'Vibrating fuzzy text with controllable hover intensity.', + files: [ + { + path: 'src/content/TextAnimations/FuzzyText/FuzzyText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FuzzyText-JS-TW', + type: 'registry:block', + title: 'FuzzyText', + description: 'Vibrating fuzzy text with controllable hover intensity.', + files: [ + { + path: 'src/tailwind/TextAnimations/FuzzyText/FuzzyText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'FuzzyText-TS-CSS', + type: 'registry:block', + title: 'FuzzyText', + description: 'Vibrating fuzzy text with controllable hover intensity.', + files: [ + { + path: 'src/ts-default/TextAnimations/FuzzyText/FuzzyText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'FuzzyText-TS-TW', + type: 'registry:block', + title: 'FuzzyText', + description: 'Vibrating fuzzy text with controllable hover intensity.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/FuzzyText/FuzzyText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Galaxy-JS-CSS', + type: 'registry:block', + title: 'Galaxy', + description: 'Parallax realistic starfield with pointer interactions.', + files: [ + { + path: 'src/content/Backgrounds/Galaxy/Galaxy.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Galaxy/Galaxy.css', + type: 'registry:item' + } + ] + }, + { + name: 'Galaxy-JS-TW', + type: 'registry:block', + title: 'Galaxy', + description: 'Parallax realistic starfield with pointer interactions.', + files: [ + { + path: 'src/tailwind/Backgrounds/Galaxy/Galaxy.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Galaxy-TS-CSS', + type: 'registry:block', + title: 'Galaxy', + description: 'Parallax realistic starfield with pointer interactions.', + files: [ + { + path: 'src/ts-default/Backgrounds/Galaxy/Galaxy.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Galaxy/Galaxy.css', + type: 'registry:item' + } + ] + }, + { + name: 'Galaxy-TS-TW', + type: 'registry:block', + title: 'Galaxy', + description: 'Parallax realistic starfield with pointer interactions.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Galaxy/Galaxy.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GhostCursor-JS-CSS', + type: 'registry:block', + title: 'GhostCursor', + description: 'Semi-transparent ghost cursor that smoothly follows the real cursor with a trailing effect.', + files: [ + { + path: 'src/content/Animations/GhostCursor/GhostCursor.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/GhostCursor/GhostCursor.css', + type: 'registry:item' + } + ] + }, + { + name: 'GhostCursor-JS-TW', + type: 'registry:block', + title: 'GhostCursor', + description: 'Semi-transparent ghost cursor that smoothly follows the real cursor with a trailing effect.', + files: [ + { + path: 'src/tailwind/Animations/GhostCursor/GhostCursor.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GhostCursor-TS-CSS', + type: 'registry:block', + title: 'GhostCursor', + description: 'Semi-transparent ghost cursor that smoothly follows the real cursor with a trailing effect.', + files: [ + { + path: 'src/ts-default/Animations/GhostCursor/GhostCursor.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/GhostCursor/GhostCursor.css', + type: 'registry:item' + } + ] + }, + { + name: 'GhostCursor-TS-TW', + type: 'registry:block', + title: 'GhostCursor', + description: 'Semi-transparent ghost cursor that smoothly follows the real cursor with a trailing effect.', + files: [ + { + path: 'src/ts-tailwind/Animations/GhostCursor/GhostCursor.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GlareHover-JS-CSS', + type: 'registry:block', + title: 'GlareHover', + description: 'Adds a realistic moving glare highlight on hover over any element.', + files: [ + { + path: 'src/content/Animations/GlareHover/GlareHover.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/GlareHover/GlareHover.css', + type: 'registry:item' + } + ] + }, + { + name: 'GlareHover-JS-TW', + type: 'registry:block', + title: 'GlareHover', + description: 'Adds a realistic moving glare highlight on hover over any element.', + files: [ + { + path: 'src/tailwind/Animations/GlareHover/GlareHover.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GlareHover-TS-CSS', + type: 'registry:block', + title: 'GlareHover', + description: 'Adds a realistic moving glare highlight on hover over any element.', + files: [ + { + path: 'src/ts-default/Animations/GlareHover/GlareHover.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/GlareHover/GlareHover.css', + type: 'registry:item' + } + ] + }, + { + name: 'GlareHover-TS-TW', + type: 'registry:block', + title: 'GlareHover', + description: 'Adds a realistic moving glare highlight on hover over any element.', + files: [ + { + path: 'src/ts-tailwind/Animations/GlareHover/GlareHover.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GlassIcons-JS-CSS', + type: 'registry:block', + title: 'GlassIcons', + description: 'Icon set styled with frosted glass blur.', + files: [ + { + path: 'src/content/Components/GlassIcons/GlassIcons.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/GlassIcons/GlassIcons.css', + type: 'registry:item' + } + ] + }, + { + name: 'GlassIcons-JS-TW', + type: 'registry:block', + title: 'GlassIcons', + description: 'Icon set styled with frosted glass blur.', + files: [ + { + path: 'src/tailwind/Components/GlassIcons/GlassIcons.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GlassIcons-TS-CSS', + type: 'registry:block', + title: 'GlassIcons', + description: 'Icon set styled with frosted glass blur.', + files: [ + { + path: 'src/ts-default/Components/GlassIcons/GlassIcons.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/GlassIcons/GlassIcons.css', + type: 'registry:item' + } + ] + }, + { + name: 'GlassIcons-TS-TW', + type: 'registry:block', + title: 'GlassIcons', + description: 'Icon set styled with frosted glass blur.', + files: [ + { + path: 'src/ts-tailwind/Components/GlassIcons/GlassIcons.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GlassSurface-JS-CSS', + type: 'registry:block', + title: 'GlassSurface', + description: 'Advanced Apple-style glass surface with real-time distortion + lighting.', + files: [ + { + path: 'src/content/Components/GlassSurface/GlassSurface.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/GlassSurface/GlassSurface.css', + type: 'registry:item' + } + ] + }, + { + name: 'GlassSurface-JS-TW', + type: 'registry:block', + title: 'GlassSurface', + description: 'Advanced Apple-style glass surface with real-time distortion + lighting.', + files: [ + { + path: 'src/tailwind/Components/GlassSurface/GlassSurface.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GlassSurface-TS-CSS', + type: 'registry:block', + title: 'GlassSurface', + description: 'Advanced Apple-style glass surface with real-time distortion + lighting.', + files: [ + { + path: 'src/ts-default/Components/GlassSurface/GlassSurface.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/GlassSurface/GlassSurface.css', + type: 'registry:item' + } + ] + }, + { + name: 'GlassSurface-TS-TW', + type: 'registry:block', + title: 'GlassSurface', + description: 'Advanced Apple-style glass surface with real-time distortion + lighting.', + files: [ + { + path: 'src/ts-tailwind/Components/GlassSurface/GlassSurface.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GlitchText-JS-CSS', + type: 'registry:block', + title: 'GlitchText', + description: 'RGB split and distortion glitch effect with jitter effects.', + files: [ + { + path: 'src/content/TextAnimations/GlitchText/GlitchText.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/GlitchText/GlitchText.css', + type: 'registry:item' + } + ] + }, + { + name: 'GlitchText-JS-TW', + type: 'registry:block', + title: 'GlitchText', + description: 'RGB split and distortion glitch effect with jitter effects.', + files: [ + { + path: 'src/tailwind/TextAnimations/GlitchText/GlitchText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GlitchText-TS-CSS', + type: 'registry:block', + title: 'GlitchText', + description: 'RGB split and distortion glitch effect with jitter effects.', + files: [ + { + path: 'src/ts-default/TextAnimations/GlitchText/GlitchText.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/GlitchText/GlitchText.css', + type: 'registry:item' + } + ] + }, + { + name: 'GlitchText-TS-TW', + type: 'registry:block', + title: 'GlitchText', + description: 'RGB split and distortion glitch effect with jitter effects.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/GlitchText/GlitchText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GooeyNav-JS-CSS', + type: 'registry:block', + title: 'GooeyNav', + description: 'Navigation indicator morphs with gooey blob transitions between items.', + files: [ + { + path: 'src/content/Components/GooeyNav/GooeyNav.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/GooeyNav/GooeyNav.css', + type: 'registry:item' + } + ] + }, + { + name: 'GooeyNav-JS-TW', + type: 'registry:block', + title: 'GooeyNav', + description: 'Navigation indicator morphs with gooey blob transitions between items.', + files: [ + { + path: 'src/tailwind/Components/GooeyNav/GooeyNav.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GooeyNav-TS-CSS', + type: 'registry:block', + title: 'GooeyNav', + description: 'Navigation indicator morphs with gooey blob transitions between items.', + files: [ + { + path: 'src/ts-default/Components/GooeyNav/GooeyNav.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/GooeyNav/GooeyNav.css', + type: 'registry:item' + } + ] + }, + { + name: 'GooeyNav-TS-TW', + type: 'registry:block', + title: 'GooeyNav', + description: 'Navigation indicator morphs with gooey blob transitions between items.', + files: [ + { + path: 'src/ts-tailwind/Components/GooeyNav/GooeyNav.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GradientBlinds-JS-CSS', + type: 'registry:block', + title: 'GradientBlinds', + description: 'Layered gradient blinds with spotlight and noise distortion.', + files: [ + { + path: 'src/content/Backgrounds/GradientBlinds/GradientBlinds.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/GradientBlinds/GradientBlinds.css', + type: 'registry:item' + } + ] + }, + { + name: 'GradientBlinds-JS-TW', + type: 'registry:block', + title: 'GradientBlinds', + description: 'Layered gradient blinds with spotlight and noise distortion.', + files: [ + { + path: 'src/tailwind/Backgrounds/GradientBlinds/GradientBlinds.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GradientBlinds-TS-CSS', + type: 'registry:block', + title: 'GradientBlinds', + description: 'Layered gradient blinds with spotlight and noise distortion.', + files: [ + { + path: 'src/ts-default/Backgrounds/GradientBlinds/GradientBlinds.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/GradientBlinds/GradientBlinds.css', + type: 'registry:item' + } + ] + }, + { + name: 'GradientBlinds-TS-TW', + type: 'registry:block', + title: 'GradientBlinds', + description: 'Layered gradient blinds with spotlight and noise distortion.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/GradientBlinds/GradientBlinds.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GradientText-JS-CSS', + type: 'registry:block', + title: 'GradientText', + description: 'Animated gradient sweep across live text with speed and color control.', + files: [ + { + path: 'src/content/TextAnimations/GradientText/GradientText.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/GradientText/GradientText.css', + type: 'registry:item' + } + ] + }, + { + name: 'GradientText-JS-TW', + type: 'registry:block', + title: 'GradientText', + description: 'Animated gradient sweep across live text with speed and color control.', + files: [ + { + path: 'src/tailwind/TextAnimations/GradientText/GradientText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GradientText-TS-CSS', + type: 'registry:block', + title: 'GradientText', + description: 'Animated gradient sweep across live text with speed and color control.', + files: [ + { + path: 'src/ts-default/TextAnimations/GradientText/GradientText.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/GradientText/GradientText.css', + type: 'registry:item' + } + ] + }, + { + name: 'GradientText-TS-TW', + type: 'registry:block', + title: 'GradientText', + description: 'Animated gradient sweep across live text with speed and color control.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/GradientText/GradientText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GradualBlur-JS-CSS', + type: 'registry:block', + title: 'GradualBlur', + description: 'Progressively un-blurs content based on scroll or trigger creating a cinematic reveal.', + files: [ + { + path: 'src/content/Animations/GradualBlur/GradualBlur.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/GradualBlur/GradualBlur.css', + type: 'registry:item' + } + ] + }, + { + name: 'GradualBlur-JS-TW', + type: 'registry:block', + title: 'GradualBlur', + description: 'Progressively un-blurs content based on scroll or trigger creating a cinematic reveal.', + files: [ + { + path: 'src/tailwind/Animations/GradualBlur/GradualBlur.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GradualBlur-TS-CSS', + type: 'registry:block', + title: 'GradualBlur', + description: 'Progressively un-blurs content based on scroll or trigger creating a cinematic reveal.', + files: [ + { + path: 'src/ts-default/Animations/GradualBlur/GradualBlur.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/GradualBlur/GradualBlur.css', + type: 'registry:item' + } + ] + }, + { + name: 'GradualBlur-TS-TW', + type: 'registry:block', + title: 'GradualBlur', + description: 'Progressively un-blurs content based on scroll or trigger creating a cinematic reveal.', + files: [ + { + path: 'src/ts-tailwind/Animations/GradualBlur/GradualBlur.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GridDistortion-JS-CSS', + type: 'registry:block', + title: 'GridDistortion', + description: 'Warped grid mesh distorts smoothly reacting to cursor.', + files: [ + { + path: 'src/content/Backgrounds/GridDistortion/GridDistortion.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/GridDistortion/GridDistortion.css', + type: 'registry:item' + } + ] + }, + { + name: 'GridDistortion-JS-TW', + type: 'registry:block', + title: 'GridDistortion', + description: 'Warped grid mesh distorts smoothly reacting to cursor.', + files: [ + { + path: 'src/tailwind/Backgrounds/GridDistortion/GridDistortion.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GridDistortion-TS-CSS', + type: 'registry:block', + title: 'GridDistortion', + description: 'Warped grid mesh distorts smoothly reacting to cursor.', + files: [ + { + path: 'src/ts-default/Backgrounds/GridDistortion/GridDistortion.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/GridDistortion/GridDistortion.css', + type: 'registry:item' + } + ] + }, + { + name: 'GridDistortion-TS-TW', + type: 'registry:block', + title: 'GridDistortion', + description: 'Warped grid mesh distorts smoothly reacting to cursor.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/GridDistortion/GridDistortion.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GridMotion-JS-CSS', + type: 'registry:block', + title: 'GridMotion', + description: 'Perspective moving grid lines based on cusror position.', + files: [ + { + path: 'src/content/Backgrounds/GridMotion/GridMotion.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/GridMotion/GridMotion.css', + type: 'registry:item' + } + ] + }, + { + name: 'GridMotion-JS-TW', + type: 'registry:block', + title: 'GridMotion', + description: 'Perspective moving grid lines based on cusror position.', + files: [ + { + path: 'src/tailwind/Backgrounds/GridMotion/GridMotion.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GridMotion-TS-CSS', + type: 'registry:block', + title: 'GridMotion', + description: 'Perspective moving grid lines based on cusror position.', + files: [ + { + path: 'src/ts-default/Backgrounds/GridMotion/GridMotion.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/GridMotion/GridMotion.css', + type: 'registry:item' + } + ] + }, + { + name: 'GridMotion-TS-TW', + type: 'registry:block', + title: 'GridMotion', + description: 'Perspective moving grid lines based on cusror position.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/GridMotion/GridMotion.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'GridScan-JS-CSS', + type: 'registry:block', + title: 'GridScan', + description: 'Animated grid room 3D scan effect and cool interactions.', + files: [ + { + path: 'src/content/Backgrounds/GridScan/GridScan.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/GridScan/GridScan.css', + type: 'registry:item' + } + ] + }, + { + name: 'GridScan-JS-TW', + type: 'registry:block', + title: 'GridScan', + description: 'Animated grid room 3D scan effect and cool interactions.', + files: [ + { + path: 'src/tailwind/Backgrounds/GridScan/GridScan.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'GridScan-TS-CSS', + type: 'registry:block', + title: 'GridScan', + description: 'Animated grid room 3D scan effect and cool interactions.', + files: [ + { + path: 'src/ts-default/Backgrounds/GridScan/GridScan.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/GridScan/GridScan.css', + type: 'registry:item' + } + ] + }, + { + name: 'GridScan-TS-TW', + type: 'registry:block', + title: 'GridScan', + description: 'Animated grid room 3D scan effect and cool interactions.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/GridScan/GridScan.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Hyperspeed-JS-CSS', + type: 'registry:block', + title: 'Hyperspeed', + description: 'Animated lines continuously moving to simulate hyperspace travel on click hold.', + files: [ + { + path: 'src/content/Backgrounds/Hyperspeed/Hyperspeed.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Hyperspeed/Hyperspeed.css', + type: 'registry:item' + } + ] + }, + { + name: 'Hyperspeed-JS-TW', + type: 'registry:block', + title: 'Hyperspeed', + description: 'Animated lines continuously moving to simulate hyperspace travel on click hold.', + files: [ + { + path: 'src/tailwind/Backgrounds/Hyperspeed/Hyperspeed.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Hyperspeed-TS-CSS', + type: 'registry:block', + title: 'Hyperspeed', + description: 'Animated lines continuously moving to simulate hyperspace travel on click hold.', + files: [ + { + path: 'src/ts-default/Backgrounds/Hyperspeed/Hyperspeed.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Hyperspeed/Hyperspeed.css', + type: 'registry:item' + } + ] + }, + { + name: 'Hyperspeed-TS-TW', + type: 'registry:block', + title: 'Hyperspeed', + description: 'Animated lines continuously moving to simulate hyperspace travel on click hold.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Hyperspeed/Hyperspeed.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ImageTrail-JS-CSS', + type: 'registry:block', + title: 'ImageTrail', + description: 'Cursor-based image trail with several built-in variants.', + files: [ + { + path: 'src/content/Animations/ImageTrail/ImageTrail.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/ImageTrail/ImageTrail.css', + type: 'registry:item' + } + ] + }, + { + name: 'ImageTrail-JS-TW', + type: 'registry:block', + title: 'ImageTrail', + description: 'Cursor-based image trail with several built-in variants.', + files: [ + { + path: 'src/tailwind/Animations/ImageTrail/ImageTrail.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ImageTrail-TS-CSS', + type: 'registry:block', + title: 'ImageTrail', + description: 'Cursor-based image trail with several built-in variants.', + files: [ + { + path: 'src/ts-default/Animations/ImageTrail/ImageTrail.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/ImageTrail/ImageTrail.css', + type: 'registry:item' + } + ] + }, + { + name: 'ImageTrail-TS-TW', + type: 'registry:block', + title: 'ImageTrail', + description: 'Cursor-based image trail with several built-in variants.', + files: [ + { + path: 'src/ts-tailwind/Animations/ImageTrail/ImageTrail.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'InfiniteMenu-JS-CSS', + type: 'registry:block', + title: 'InfiniteMenu', + description: 'Horizontally looping menu effect that scrolls endlessly with seamless wrap.', + files: [ + { + path: 'src/content/Components/InfiniteMenu/InfiniteMenu.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/InfiniteMenu/InfiniteMenu.css', + type: 'registry:item' + } + ] + }, + { + name: 'InfiniteMenu-JS-TW', + type: 'registry:block', + title: 'InfiniteMenu', + description: 'Horizontally looping menu effect that scrolls endlessly with seamless wrap.', + files: [ + { + path: 'src/tailwind/Components/InfiniteMenu/InfiniteMenu.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'InfiniteMenu-TS-CSS', + type: 'registry:block', + title: 'InfiniteMenu', + description: 'Horizontally looping menu effect that scrolls endlessly with seamless wrap.', + files: [ + { + path: 'src/ts-default/Components/InfiniteMenu/InfiniteMenu.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/InfiniteMenu/InfiniteMenu.css', + type: 'registry:item' + } + ] + }, + { + name: 'InfiniteMenu-TS-TW', + type: 'registry:block', + title: 'InfiniteMenu', + description: 'Horizontally looping menu effect that scrolls endlessly with seamless wrap.', + files: [ + { + path: 'src/ts-tailwind/Components/InfiniteMenu/InfiniteMenu.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Iridescence-JS-CSS', + type: 'registry:block', + title: 'Iridescence', + description: 'Slick iridescent shader with shifting waves.', + files: [ + { + path: 'src/content/Backgrounds/Iridescence/Iridescence.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Iridescence/Iridescence.css', + type: 'registry:item' + } + ] + }, + { + name: 'Iridescence-JS-TW', + type: 'registry:block', + title: 'Iridescence', + description: 'Slick iridescent shader with shifting waves.', + files: [ + { + path: 'src/tailwind/Backgrounds/Iridescence/Iridescence.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Iridescence-TS-CSS', + type: 'registry:block', + title: 'Iridescence', + description: 'Slick iridescent shader with shifting waves.', + files: [ + { + path: 'src/ts-default/Backgrounds/Iridescence/Iridescence.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Iridescence/Iridescence.css', + type: 'registry:item' + } + ] + }, + { + name: 'Iridescence-TS-TW', + type: 'registry:block', + title: 'Iridescence', + description: 'Slick iridescent shader with shifting waves.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Iridescence/Iridescence.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Lanyard-JS-CSS', + type: 'registry:block', + title: 'Lanyard', + description: 'Swinging 3D lanyard / badge card with realistic inertial motion.', + files: [ + { + path: 'src/content/Components/Lanyard/Lanyard.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/Lanyard/Lanyard.css', + type: 'registry:item' + } + ] + }, + { + name: 'Lanyard-JS-TW', + type: 'registry:block', + title: 'Lanyard', + description: 'Swinging 3D lanyard / badge card with realistic inertial motion.', + files: [ + { + path: 'src/tailwind/Components/Lanyard/Lanyard.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Lanyard-TS-CSS', + type: 'registry:block', + title: 'Lanyard', + description: 'Swinging 3D lanyard / badge card with realistic inertial motion.', + files: [ + { + path: 'src/ts-default/Components/Lanyard/Lanyard.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/Lanyard/Lanyard.css', + type: 'registry:item' + } + ] + }, + { + name: 'Lanyard-TS-TW', + type: 'registry:block', + title: 'Lanyard', + description: 'Swinging 3D lanyard / badge card with realistic inertial motion.', + files: [ + { + path: 'src/ts-tailwind/Components/Lanyard/Lanyard.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'LaserFlow-JS-CSS', + type: 'registry:block', + title: 'LaserFlow', + description: 'Dynamic laser light that flows onto a surface, customizable effect.', + files: [ + { + path: 'src/content/Animations/LaserFlow/LaserFlow.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/LaserFlow/LaserFlow.css', + type: 'registry:item' + } + ] + }, + { + name: 'LaserFlow-JS-TW', + type: 'registry:block', + title: 'LaserFlow', + description: 'Dynamic laser light that flows onto a surface, customizable effect.', + files: [ + { + path: 'src/tailwind/Animations/LaserFlow/LaserFlow.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'LaserFlow-TS-CSS', + type: 'registry:block', + title: 'LaserFlow', + description: 'Dynamic laser light that flows onto a surface, customizable effect.', + files: [ + { + path: 'src/ts-default/Animations/LaserFlow/LaserFlow.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/LaserFlow/LaserFlow.css', + type: 'registry:item' + } + ] + }, + { + name: 'LaserFlow-TS-TW', + type: 'registry:block', + title: 'LaserFlow', + description: 'Dynamic laser light that flows onto a surface, customizable effect.', + files: [ + { + path: 'src/ts-tailwind/Animations/LaserFlow/LaserFlow.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'LetterGlitch-JS-CSS', + type: 'registry:block', + title: 'LetterGlitch', + description: 'Matrix style letter animation.', + files: [ + { + path: 'src/content/Backgrounds/LetterGlitch/LetterGlitch.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'LetterGlitch-JS-TW', + type: 'registry:block', + title: 'LetterGlitch', + description: 'Matrix style letter animation.', + files: [ + { + path: 'src/tailwind/Backgrounds/LetterGlitch/LetterGlitch.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'LetterGlitch-TS-CSS', + type: 'registry:block', + title: 'LetterGlitch', + description: 'Matrix style letter animation.', + files: [ + { + path: 'src/ts-default/Backgrounds/LetterGlitch/LetterGlitch.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'LetterGlitch-TS-TW', + type: 'registry:block', + title: 'LetterGlitch', + description: 'Matrix style letter animation.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/LetterGlitch/LetterGlitch.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Lightning-JS-CSS', + type: 'registry:block', + title: 'Lightning', + description: 'Procedural lightning bolts with branching and glow flicker.', + files: [ + { + path: 'src/content/Backgrounds/Lightning/Lightning.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Lightning/Lightning.css', + type: 'registry:item' + } + ] + }, + { + name: 'Lightning-JS-TW', + type: 'registry:block', + title: 'Lightning', + description: 'Procedural lightning bolts with branching and glow flicker.', + files: [ + { + path: 'src/tailwind/Backgrounds/Lightning/Lightning.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Lightning-TS-CSS', + type: 'registry:block', + title: 'Lightning', + description: 'Procedural lightning bolts with branching and glow flicker.', + files: [ + { + path: 'src/ts-default/Backgrounds/Lightning/Lightning.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Lightning/Lightning.css', + type: 'registry:item' + } + ] + }, + { + name: 'Lightning-TS-TW', + type: 'registry:block', + title: 'Lightning', + description: 'Procedural lightning bolts with branching and glow flicker.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Lightning/Lightning.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'LightRays-JS-CSS', + type: 'registry:block', + title: 'LightRays', + description: 'Volumetric light rays/beams with customizable direction.', + files: [ + { + path: 'src/content/Backgrounds/LightRays/LightRays.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/LightRays/LightRays.css', + type: 'registry:item' + } + ] + }, + { + name: 'LightRays-JS-TW', + type: 'registry:block', + title: 'LightRays', + description: 'Volumetric light rays/beams with customizable direction.', + files: [ + { + path: 'src/tailwind/Backgrounds/LightRays/LightRays.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'LightRays-TS-CSS', + type: 'registry:block', + title: 'LightRays', + description: 'Volumetric light rays/beams with customizable direction.', + files: [ + { + path: 'src/ts-default/Backgrounds/LightRays/LightRays.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/LightRays/LightRays.css', + type: 'registry:item' + } + ] + }, + { + name: 'LightRays-TS-TW', + type: 'registry:block', + title: 'LightRays', + description: 'Volumetric light rays/beams with customizable direction.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/LightRays/LightRays.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'LiquidChrome-JS-CSS', + type: 'registry:block', + title: 'LiquidChrome', + description: 'Liquid metallic chrome shader with flowing reflective surface.', + files: [ + { + path: 'src/content/Backgrounds/LiquidChrome/LiquidChrome.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/LiquidChrome/LiquidChrome.css', + type: 'registry:item' + } + ] + }, + { + name: 'LiquidChrome-JS-TW', + type: 'registry:block', + title: 'LiquidChrome', + description: 'Liquid metallic chrome shader with flowing reflective surface.', + files: [ + { + path: 'src/tailwind/Backgrounds/LiquidChrome/LiquidChrome.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'LiquidChrome-TS-CSS', + type: 'registry:block', + title: 'LiquidChrome', + description: 'Liquid metallic chrome shader with flowing reflective surface.', + files: [ + { + path: 'src/ts-default/Backgrounds/LiquidChrome/LiquidChrome.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/LiquidChrome/LiquidChrome.css', + type: 'registry:item' + } + ] + }, + { + name: 'LiquidChrome-TS-TW', + type: 'registry:block', + title: 'LiquidChrome', + description: 'Liquid metallic chrome shader with flowing reflective surface.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/LiquidChrome/LiquidChrome.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'LiquidEther-JS-CSS', + type: 'registry:block', + title: 'LiquidEther', + description: 'Interactive liquid shader with flowing distortion and customizable colors.', + files: [ + { + path: 'src/content/Backgrounds/LiquidEther/LiquidEther.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/LiquidEther/LiquidEther.css', + type: 'registry:item' + } + ] + }, + { + name: 'LiquidEther-JS-TW', + type: 'registry:block', + title: 'LiquidEther', + description: 'Interactive liquid shader with flowing distortion and customizable colors.', + files: [ + { + path: 'src/tailwind/Backgrounds/LiquidEther/LiquidEther.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'LiquidEther-TS-CSS', + type: 'registry:block', + title: 'LiquidEther', + description: 'Interactive liquid shader with flowing distortion and customizable colors.', + files: [ + { + path: 'src/ts-default/Backgrounds/LiquidEther/LiquidEther.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/LiquidEther/LiquidEther.css', + type: 'registry:item' + } + ] + }, + { + name: 'LiquidEther-TS-TW', + type: 'registry:block', + title: 'LiquidEther', + description: 'Interactive liquid shader with flowing distortion and customizable colors.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/LiquidEther/LiquidEther.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'LogoLoop-JS-CSS', + type: 'registry:block', + title: 'LogoLoop', + description: 'Continuously looping marquee of brand or tech logos with seamless repeat and hover pause.', + files: [ + { + path: 'src/content/Animations/LogoLoop/LogoLoop.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/LogoLoop/LogoLoop.css', + type: 'registry:item' + } + ] + }, + { + name: 'LogoLoop-JS-TW', + type: 'registry:block', + title: 'LogoLoop', + description: 'Continuously looping marquee of brand or tech logos with seamless repeat and hover pause.', + files: [ + { + path: 'src/tailwind/Animations/LogoLoop/LogoLoop.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'LogoLoop-TS-CSS', + type: 'registry:block', + title: 'LogoLoop', + description: 'Continuously looping marquee of brand or tech logos with seamless repeat and hover pause.', + files: [ + { + path: 'src/ts-default/Animations/LogoLoop/LogoLoop.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/LogoLoop/LogoLoop.css', + type: 'registry:item' + } + ] + }, + { + name: 'LogoLoop-TS-TW', + type: 'registry:block', + title: 'LogoLoop', + description: 'Continuously looping marquee of brand or tech logos with seamless repeat and hover pause.', + files: [ + { + path: 'src/ts-tailwind/Animations/LogoLoop/LogoLoop.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'MagicBento-JS-CSS', + type: 'registry:block', + title: 'MagicBento', + description: 'Interactive bento grid tiles expand + animate with various options.', + files: [ + { + path: 'src/content/Components/MagicBento/MagicBento.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/MagicBento/MagicBento.css', + type: 'registry:item' + } + ] + }, + { + name: 'MagicBento-JS-TW', + type: 'registry:block', + title: 'MagicBento', + description: 'Interactive bento grid tiles expand + animate with various options.', + files: [ + { + path: 'src/tailwind/Components/MagicBento/MagicBento.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'MagicBento-TS-CSS', + type: 'registry:block', + title: 'MagicBento', + description: 'Interactive bento grid tiles expand + animate with various options.', + files: [ + { + path: 'src/ts-default/Components/MagicBento/MagicBento.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/MagicBento/MagicBento.css', + type: 'registry:item' + } + ] + }, + { + name: 'MagicBento-TS-TW', + type: 'registry:block', + title: 'MagicBento', + description: 'Interactive bento grid tiles expand + animate with various options.', + files: [ + { + path: 'src/ts-tailwind/Components/MagicBento/MagicBento.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Magnet-JS-CSS', + type: 'registry:block', + title: 'Magnet', + description: 'Elements magnetically ease toward the cursor then settle back with spring physics.', + files: [ + { + path: 'src/content/Animations/Magnet/Magnet.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Magnet-JS-TW', + type: 'registry:block', + title: 'Magnet', + description: 'Elements magnetically ease toward the cursor then settle back with spring physics.', + files: [ + { + path: 'src/tailwind/Animations/Magnet/Magnet.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Magnet-TS-CSS', + type: 'registry:block', + title: 'Magnet', + description: 'Elements magnetically ease toward the cursor then settle back with spring physics.', + files: [ + { + path: 'src/ts-default/Animations/Magnet/Magnet.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Magnet-TS-TW', + type: 'registry:block', + title: 'Magnet', + description: 'Elements magnetically ease toward the cursor then settle back with spring physics.', + files: [ + { + path: 'src/ts-tailwind/Animations/Magnet/Magnet.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'MagnetLines-JS-CSS', + type: 'registry:block', + title: 'MagnetLines', + description: 'Animated field lines bend toward the cursor.', + files: [ + { + path: 'src/content/Animations/MagnetLines/MagnetLines.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/MagnetLines/MagnetLines.css', + type: 'registry:item' + } + ] + }, + { + name: 'MagnetLines-JS-TW', + type: 'registry:block', + title: 'MagnetLines', + description: 'Animated field lines bend toward the cursor.', + files: [ + { + path: 'src/tailwind/Animations/MagnetLines/MagnetLines.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'MagnetLines-TS-CSS', + type: 'registry:block', + title: 'MagnetLines', + description: 'Animated field lines bend toward the cursor.', + files: [ + { + path: 'src/ts-default/Animations/MagnetLines/MagnetLines.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/MagnetLines/MagnetLines.css', + type: 'registry:item' + } + ] + }, + { + name: 'MagnetLines-TS-TW', + type: 'registry:block', + title: 'MagnetLines', + description: 'Animated field lines bend toward the cursor.', + files: [ + { + path: 'src/ts-tailwind/Animations/MagnetLines/MagnetLines.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Masonry-JS-CSS', + type: 'registry:block', + title: 'Masonry', + description: 'Responsive masonry layout with animated reflow + gaps optimization.', + files: [ + { + path: 'src/content/Components/Masonry/Masonry.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/Masonry/Masonry.css', + type: 'registry:item' + } + ] + }, + { + name: 'Masonry-JS-TW', + type: 'registry:block', + title: 'Masonry', + description: 'Responsive masonry layout with animated reflow + gaps optimization.', + files: [ + { + path: 'src/tailwind/Components/Masonry/Masonry.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Masonry-TS-CSS', + type: 'registry:block', + title: 'Masonry', + description: 'Responsive masonry layout with animated reflow + gaps optimization.', + files: [ + { + path: 'src/ts-default/Components/Masonry/Masonry.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/Masonry/Masonry.css', + type: 'registry:item' + } + ] + }, + { + name: 'Masonry-TS-TW', + type: 'registry:block', + title: 'Masonry', + description: 'Responsive masonry layout with animated reflow + gaps optimization.', + files: [ + { + path: 'src/ts-tailwind/Components/Masonry/Masonry.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'MetaBalls-JS-CSS', + type: 'registry:block', + title: 'MetaBalls', + description: 'Liquid metaball blobs that merge and separate with smooth implicit surface animation.', + files: [ + { + path: 'src/content/Animations/MetaBalls/MetaBalls.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/MetaBalls/MetaBalls.css', + type: 'registry:item' + } + ] + }, + { + name: 'MetaBalls-JS-TW', + type: 'registry:block', + title: 'MetaBalls', + description: 'Liquid metaball blobs that merge and separate with smooth implicit surface animation.', + files: [ + { + path: 'src/tailwind/Animations/MetaBalls/MetaBalls.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'MetaBalls-TS-CSS', + type: 'registry:block', + title: 'MetaBalls', + description: 'Liquid metaball blobs that merge and separate with smooth implicit surface animation.', + files: [ + { + path: 'src/ts-default/Animations/MetaBalls/MetaBalls.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/MetaBalls/MetaBalls.css', + type: 'registry:item' + } + ] + }, + { + name: 'MetaBalls-TS-TW', + type: 'registry:block', + title: 'MetaBalls', + description: 'Liquid metaball blobs that merge and separate with smooth implicit surface animation.', + files: [ + { + path: 'src/ts-tailwind/Animations/MetaBalls/MetaBalls.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'MetallicPaint-JS-CSS', + type: 'registry:block', + title: 'MetallicPaint', + description: 'Liquid metallic paint shader which can be applied to SVG elements.', + files: [ + { + path: 'src/content/Animations/MetallicPaint/MetallicPaint.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/MetallicPaint/MetallicPaint.css', + type: 'registry:item' + } + ] + }, + { + name: 'MetallicPaint-JS-TW', + type: 'registry:block', + title: 'MetallicPaint', + description: 'Liquid metallic paint shader which can be applied to SVG elements.', + files: [ + { + path: 'src/tailwind/Animations/MetallicPaint/MetallicPaint.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'MetallicPaint-TS-CSS', + type: 'registry:block', + title: 'MetallicPaint', + description: 'Liquid metallic paint shader which can be applied to SVG elements.', + files: [ + { + path: 'src/ts-default/Animations/MetallicPaint/MetallicPaint.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/MetallicPaint/MetallicPaint.css', + type: 'registry:item' + } + ] + }, + { + name: 'MetallicPaint-TS-TW', + type: 'registry:block', + title: 'MetallicPaint', + description: 'Liquid metallic paint shader which can be applied to SVG elements.', + files: [ + { + path: 'src/ts-tailwind/Animations/MetallicPaint/MetallicPaint.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ModelViewer-JS-CSS', + type: 'registry:block', + title: 'ModelViewer', + description: 'Three.js model viewer with orbit controls and lighting presets.', + files: [ + { + path: 'src/content/Components/ModelViewer/ModelViewer.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ModelViewer-JS-TW', + type: 'registry:block', + title: 'ModelViewer', + description: 'Three.js model viewer with orbit controls and lighting presets.', + files: [ + { + path: 'src/tailwind/Components/ModelViewer/ModelViewer.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ModelViewer-TS-CSS', + type: 'registry:block', + title: 'ModelViewer', + description: 'Three.js model viewer with orbit controls and lighting presets.', + files: [ + { + path: 'src/ts-default/Components/ModelViewer/ModelViewer.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ModelViewer-TS-TW', + type: 'registry:block', + title: 'ModelViewer', + description: 'Three.js model viewer with orbit controls and lighting presets.', + files: [ + { + path: 'src/ts-tailwind/Components/ModelViewer/ModelViewer.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Noise-JS-CSS', + type: 'registry:block', + title: 'Noise', + description: 'Animated film grain / noise overlay adding subtle texture and motion.', + files: [ + { + path: 'src/content/Animations/Noise/Noise.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/Noise/Noise.css', + type: 'registry:item' + } + ] + }, + { + name: 'Noise-JS-TW', + type: 'registry:block', + title: 'Noise', + description: 'Animated film grain / noise overlay adding subtle texture and motion.', + files: [ + { + path: 'src/tailwind/Animations/Noise/Noise.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Noise-TS-CSS', + type: 'registry:block', + title: 'Noise', + description: 'Animated film grain / noise overlay adding subtle texture and motion.', + files: [ + { + path: 'src/ts-default/Animations/Noise/Noise.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/Noise/Noise.css', + type: 'registry:item' + } + ] + }, + { + name: 'Noise-TS-TW', + type: 'registry:block', + title: 'Noise', + description: 'Animated film grain / noise overlay adding subtle texture and motion.', + files: [ + { + path: 'src/ts-tailwind/Animations/Noise/Noise.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Orb-JS-CSS', + type: 'registry:block', + title: 'Orb', + description: 'Floating energy orb with customizable hover effect.', + files: [ + { + path: 'src/content/Backgrounds/Orb/Orb.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Orb/Orb.css', + type: 'registry:item' + } + ] + }, + { + name: 'Orb-JS-TW', + type: 'registry:block', + title: 'Orb', + description: 'Floating energy orb with customizable hover effect.', + files: [ + { + path: 'src/tailwind/Backgrounds/Orb/Orb.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Orb-TS-CSS', + type: 'registry:block', + title: 'Orb', + description: 'Floating energy orb with customizable hover effect.', + files: [ + { + path: 'src/ts-default/Backgrounds/Orb/Orb.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Orb/Orb.css', + type: 'registry:item' + } + ] + }, + { + name: 'Orb-TS-TW', + type: 'registry:block', + title: 'Orb', + description: 'Floating energy orb with customizable hover effect.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Orb/Orb.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Particles-JS-CSS', + type: 'registry:block', + title: 'Particles', + description: 'Configurable particle system.', + files: [ + { + path: 'src/content/Backgrounds/Particles/Particles.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Particles/Particles.css', + type: 'registry:item' + } + ] + }, + { + name: 'Particles-JS-TW', + type: 'registry:block', + title: 'Particles', + description: 'Configurable particle system.', + files: [ + { + path: 'src/tailwind/Backgrounds/Particles/Particles.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Particles-TS-CSS', + type: 'registry:block', + title: 'Particles', + description: 'Configurable particle system.', + files: [ + { + path: 'src/ts-default/Backgrounds/Particles/Particles.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Particles/Particles.css', + type: 'registry:item' + } + ] + }, + { + name: 'Particles-TS-TW', + type: 'registry:block', + title: 'Particles', + description: 'Configurable particle system.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Particles/Particles.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'PillNav-JS-CSS', + type: 'registry:block', + title: 'PillNav', + description: 'Minimal pill nav with sliding active highlight + smooth easing.', + files: [ + { + path: 'src/content/Components/PillNav/PillNav.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/PillNav/PillNav.css', + type: 'registry:item' + } + ] + }, + { + name: 'PillNav-JS-TW', + type: 'registry:block', + title: 'PillNav', + description: 'Minimal pill nav with sliding active highlight + smooth easing.', + files: [ + { + path: 'src/tailwind/Components/PillNav/PillNav.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'PillNav-TS-CSS', + type: 'registry:block', + title: 'PillNav', + description: 'Minimal pill nav with sliding active highlight + smooth easing.', + files: [ + { + path: 'src/ts-default/Components/PillNav/PillNav.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/PillNav/PillNav.css', + type: 'registry:item' + } + ] + }, + { + name: 'PillNav-TS-TW', + type: 'registry:block', + title: 'PillNav', + description: 'Minimal pill nav with sliding active highlight + smooth easing.', + files: [ + { + path: 'src/ts-tailwind/Components/PillNav/PillNav.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'PixelBlast-JS-CSS', + type: 'registry:block', + title: 'PixelBlast', + description: 'Exploding pixel particle bursts with optional liquid postprocessing.', + files: [ + { + path: 'src/content/Backgrounds/PixelBlast/PixelBlast.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/PixelBlast/PixelBlast.css', + type: 'registry:item' + } + ] + }, + { + name: 'PixelBlast-JS-TW', + type: 'registry:block', + title: 'PixelBlast', + description: 'Exploding pixel particle bursts with optional liquid postprocessing.', + files: [ + { + path: 'src/tailwind/Backgrounds/PixelBlast/PixelBlast.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'PixelBlast-TS-CSS', + type: 'registry:block', + title: 'PixelBlast', + description: 'Exploding pixel particle bursts with optional liquid postprocessing.', + files: [ + { + path: 'src/ts-default/Backgrounds/PixelBlast/PixelBlast.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/PixelBlast/PixelBlast.css', + type: 'registry:item' + } + ] + }, + { + name: 'PixelBlast-TS-TW', + type: 'registry:block', + title: 'PixelBlast', + description: 'Exploding pixel particle bursts with optional liquid postprocessing.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/PixelBlast/PixelBlast.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'PixelCard-JS-CSS', + type: 'registry:block', + title: 'PixelCard', + description: 'Card content revealed through pixel expansion transition.', + files: [ + { + path: 'src/content/Components/PixelCard/PixelCard.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/PixelCard/PixelCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'PixelCard-JS-TW', + type: 'registry:block', + title: 'PixelCard', + description: 'Card content revealed through pixel expansion transition.', + files: [ + { + path: 'src/tailwind/Components/PixelCard/PixelCard.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'PixelCard-TS-CSS', + type: 'registry:block', + title: 'PixelCard', + description: 'Card content revealed through pixel expansion transition.', + files: [ + { + path: 'src/ts-default/Components/PixelCard/PixelCard.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/PixelCard/PixelCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'PixelCard-TS-TW', + type: 'registry:block', + title: 'PixelCard', + description: 'Card content revealed through pixel expansion transition.', + files: [ + { + path: 'src/ts-tailwind/Components/PixelCard/PixelCard.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'PixelTrail-JS-CSS', + type: 'registry:block', + title: 'PixelTrail', + description: 'Pixelated cursor trail emitting fading squares with retro digital feel.', + files: [ + { + path: 'src/content/Animations/PixelTrail/PixelTrail.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/PixelTrail/PixelTrail.css', + type: 'registry:item' + } + ] + }, + { + name: 'PixelTrail-JS-TW', + type: 'registry:block', + title: 'PixelTrail', + description: 'Pixelated cursor trail emitting fading squares with retro digital feel.', + files: [ + { + path: 'src/tailwind/Animations/PixelTrail/PixelTrail.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'PixelTrail-TS-CSS', + type: 'registry:block', + title: 'PixelTrail', + description: 'Pixelated cursor trail emitting fading squares with retro digital feel.', + files: [ + { + path: 'src/ts-default/Animations/PixelTrail/PixelTrail.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/PixelTrail/PixelTrail.css', + type: 'registry:item' + } + ] + }, + { + name: 'PixelTrail-TS-TW', + type: 'registry:block', + title: 'PixelTrail', + description: 'Pixelated cursor trail emitting fading squares with retro digital feel.', + files: [ + { + path: 'src/ts-tailwind/Animations/PixelTrail/PixelTrail.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'PixelTransition-JS-CSS', + type: 'registry:block', + title: 'PixelTransition', + description: 'Pixel dissolve transition for content reveal on hover.', + files: [ + { + path: 'src/content/Animations/PixelTransition/PixelTransition.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/PixelTransition/PixelTransition.css', + type: 'registry:item' + } + ] + }, + { + name: 'PixelTransition-JS-TW', + type: 'registry:block', + title: 'PixelTransition', + description: 'Pixel dissolve transition for content reveal on hover.', + files: [ + { + path: 'src/tailwind/Animations/PixelTransition/PixelTransition.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'PixelTransition-TS-CSS', + type: 'registry:block', + title: 'PixelTransition', + description: 'Pixel dissolve transition for content reveal on hover.', + files: [ + { + path: 'src/ts-default/Animations/PixelTransition/PixelTransition.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/PixelTransition/PixelTransition.css', + type: 'registry:item' + } + ] + }, + { + name: 'PixelTransition-TS-TW', + type: 'registry:block', + title: 'PixelTransition', + description: 'Pixel dissolve transition for content reveal on hover.', + files: [ + { + path: 'src/ts-tailwind/Animations/PixelTransition/PixelTransition.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Plasma-JS-CSS', + type: 'registry:block', + title: 'Plasma', + description: 'Organic plasma gradients swirl + morph with smooth turbulence.', + files: [ + { + path: 'src/content/Backgrounds/Plasma/Plasma.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Plasma/Plasma.css', + type: 'registry:item' + } + ] + }, + { + name: 'Plasma-JS-TW', + type: 'registry:block', + title: 'Plasma', + description: 'Organic plasma gradients swirl + morph with smooth turbulence.', + files: [ + { + path: 'src/tailwind/Backgrounds/Plasma/Plasma.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Plasma-TS-CSS', + type: 'registry:block', + title: 'Plasma', + description: 'Organic plasma gradients swirl + morph with smooth turbulence.', + files: [ + { + path: 'src/ts-default/Backgrounds/Plasma/Plasma.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Plasma/Plasma.css', + type: 'registry:item' + } + ] + }, + { + name: 'Plasma-TS-TW', + type: 'registry:block', + title: 'Plasma', + description: 'Organic plasma gradients swirl + morph with smooth turbulence.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Plasma/Plasma.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Prism-JS-CSS', + type: 'registry:block', + title: 'Prism', + description: 'Rotating prism with configurable intensity, size, and colors.', + files: [ + { + path: 'src/content/Backgrounds/Prism/Prism.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Prism/Prism.css', + type: 'registry:item' + } + ] + }, + { + name: 'Prism-JS-TW', + type: 'registry:block', + title: 'Prism', + description: 'Rotating prism with configurable intensity, size, and colors.', + files: [ + { + path: 'src/tailwind/Backgrounds/Prism/Prism.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Prism-TS-CSS', + type: 'registry:block', + title: 'Prism', + description: 'Rotating prism with configurable intensity, size, and colors.', + files: [ + { + path: 'src/ts-default/Backgrounds/Prism/Prism.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Prism/Prism.css', + type: 'registry:item' + } + ] + }, + { + name: 'Prism-TS-TW', + type: 'registry:block', + title: 'Prism', + description: 'Rotating prism with configurable intensity, size, and colors.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Prism/Prism.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'PrismaticBurst-JS-CSS', + type: 'registry:block', + title: 'PrismaticBurst', + description: 'Burst of light rays with controllable color, distortion, amount.', + files: [ + { + path: 'src/content/Backgrounds/PrismaticBurst/PrismaticBurst.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/PrismaticBurst/PrismaticBurst.css', + type: 'registry:item' + } + ] + }, + { + name: 'PrismaticBurst-JS-TW', + type: 'registry:block', + title: 'PrismaticBurst', + description: 'Burst of light rays with controllable color, distortion, amount.', + files: [ + { + path: 'src/tailwind/Backgrounds/PrismaticBurst/PrismaticBurst.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'PrismaticBurst-TS-CSS', + type: 'registry:block', + title: 'PrismaticBurst', + description: 'Burst of light rays with controllable color, distortion, amount.', + files: [ + { + path: 'src/ts-default/Backgrounds/PrismaticBurst/PrismaticBurst.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/PrismaticBurst/PrismaticBurst.css', + type: 'registry:item' + } + ] + }, + { + name: 'PrismaticBurst-TS-TW', + type: 'registry:block', + title: 'PrismaticBurst', + description: 'Burst of light rays with controllable color, distortion, amount.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/PrismaticBurst/PrismaticBurst.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ProfileCard-JS-CSS', + type: 'registry:block', + title: 'ProfileCard', + description: 'Animated profile card glare with 3D hover effect.', + files: [ + { + path: 'src/content/Components/ProfileCard/ProfileCard.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/ProfileCard/ProfileCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'ProfileCard-TS-CSS', + type: 'registry:block', + title: 'ProfileCard', + description: 'Animated profile card glare with 3D hover effect.', + files: [ + { + path: 'src/ts-default/Components/ProfileCard/ProfileCard.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/ProfileCard/ProfileCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'Ribbons-JS-CSS', + type: 'registry:block', + title: 'Ribbons', + description: 'Flowing responsive ribbons/cursor trail driven by physics and pointer motion.', + files: [ + { + path: 'src/content/Animations/Ribbons/Ribbons.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/Ribbons/Ribbons.css', + type: 'registry:item' + } + ] + }, + { + name: 'Ribbons-JS-TW', + type: 'registry:block', + title: 'Ribbons', + description: 'Flowing responsive ribbons/cursor trail driven by physics and pointer motion.', + files: [ + { + path: 'src/tailwind/Animations/Ribbons/Ribbons.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Ribbons-TS-CSS', + type: 'registry:block', + title: 'Ribbons', + description: 'Flowing responsive ribbons/cursor trail driven by physics and pointer motion.', + files: [ + { + path: 'src/ts-default/Animations/Ribbons/Ribbons.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/Ribbons/Ribbons.css', + type: 'registry:item' + } + ] + }, + { + name: 'Ribbons-TS-TW', + type: 'registry:block', + title: 'Ribbons', + description: 'Flowing responsive ribbons/cursor trail driven by physics and pointer motion.', + files: [ + { + path: 'src/ts-tailwind/Animations/Ribbons/Ribbons.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'RippleGrid-JS-CSS', + type: 'registry:block', + title: 'RippleGrid', + description: 'A grid that continuously animates with a ripple effect.', + files: [ + { + path: 'src/content/Backgrounds/RippleGrid/RippleGrid.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/RippleGrid/RippleGrid.css', + type: 'registry:item' + } + ] + }, + { + name: 'RippleGrid-JS-TW', + type: 'registry:block', + title: 'RippleGrid', + description: 'A grid that continuously animates with a ripple effect.', + files: [ + { + path: 'src/tailwind/Backgrounds/RippleGrid/RippleGrid.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'RippleGrid-TS-CSS', + type: 'registry:block', + title: 'RippleGrid', + description: 'A grid that continuously animates with a ripple effect.', + files: [ + { + path: 'src/ts-default/Backgrounds/RippleGrid/RippleGrid.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/RippleGrid/RippleGrid.css', + type: 'registry:item' + } + ] + }, + { + name: 'RippleGrid-TS-TW', + type: 'registry:block', + title: 'RippleGrid', + description: 'A grid that continuously animates with a ripple effect.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/RippleGrid/RippleGrid.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'RotatingText-JS-CSS', + type: 'registry:block', + title: 'RotatingText', + description: 'Cycles through multiple phrases with 3D rotate / flip transitions.', + files: [ + { + path: 'src/content/TextAnimations/RotatingText/RotatingText.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/RotatingText/RotatingText.css', + type: 'registry:item' + } + ] + }, + { + name: 'RotatingText-JS-TW', + type: 'registry:block', + title: 'RotatingText', + description: 'Cycles through multiple phrases with 3D rotate / flip transitions.', + files: [ + { + path: 'src/tailwind/TextAnimations/RotatingText/RotatingText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'RotatingText-TS-CSS', + type: 'registry:block', + title: 'RotatingText', + description: 'Cycles through multiple phrases with 3D rotate / flip transitions.', + files: [ + { + path: 'src/ts-default/TextAnimations/RotatingText/RotatingText.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/RotatingText/RotatingText.css', + type: 'registry:item' + } + ] + }, + { + name: 'RotatingText-TS-TW', + type: 'registry:block', + title: 'RotatingText', + description: 'Cycles through multiple phrases with 3D rotate / flip transitions.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/RotatingText/RotatingText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrambledText-JS-CSS', + type: 'registry:block', + title: 'ScrambledText', + description: 'Detects cursor position and applies a distortion effect to text.', + files: [ + { + path: 'src/content/TextAnimations/ScrambledText/ScrambledText.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/ScrambledText/ScrambledText.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrambledText-JS-TW', + type: 'registry:block', + title: 'ScrambledText', + description: 'Detects cursor position and applies a distortion effect to text.', + files: [ + { + path: 'src/tailwind/TextAnimations/ScrambledText/ScrambledText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrambledText-TS-CSS', + type: 'registry:block', + title: 'ScrambledText', + description: 'Detects cursor position and applies a distortion effect to text.', + files: [ + { + path: 'src/ts-default/TextAnimations/ScrambledText/ScrambledText.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/ScrambledText/ScrambledText.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrambledText-TS-TW', + type: 'registry:block', + title: 'ScrambledText', + description: 'Detects cursor position and applies a distortion effect to text.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/ScrambledText/ScrambledText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrollFloat-JS-CSS', + type: 'registry:block', + title: 'ScrollFloat', + description: 'Text gently floats / parallax shifts on scroll.', + files: [ + { + path: 'src/content/TextAnimations/ScrollFloat/ScrollFloat.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/ScrollFloat/ScrollFloat.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrollFloat-JS-TW', + type: 'registry:block', + title: 'ScrollFloat', + description: 'Text gently floats / parallax shifts on scroll.', + files: [ + { + path: 'src/tailwind/TextAnimations/ScrollFloat/ScrollFloat.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrollFloat-TS-CSS', + type: 'registry:block', + title: 'ScrollFloat', + description: 'Text gently floats / parallax shifts on scroll.', + files: [ + { + path: 'src/ts-default/TextAnimations/ScrollFloat/ScrollFloat.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/ScrollFloat/ScrollFloat.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrollFloat-TS-TW', + type: 'registry:block', + title: 'ScrollFloat', + description: 'Text gently floats / parallax shifts on scroll.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/ScrollFloat/ScrollFloat.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrollReveal-JS-CSS', + type: 'registry:block', + title: 'ScrollReveal', + description: 'Text gently unblurs and reveals on scroll.', + files: [ + { + path: 'src/content/TextAnimations/ScrollReveal/ScrollReveal.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/ScrollReveal/ScrollReveal.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrollReveal-JS-TW', + type: 'registry:block', + title: 'ScrollReveal', + description: 'Text gently unblurs and reveals on scroll.', + files: [ + { + path: 'src/tailwind/TextAnimations/ScrollReveal/ScrollReveal.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrollReveal-TS-CSS', + type: 'registry:block', + title: 'ScrollReveal', + description: 'Text gently unblurs and reveals on scroll.', + files: [ + { + path: 'src/ts-default/TextAnimations/ScrollReveal/ScrollReveal.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/ScrollReveal/ScrollReveal.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrollReveal-TS-TW', + type: 'registry:block', + title: 'ScrollReveal', + description: 'Text gently unblurs and reveals on scroll.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/ScrollReveal/ScrollReveal.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrollStack-JS-CSS', + type: 'registry:block', + title: 'ScrollStack', + description: 'Overlapping card stack reveals on scroll with depth layering.', + files: [ + { + path: 'src/content/Components/ScrollStack/ScrollStack.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/ScrollStack/ScrollStack.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrollStack-JS-TW', + type: 'registry:block', + title: 'ScrollStack', + description: 'Overlapping card stack reveals on scroll with depth layering.', + files: [ + { + path: 'src/tailwind/Components/ScrollStack/ScrollStack.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrollStack-TS-CSS', + type: 'registry:block', + title: 'ScrollStack', + description: 'Overlapping card stack reveals on scroll with depth layering.', + files: [ + { + path: 'src/ts-default/Components/ScrollStack/ScrollStack.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/ScrollStack/ScrollStack.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrollStack-TS-TW', + type: 'registry:block', + title: 'ScrollStack', + description: 'Overlapping card stack reveals on scroll with depth layering.', + files: [ + { + path: 'src/ts-tailwind/Components/ScrollStack/ScrollStack.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrollVelocity-JS-CSS', + type: 'registry:block', + title: 'ScrollVelocity', + description: "Text marquee animatio - speed and distortion scale with user's scroll velocity.", + files: [ + { + path: 'src/content/TextAnimations/ScrollVelocity/ScrollVelocity.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/ScrollVelocity/ScrollVelocity.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrollVelocity-JS-TW', + type: 'registry:block', + title: 'ScrollVelocity', + description: "Text marquee animatio - speed and distortion scale with user's scroll velocity.", + files: [ + { + path: 'src/tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ScrollVelocity-TS-CSS', + type: 'registry:block', + title: 'ScrollVelocity', + description: "Text marquee animatio - speed and distortion scale with user's scroll velocity.", + files: [ + { + path: 'src/ts-default/TextAnimations/ScrollVelocity/ScrollVelocity.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/ScrollVelocity/ScrollVelocity.css', + type: 'registry:item' + } + ] + }, + { + name: 'ScrollVelocity-TS-TW', + type: 'registry:block', + title: 'ScrollVelocity', + description: "Text marquee animatio - speed and distortion scale with user's scroll velocity.", + files: [ + { + path: 'src/ts-tailwind/TextAnimations/ScrollVelocity/ScrollVelocity.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ShapeBlur-JS-CSS', + type: 'registry:block', + title: 'ShapeBlur', + description: 'Morphing blurred geometric shape. The effect occurs on hover.', + files: [ + { + path: 'src/content/Animations/ShapeBlur/ShapeBlur.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ShapeBlur-JS-TW', + type: 'registry:block', + title: 'ShapeBlur', + description: 'Morphing blurred geometric shape. The effect occurs on hover.', + files: [ + { + path: 'src/tailwind/Animations/ShapeBlur/ShapeBlur.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ShapeBlur-TS-CSS', + type: 'registry:block', + title: 'ShapeBlur', + description: 'Morphing blurred geometric shape. The effect occurs on hover.', + files: [ + { + path: 'src/ts-default/Animations/ShapeBlur/ShapeBlur.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ShapeBlur-TS-TW', + type: 'registry:block', + title: 'ShapeBlur', + description: 'Morphing blurred geometric shape. The effect occurs on hover.', + files: [ + { + path: 'src/ts-tailwind/Animations/ShapeBlur/ShapeBlur.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'ShinyText-JS-CSS', + type: 'registry:block', + title: 'ShinyText', + description: 'Metallic sheen sweeps across text producing a reflective highlight.', + files: [ + { + path: 'src/content/TextAnimations/ShinyText/ShinyText.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/ShinyText/ShinyText.css', + type: 'registry:item' + } + ] + }, + { + name: 'ShinyText-JS-TW', + type: 'registry:block', + title: 'ShinyText', + description: 'Metallic sheen sweeps across text producing a reflective highlight.', + files: [ + { + path: 'src/tailwind/TextAnimations/ShinyText/ShinyText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'ShinyText-TS-CSS', + type: 'registry:block', + title: 'ShinyText', + description: 'Metallic sheen sweeps across text producing a reflective highlight.', + files: [ + { + path: 'src/ts-default/TextAnimations/ShinyText/ShinyText.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/ShinyText/ShinyText.css', + type: 'registry:item' + } + ] + }, + { + name: 'ShinyText-TS-TW', + type: 'registry:block', + title: 'ShinyText', + description: 'Metallic sheen sweeps across text producing a reflective highlight.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/ShinyText/ShinyText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Shuffle-JS-CSS', + type: 'registry:block', + title: 'Shuffle', + description: 'Animated text reveal where characters shuffle before settling.', + files: [ + { + path: 'src/content/TextAnimations/Shuffle/Shuffle.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/Shuffle/Shuffle.css', + type: 'registry:item' + } + ] + }, + { + name: 'Shuffle-JS-TW', + type: 'registry:block', + title: 'Shuffle', + description: 'Animated text reveal where characters shuffle before settling.', + files: [ + { + path: 'src/tailwind/TextAnimations/Shuffle/Shuffle.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Shuffle-TS-CSS', + type: 'registry:block', + title: 'Shuffle', + description: 'Animated text reveal where characters shuffle before settling.', + files: [ + { + path: 'src/ts-default/TextAnimations/Shuffle/Shuffle.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/Shuffle/Shuffle.css', + type: 'registry:item' + } + ] + }, + { + name: 'Shuffle-TS-TW', + type: 'registry:block', + title: 'Shuffle', + description: 'Animated text reveal where characters shuffle before settling.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/Shuffle/Shuffle.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Silk-JS-CSS', + type: 'registry:block', + title: 'Silk', + description: 'Smooth waves background with soft lighting.', + files: [ + { + path: 'src/content/Backgrounds/Silk/Silk.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Silk-JS-TW', + type: 'registry:block', + title: 'Silk', + description: 'Smooth waves background with soft lighting.', + files: [ + { + path: 'src/tailwind/Backgrounds/Silk/Silk.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Silk-TS-CSS', + type: 'registry:block', + title: 'Silk', + description: 'Smooth waves background with soft lighting.', + files: [ + { + path: 'src/ts-default/Backgrounds/Silk/Silk.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Silk-TS-TW', + type: 'registry:block', + title: 'Silk', + description: 'Smooth waves background with soft lighting.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Silk/Silk.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'SplashCursor-JS-CSS', + type: 'registry:block', + title: 'SplashCursor', + description: 'Liquid splash burst at cursor with curling ripples and waves.', + files: [ + { + path: 'src/content/Animations/SplashCursor/SplashCursor.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'SplashCursor-JS-TW', + type: 'registry:block', + title: 'SplashCursor', + description: 'Liquid splash burst at cursor with curling ripples and waves.', + files: [ + { + path: 'src/tailwind/Animations/SplashCursor/SplashCursor.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'SplashCursor-TS-CSS', + type: 'registry:block', + title: 'SplashCursor', + description: 'Liquid splash burst at cursor with curling ripples and waves.', + files: [ + { + path: 'src/ts-default/Animations/SplashCursor/SplashCursor.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'SplashCursor-TS-TW', + type: 'registry:block', + title: 'SplashCursor', + description: 'Liquid splash burst at cursor with curling ripples and waves.', + files: [ + { + path: 'src/ts-tailwind/Animations/SplashCursor/SplashCursor.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'SplitText-JS-CSS', + type: 'registry:block', + title: 'SplitText', + description: 'Splits text into characters / words for staggered entrance animation.', + files: [ + { + path: 'src/content/TextAnimations/SplitText/SplitText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'SplitText-JS-TW', + type: 'registry:block', + title: 'SplitText', + description: 'Splits text into characters / words for staggered entrance animation.', + files: [ + { + path: 'src/tailwind/TextAnimations/SplitText/SplitText.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'SplitText-TS-CSS', + type: 'registry:block', + title: 'SplitText', + description: 'Splits text into characters / words for staggered entrance animation.', + files: [ + { + path: 'src/ts-default/TextAnimations/SplitText/SplitText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'SplitText-TS-TW', + type: 'registry:block', + title: 'SplitText', + description: 'Splits text into characters / words for staggered entrance animation.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/SplitText/SplitText.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'SpotlightCard-JS-CSS', + type: 'registry:block', + title: 'SpotlightCard', + description: 'Dynamic spotlight follows cursor casting gradient illumination.', + files: [ + { + path: 'src/content/Components/SpotlightCard/SpotlightCard.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/SpotlightCard/SpotlightCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'SpotlightCard-JS-TW', + type: 'registry:block', + title: 'SpotlightCard', + description: 'Dynamic spotlight follows cursor casting gradient illumination.', + files: [ + { + path: 'src/tailwind/Components/SpotlightCard/SpotlightCard.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'SpotlightCard-TS-CSS', + type: 'registry:block', + title: 'SpotlightCard', + description: 'Dynamic spotlight follows cursor casting gradient illumination.', + files: [ + { + path: 'src/ts-default/Components/SpotlightCard/SpotlightCard.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/SpotlightCard/SpotlightCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'SpotlightCard-TS-TW', + type: 'registry:block', + title: 'SpotlightCard', + description: 'Dynamic spotlight follows cursor casting gradient illumination.', + files: [ + { + path: 'src/ts-tailwind/Components/SpotlightCard/SpotlightCard.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Squares-JS-CSS', + type: 'registry:block', + title: 'Squares', + description: 'Animated squares with scaling + direction customization.', + files: [ + { + path: 'src/content/Backgrounds/Squares/Squares.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Squares/Squares.css', + type: 'registry:item' + } + ] + }, + { + name: 'Squares-JS-TW', + type: 'registry:block', + title: 'Squares', + description: 'Animated squares with scaling + direction customization.', + files: [ + { + path: 'src/tailwind/Backgrounds/Squares/Squares.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Squares-TS-CSS', + type: 'registry:block', + title: 'Squares', + description: 'Animated squares with scaling + direction customization.', + files: [ + { + path: 'src/ts-default/Backgrounds/Squares/Squares.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Squares/Squares.css', + type: 'registry:item' + } + ] + }, + { + name: 'Squares-TS-TW', + type: 'registry:block', + title: 'Squares', + description: 'Animated squares with scaling + direction customization.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Squares/Squares.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Stack-JS-CSS', + type: 'registry:block', + title: 'Stack', + description: 'Layered stack with swipe animations and smooth transitions.', + files: [ + { + path: 'src/content/Components/Stack/Stack.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/Stack/Stack.css', + type: 'registry:item' + } + ] + }, + { + name: 'Stack-JS-TW', + type: 'registry:block', + title: 'Stack', + description: 'Layered stack with swipe animations and smooth transitions.', + files: [ + { + path: 'src/tailwind/Components/Stack/Stack.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Stack-TS-CSS', + type: 'registry:block', + title: 'Stack', + description: 'Layered stack with swipe animations and smooth transitions.', + files: [ + { + path: 'src/ts-default/Components/Stack/Stack.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/Stack/Stack.css', + type: 'registry:item' + } + ] + }, + { + name: 'Stack-TS-TW', + type: 'registry:block', + title: 'Stack', + description: 'Layered stack with swipe animations and smooth transitions.', + files: [ + { + path: 'src/ts-tailwind/Components/Stack/Stack.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'StaggeredMenu-JS-CSS', + type: 'registry:block', + title: 'StaggeredMenu', + description: 'Menu with staggered item animations and smooth transitions on open/close.', + files: [ + { + path: 'src/content/Components/StaggeredMenu/StaggeredMenu.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/StaggeredMenu/StaggeredMenu.css', + type: 'registry:item' + } + ] + }, + { + name: 'StaggeredMenu-JS-TW', + type: 'registry:block', + title: 'StaggeredMenu', + description: 'Menu with staggered item animations and smooth transitions on open/close.', + files: [ + { + path: 'src/tailwind/Components/StaggeredMenu/StaggeredMenu.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'StaggeredMenu-TS-CSS', + type: 'registry:block', + title: 'StaggeredMenu', + description: 'Menu with staggered item animations and smooth transitions on open/close.', + files: [ + { + path: 'src/ts-default/Components/StaggeredMenu/StaggeredMenu.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/StaggeredMenu/StaggeredMenu.css', + type: 'registry:item' + } + ] + }, + { + name: 'StaggeredMenu-TS-TW', + type: 'registry:block', + title: 'StaggeredMenu', + description: 'Menu with staggered item animations and smooth transitions on open/close.', + files: [ + { + path: 'src/ts-tailwind/Components/StaggeredMenu/StaggeredMenu.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'StarBorder-JS-CSS', + type: 'registry:block', + title: 'StarBorder', + description: 'Animated star / sparkle border orbiting content with twinkle pulses.', + files: [ + { + path: 'src/content/Animations/StarBorder/StarBorder.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/StarBorder/StarBorder.css', + type: 'registry:item' + } + ] + }, + { + name: 'StarBorder-JS-TW', + type: 'registry:block', + title: 'StarBorder', + description: 'Animated star / sparkle border orbiting content with twinkle pulses.', + files: [ + { + path: 'src/tailwind/Animations/StarBorder/StarBorder.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'StarBorder-TS-CSS', + type: 'registry:block', + title: 'StarBorder', + description: 'Animated star / sparkle border orbiting content with twinkle pulses.', + files: [ + { + path: 'src/ts-default/Animations/StarBorder/StarBorder.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/StarBorder/StarBorder.css', + type: 'registry:item' + } + ] + }, + { + name: 'StarBorder-TS-TW', + type: 'registry:block', + title: 'StarBorder', + description: 'Animated star / sparkle border orbiting content with twinkle pulses.', + files: [ + { + path: 'src/ts-tailwind/Animations/StarBorder/StarBorder.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Stepper-JS-CSS', + type: 'registry:block', + title: 'Stepper', + description: 'Animated multi-step progress indicator with active state transitions.', + files: [ + { + path: 'src/content/Components/Stepper/Stepper.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/Stepper/Stepper.css', + type: 'registry:item' + } + ] + }, + { + name: 'Stepper-JS-TW', + type: 'registry:block', + title: 'Stepper', + description: 'Animated multi-step progress indicator with active state transitions.', + files: [ + { + path: 'src/tailwind/Components/Stepper/Stepper.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Stepper-TS-CSS', + type: 'registry:block', + title: 'Stepper', + description: 'Animated multi-step progress indicator with active state transitions.', + files: [ + { + path: 'src/ts-default/Components/Stepper/Stepper.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/Stepper/Stepper.css', + type: 'registry:item' + } + ] + }, + { + name: 'Stepper-TS-TW', + type: 'registry:block', + title: 'Stepper', + description: 'Animated multi-step progress indicator with active state transitions.', + files: [ + { + path: 'src/ts-tailwind/Components/Stepper/Stepper.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'StickerPeel-JS-CSS', + type: 'registry:block', + title: 'StickerPeel', + description: 'Sticker corner lift + peel interaction using 3D transform and shadow depth.', + files: [ + { + path: 'src/content/Animations/StickerPeel/StickerPeel.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/StickerPeel/StickerPeel.css', + type: 'registry:item' + } + ] + }, + { + name: 'StickerPeel-JS-TW', + type: 'registry:block', + title: 'StickerPeel', + description: 'Sticker corner lift + peel interaction using 3D transform and shadow depth.', + files: [ + { + path: 'src/tailwind/Animations/StickerPeel/StickerPeel.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'StickerPeel-TS-CSS', + type: 'registry:block', + title: 'StickerPeel', + description: 'Sticker corner lift + peel interaction using 3D transform and shadow depth.', + files: [ + { + path: 'src/ts-default/Animations/StickerPeel/StickerPeel.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/StickerPeel/StickerPeel.css', + type: 'registry:item' + } + ] + }, + { + name: 'StickerPeel-TS-TW', + type: 'registry:block', + title: 'StickerPeel', + description: 'Sticker corner lift + peel interaction using 3D transform and shadow depth.', + files: [ + { + path: 'src/ts-tailwind/Animations/StickerPeel/StickerPeel.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'TargetCursor-JS-CSS', + type: 'registry:block', + title: 'TargetCursor', + description: 'A cursor follow animation with 4 corners that lock onto targets.', + files: [ + { + path: 'src/content/Animations/TargetCursor/TargetCursor.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Animations/TargetCursor/TargetCursor.css', + type: 'registry:item' + } + ] + }, + { + name: 'TargetCursor-JS-TW', + type: 'registry:block', + title: 'TargetCursor', + description: 'A cursor follow animation with 4 corners that lock onto targets.', + files: [ + { + path: 'src/tailwind/Animations/TargetCursor/TargetCursor.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'TargetCursor-TS-CSS', + type: 'registry:block', + title: 'TargetCursor', + description: 'A cursor follow animation with 4 corners that lock onto targets.', + files: [ + { + path: 'src/ts-default/Animations/TargetCursor/TargetCursor.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Animations/TargetCursor/TargetCursor.css', + type: 'registry:item' + } + ] + }, + { + name: 'TargetCursor-TS-TW', + type: 'registry:block', + title: 'TargetCursor', + description: 'A cursor follow animation with 4 corners that lock onto targets.', + files: [ + { + path: 'src/ts-tailwind/Animations/TargetCursor/TargetCursor.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'TextCursor-JS-CSS', + type: 'registry:block', + title: 'TextCursor', + description: 'Make any text element follow your cursor, leaving a trail of copies behind it.', + files: [ + { + path: 'src/content/TextAnimations/TextCursor/TextCursor.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/TextCursor/TextCursor.css', + type: 'registry:item' + } + ] + }, + { + name: 'TextCursor-JS-TW', + type: 'registry:block', + title: 'TextCursor', + description: 'Make any text element follow your cursor, leaving a trail of copies behind it.', + files: [ + { + path: 'src/tailwind/TextAnimations/TextCursor/TextCursor.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'TextCursor-TS-CSS', + type: 'registry:block', + title: 'TextCursor', + description: 'Make any text element follow your cursor, leaving a trail of copies behind it.', + files: [ + { + path: 'src/ts-default/TextAnimations/TextCursor/TextCursor.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/TextCursor/TextCursor.css', + type: 'registry:item' + } + ] + }, + { + name: 'TextCursor-TS-TW', + type: 'registry:block', + title: 'TextCursor', + description: 'Make any text element follow your cursor, leaving a trail of copies behind it.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/TextCursor/TextCursor.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'TextPressure-JS-CSS', + type: 'registry:block', + title: 'TextPressure', + description: 'Characters scale / warp interactively based on pointer pressure zone.', + files: [ + { + path: 'src/content/TextAnimations/TextPressure/TextPressure.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'TextPressure-JS-TW', + type: 'registry:block', + title: 'TextPressure', + description: 'Characters scale / warp interactively based on pointer pressure zone.', + files: [ + { + path: 'src/tailwind/TextAnimations/TextPressure/TextPressure.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'TextPressure-TS-CSS', + type: 'registry:block', + title: 'TextPressure', + description: 'Characters scale / warp interactively based on pointer pressure zone.', + files: [ + { + path: 'src/ts-default/TextAnimations/TextPressure/TextPressure.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'TextPressure-TS-TW', + type: 'registry:block', + title: 'TextPressure', + description: 'Characters scale / warp interactively based on pointer pressure zone.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/TextPressure/TextPressure.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'TextType-JS-CSS', + type: 'registry:block', + title: 'TextType', + description: 'Typewriter effect with blinking cursor and adjustable typing cadence.', + files: [ + { + path: 'src/content/TextAnimations/TextType/TextType.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/TextType/TextType.css', + type: 'registry:item' + } + ] + }, + { + name: 'TextType-JS-TW', + type: 'registry:block', + title: 'TextType', + description: 'Typewriter effect with blinking cursor and adjustable typing cadence.', + files: [ + { + path: 'src/tailwind/TextAnimations/TextType/TextType.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'TextType-TS-CSS', + type: 'registry:block', + title: 'TextType', + description: 'Typewriter effect with blinking cursor and adjustable typing cadence.', + files: [ + { + path: 'src/ts-default/TextAnimations/TextType/TextType.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/TextType/TextType.css', + type: 'registry:item' + } + ] + }, + { + name: 'TextType-TS-TW', + type: 'registry:block', + title: 'TextType', + description: 'Typewriter effect with blinking cursor and adjustable typing cadence.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/TextType/TextType.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Threads-JS-CSS', + type: 'registry:block', + title: 'Threads', + description: 'Animated pattern of lines forming a fabric-like motion.', + files: [ + { + path: 'src/content/Backgrounds/Threads/Threads.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Threads/Threads.css', + type: 'registry:item' + } + ] + }, + { + name: 'Threads-JS-TW', + type: 'registry:block', + title: 'Threads', + description: 'Animated pattern of lines forming a fabric-like motion.', + files: [ + { + path: 'src/tailwind/Backgrounds/Threads/Threads.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Threads-TS-CSS', + type: 'registry:block', + title: 'Threads', + description: 'Animated pattern of lines forming a fabric-like motion.', + files: [ + { + path: 'src/ts-default/Backgrounds/Threads/Threads.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Threads/Threads.css', + type: 'registry:item' + } + ] + }, + { + name: 'Threads-TS-TW', + type: 'registry:block', + title: 'Threads', + description: 'Animated pattern of lines forming a fabric-like motion.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Threads/Threads.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'TiltedCard-JS-CSS', + type: 'registry:block', + title: 'TiltedCard', + description: '3D perspective tilt card reacting to pointer.', + files: [ + { + path: 'src/content/Components/TiltedCard/TiltedCard.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Components/TiltedCard/TiltedCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'TiltedCard-JS-TW', + type: 'registry:block', + title: 'TiltedCard', + description: '3D perspective tilt card reacting to pointer.', + files: [ + { + path: 'src/tailwind/Components/TiltedCard/TiltedCard.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'TiltedCard-TS-CSS', + type: 'registry:block', + title: 'TiltedCard', + description: '3D perspective tilt card reacting to pointer.', + files: [ + { + path: 'src/ts-default/Components/TiltedCard/TiltedCard.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Components/TiltedCard/TiltedCard.css', + type: 'registry:item' + } + ] + }, + { + name: 'TiltedCard-TS-TW', + type: 'registry:block', + title: 'TiltedCard', + description: '3D perspective tilt card reacting to pointer.', + files: [ + { + path: 'src/ts-tailwind/Components/TiltedCard/TiltedCard.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'TrueFocus-JS-CSS', + type: 'registry:block', + title: 'TrueFocus', + description: 'Applies dynamic blur / clarity based over a series of words in order.', + files: [ + { + path: 'src/content/TextAnimations/TrueFocus/TrueFocus.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/TrueFocus/TrueFocus.css', + type: 'registry:item' + } + ] + }, + { + name: 'TrueFocus-JS-TW', + type: 'registry:block', + title: 'TrueFocus', + description: 'Applies dynamic blur / clarity based over a series of words in order.', + files: [ + { + path: 'src/tailwind/TextAnimations/TrueFocus/TrueFocus.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'TrueFocus-TS-CSS', + type: 'registry:block', + title: 'TrueFocus', + description: 'Applies dynamic blur / clarity based over a series of words in order.', + files: [ + { + path: 'src/ts-default/TextAnimations/TrueFocus/TrueFocus.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/TrueFocus/TrueFocus.css', + type: 'registry:item' + } + ] + }, + { + name: 'TrueFocus-TS-TW', + type: 'registry:block', + title: 'TrueFocus', + description: 'Applies dynamic blur / clarity based over a series of words in order.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/TrueFocus/TrueFocus.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'VariableProximity-JS-CSS', + type: 'registry:block', + title: 'VariableProximity', + description: 'Letter styling changes continuously with pointer distance mapping.', + files: [ + { + path: 'src/content/TextAnimations/VariableProximity/VariableProximity.jsx', + type: 'registry:component' + }, + { + path: 'src/content/TextAnimations/VariableProximity/VariableProximity.css', + type: 'registry:item' + } + ] + }, + { + name: 'VariableProximity-JS-TW', + type: 'registry:block', + title: 'VariableProximity', + description: 'Letter styling changes continuously with pointer distance mapping.', + files: [ + { + path: 'src/tailwind/TextAnimations/VariableProximity/VariableProximity.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'VariableProximity-TS-CSS', + type: 'registry:block', + title: 'VariableProximity', + description: 'Letter styling changes continuously with pointer distance mapping.', + files: [ + { + path: 'src/ts-default/TextAnimations/VariableProximity/VariableProximity.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/TextAnimations/VariableProximity/VariableProximity.css', + type: 'registry:item' + } + ] + }, + { + name: 'VariableProximity-TS-TW', + type: 'registry:block', + title: 'VariableProximity', + description: 'Letter styling changes continuously with pointer distance mapping.', + files: [ + { + path: 'src/ts-tailwind/TextAnimations/VariableProximity/VariableProximity.tsx', + type: 'registry:component' + } + ] + }, + { + name: 'Waves-JS-CSS', + type: 'registry:block', + title: 'Waves', + description: 'Layered lines that form smooth wave patterns with animation.', + files: [ + { + path: 'src/content/Backgrounds/Waves/Waves.jsx', + type: 'registry:component' + }, + { + path: 'src/content/Backgrounds/Waves/Waves.css', + type: 'registry:item' + } + ] + }, + { + name: 'Waves-JS-TW', + type: 'registry:block', + title: 'Waves', + description: 'Layered lines that form smooth wave patterns with animation.', + files: [ + { + path: 'src/tailwind/Backgrounds/Waves/Waves.jsx', + type: 'registry:component' + } + ] + }, + { + name: 'Waves-TS-CSS', + type: 'registry:block', + title: 'Waves', + description: 'Layered lines that form smooth wave patterns with animation.', + files: [ + { + path: 'src/ts-default/Backgrounds/Waves/Waves.tsx', + type: 'registry:component' + }, + { + path: 'src/ts-default/Backgrounds/Waves/Waves.css', + type: 'registry:item' + } + ] + }, + { + name: 'Waves-TS-TW', + type: 'registry:block', + title: 'Waves', + description: 'Layered lines that form smooth wave patterns with animation.', + files: [ + { + path: 'src/ts-tailwind/Backgrounds/Waves/Waves.tsx', + type: 'registry:component' + } + ] + } + ] + } +}); diff --git a/package-lock.json b/package-lock.json index 6a195599..14296ae2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "three": "^0.167.1" }, "devDependencies": { + "@jsrepo/shadcn": "^0.0.1-beta.10", "@types/matter-js": "^0.19.8", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -57,7 +58,7 @@ "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", - "jsrepo": "^1.30.1", + "jsrepo": "^3.0.0-beta.8", "postcss-safe-parser": "^7.0.1", "prettier": "^3.6.2", "shadcn": "^3.1.0", @@ -88,43 +89,6 @@ "nup": "bin/nup.mjs" } }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.39.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.39.0.tgz", - "integrity": "sha512-eMyDIPRZbt1CCLErRCi3exlAvNkBtRe+kW5vvJyef93PmNr/clstYgHhtvmkxN82nlKgzyGPCyGxrm0JQ1ZIdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - } - }, - "node_modules/@anthropic-ai/sdk/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@ark-ui/react": { "version": "5.25.0", "resolved": "https://registry.npmjs.org/@ark-ui/react/-/react-5.25.0.tgz", @@ -668,36 +632,6 @@ "node": ">=6.9.0" } }, - "node_modules/@biomejs/js-api": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@biomejs/js-api/-/js-api-0.7.1.tgz", - "integrity": "sha512-VFdgFFZWcyCQxZcAasyv8Enpexn4CblMdWmr6izLYHTLcbd+z9x/LuKU71qnmClABfnYqZjiY7c8DKTVri3Ajw==", - "dev": true, - "license": "MIT OR Apache-2.0", - "peerDependencies": { - "@biomejs/wasm-bundler": "^1.9.2", - "@biomejs/wasm-nodejs": "^1.9.2", - "@biomejs/wasm-web": "^1.9.2" - }, - "peerDependenciesMeta": { - "@biomejs/wasm-bundler": { - "optional": true - }, - "@biomejs/wasm-nodejs": { - "optional": true - }, - "@biomejs/wasm-web": { - "optional": true - } - } - }, - "node_modules/@biomejs/wasm-nodejs": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@biomejs/wasm-nodejs/-/wasm-nodejs-1.9.4.tgz", - "integrity": "sha512-ZqNlhKcZW6MW1LxWIOfh9YVrBykvzyFad3bOh6JJFraDnNa3NXboRDiaI8dmrbb0ZHXCU1Tsq6WQsKV2Vpp5dw==", - "dev": true, - "license": "MIT OR Apache-2.0" - }, "node_modules/@bundled-es-modules/cookie": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", @@ -749,29 +683,6 @@ "react-dom": ">=18" } }, - "node_modules/@clack/core": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.4.2.tgz", - "integrity": "sha512-NYQfcEy8MWIxrT5Fj8nIVchfRFA26yYKJcvBS7WlUIlw2OmQOY9DhGGXMovyI5J5PpxrCPGkgUi207EBrjpBvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, - "node_modules/@clack/prompts": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.10.1.tgz", - "integrity": "sha512-Q0T02vx8ZM9XSv9/Yde0jTmmBQufZhPJfYAg2XrrrxWWaZgq1rr8nU8Hv710BQ1dhoP8rtY7YUdpGej2Qza/cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@clack/core": "0.4.2", - "picocolors": "^1.0.0", - "sisteransi": "^1.0.5" - } - }, "node_modules/@dimforge/rapier3d-compat": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.15.0.tgz", @@ -1840,6 +1751,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsrepo/shadcn": { + "version": "0.0.1-beta.10", + "resolved": "https://registry.npmjs.org/@jsrepo/shadcn/-/shadcn-0.0.1-beta.10.tgz", + "integrity": "sha512-EkaxahkAJADc4M1hRt1sOM1Td6NPb52XKohYxvdS4trpET1XcQH6OTlt51f/fVfCO6jvd3OizmIwS0zxnDqZfg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "jsrepo": "3.0.0-beta.9" + } + }, "node_modules/@mediapipe/tasks-vision": { "version": "0.10.17", "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", @@ -1992,372 +1913,6 @@ "node": ">= 8" } }, - "node_modules/@octokit/app": { - "version": "15.1.6", - "resolved": "https://registry.npmjs.org/@octokit/app/-/app-15.1.6.tgz", - "integrity": "sha512-WELCamoCJo9SN0lf3SWZccf68CF0sBNPQuLYmZ/n87p5qvBJDe9aBtr5dHkh7T9nxWZ608pizwsUbypSzZAiUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-app": "^7.2.1", - "@octokit/auth-unauthenticated": "^6.1.3", - "@octokit/core": "^6.1.5", - "@octokit/oauth-app": "^7.1.6", - "@octokit/plugin-paginate-rest": "^12.0.0", - "@octokit/types": "^14.0.0", - "@octokit/webhooks": "^13.6.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-app": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.2.2.tgz", - "integrity": "sha512-p6hJtEyQDCJEPN9ijjhEC/kpFHMHN4Gca9r+8S0S8EJi7NaWftaEmexjxxpT1DFBeJpN4u/5RE22ArnyypupJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^8.1.4", - "@octokit/auth-oauth-user": "^5.1.4", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "toad-cache": "^3.7.0", - "universal-github-app-jwt": "^2.2.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-app": { - "version": "8.1.4", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.4.tgz", - "integrity": "sha512-71iBa5SflSXcclk/OL3lJzdt4iFs56OJdpBGEBl1wULp7C58uiswZLV6TdRaiAzHP1LT8ezpbHlKuxADb+4NkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^7.1.5", - "@octokit/auth-oauth-user": "^5.1.4", - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-device": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.5.tgz", - "integrity": "sha512-lR00+k7+N6xeECj0JuXeULQ2TSBB/zjTAmNF2+vyGPDEFx1dgk1hTDmL13MjbSmzusuAmuJD8Pu39rjp9jH6yw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/oauth-methods": "^5.1.5", - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-oauth-user": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.6.tgz", - "integrity": "sha512-/R8vgeoulp7rJs+wfJ2LtXEVC7pjQTIqDab7wPKwVG6+2v/lUnCOub6vaHmysQBbb45FknM3tbHW8TOVqYHxCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-device": "^7.1.5", - "@octokit/oauth-methods": "^5.1.5", - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-token": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", - "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/auth-unauthenticated": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-6.1.3.tgz", - "integrity": "sha512-d5gWJla3WdSl1yjbfMpET+hUSFCE15qM0KVSB0H1shyuJihf/RL1KqWoZMIaonHvlNojkL9XtLFp8QeLe+1iwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", - "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-token": "^5.0.0", - "@octokit/graphql": "^8.2.2", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "before-after-hook": "^3.0.2", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/endpoint": { - "version": "10.1.4", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", - "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/graphql": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz", - "integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request": "^9.2.3", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-app": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-7.1.6.tgz", - "integrity": "sha512-OMcMzY2WFARg80oJNFwWbY51TBUfLH4JGTy119cqiDawSFXSIBujxmpXiKbGWQlvfn0CxE6f7/+c6+Kr5hI2YA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/auth-oauth-app": "^8.1.3", - "@octokit/auth-oauth-user": "^5.1.3", - "@octokit/auth-unauthenticated": "^6.1.2", - "@octokit/core": "^6.1.4", - "@octokit/oauth-authorization-url": "^7.1.1", - "@octokit/oauth-methods": "^5.1.4", - "@types/aws-lambda": "^8.10.83", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-authorization-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", - "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/oauth-methods": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.5.tgz", - "integrity": "sha512-Ev7K8bkYrYLhoOSZGVAGsLEscZQyq7XQONCBBAl2JdMg7IT3PQn/y8P0KjloPoYpI5UylqYrLeUcScaYWXwDvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/oauth-authorization-url": "^7.0.0", - "@octokit/request": "^9.2.3", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/openapi-types": { - "version": "25.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", - "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/openapi-webhooks-types": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-11.0.0.tgz", - "integrity": "sha512-ZBzCFj98v3SuRM7oBas6BHZMJRadlnDoeFfvm1olVxZnYeU6Vh97FhPxyS5aLh5pN51GYv2I51l/hVUAVkGBlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@octokit/plugin-paginate-graphql": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.4.tgz", - "integrity": "sha512-pLZES1jWaOynXKHOqdnwZ5ULeVR6tVVCMm+AUbp0htdcyXDU95WbkYdU4R2ej1wKj5Tu94Mee2Ne0PjPO9cCyA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-12.0.0.tgz", - "integrity": "sha512-MPd6WK1VtZ52lFrgZ0R2FlaoiWllzgqFHaSZxvp72NmoDeZ0m8GeJdg4oB6ctqMTYyrnDYp592Xma21mrgiyDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-14.0.0.tgz", - "integrity": "sha512-iQt6ovem4b7zZYZQtdv+PwgbL5VPq37th1m2x2TdkgimIDJpsi2A6Q/OI/23i/hR6z5mL0EgisNR4dcbmckSZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-retry": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.2.1.tgz", - "integrity": "sha512-wUc3gv0D6vNHpGxSaR3FlqJpTXGWgqmk607N9L3LvPL4QjaxDgX/1nY2mGpT37Khn+nlIXdljczkRnNdTTV3/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": ">=6" - } - }, - "node_modules/@octokit/plugin-throttling": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-10.0.0.tgz", - "integrity": "sha512-Kuq5/qs0DVYTHZuBAzCZStCzo2nKvVRo/TDNhCcpC2TKiOGz/DisXMCvjt3/b5kr6SCI1Y8eeeJTHBxxpFvZEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0", - "bottleneck": "^2.15.3" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "@octokit/core": "^6.1.3" - } - }, - "node_modules/@octokit/request": { - "version": "9.2.4", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", - "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/endpoint": "^10.1.4", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "fast-content-type-parse": "^2.0.0", - "universal-user-agent": "^7.0.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/request-error": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", - "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/types": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", - "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-types": "^25.1.0" - } - }, - "node_modules/@octokit/webhooks": { - "version": "13.9.1", - "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-13.9.1.tgz", - "integrity": "sha512-Nss2b4Jyn4wB3EAqAPJypGuCJFalz/ZujKBQQ5934To7Xw9xjf4hkr/EAByxQY7hp7MKd790bWGz7XYSTsHmaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/openapi-webhooks-types": "11.0.0", - "@octokit/request-error": "^6.1.7", - "@octokit/webhooks-methods": "^5.1.1" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@octokit/webhooks-methods": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-5.1.1.tgz", - "integrity": "sha512-NGlEHZDseJTCj8TMMFehzwa9g7On4KJMPVHDSrHxCQumL6uSQR8wIkP/qesv52fXqV1BPf4pTxwtS31ldAt9Xg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, "node_modules/@open-draft/deferred-promise": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", @@ -2383,10 +1938,27 @@ "dev": true, "license": "MIT" }, + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.96.0.tgz", + "integrity": "sha512-CofbPOiW1PG+hi8bgElJPK0ioHfw8nt4Vw9d+Q9JuMhygS6LbQyu1W6tIFZ1OPFofeFRdWus3vD29FBx+tvFOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.64.0.tgz", - "integrity": "sha512-FfmLZWrt5rsG+wzruv0xfYci1fE/GQ/HnUCmB+j3keU4SfDxkxSIGUTphxdcE8S4ISoLelgeVZiE8QDGRhmSoQ==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.96.0.tgz", + "integrity": "sha512-+HZ2L1a/1BsUXYik8XqQwT2Tl5Z3jRQ/RRQiPV9UsB2skKyd91NLDlQlMpdhjLGs9Qe7Y42unFjRg2iHjIiwnw==", "cpu": [ "arm64" ], @@ -2397,13 +1969,30 @@ "darwin" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.96.0.tgz", + "integrity": "sha512-GC8wH1W0XaCLyTeGsmyaMdnItiYQkqfTcn9Ygc55AWI+m11lCjQeoKDIsDCm/QwrKLCN07u3WWWsuPs5ubfXpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.64.0.tgz", - "integrity": "sha512-FFbtYNdlRw6d/KcfSxqOAJAI4evijC+i+PHQkpB8JJGr+mPzQEPKwVa8vh2Qe/lcspaQs6IrR2GRpJ+5UvciRw==", + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.96.0.tgz", + "integrity": "sha512-8SeXi2FmlN15uPY5oM03cua5RXBDYmY34Uewongv6RUiAaU/kWxLvzuijpyNC+yQ1r4fC2LbWJhAsKpX5qkA6g==", "cpu": [ "x64" ], @@ -2411,16 +2000,16 @@ "license": "MIT", "optional": true, "os": [ - "darwin" + "freebsd" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.64.0.tgz", - "integrity": "sha512-u113yYpeTW0rQBp6Lld2PvdEMzVQmTq8n2T4WDb7UNGQFCMzoURCKgahkIZCStph4+zHAFU5uKwG5waQaswCyw==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.96.0.tgz", + "integrity": "sha512-UEs+Zf6T2/FwQlLgv7gfZsKmY19sl3hK57r2BQVc2eCmCmF/deeqDcWyFjzkNLgdDDucY60PoNhNGClDm605uQ==", "cpu": [ "arm" ], @@ -2431,13 +2020,30 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.96.0.tgz", + "integrity": "sha512-1kuWvjR2+ORJMoyxt9LSbLcDhXZnL25XOuv9VmH6NmSPvLgewzuubSlm++W03x+U7SzWFilBsdwIHtD/0mjERw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.64.0.tgz", - "integrity": "sha512-cqWgdJcXJ2u2Rcjd/+4mY10DPISZtKosgyL7eMZwZdCNJD8q2ohS57pk6IbCmopF55QAh9/Py8rajblKbFCJBg==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.96.0.tgz", + "integrity": "sha512-PHH4ETR1t0fymxuhpQNj3Z9t/78/zZa2Lj3Z3I0ZOd+/Ex+gtdhGoB5xYyy7lcYGAPMfZ+Gmr+dTCr1GYNZ3BA==", "cpu": [ "arm64" ], @@ -2448,13 +2054,13 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.64.0.tgz", - "integrity": "sha512-b7Ma+CDlkK+UIU/Zr8Ydo+q3A9ouWUhV8PzWcnfOxiOwK+JEaoz5N02ixAPK8qvO+IKqzP00HzxPD8tUto8GcA==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.96.0.tgz", + "integrity": "sha512-fjDPbZjkqaDSTBe0FM8nZ9zBw4B/NF/I0gH7CfvNDwIj9smISaNFypYeomkvubORpnbX9ORhvhYwg3TxQ60OGA==", "cpu": [ "arm64" ], @@ -2465,13 +2071,47 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.96.0.tgz", + "integrity": "sha512-59KAHd/6/LmjkdSAuJn0piKmwSavMasWNUKuYLX/UnqI5KkGIp14+LBwwaBG6KzOtIq1NrRCnmlL4XSEaNkzTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.96.0.tgz", + "integrity": "sha512-VtupojtgahY8XmLwpVpM3C1WQEgMD1JxpB8lzUtdSLwosWaaz1EAl+VXWNuxTTZusNuLBtmR+F0qql22ISi/9g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.64.0.tgz", - "integrity": "sha512-7o/qfZNZ0kt1o5vtqUz6nQkV6tuCGor4+gOmqtrb2TtnAo3qxYwPXZVjd9LKv39Z+Nfpqz/2cnR+GIqUNqv34A==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.96.0.tgz", + "integrity": "sha512-8XSY9aUYY+5I4I1mhSEWmYqdUrJi3J5cCAInvEVHyTnDAPkhb+tnLGVZD696TpW+lFOLrTFF2V5GMWJVafqIUA==", "cpu": [ "x64" ], @@ -2482,13 +2122,13 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.64.0.tgz", - "integrity": "sha512-nuL0rqoWgvO11pP7g5FYdTDsjX93mt8ZFtUaOL4HMVkvRAx3XiKltJBYXXWiI2kySbHRC/XHJftAKWEgGhcXgg==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.96.0.tgz", + "integrity": "sha512-IIVNtqhA0uxKkD8Y6aZinKO/sOD5O62VlduE54FnUU2rzZEszrZQLL8nMGVZhTdPaKW5M1aeLmjcdnOs6er1Jg==", "cpu": [ "x64" ], @@ -2499,13 +2139,13 @@ "linux" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.64.0.tgz", - "integrity": "sha512-iZ5LeOPDo0gCISzcq1JKo3HGqXwuQDTgHVPBUs+UFdCL9WJ9DmNkXXQPLVYEyyI/YFXg15y7Rv2L+FEvpvYa+w==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.96.0.tgz", + "integrity": "sha512-TJ/sNPbVD4u6kUwm7sDKa5iRDEB8vd7ZIMjYqFrrAo9US1RGYOSvt6Ie9sDRekUL9fZhNsykvSrpmIj6dg/C2w==", "cpu": [ "wasm32" ], @@ -2513,16 +2153,29 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.9" + "@napi-rs/wasm-runtime": "^1.0.7" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, "node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.64.0.tgz", - "integrity": "sha512-9kWLwYOT9sCVrFL3Egpt4+viAYtYOwmstGoy/CPikC0fxEpB760qln8u+MfZpbrH0Df2XgEdAUTqiwnRwcp+uA==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.96.0.tgz", + "integrity": "sha512-zCOhRB7MYVIHLj+2QYoTuLyaipiD8JG/ggUjfsMUaupRPpvwQNhsxINLIcTcb0AS+OsT7/OREhydjO74STqQzQ==", "cpu": [ "arm64" ], @@ -2533,13 +2186,13 @@ "win32" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.64.0.tgz", - "integrity": "sha512-EHQglaBx4LpNw9BMA65aM36isTpuAdWxGbAUH7w55GYIGjVG7hIsMx/MuOrJXsmOBVmRokoYNYLN7X5aTd5TmQ==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.96.0.tgz", + "integrity": "sha512-J6zfx9TE0oS+TrqBUjMVMOi/d/j3HMj69Pip263pETOEPm788N0HXKPsc2X2jUfSTHzD9vmdjq0QFymbf2LhWg==", "cpu": [ "x64" ], @@ -2550,13 +2203,13 @@ "win32" ], "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@oxc-project/types": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.64.0.tgz", - "integrity": "sha512-B0dxuEZFV6M4tXjPFwDSaED5/J55YUhODBaF09xNFNRrEyzQLKZuhKXAw1xYK8bO4K8Jn1d21TZfei3kAIE8dA==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.96.0.tgz", + "integrity": "sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==", "dev": true, "license": "MIT", "funding": { @@ -2568,6 +2221,19 @@ "resolved": "https://registry.npmjs.org/@pandacss/is-valid-prop/-/is-valid-prop-0.54.0.tgz", "integrity": "sha512-UhRgg1k9VKRCBAHl+XUK3lvN0k9bYifzYGZOqajDid4L1DyU813A1L0ZwN4iV9WX5TX3PfUugqtgG9LnIeFGBQ==" }, + "node_modules/@quansync/fs": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-0.1.5.tgz", + "integrity": "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^0.2.11" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/@react-three/drei": { "version": "10.7.6", "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.6.tgz", @@ -3021,16 +2687,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", - "integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8.9.0" - } - }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -3384,13 +3040,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/aws-lambda": { - "version": "8.10.152", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.152.tgz", - "integrity": "sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3475,23 +3124,13 @@ "version": "18.19.127", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.127.tgz", "integrity": "sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "undici-types": "~5.26.4" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.4" - } - }, "node_modules/@types/offscreencanvas": { "version": "2019.7.3", "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", @@ -3646,8 +3285,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.22.tgz", "integrity": "sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/parser": "^7.28.4", "@vue/shared": "3.5.22", @@ -3660,8 +3300,9 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "devOptional": true, "license": "BSD-2-Clause", + "optional": true, + "peer": true, "engines": { "node": ">=0.12" }, @@ -3673,15 +3314,17 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@vue/compiler-dom": { "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.22.tgz", "integrity": "sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/compiler-core": "3.5.22", "@vue/shared": "3.5.22" @@ -3691,8 +3334,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.22.tgz", "integrity": "sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@babel/parser": "^7.28.4", "@vue/compiler-core": "3.5.22", @@ -3709,15 +3353,17 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@vue/compiler-ssr": { "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.22.tgz", "integrity": "sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.22", "@vue/shared": "3.5.22" @@ -3727,8 +3373,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.22.tgz", "integrity": "sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/shared": "3.5.22" } @@ -3737,8 +3384,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.22.tgz", "integrity": "sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/reactivity": "3.5.22", "@vue/shared": "3.5.22" @@ -3748,8 +3396,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.22.tgz", "integrity": "sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/reactivity": "3.5.22", "@vue/runtime-core": "3.5.22", @@ -3761,8 +3410,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.22.tgz", "integrity": "sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/compiler-ssr": "3.5.22", "@vue/shared": "3.5.22" @@ -3775,8 +3425,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.22.tgz", "integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@webgpu/types": { "version": "0.1.65", @@ -4627,19 +4278,6 @@ "integrity": "sha512-4nU9lfFlLLW/4T+/HaP+HdHYFeWvacxSVcccv0JSf+ZTC110IldV48kZELP+wFg9xDL/jCPPjlRtO1K64EIwgA==", "license": "MIT" }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -4710,19 +4348,6 @@ "node": ">= 14" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -4740,80 +4365,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -4860,16 +4411,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", - "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -5031,23 +4572,6 @@ "node": ">= 0.4" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/atomically": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.0.3.tgz", - "integrity": "sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==", - "dev": true, - "dependencies": { - "stubborn-fs": "^1.2.5", - "when-exit": "^2.1.1" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -5064,16 +4588,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axobject-query": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", - "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -5126,13 +4640,6 @@ "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/before-after-hook": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", - "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -5163,62 +4670,6 @@ "node": ">=18" } }, - "node_modules/bottleneck": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", - "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/boxen": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^8.0.0", - "chalk": "^5.3.0", - "cli-boxes": "^3.0.0", - "string-width": "^7.2.0", - "type-fest": "^4.21.0", - "widest-line": "^5.0.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5370,19 +4821,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/camera-controls": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.0.tgz", @@ -5489,26 +4927,13 @@ "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://polar.sh/cva" } }, "node_modules/cli-cursor": { @@ -5641,19 +5066,6 @@ "dev": true, "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/comma-separated-tokens": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", @@ -5665,13 +5077,13 @@ } }, "node_modules/commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/complex.js": { @@ -5719,67 +5131,6 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/conf": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/conf/-/conf-13.1.0.tgz", - "integrity": "sha512-Bi6v586cy1CoTFViVO4lGTtx780lfF96fUmS1lSX6wpZf6330NvHUu6fReVuDP1de8Mg0nkZb01c8tAQdz1o3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "atomically": "^2.0.3", - "debounce-fn": "^6.0.0", - "dot-prop": "^9.0.0", - "env-paths": "^3.0.0", - "json-schema-typed": "^8.0.1", - "semver": "^7.6.3", - "uint8array-extras": "^1.4.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/conf/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/conf/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/conf/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -5891,16 +5242,6 @@ "node": ">= 8" } }, - "node_modules/css-dependency": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/css-dependency/-/css-dependency-0.0.3.tgz", - "integrity": "sha512-jLQuve6jhpjkH3+k2Y8jK3j27Hm3rnIsRW/8oOf9oxFOBI5iu6sndwSv6lj5dNfO9JVP6cNb8Xs+VXhndgtLfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.1.0" - } - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -5971,22 +5312,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/debounce-fn": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-6.0.0.tgz", - "integrity": "sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -6078,15 +5403,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } + "license": "MIT" }, "node_modules/depd": { "version": "2.0.0", @@ -6116,16 +5438,6 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", - "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -6149,35 +5461,6 @@ "csstype": "^3.0.2" } }, - "node_modules/dot-prop": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^4.18.2" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dot-prop/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dotenv": { "version": "17.2.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", @@ -6274,32 +5557,6 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/env-paths": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", - "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", @@ -6733,13 +5990,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esm-env": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", - "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "dev": true, - "license": "MIT" - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -6785,16 +6035,6 @@ "node": ">=0.10" } }, - "node_modules/esrap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", - "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -6818,16 +6058,6 @@ "node": ">=4.0" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -6848,16 +6078,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -7006,23 +6226,6 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, - "node_modules/fast-content-type-parse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", - "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "MIT" - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7080,23 +6283,6 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -7299,30 +6485,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", - "dev": true, - "license": "MIT" - }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -7331,20 +6493,6 @@ "node": ">=0.4.x" } }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -7628,19 +6776,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/gl-matrix": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", @@ -7971,16 +7106,6 @@ "node": ">=18.18.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -8467,16 +7592,6 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "license": "MIT" }, - "node_modules/is-reference": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", - "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.6" - } - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -8720,9 +7835,9 @@ "license": "MIT" }, "node_modules/jiti": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", - "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -8779,13 +7894,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema-typed": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.1.tgz", - "integrity": "sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -8820,96 +7928,30 @@ } }, "node_modules/jsrepo": { - "version": "1.47.1", - "resolved": "https://registry.npmjs.org/jsrepo/-/jsrepo-1.47.1.tgz", - "integrity": "sha512-m126Q8u8cQiZfZC8o4ntSwlNGU+pOAQjphYXL0dguV8zs9f4PlYgwUGnLh73j4Qvyc1usvBLHLMTfk94GMw+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@anthropic-ai/sdk": "^0.39.0", - "@biomejs/js-api": "^0.7.1", - "@biomejs/wasm-nodejs": "^1.9.4", - "@clack/prompts": "^0.10.1", - "boxen": "^8.0.1", - "chalk": "^5.4.1", - "commander": "^13.1.0", - "conf": "^13.1.0", - "css-dependency": "^0.0.3", - "diff": "^7.0.0", - "escape-string-regexp": "^5.0.0", - "estree-walker": "^3.0.3", - "execa": "^9.5.2", - "get-tsconfig": "^4.10.0", - "ignore": "^7.0.3", - "is-unicode-supported": "^2.1.0", - "node-fetch": "^3.3.2", - "octokit": "^4.1.2", - "ollama": "^0.5.14", - "openai": "^4.93.0", - "oxc-parser": "^0.64.0", - "package-manager-detector": "^1.1.0", - "parse5": "^7.2.1", - "pathe": "^2.0.3", - "prettier": "^3.5.3", - "prettier-plugin-svelte": "^3.3.3", - "semver": "^7.7.1", - "sisteransi": "^1.0.5", - "svelte": "^5.25.9", - "valibot": "1.0.0", - "validate-npm-package-name": "^6.0.0", - "vue": "^3.5.13" - }, - "bin": { - "jsrepo": "dist/index.js" - } - }, - "node_modules/jsrepo/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jsrepo/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "version": "3.0.0-beta.9", + "resolved": "https://registry.npmjs.org/jsrepo/-/jsrepo-3.0.0-beta.9.tgz", + "integrity": "sha512-sHVp7H8qEE/o+4wK5NhsPqXqXkMhyv2ZJzaUqyB5GVNpbon3/VJxD1qi0iYs4SN8pkc/vwx0H6kbZp1THxSEeg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "commander": "^14.0.2", + "oxc-parser": "^0.96.0", + "unconfig": "^7.3.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jsrepo/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/jsrepo/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", "bin": { - "semver": "bin/semver.js" + "jsrepo": "dist/bin.js" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "svelte": "^5.41.4", + "vue": "^3.5.22" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } } }, "node_modules/jsx-ast-utils": { @@ -9231,13 +8273,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, - "node_modules/locate-character": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", - "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "dev": true, - "license": "MIT" - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -9489,29 +8524,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -9944,45 +8956,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/octokit": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/octokit/-/octokit-4.1.4.tgz", - "integrity": "sha512-cRvxRte6FU3vAHRC9+PMSY3D+mRAs2Rd9emMoqp70UGRvJRM3sbAoim2IXRZNNsf8wVfn4sGxVBHRAP+JBVX/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@octokit/app": "^15.1.6", - "@octokit/core": "^6.1.5", - "@octokit/oauth-app": "^7.1.6", - "@octokit/plugin-paginate-graphql": "^5.2.4", - "@octokit/plugin-paginate-rest": "^12.0.0", - "@octokit/plugin-rest-endpoint-methods": "^14.0.0", - "@octokit/plugin-retry": "^7.2.1", - "@octokit/plugin-throttling": "^10.0.0", - "@octokit/request-error": "^6.1.8", - "@octokit/types": "^14.0.0", - "@octokit/webhooks": "^13.8.3" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/ogl": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ogl/-/ogl-1.0.11.tgz", "integrity": "sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==", "license": "Unlicense" }, - "node_modules/ollama": { - "version": "0.5.18", - "resolved": "https://registry.npmjs.org/ollama/-/ollama-0.5.18.tgz", - "integrity": "sha512-lTFqTf9bo7Cd3hpF6CviBe/DEhewjoZYd9N/uCe7O20qYTvGqrNOFOBDj3lbZgFWHUgDv5EeyusYxsZSLS8nvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-fetch": "^3.6.20" - } - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -10022,58 +9001,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openai": { - "version": "4.104.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", - "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.23.8" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/openai/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -10171,31 +9098,36 @@ } }, "node_modules/oxc-parser": { - "version": "0.64.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.64.0.tgz", - "integrity": "sha512-T5/h7Iv3kwUwTaOwOLz2yTwz2LsUfdu5IXTmZuMEDYL2Bp/dxGdxQZHaz8lc4bUBU9Swnb+caioKk4FLBT7prg==", + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.96.0.tgz", + "integrity": "sha512-ucs6niJ5mZlYP3oTl4AK2eD2m7WLoSaljswnSFVYWrXzme5PtM97S7Ve1Tjx+/TKjanmEZuSt1f1qYi6SZvntw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "^0.64.0" + "@oxc-project/types": "^0.96.0" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-parser/binding-darwin-arm64": "0.64.0", - "@oxc-parser/binding-darwin-x64": "0.64.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.64.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.64.0", - "@oxc-parser/binding-linux-arm64-musl": "0.64.0", - "@oxc-parser/binding-linux-x64-gnu": "0.64.0", - "@oxc-parser/binding-linux-x64-musl": "0.64.0", - "@oxc-parser/binding-wasm32-wasi": "0.64.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.64.0", - "@oxc-parser/binding-win32-x64-msvc": "0.64.0" + "@oxc-parser/binding-android-arm64": "0.96.0", + "@oxc-parser/binding-darwin-arm64": "0.96.0", + "@oxc-parser/binding-darwin-x64": "0.96.0", + "@oxc-parser/binding-freebsd-x64": "0.96.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.96.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.96.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.96.0", + "@oxc-parser/binding-linux-arm64-musl": "0.96.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.96.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.96.0", + "@oxc-parser/binding-linux-x64-gnu": "0.96.0", + "@oxc-parser/binding-linux-x64-musl": "0.96.0", + "@oxc-parser/binding-wasm32-wasi": "0.96.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.96.0", + "@oxc-parser/binding-win32-x64-msvc": "0.96.0" } }, "node_modules/p-limit": { @@ -10298,19 +9230,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -10379,13 +9298,6 @@ "node": ">=8" } }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/perfect-freehand": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/perfect-freehand/-/perfect-freehand-1.2.2.tgz", @@ -10527,17 +9439,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/prettier-plugin-svelte": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.4.0.tgz", - "integrity": "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "prettier": "^3.0.0", - "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" - } - }, "node_modules/pretty-ms": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", @@ -10676,6 +9577,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11074,16 +9992,6 @@ "node": ">=4" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -11511,16 +10419,6 @@ "shadcn": "dist/index.js" } }, - "node_modules/shadcn/node_modules/commander": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", - "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/shadcn/node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -12015,12 +10913,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stubborn-fs": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-1.2.5.tgz", - "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==", - "dev": true - }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -12064,36 +10956,10 @@ "react": ">=17.0" } }, - "node_modules/svelte": { - "version": "5.39.6", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.39.6.tgz", - "integrity": "sha512-bOJXmuwLNaoqPCTWO8mPu/fwxI5peGE5Efe7oo6Cakpz/G60vsnVF6mxbGODaxMUFUKEnjm6XOwHEqOht6cbvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "@jridgewell/sourcemap-codec": "^1.5.0", - "@sveltejs/acorn-typescript": "^1.0.5", - "@types/estree": "^1.0.5", - "acorn": "^8.12.1", - "aria-query": "^5.3.1", - "axobject-query": "^4.1.0", - "clsx": "^2.1.1", - "esm-env": "^1.2.1", - "esrap": "^2.1.0", - "is-reference": "^3.0.3", - "locate-character": "^3.0.0", - "magic-string": "^0.30.11", - "zimmerframe": "^1.1.2" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/tailwind-merge": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", - "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", "license": "MIT", "funding": { "type": "github", @@ -12242,16 +11108,6 @@ "node": ">=8.0" } }, - "node_modules/toad-cache": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", - "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -12275,13 +11131,6 @@ "node": ">=16" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, "node_modules/tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -12572,19 +11421,6 @@ "node": ">=14.17" } }, - "node_modules/uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -12604,12 +11440,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unconfig": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-7.4.0.tgz", + "integrity": "sha512-KM0SrvIvwQXJnbiSzur1Y+5jHSLVPhS31H5qzgjDQxGqS3PWrH6X7TxYX/JTuTlItarHkZ9ePK9t01Q6wu1c4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^0.1.5", + "defu": "^6.1.4", + "jiti": "^2.6.1", + "quansync": "^0.2.11", + "unconfig-core": "7.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unconfig-core": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.4.0.tgz", + "integrity": "sha512-3ew7rvES5x2LCZ/QRKV3nQQpq7eFYuszQuvZrhTHxDPKc34QFjRXI17XGiZI+WQTVIXKYeBti4v3LS39NWmhmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^0.1.5", + "quansync": "^0.2.11" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/unicorn-magic": { "version": "0.3.0", @@ -12624,20 +11492,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/universal-github-app-jwt": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", - "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/universal-user-agent": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", - "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", - "dev": true, - "license": "ISC" - }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -12733,31 +11587,6 @@ "node": ">= 4" } }, - "node_modules/valibot": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0.tgz", - "integrity": "sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">=5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/validate-npm-package-name": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", - "integrity": "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -12831,8 +11660,9 @@ "version": "3.5.22", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.22.tgz", "integrity": "sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==", - "devOptional": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.22", "@vue/compiler-sfc": "3.5.22", @@ -12849,16 +11679,6 @@ } } }, - "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/webgl-constants": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", @@ -12870,38 +11690,6 @@ "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==", "license": "MIT" }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/when-exit": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.4.tgz", - "integrity": "sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==", - "dev": true, - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -13006,22 +11794,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/widest-line": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -13032,53 +11804,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13211,13 +11936,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zimmerframe": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", - "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", diff --git a/package.json b/package.json index 1fa8a4a3..e959d202 100644 --- a/package.json +++ b/package.json @@ -5,16 +5,12 @@ "type": "module", "scripts": { "dev": "vite", - "build": "npm run jsrepo:build && npm run shadcn:build && npm run llms:text && vite build", + "build": "npm run registry:build && npm run llms:text && vite build", "new:component": "node scripts/generateComponent.js", "shadcn:build": "npm run shadcn:generate && shadcn build", "shadcn:generate": "node ./scripts/generateShadcnRegistry.js", "llms:text": "node ./scripts/generateLlmsText.js", - "jsrepo:build": "concurrently -p \"[{name}]\" -n \"default,tailwind,ts-default,ts-tailwind,readmes\" \"npm run jsrepo:default\" \"npm run jsrepo:tailwind\" \"npm run jsrepo:ts-default\" \"npm run jsrepo:ts-tailwind\" \"node ./scripts/generateJsrepoReadmes.js\"", - "jsrepo:default": "jsrepo build --dirs ./src/content --output-dir ./public/default", - "jsrepo:tailwind": "jsrepo build --dirs ./src/tailwind --output-dir ./public/tailwind", - "jsrepo:ts-default": "jsrepo build --dirs ./src/ts-default --output-dir ./public/ts/default", - "jsrepo:ts-tailwind": "jsrepo build --dirs ./src/ts-tailwind --output-dir ./public/ts/tailwind", + "registry:build": "jsrepo build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "format": "prettier --write ." }, @@ -58,6 +54,7 @@ "three": "^0.167.1" }, "devDependencies": { + "@jsrepo/shadcn": "^0.0.1-beta.10", "@types/matter-js": "^0.19.8", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", @@ -68,7 +65,7 @@ "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", - "jsrepo": "^1.30.1", + "jsrepo": "^3.0.0-beta.8", "postcss-safe-parser": "^7.0.1", "prettier": "^3.6.2", "shadcn": "^3.1.0", diff --git a/public/r/ASCIIText-JS-CSS.json b/public/r/ASCIIText-JS-CSS.json index 801537b0..4a81a5dc 100644 --- a/public/r/ASCIIText-JS-CSS.json +++ b/public/r/ASCIIText-JS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "ASCIIText-JS-CSS", - "type": "registry:block", - "title": "ASCIIText", - "description": "Renders text with an animated ASCII background for a retro feel.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/default/src/content/TextAnimations/ASCIIText/ASCIIText.jsx", - "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n\n onMouseMove(e) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx, w, h) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n\n render() {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w, h) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt) {\n const e = evt.touches ? evt.touches[0] : evt;\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n this.mesh.material.uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerRef.current,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "ASCIIText-JS-CSS", + "title": "ASCIIText", + "description": "Renders text with an animated ASCII background for a retro feel.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/TextAnimations/ASCIIText/ASCIIText.jsx", + "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n\n onMouseMove(e) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx, w, h) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n\n render() {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w, h) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt) {\n const e = evt.touches ? evt.touches[0] : evt;\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n this.mesh.material.uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerRef.current,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/ASCIIText-JS-TW.json b/public/r/ASCIIText-JS-TW.json index ae355c7d..ff2e1dd5 100644 --- a/public/r/ASCIIText-JS-TW.json +++ b/public/r/ASCIIText-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "ASCIIText-JS-TW", - "type": "registry:block", - "title": "ASCIIText", - "description": "Renders text with an animated ASCII background for a retro feel.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/TextAnimations/ASCIIText/ASCIIText.jsx", - "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '0';\n this.pre.style.top = '0';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n\n onMouseMove(e) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx, w, h) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n\n render() {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w, h) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt) {\n const e = evt.touches ? evt.touches[0] : evt;\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n this.mesh.material.uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerRef.current,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "ASCIIText-JS-TW", + "title": "ASCIIText", + "description": "Renders text with an animated ASCII background for a retro feel.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/TextAnimations/ASCIIText/ASCIIText.jsx", + "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nMath.map = function (n, start, stop, start2, stop2) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n};\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\nclass AsciiFilter {\n constructor(renderer, { fontSize, fontFamily, charset, invert } = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n this.context.webkitImageSmoothingEnabled = false;\n this.context.mozImageSmoothingEnabled = false;\n this.context.msImageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width, height) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '0';\n this.pre.style.top = '0';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n\n render(scene, camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n\n onMouseMove(e) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx, w, h) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\nclass CanvasTxt {\n constructor(txt, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' } = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n\n render() {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\nclass CanvAscii {\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerElem,\n width,\n height\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w, h) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt) {\n const e = evt.touches ? evt.touches[0] : evt;\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n this.mesh.material.uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = Math.map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = Math.map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(obj => {\n if (obj.isMesh && typeof obj.material === 'object' && obj.material !== null) {\n Object.keys(obj.material).forEach(key => {\n const matProp = obj.material[key];\n if (matProp !== null && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n obj.material.dispose();\n obj.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerRef.current,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/ASCIIText-TS-CSS.json b/public/r/ASCIIText-TS-CSS.json index b2d1c89a..e9f4b9b3 100644 --- a/public/r/ASCIIText-TS-CSS.json +++ b/public/r/ASCIIText-TS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "ASCIIText-TS-CSS", - "type": "registry:block", - "title": "ASCIIText", - "description": "Renders text with an animated ASCII background for a retro feel.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/TextAnimations/ASCIIText/ASCIIText.tsx", - "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer!: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry: THREE.PlaneGeometry | undefined;\n material: THREE.ShaderMaterial | undefined;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center: { x: number; y: number } = { x: 0, y: 0 };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current!,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n asciiRef.current?.dispose();\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "ASCIIText-TS-CSS", + "title": "ASCIIText", + "description": "Renders text with an animated ASCII background for a retro feel.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/TextAnimations/ASCIIText/ASCIIText.tsx", + "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer!: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry: THREE.PlaneGeometry | undefined;\n material: THREE.ShaderMaterial | undefined;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center: { x: number; y: number } = { x: 0, y: 0 };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current!,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n asciiRef.current?.dispose();\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/ASCIIText-TS-TW.json b/public/r/ASCIIText-TS-TW.json index d7f93070..2bdf7554 100644 --- a/public/r/ASCIIText-TS-TW.json +++ b/public/r/ASCIIText-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "ASCIIText-TS-TW", - "type": "registry:block", - "title": "ASCIIText", - "description": "Renders text with an animated ASCII background for a retro feel.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/TextAnimations/ASCIIText/ASCIIText.tsx", - "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry!: THREE.PlaneGeometry;\n material!: THREE.ShaderMaterial;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center!: { x: number; y: number };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current!,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "ASCIIText-TS-TW", + "title": "ASCIIText", + "description": "Renders text with an animated ASCII background for a retro feel.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/TextAnimations/ASCIIText/ASCIIText.tsx", + "content": "// Component ported and enhanced from https://codepen.io/JuanFuentes/pen/eYEeoyE\n\nimport { useRef, useEffect } from 'react';\nimport * as THREE from 'three';\n\nconst vertexShader = `\nvarying vec2 vUv;\nuniform float uTime;\nuniform float mouse;\nuniform float uEnableWaves;\n\nvoid main() {\n vUv = uv;\n float time = uTime * 5.;\n\n float waveFactor = uEnableWaves;\n\n vec3 transformed = position;\n\n transformed.x += sin(time + position.y) * 0.5 * waveFactor;\n transformed.y += cos(time + position.z) * 0.15 * waveFactor;\n transformed.z += sin(time + position.x) * waveFactor;\n\n gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);\n}\n`;\n\nconst fragmentShader = `\nvarying vec2 vUv;\nuniform float mouse;\nuniform float uTime;\nuniform sampler2D uTexture;\n\nvoid main() {\n float time = uTime;\n vec2 pos = vUv;\n \n float move = sin(time + mouse) * 0.01;\n float r = texture2D(uTexture, pos + cos(time * 2. - time + pos.x) * .01).r;\n float g = texture2D(uTexture, pos + tan(time * .5 + pos.x - time) * .01).g;\n float b = texture2D(uTexture, pos - cos(time * 2. + time + pos.y) * .01).b;\n float a = texture2D(uTexture, pos).a;\n gl_FragColor = vec4(r, g, b, a);\n}\n`;\n\nfunction map(n: number, start: number, stop: number, start2: number, stop2: number) {\n return ((n - start) / (stop - start)) * (stop2 - start2) + start2;\n}\n\nconst PX_RATIO = typeof window !== 'undefined' ? window.devicePixelRatio : 1;\n\ninterface AsciiFilterOptions {\n fontSize?: number;\n fontFamily?: string;\n charset?: string;\n invert?: boolean;\n}\n\nclass AsciiFilter {\n renderer: THREE.WebGLRenderer;\n domElement: HTMLDivElement;\n pre: HTMLPreElement;\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n deg: number;\n invert: boolean;\n fontSize: number;\n fontFamily: string;\n charset: string;\n width: number = 0;\n height: number = 0;\n center: { x: number; y: number } = { x: 0, y: 0 };\n mouse: { x: number; y: number } = { x: 0, y: 0 };\n cols: number = 0;\n rows: number = 0;\n\n constructor(renderer: THREE.WebGLRenderer, { fontSize, fontFamily, charset, invert }: AsciiFilterOptions = {}) {\n this.renderer = renderer;\n this.domElement = document.createElement('div');\n this.domElement.style.position = 'absolute';\n this.domElement.style.top = '0';\n this.domElement.style.left = '0';\n this.domElement.style.width = '100%';\n this.domElement.style.height = '100%';\n\n this.pre = document.createElement('pre');\n this.domElement.appendChild(this.pre);\n\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.domElement.appendChild(this.canvas);\n\n this.deg = 0;\n this.invert = invert ?? true;\n this.fontSize = fontSize ?? 12;\n this.fontFamily = fontFamily ?? \"'Courier New', monospace\";\n this.charset = charset ?? ' .\\'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$';\n\n if (this.context) {\n this.context.imageSmoothingEnabled = false;\n this.context.imageSmoothingEnabled = false;\n }\n\n this.onMouseMove = this.onMouseMove.bind(this);\n document.addEventListener('mousemove', this.onMouseMove);\n }\n\n setSize(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.renderer.setSize(width, height);\n this.reset();\n\n this.center = { x: width / 2, y: height / 2 };\n this.mouse = { x: this.center.x, y: this.center.y };\n }\n\n reset() {\n if (this.context) {\n this.context.font = `${this.fontSize}px ${this.fontFamily}`;\n const charWidth = this.context.measureText('A').width;\n\n this.cols = Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize)));\n this.rows = Math.floor(this.height / this.fontSize);\n\n this.canvas.width = this.cols;\n this.canvas.height = this.rows;\n this.pre.style.fontFamily = this.fontFamily;\n this.pre.style.fontSize = `${this.fontSize}px`;\n this.pre.style.margin = '0';\n this.pre.style.padding = '0';\n this.pre.style.lineHeight = '1em';\n this.pre.style.position = 'absolute';\n this.pre.style.left = '50%';\n this.pre.style.top = '50%';\n this.pre.style.transform = 'translate(-50%, -50%)';\n this.pre.style.zIndex = '9';\n this.pre.style.backgroundAttachment = 'fixed';\n this.pre.style.mixBlendMode = 'difference';\n }\n }\n\n render(scene: THREE.Scene, camera: THREE.Camera) {\n this.renderer.render(scene, camera);\n\n const w = this.canvas.width;\n const h = this.canvas.height;\n if (this.context) {\n this.context.clearRect(0, 0, w, h);\n if (this.context && w && h) {\n this.context.drawImage(this.renderer.domElement, 0, 0, w, h);\n }\n\n this.asciify(this.context, w, h);\n this.hue();\n }\n }\n\n onMouseMove(e: MouseEvent) {\n this.mouse = { x: e.clientX * PX_RATIO, y: e.clientY * PX_RATIO };\n }\n\n get dx() {\n return this.mouse.x - this.center.x;\n }\n\n get dy() {\n return this.mouse.y - this.center.y;\n }\n\n hue() {\n const deg = (Math.atan2(this.dy, this.dx) * 180) / Math.PI;\n this.deg += (deg - this.deg) * 0.075;\n this.domElement.style.filter = `hue-rotate(${this.deg.toFixed(1)}deg)`;\n }\n\n asciify(ctx: CanvasRenderingContext2D, w: number, h: number) {\n if (w && h) {\n const imgData = ctx.getImageData(0, 0, w, h).data;\n let str = '';\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n const i = x * 4 + y * 4 * w;\n const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];\n\n if (a === 0) {\n str += ' ';\n continue;\n }\n\n let gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;\n let idx = Math.floor((1 - gray) * (this.charset.length - 1));\n if (this.invert) idx = this.charset.length - idx - 1;\n str += this.charset[idx];\n }\n str += '\\n';\n }\n this.pre.innerHTML = str;\n }\n }\n\n dispose() {\n document.removeEventListener('mousemove', this.onMouseMove);\n }\n}\n\ninterface CanvasTxtOptions {\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n}\n\nclass CanvasTxt {\n canvas: HTMLCanvasElement;\n context: CanvasRenderingContext2D | null;\n txt: string;\n fontSize: number;\n fontFamily: string;\n color: string;\n font: string;\n\n constructor(txt: string, { fontSize = 200, fontFamily = 'Arial', color = '#fdf9f3' }: CanvasTxtOptions = {}) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d');\n this.txt = txt;\n this.fontSize = fontSize;\n this.fontFamily = fontFamily;\n this.color = color;\n\n this.font = `600 ${this.fontSize}px ${this.fontFamily}`;\n }\n\n resize() {\n if (this.context) {\n this.context.font = this.font;\n const metrics = this.context.measureText(this.txt);\n\n const textWidth = Math.ceil(metrics.width) + 20;\n const textHeight = Math.ceil(metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) + 20;\n\n this.canvas.width = textWidth;\n this.canvas.height = textHeight;\n }\n }\n\n render() {\n if (this.context) {\n this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);\n this.context.fillStyle = this.color;\n this.context.font = this.font;\n\n const metrics = this.context.measureText(this.txt);\n const yPos = 10 + metrics.actualBoundingBoxAscent;\n\n this.context.fillText(this.txt, 10, yPos);\n }\n }\n\n get width() {\n return this.canvas.width;\n }\n\n get height() {\n return this.canvas.height;\n }\n\n get texture() {\n return this.canvas;\n }\n}\n\ninterface CanvAsciiOptions {\n text: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n enableWaves: boolean;\n}\n\nclass CanvAscii {\n textString: string;\n asciiFontSize: number;\n textFontSize: number;\n textColor: string;\n planeBaseHeight: number;\n container: HTMLElement;\n width: number;\n height: number;\n enableWaves: boolean;\n camera: THREE.PerspectiveCamera;\n scene: THREE.Scene;\n mouse: { x: number; y: number };\n textCanvas!: CanvasTxt;\n texture!: THREE.CanvasTexture;\n geometry!: THREE.PlaneGeometry;\n material!: THREE.ShaderMaterial;\n mesh!: THREE.Mesh;\n renderer!: THREE.WebGLRenderer;\n filter!: AsciiFilter;\n center!: { x: number; y: number };\n animationFrameId: number = 0;\n\n constructor(\n { text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves }: CanvAsciiOptions,\n containerElem: HTMLElement,\n width: number,\n height: number\n ) {\n this.textString = text;\n this.asciiFontSize = asciiFontSize;\n this.textFontSize = textFontSize;\n this.textColor = textColor;\n this.planeBaseHeight = planeBaseHeight;\n this.container = containerElem;\n this.width = width;\n this.height = height;\n this.enableWaves = enableWaves;\n\n this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);\n this.camera.position.z = 30;\n\n this.scene = new THREE.Scene();\n this.mouse = { x: 0, y: 0 };\n\n this.onMouseMove = this.onMouseMove.bind(this);\n\n this.setMesh();\n this.setRenderer();\n }\n\n setMesh() {\n this.textCanvas = new CanvasTxt(this.textString, {\n fontSize: this.textFontSize,\n fontFamily: 'IBM Plex Mono',\n color: this.textColor\n });\n this.textCanvas.resize();\n this.textCanvas.render();\n\n this.texture = new THREE.CanvasTexture(this.textCanvas.texture);\n this.texture.minFilter = THREE.NearestFilter;\n\n const textAspect = this.textCanvas.width / this.textCanvas.height;\n const baseH = this.planeBaseHeight;\n const planeW = baseH * textAspect;\n const planeH = baseH;\n\n this.geometry = new THREE.PlaneGeometry(planeW, planeH, 36, 36);\n this.material = new THREE.ShaderMaterial({\n vertexShader,\n fragmentShader,\n transparent: true,\n uniforms: {\n uTime: { value: 0 },\n mouse: { value: 1.0 },\n uTexture: { value: this.texture },\n uEnableWaves: { value: this.enableWaves ? 1.0 : 0.0 }\n }\n });\n\n this.mesh = new THREE.Mesh(this.geometry, this.material);\n this.scene.add(this.mesh);\n }\n\n setRenderer() {\n this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });\n this.renderer.setPixelRatio(1);\n this.renderer.setClearColor(0x000000, 0);\n\n this.filter = new AsciiFilter(this.renderer, {\n fontFamily: 'IBM Plex Mono',\n fontSize: this.asciiFontSize,\n invert: true\n });\n\n this.container.appendChild(this.filter.domElement);\n this.setSize(this.width, this.height);\n\n this.container.addEventListener('mousemove', this.onMouseMove);\n this.container.addEventListener('touchmove', this.onMouseMove);\n }\n\n setSize(w: number, h: number) {\n this.width = w;\n this.height = h;\n\n this.camera.aspect = w / h;\n this.camera.updateProjectionMatrix();\n\n this.filter.setSize(w, h);\n\n this.center = { x: w / 2, y: h / 2 };\n }\n\n load() {\n this.animate();\n }\n\n onMouseMove(evt: MouseEvent | TouchEvent) {\n const e = (evt as TouchEvent).touches ? (evt as TouchEvent).touches[0] : (evt as MouseEvent);\n const bounds = this.container.getBoundingClientRect();\n const x = e.clientX - bounds.left;\n const y = e.clientY - bounds.top;\n this.mouse = { x, y };\n }\n\n animate() {\n const animateFrame = () => {\n this.animationFrameId = requestAnimationFrame(animateFrame);\n this.render();\n };\n animateFrame();\n }\n\n render() {\n const time = new Date().getTime() * 0.001;\n\n this.textCanvas.render();\n this.texture.needsUpdate = true;\n\n (this.mesh.material as THREE.ShaderMaterial).uniforms.uTime.value = Math.sin(time);\n\n this.updateRotation();\n this.filter.render(this.scene, this.camera);\n }\n\n updateRotation() {\n const x = map(this.mouse.y, 0, this.height, 0.5, -0.5);\n const y = map(this.mouse.x, 0, this.width, -0.5, 0.5);\n\n this.mesh.rotation.x += (x - this.mesh.rotation.x) * 0.05;\n this.mesh.rotation.y += (y - this.mesh.rotation.y) * 0.05;\n }\n\n clear() {\n this.scene.traverse(object => {\n const obj = object as unknown as THREE.Mesh;\n if (!obj.isMesh) return;\n [obj.material].flat().forEach(material => {\n material.dispose();\n Object.keys(material).forEach(key => {\n const matProp = material[key as keyof typeof material];\n if (matProp && typeof matProp === 'object' && 'dispose' in matProp && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n });\n obj.geometry.dispose();\n });\n this.scene.clear();\n }\n\n dispose() {\n cancelAnimationFrame(this.animationFrameId);\n this.filter.dispose();\n this.container.removeChild(this.filter.domElement);\n this.container.removeEventListener('mousemove', this.onMouseMove);\n this.container.removeEventListener('touchmove', this.onMouseMove);\n this.clear();\n this.renderer.dispose();\n }\n}\n\ninterface ASCIITextProps {\n text?: string;\n asciiFontSize?: number;\n textFontSize?: number;\n textColor?: string;\n planeBaseHeight?: number;\n enableWaves?: boolean;\n}\n\nexport default function ASCIIText({\n text = 'David!',\n asciiFontSize = 8,\n textFontSize = 200,\n textColor = '#fdf9f3',\n planeBaseHeight = 8,\n enableWaves = true\n}: ASCIITextProps) {\n const containerRef = useRef(null);\n const asciiRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n const { width, height } = containerRef.current.getBoundingClientRect();\n\n if (width === 0 || height === 0) {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting && entry.boundingClientRect.width > 0 && entry.boundingClientRect.height > 0) {\n const { width: w, height: h } = entry.boundingClientRect;\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current!,\n w,\n h\n );\n asciiRef.current.load();\n\n observer.disconnect();\n }\n },\n { threshold: 0.1 }\n );\n\n observer.observe(containerRef.current);\n\n return () => {\n observer.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }\n\n asciiRef.current = new CanvAscii(\n {\n text,\n asciiFontSize,\n textFontSize,\n textColor,\n planeBaseHeight,\n enableWaves\n },\n containerRef.current,\n width,\n height\n );\n asciiRef.current.load();\n\n const ro = new ResizeObserver(entries => {\n if (!entries[0] || !asciiRef.current) return;\n const { width: w, height: h } = entries[0].contentRect;\n if (w > 0 && h > 0) {\n asciiRef.current.setSize(w, h);\n }\n });\n ro.observe(containerRef.current);\n\n return () => {\n ro.disconnect();\n if (asciiRef.current) {\n asciiRef.current.dispose();\n }\n };\n }, [text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves]);\n\n return (\n \n \n \n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/AnimatedContent-JS-CSS.json b/public/r/AnimatedContent-JS-CSS.json index a4d80fc7..f0edd2d4 100644 --- a/public/r/AnimatedContent-JS-CSS.json +++ b/public/r/AnimatedContent-JS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedContent-JS-CSS", - "type": "registry:block", - "title": "AnimatedContent", - "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Animations/AnimatedContent/AnimatedContent.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\nconst AnimatedContent = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedContent-JS-CSS", + "title": "AnimatedContent", + "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Animations/AnimatedContent/AnimatedContent.jsx", + "content": "import { useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\nconst AnimatedContent = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/AnimatedContent-JS-TW.json b/public/r/AnimatedContent-JS-TW.json index 38cf7b78..d072dbd3 100644 --- a/public/r/AnimatedContent-JS-TW.json +++ b/public/r/AnimatedContent-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedContent-JS-TW", - "type": "registry:block", - "title": "AnimatedContent", - "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Animations/AnimatedContent/AnimatedContent.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\nconst AnimatedContent = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedContent-JS-TW", + "title": "AnimatedContent", + "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/Animations/AnimatedContent/AnimatedContent.jsx", + "content": "import { useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\nconst AnimatedContent = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/AnimatedContent-TS-CSS.json b/public/r/AnimatedContent-TS-CSS.json index cfecbbb5..4026f7f0 100644 --- a/public/r/AnimatedContent-TS-CSS.json +++ b/public/r/AnimatedContent-TS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedContent-TS-CSS", - "type": "registry:block", - "title": "AnimatedContent", - "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Animations/AnimatedContent/AnimatedContent.tsx", - "content": "import React, { useRef, useEffect, ReactNode } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\ninterface AnimatedContentProps {\n children: ReactNode;\n distance?: number;\n direction?: 'vertical' | 'horizontal';\n reverse?: boolean;\n duration?: number;\n ease?: string | ((progress: number) => number);\n initialOpacity?: number;\n animateOpacity?: boolean;\n scale?: number;\n threshold?: number;\n delay?: number;\n onComplete?: () => void;\n}\n\nconst AnimatedContent: React.FC = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedContent-TS-CSS", + "title": "AnimatedContent", + "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/Animations/AnimatedContent/AnimatedContent.tsx", + "content": "import React, { useRef, useEffect, ReactNode } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\ninterface AnimatedContentProps {\n children: ReactNode;\n distance?: number;\n direction?: 'vertical' | 'horizontal';\n reverse?: boolean;\n duration?: number;\n ease?: string | ((progress: number) => number);\n initialOpacity?: number;\n animateOpacity?: boolean;\n scale?: number;\n threshold?: number;\n delay?: number;\n onComplete?: () => void;\n}\n\nconst AnimatedContent: React.FC = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/AnimatedContent-TS-TW.json b/public/r/AnimatedContent-TS-TW.json index 4429c246..4eed8c8e 100644 --- a/public/r/AnimatedContent-TS-TW.json +++ b/public/r/AnimatedContent-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedContent-TS-TW", - "type": "registry:block", - "title": "AnimatedContent", - "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Animations/AnimatedContent/AnimatedContent.tsx", - "content": "import React, { useRef, useEffect, ReactNode } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\ninterface AnimatedContentProps {\n children: ReactNode;\n distance?: number;\n direction?: 'vertical' | 'horizontal';\n reverse?: boolean;\n duration?: number;\n ease?: string | ((progress: number) => number);\n initialOpacity?: number;\n animateOpacity?: boolean;\n scale?: number;\n threshold?: number;\n delay?: number;\n onComplete?: () => void;\n}\n\nconst AnimatedContent: React.FC = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedContent-TS-TW", + "title": "AnimatedContent", + "description": "Wrapper that animates any children on scroll or mount with configurable direction, distance, duration and easing.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/Animations/AnimatedContent/AnimatedContent.tsx", + "content": "import React, { useRef, useEffect, ReactNode } from 'react';\nimport { gsap } from 'gsap';\nimport { ScrollTrigger } from 'gsap/ScrollTrigger';\n\ngsap.registerPlugin(ScrollTrigger);\n\ninterface AnimatedContentProps {\n children: ReactNode;\n distance?: number;\n direction?: 'vertical' | 'horizontal';\n reverse?: boolean;\n duration?: number;\n ease?: string | ((progress: number) => number);\n initialOpacity?: number;\n animateOpacity?: boolean;\n scale?: number;\n threshold?: number;\n delay?: number;\n onComplete?: () => void;\n}\n\nconst AnimatedContent: React.FC = ({\n children,\n distance = 100,\n direction = 'vertical',\n reverse = false,\n duration = 0.8,\n ease = 'power3.out',\n initialOpacity = 0,\n animateOpacity = true,\n scale = 1,\n threshold = 0.1,\n delay = 0,\n onComplete\n}) => {\n const ref = useRef(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const axis = direction === 'horizontal' ? 'x' : 'y';\n const offset = reverse ? -distance : distance;\n const startPct = (1 - threshold) * 100;\n\n gsap.set(el, {\n [axis]: offset,\n scale,\n opacity: animateOpacity ? initialOpacity : 1\n });\n\n gsap.to(el, {\n [axis]: 0,\n scale: 1,\n opacity: 1,\n duration,\n ease,\n delay,\n onComplete,\n scrollTrigger: {\n trigger: el,\n start: `top ${startPct}%`,\n toggleActions: 'play none none none',\n once: true\n }\n });\n\n return () => {\n ScrollTrigger.getAll().forEach(t => t.kill());\n gsap.killTweensOf(el);\n };\n }, [\n distance,\n direction,\n reverse,\n duration,\n ease,\n initialOpacity,\n animateOpacity,\n scale,\n threshold,\n delay,\n onComplete\n ]);\n\n return
{children}
;\n};\n\nexport default AnimatedContent;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/AnimatedList-JS-CSS.json b/public/r/AnimatedList-JS-CSS.json index 8042ff22..c845a3bf 100644 --- a/public/r/AnimatedList-JS-CSS.json +++ b/public/r/AnimatedList-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedList-JS-CSS", - "type": "registry:block", - "title": "AnimatedList", - "description": "List items enter with staggered motion variants for polished reveals.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/default/src/content/Components/AnimatedList/AnimatedList.jsx", - "content": "import { useRef, useState, useEffect } from 'react';\nimport { motion, useInView } from 'motion/react';\nimport './AnimatedList.css';\n\nconst AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, triggerOnce: false });\n return (\n \n {children}\n \n );\n};\n\nconst AnimatedList = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = e => {\n const { scrollTop, scrollHeight, clientHeight } = e.target;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = e => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`);\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n
\n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n
\n
\n \n )}\n
\n );\n};\n\nexport default AnimatedList;\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Components/AnimatedList/AnimatedList.css", - "content": ".scroll-list-container {\n position: relative;\n width: 500px;\n}\n\n.scroll-list {\n max-height: 400px;\n overflow-y: auto;\n padding: 16px;\n}\n\n.scroll-list::-webkit-scrollbar {\n width: 8px;\n}\n\n.scroll-list::-webkit-scrollbar-track {\n background: #060010;\n}\n\n.scroll-list::-webkit-scrollbar-thumb {\n background: #271e37;\n border-radius: 4px;\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n\n.item {\n padding: 16px;\n background-color: #170d27;\n border-radius: 8px;\n margin-bottom: 1rem;\n}\n\n.item.selected {\n background-color: #271e37;\n}\n\n.item-text {\n color: white;\n margin: 0;\n}\n\n.top-gradient {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 50px;\n background: linear-gradient(to bottom, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n\n.bottom-gradient {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 100px;\n background: linear-gradient(to top, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedList-JS-CSS", + "title": "AnimatedList", + "description": "List items enter with staggered motion variants for polished reveals.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Components/AnimatedList/AnimatedList.jsx", + "content": "import { useRef, useState, useEffect } from 'react';\nimport { motion, useInView } from 'motion/react';\nimport './AnimatedList.css';\n\nconst AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, triggerOnce: false });\n return (\n \n {children}\n \n );\n};\n\nconst AnimatedList = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = e => {\n const { scrollTop, scrollHeight, clientHeight } = e.target;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = e => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`);\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n
\n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n
\n
\n \n )}\n
\n );\n};\n\nexport default AnimatedList;\n" + }, + { + "type": "registry:item", + "path": "src/content/Components/AnimatedList/AnimatedList.css", + "content": ".scroll-list-container {\n position: relative;\n width: 500px;\n}\n\n.scroll-list {\n max-height: 400px;\n overflow-y: auto;\n padding: 16px;\n}\n\n.scroll-list::-webkit-scrollbar {\n width: 8px;\n}\n\n.scroll-list::-webkit-scrollbar-track {\n background: #060010;\n}\n\n.scroll-list::-webkit-scrollbar-thumb {\n background: #271e37;\n border-radius: 4px;\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n\n.item {\n padding: 16px;\n background-color: #170d27;\n border-radius: 8px;\n margin-bottom: 1rem;\n}\n\n.item.selected {\n background-color: #271e37;\n}\n\n.item-text {\n color: white;\n margin: 0;\n}\n\n.top-gradient {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 50px;\n background: linear-gradient(to bottom, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n\n.bottom-gradient {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 100px;\n background: linear-gradient(to top, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/AnimatedList-JS-TW.json b/public/r/AnimatedList-JS-TW.json index 16b95d45..a66c08bd 100644 --- a/public/r/AnimatedList-JS-TW.json +++ b/public/r/AnimatedList-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedList-JS-TW", - "type": "registry:block", - "title": "AnimatedList", - "description": "List items enter with staggered motion variants for polished reveals.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Components/AnimatedList/AnimatedList.jsx", - "content": "import { useRef, useState, useEffect } from 'react';\nimport { motion, useInView } from 'motion/react';\n\nconst AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, triggerOnce: false });\n return (\n \n {children}\n \n );\n};\n\nconst AnimatedList = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = e => {\n const { scrollTop, scrollHeight, clientHeight } = e.target;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = e => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`);\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n \n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n \n \n \n )}\n \n );\n};\n\nexport default AnimatedList;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedList-JS-TW", + "title": "AnimatedList", + "description": "List items enter with staggered motion variants for polished reveals.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/Components/AnimatedList/AnimatedList.jsx", + "content": "import { useRef, useState, useEffect } from 'react';\nimport { motion, useInView } from 'motion/react';\n\nconst AnimatedItem = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, triggerOnce: false });\n return (\n \n {children}\n \n );\n};\n\nconst AnimatedList = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = e => {\n const { scrollTop, scrollHeight, clientHeight } = e.target;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = e => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`);\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n \n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n \n \n \n )}\n \n );\n};\n\nexport default AnimatedList;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/AnimatedList-TS-CSS.json b/public/r/AnimatedList-TS-CSS.json index 7886e8f2..cdbcb634 100644 --- a/public/r/AnimatedList-TS-CSS.json +++ b/public/r/AnimatedList-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedList-TS-CSS", - "type": "registry:block", - "title": "AnimatedList", - "description": "List items enter with staggered motion variants for polished reveals.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Components/AnimatedList/AnimatedList.tsx", - "content": "import React, { useRef, useState, useEffect, ReactNode, MouseEventHandler, UIEvent } from 'react';\nimport { motion, useInView } from 'motion/react';\nimport './AnimatedList.css';\n\ninterface AnimatedItemProps {\n children: ReactNode;\n delay?: number;\n index: number;\n onMouseEnter?: MouseEventHandler;\n onClick?: MouseEventHandler;\n}\n\nconst AnimatedItem: React.FC = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, once: false });\n return (\n \n {children}\n \n );\n};\n\ninterface AnimatedListProps {\n items?: string[];\n onItemSelect?: (item: string, index: number) => void;\n showGradients?: boolean;\n enableArrowNavigation?: boolean;\n className?: string;\n itemClassName?: string;\n displayScrollbar?: boolean;\n initialSelectedIndex?: number;\n}\n\nconst AnimatedList: React.FC = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = (e: UIEvent) => {\n const target = e.target as HTMLDivElement;\n const { scrollTop, scrollHeight, clientHeight } = target;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`) as HTMLElement | null;\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n
\n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n
\n
\n \n )}\n
\n );\n};\n\nexport default AnimatedList;\n", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Components/AnimatedList/AnimatedList.css", - "content": ".scroll-list-container {\n position: relative;\n width: 500px;\n}\n\n.scroll-list {\n max-height: 400px;\n overflow-y: auto;\n padding: 16px;\n}\n\n.scroll-list::-webkit-scrollbar {\n width: 8px;\n}\n\n.scroll-list::-webkit-scrollbar-track {\n background: #060606;\n}\n\n.scroll-list::-webkit-scrollbar-thumb {\n background: #222;\n border-radius: 4px;\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n\n.item {\n padding: 16px;\n background-color: #111;\n border-radius: 8px;\n margin-bottom: 1rem;\n}\n\n.item.selected {\n background-color: #222;\n}\n\n.item-text {\n color: white;\n margin: 0;\n}\n\n.top-gradient {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 50px;\n background: linear-gradient(to bottom, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n\n.bottom-gradient {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 100px;\n background: linear-gradient(to top, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedList-TS-CSS", + "title": "AnimatedList", + "description": "List items enter with staggered motion variants for polished reveals.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/Components/AnimatedList/AnimatedList.tsx", + "content": "import React, { useRef, useState, useEffect, ReactNode, MouseEventHandler, UIEvent } from 'react';\nimport { motion, useInView } from 'motion/react';\nimport './AnimatedList.css';\n\ninterface AnimatedItemProps {\n children: ReactNode;\n delay?: number;\n index: number;\n onMouseEnter?: MouseEventHandler;\n onClick?: MouseEventHandler;\n}\n\nconst AnimatedItem: React.FC = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, once: false });\n return (\n \n {children}\n \n );\n};\n\ninterface AnimatedListProps {\n items?: string[];\n onItemSelect?: (item: string, index: number) => void;\n showGradients?: boolean;\n enableArrowNavigation?: boolean;\n className?: string;\n itemClassName?: string;\n displayScrollbar?: boolean;\n initialSelectedIndex?: number;\n}\n\nconst AnimatedList: React.FC = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = (e: UIEvent) => {\n const target = e.target as HTMLDivElement;\n const { scrollTop, scrollHeight, clientHeight } = target;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`) as HTMLElement | null;\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n
\n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n
\n
\n \n )}\n
\n );\n};\n\nexport default AnimatedList;\n" + }, + { + "type": "registry:item", + "path": "src/ts-default/Components/AnimatedList/AnimatedList.css", + "content": ".scroll-list-container {\n position: relative;\n width: 500px;\n}\n\n.scroll-list {\n max-height: 400px;\n overflow-y: auto;\n padding: 16px;\n}\n\n.scroll-list::-webkit-scrollbar {\n width: 8px;\n}\n\n.scroll-list::-webkit-scrollbar-track {\n background: #060606;\n}\n\n.scroll-list::-webkit-scrollbar-thumb {\n background: #222;\n border-radius: 4px;\n}\n\n.no-scrollbar::-webkit-scrollbar {\n display: none;\n}\n\n.no-scrollbar {\n -ms-overflow-style: none;\n scrollbar-width: none;\n}\n\n.item {\n padding: 16px;\n background-color: #111;\n border-radius: 8px;\n margin-bottom: 1rem;\n}\n\n.item.selected {\n background-color: #222;\n}\n\n.item-text {\n color: white;\n margin: 0;\n}\n\n.top-gradient {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 50px;\n background: linear-gradient(to bottom, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n\n.bottom-gradient {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 100px;\n background: linear-gradient(to top, #060010, transparent);\n pointer-events: none;\n transition: opacity 0.3s ease;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/AnimatedList-TS-TW.json b/public/r/AnimatedList-TS-TW.json index bebdf815..d8255363 100644 --- a/public/r/AnimatedList-TS-TW.json +++ b/public/r/AnimatedList-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "AnimatedList-TS-TW", - "type": "registry:block", - "title": "AnimatedList", - "description": "List items enter with staggered motion variants for polished reveals.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Components/AnimatedList/AnimatedList.tsx", - "content": "import React, { useRef, useState, useEffect, ReactNode, MouseEventHandler, UIEvent } from 'react';\nimport { motion, useInView } from 'motion/react';\n\ninterface AnimatedItemProps {\n children: ReactNode;\n delay?: number;\n index: number;\n onMouseEnter?: MouseEventHandler;\n onClick?: MouseEventHandler;\n}\n\nconst AnimatedItem: React.FC = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, once: false });\n return (\n \n {children}\n \n );\n};\n\ninterface AnimatedListProps {\n items?: string[];\n onItemSelect?: (item: string, index: number) => void;\n showGradients?: boolean;\n enableArrowNavigation?: boolean;\n className?: string;\n itemClassName?: string;\n displayScrollbar?: boolean;\n initialSelectedIndex?: number;\n}\n\nconst AnimatedList: React.FC = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = (e: UIEvent) => {\n const { scrollTop, scrollHeight, clientHeight } = e.target as HTMLDivElement;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`) as HTMLElement | null;\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n \n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n \n \n \n )}\n \n );\n};\n\nexport default AnimatedList;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "AnimatedList-TS-TW", + "title": "AnimatedList", + "description": "List items enter with staggered motion variants for polished reveals.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/Components/AnimatedList/AnimatedList.tsx", + "content": "import React, { useRef, useState, useEffect, ReactNode, MouseEventHandler, UIEvent } from 'react';\nimport { motion, useInView } from 'motion/react';\n\ninterface AnimatedItemProps {\n children: ReactNode;\n delay?: number;\n index: number;\n onMouseEnter?: MouseEventHandler;\n onClick?: MouseEventHandler;\n}\n\nconst AnimatedItem: React.FC = ({ children, delay = 0, index, onMouseEnter, onClick }) => {\n const ref = useRef(null);\n const inView = useInView(ref, { amount: 0.5, once: false });\n return (\n \n {children}\n \n );\n};\n\ninterface AnimatedListProps {\n items?: string[];\n onItemSelect?: (item: string, index: number) => void;\n showGradients?: boolean;\n enableArrowNavigation?: boolean;\n className?: string;\n itemClassName?: string;\n displayScrollbar?: boolean;\n initialSelectedIndex?: number;\n}\n\nconst AnimatedList: React.FC = ({\n items = [\n 'Item 1',\n 'Item 2',\n 'Item 3',\n 'Item 4',\n 'Item 5',\n 'Item 6',\n 'Item 7',\n 'Item 8',\n 'Item 9',\n 'Item 10',\n 'Item 11',\n 'Item 12',\n 'Item 13',\n 'Item 14',\n 'Item 15'\n ],\n onItemSelect,\n showGradients = true,\n enableArrowNavigation = true,\n className = '',\n itemClassName = '',\n displayScrollbar = true,\n initialSelectedIndex = -1\n}) => {\n const listRef = useRef(null);\n const [selectedIndex, setSelectedIndex] = useState(initialSelectedIndex);\n const [keyboardNav, setKeyboardNav] = useState(false);\n const [topGradientOpacity, setTopGradientOpacity] = useState(0);\n const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);\n\n const handleScroll = (e: UIEvent) => {\n const { scrollTop, scrollHeight, clientHeight } = e.target as HTMLDivElement;\n setTopGradientOpacity(Math.min(scrollTop / 50, 1));\n const bottomDistance = scrollHeight - (scrollTop + clientHeight);\n setBottomGradientOpacity(scrollHeight <= clientHeight ? 0 : Math.min(bottomDistance / 50, 1));\n };\n\n useEffect(() => {\n if (!enableArrowNavigation) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));\n } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) {\n e.preventDefault();\n setKeyboardNav(true);\n setSelectedIndex(prev => Math.max(prev - 1, 0));\n } else if (e.key === 'Enter') {\n if (selectedIndex >= 0 && selectedIndex < items.length) {\n e.preventDefault();\n if (onItemSelect) {\n onItemSelect(items[selectedIndex], selectedIndex);\n }\n }\n }\n };\n\n window.addEventListener('keydown', handleKeyDown);\n return () => window.removeEventListener('keydown', handleKeyDown);\n }, [items, selectedIndex, onItemSelect, enableArrowNavigation]);\n\n useEffect(() => {\n if (!keyboardNav || selectedIndex < 0 || !listRef.current) return;\n const container = listRef.current;\n const selectedItem = container.querySelector(`[data-index=\"${selectedIndex}\"]`) as HTMLElement | null;\n if (selectedItem) {\n const extraMargin = 50;\n const containerScrollTop = container.scrollTop;\n const containerHeight = container.clientHeight;\n const itemTop = selectedItem.offsetTop;\n const itemBottom = itemTop + selectedItem.offsetHeight;\n if (itemTop < containerScrollTop + extraMargin) {\n container.scrollTo({ top: itemTop - extraMargin, behavior: 'smooth' });\n } else if (itemBottom > containerScrollTop + containerHeight - extraMargin) {\n container.scrollTo({\n top: itemBottom - containerHeight + extraMargin,\n behavior: 'smooth'\n });\n }\n }\n setKeyboardNav(false);\n }, [selectedIndex, keyboardNav]);\n\n return (\n
\n \n {items.map((item, index) => (\n setSelectedIndex(index)}\n onClick={() => {\n setSelectedIndex(index);\n if (onItemSelect) {\n onItemSelect(item, index);\n }\n }}\n >\n
\n

{item}

\n
\n \n ))}\n
\n {showGradients && (\n <>\n \n \n \n )}\n \n );\n};\n\nexport default AnimatedList;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/Aurora-JS-CSS.json b/public/r/Aurora-JS-CSS.json index 2d1f1263..ea37b37d 100644 --- a/public/r/Aurora-JS-CSS.json +++ b/public/r/Aurora-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Aurora-JS-CSS", - "type": "registry:block", - "title": "Aurora", - "description": "Flowing aurora gradient background.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/default/src/content/Backgrounds/Aurora/Aurora.jsx", - "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nimport './Aurora.css';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\nexport default function Aurora(props) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = t => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [amplitude]);\n\n return
;\n}\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Backgrounds/Aurora/Aurora.css", - "content": ".aurora-container {\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Aurora-JS-CSS", + "title": "Aurora", + "description": "Flowing aurora gradient background.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Backgrounds/Aurora/Aurora.jsx", + "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nimport './Aurora.css';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\nexport default function Aurora(props) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = t => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [amplitude]);\n\n return
;\n}\n" + }, + { + "type": "registry:item", + "path": "src/content/Backgrounds/Aurora/Aurora.css", + "content": ".aurora-container {\n width: 100%;\n height: 100%;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Aurora-JS-TW.json b/public/r/Aurora-JS-TW.json index 9d473a0b..fdffc7d7 100644 --- a/public/r/Aurora-JS-TW.json +++ b/public/r/Aurora-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Aurora-JS-TW", - "type": "registry:block", - "title": "Aurora", - "description": "Flowing aurora gradient background.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Backgrounds/Aurora/Aurora.jsx", - "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\nexport default function Aurora(props) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = t => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [amplitude]);\n\n return
;\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Aurora-JS-TW", + "title": "Aurora", + "description": "Flowing aurora gradient background.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/Backgrounds/Aurora/Aurora.jsx", + "content": "import { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\nexport default function Aurora(props) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = t => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [amplitude]);\n\n return
;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Aurora-TS-CSS.json b/public/r/Aurora-TS-CSS.json index deffc191..5c977cca 100644 --- a/public/r/Aurora-TS-CSS.json +++ b/public/r/Aurora-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Aurora-TS-CSS", - "type": "registry:block", - "title": "Aurora", - "description": "Flowing aurora gradient background.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Backgrounds/Aurora/Aurora.tsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\n\nimport './Aurora.css';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\ninterface AuroraProps {\n colorStops?: string[];\n amplitude?: number;\n blend?: number;\n time?: number;\n speed?: number;\n}\n\nexport default function Aurora(props: AuroraProps) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program: Program | undefined;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = (t: number) => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n if (program) {\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map((hex: string) => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n }\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [amplitude]);\n\n return
;\n}\n", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Backgrounds/Aurora/Aurora.css", - "content": ".aurora-container {\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Aurora-TS-CSS", + "title": "Aurora", + "description": "Flowing aurora gradient background.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/Backgrounds/Aurora/Aurora.tsx", + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\n\nimport './Aurora.css';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\ninterface AuroraProps {\n colorStops?: string[];\n amplitude?: number;\n blend?: number;\n time?: number;\n speed?: number;\n}\n\nexport default function Aurora(props: AuroraProps) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program: Program | undefined;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = (t: number) => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n if (program) {\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map((hex: string) => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n }\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [amplitude]);\n\n return
;\n}\n" + }, + { + "type": "registry:item", + "path": "src/ts-default/Backgrounds/Aurora/Aurora.css", + "content": ".aurora-container {\n width: 100%;\n height: 100%;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Aurora-TS-TW.json b/public/r/Aurora-TS-TW.json index 31bca370..31ccb939 100644 --- a/public/r/Aurora-TS-TW.json +++ b/public/r/Aurora-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Aurora-TS-TW", - "type": "registry:block", - "title": "Aurora", - "description": "Flowing aurora gradient background.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Backgrounds/Aurora/Aurora.tsx", - "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\ninterface AuroraProps {\n colorStops?: string[];\n amplitude?: number;\n blend?: number;\n time?: number;\n speed?: number;\n}\n\nexport default function Aurora(props: AuroraProps) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program: Program | undefined;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = (t: number) => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n if (program) {\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map((hex: string) => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n }\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [amplitude]);\n\n return
;\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Aurora-TS-TW", + "title": "Aurora", + "description": "Flowing aurora gradient background.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/Backgrounds/Aurora/Aurora.tsx", + "content": "import { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Color, Triangle } from 'ogl';\n\nconst VERT = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAG = `#version 300 es\nprecision highp float;\n\nuniform float uTime;\nuniform float uAmplitude;\nuniform vec3 uColorStops[3];\nuniform vec2 uResolution;\nuniform float uBlend;\n\nout vec4 fragColor;\n\nvec3 permute(vec3 x) {\n return mod(((x * 34.0) + 1.0) * x, 289.0);\n}\n\nfloat snoise(vec2 v){\n const vec4 C = vec4(\n 0.211324865405187, 0.366025403784439,\n -0.577350269189626, 0.024390243902439\n );\n vec2 i = floor(v + dot(v, C.yy));\n vec2 x0 = v - i + dot(i, C.xx);\n vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);\n vec4 x12 = x0.xyxy + C.xxzz;\n x12.xy -= i1;\n i = mod(i, 289.0);\n\n vec3 p = permute(\n permute(i.y + vec3(0.0, i1.y, 1.0))\n + i.x + vec3(0.0, i1.x, 1.0)\n );\n\n vec3 m = max(\n 0.5 - vec3(\n dot(x0, x0),\n dot(x12.xy, x12.xy),\n dot(x12.zw, x12.zw)\n ), \n 0.0\n );\n m = m * m;\n m = m * m;\n\n vec3 x = 2.0 * fract(p * C.www) - 1.0;\n vec3 h = abs(x) - 0.5;\n vec3 ox = floor(x + 0.5);\n vec3 a0 = x - ox;\n m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);\n\n vec3 g;\n g.x = a0.x * x0.x + h.x * x0.y;\n g.yz = a0.yz * x12.xz + h.yz * x12.yw;\n return 130.0 * dot(m, g);\n}\n\nstruct ColorStop {\n vec3 color;\n float position;\n};\n\n#define COLOR_RAMP(colors, factor, finalColor) { \\\n int index = 0; \\\n for (int i = 0; i < 2; i++) { \\\n ColorStop currentColor = colors[i]; \\\n bool isInBetween = currentColor.position <= factor; \\\n index = int(mix(float(index), float(i), float(isInBetween))); \\\n } \\\n ColorStop currentColor = colors[index]; \\\n ColorStop nextColor = colors[index + 1]; \\\n float range = nextColor.position - currentColor.position; \\\n float lerpFactor = (factor - currentColor.position) / range; \\\n finalColor = mix(currentColor.color, nextColor.color, lerpFactor); \\\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / uResolution;\n \n ColorStop colors[3];\n colors[0] = ColorStop(uColorStops[0], 0.0);\n colors[1] = ColorStop(uColorStops[1], 0.5);\n colors[2] = ColorStop(uColorStops[2], 1.0);\n \n vec3 rampColor;\n COLOR_RAMP(colors, uv.x, rampColor);\n \n float height = snoise(vec2(uv.x * 2.0 + uTime * 0.1, uTime * 0.25)) * 0.5 * uAmplitude;\n height = exp(height);\n height = (uv.y * 2.0 - height + 0.2);\n float intensity = 0.6 * height;\n \n float midPoint = 0.20;\n float auroraAlpha = smoothstep(midPoint - uBlend * 0.5, midPoint + uBlend * 0.5, intensity);\n \n vec3 auroraColor = intensity * rampColor;\n \n fragColor = vec4(auroraColor * auroraAlpha, auroraAlpha);\n}\n`;\n\ninterface AuroraProps {\n colorStops?: string[];\n amplitude?: number;\n blend?: number;\n time?: number;\n speed?: number;\n}\n\nexport default function Aurora(props: AuroraProps) {\n const { colorStops = ['#5227FF', '#7cff67', '#5227FF'], amplitude = 1.0, blend = 0.5 } = props;\n const propsRef = useRef(props);\n propsRef.current = props;\n\n const ctnDom = useRef(null);\n\n useEffect(() => {\n const ctn = ctnDom.current;\n if (!ctn) return;\n\n const renderer = new Renderer({\n alpha: true,\n premultipliedAlpha: true,\n antialias: true\n });\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 0);\n gl.enable(gl.BLEND);\n gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n gl.canvas.style.backgroundColor = 'transparent';\n\n let program: Program | undefined;\n\n function resize() {\n if (!ctn) return;\n const width = ctn.offsetWidth;\n const height = ctn.offsetHeight;\n renderer.setSize(width, height);\n if (program) {\n program.uniforms.uResolution.value = [width, height];\n }\n }\n window.addEventListener('resize', resize);\n\n const geometry = new Triangle(gl);\n if (geometry.attributes.uv) {\n delete geometry.attributes.uv;\n }\n\n const colorStopsArray = colorStops.map(hex => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n\n program = new Program(gl, {\n vertex: VERT,\n fragment: FRAG,\n uniforms: {\n uTime: { value: 0 },\n uAmplitude: { value: amplitude },\n uColorStops: { value: colorStopsArray },\n uResolution: { value: [ctn.offsetWidth, ctn.offsetHeight] },\n uBlend: { value: blend }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n ctn.appendChild(gl.canvas);\n\n let animateId = 0;\n const update = (t: number) => {\n animateId = requestAnimationFrame(update);\n const { time = t * 0.01, speed = 1.0 } = propsRef.current;\n if (program) {\n program.uniforms.uTime.value = time * speed * 0.1;\n program.uniforms.uAmplitude.value = propsRef.current.amplitude ?? 1.0;\n program.uniforms.uBlend.value = propsRef.current.blend ?? blend;\n const stops = propsRef.current.colorStops ?? colorStops;\n program.uniforms.uColorStops.value = stops.map((hex: string) => {\n const c = new Color(hex);\n return [c.r, c.g, c.b];\n });\n renderer.render({ scene: mesh });\n }\n };\n animateId = requestAnimationFrame(update);\n\n resize();\n\n return () => {\n cancelAnimationFrame(animateId);\n window.removeEventListener('resize', resize);\n if (ctn && gl.canvas.parentNode === ctn) {\n ctn.removeChild(gl.canvas);\n }\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [amplitude]);\n\n return
;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Balatro-JS-CSS.json b/public/r/Balatro-JS-CSS.json index 08d4a61c..50865d0e 100644 --- a/public/r/Balatro-JS-CSS.json +++ b/public/r/Balatro-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Balatro-JS-CSS", - "type": "registry:block", - "title": "Balatro", - "description": "The balatro shader, fully customizalbe and interactive.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/default/src/content/Backgrounds/Balatro/Balatro.jsx", - "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nimport './Balatro.css';\n\nfunction hexToVec4(hex) {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId;\n\n function update(time) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction,\n containerRef\n ]);\n\n return
;\n}\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Backgrounds/Balatro/Balatro.css", - "content": ".balatro-container {\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Balatro-JS-CSS", + "title": "Balatro", + "description": "The balatro shader, fully customizalbe and interactive.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Backgrounds/Balatro/Balatro.jsx", + "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nimport './Balatro.css';\n\nfunction hexToVec4(hex) {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId;\n\n function update(time) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction,\n containerRef\n ]);\n\n return
;\n}\n" + }, + { + "type": "registry:item", + "path": "src/content/Backgrounds/Balatro/Balatro.css", + "content": ".balatro-container {\n width: 100%;\n height: 100%;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Balatro-JS-TW.json b/public/r/Balatro-JS-TW.json index 7acd2f0d..240798cf 100644 --- a/public/r/Balatro-JS-TW.json +++ b/public/r/Balatro-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Balatro-JS-TW", - "type": "registry:block", - "title": "Balatro", - "description": "The balatro shader, fully customizalbe and interactive.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Backgrounds/Balatro/Balatro.jsx", - "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nfunction hexToVec4(hex) {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId;\n\n function update(time) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction,\n containerRef\n ]);\n\n return
;\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Balatro-JS-TW", + "title": "Balatro", + "description": "The balatro shader, fully customizalbe and interactive.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/Backgrounds/Balatro/Balatro.jsx", + "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nfunction hexToVec4(hex) {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId;\n\n function update(time) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction,\n containerRef\n ]);\n\n return
;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Balatro-TS-CSS.json b/public/r/Balatro-TS-CSS.json index 0585eed8..17de4178 100644 --- a/public/r/Balatro-TS-CSS.json +++ b/public/r/Balatro-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Balatro-TS-CSS", - "type": "registry:block", - "title": "Balatro", - "description": "The balatro shader, fully customizalbe and interactive.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Backgrounds/Balatro/Balatro.tsx", - "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nimport './Balatro.css';\n\ninterface BalatroProps {\n spinRotation?: number;\n spinSpeed?: number;\n offset?: [number, number];\n color1?: string;\n color2?: string;\n color3?: string;\n contrast?: number;\n lighting?: number;\n spinAmount?: number;\n pixelFilter?: number;\n spinEase?: number;\n isRotate?: boolean;\n mouseInteraction?: boolean;\n}\n\nfunction hexToVec4(hex: string): [number, number, number, number] {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}: BalatroProps) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program: Program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId: number;\n\n function update(time: number) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e: MouseEvent) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction\n ]);\n\n return
;\n}\n", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Backgrounds/Balatro/Balatro.css", - "content": ".balatro-container {\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Balatro-TS-CSS", + "title": "Balatro", + "description": "The balatro shader, fully customizalbe and interactive.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/Backgrounds/Balatro/Balatro.tsx", + "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\nimport './Balatro.css';\n\ninterface BalatroProps {\n spinRotation?: number;\n spinSpeed?: number;\n offset?: [number, number];\n color1?: string;\n color2?: string;\n color3?: string;\n contrast?: number;\n lighting?: number;\n spinAmount?: number;\n pixelFilter?: number;\n spinEase?: number;\n isRotate?: boolean;\n mouseInteraction?: boolean;\n}\n\nfunction hexToVec4(hex: string): [number, number, number, number] {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}: BalatroProps) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program: Program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId: number;\n\n function update(time: number) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e: MouseEvent) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction\n ]);\n\n return
;\n}\n" + }, + { + "type": "registry:item", + "path": "src/ts-default/Backgrounds/Balatro/Balatro.css", + "content": ".balatro-container {\n width: 100%;\n height: 100%;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Balatro-TS-TW.json b/public/r/Balatro-TS-TW.json index fe54bcf5..7ee44838 100644 --- a/public/r/Balatro-TS-TW.json +++ b/public/r/Balatro-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Balatro-TS-TW", - "type": "registry:block", - "title": "Balatro", - "description": "The balatro shader, fully customizalbe and interactive.", - "dependencies": [ - "ogl" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Backgrounds/Balatro/Balatro.tsx", - "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\ninterface BalatroProps {\n spinRotation?: number;\n spinSpeed?: number;\n offset?: [number, number];\n color1?: string;\n color2?: string;\n color3?: string;\n contrast?: number;\n lighting?: number;\n spinAmount?: number;\n pixelFilter?: number;\n spinEase?: number;\n isRotate?: boolean;\n mouseInteraction?: boolean;\n}\n\nfunction hexToVec4(hex: string): [number, number, number, number] {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}: BalatroProps) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program: Program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId: number;\n\n function update(time: number) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e: MouseEvent) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction\n ]);\n\n return
;\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Balatro-TS-TW", + "title": "Balatro", + "description": "The balatro shader, fully customizalbe and interactive.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/Backgrounds/Balatro/Balatro.tsx", + "content": "import { Renderer, Program, Mesh, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\n\ninterface BalatroProps {\n spinRotation?: number;\n spinSpeed?: number;\n offset?: [number, number];\n color1?: string;\n color2?: string;\n color3?: string;\n contrast?: number;\n lighting?: number;\n spinAmount?: number;\n pixelFilter?: number;\n spinEase?: number;\n isRotate?: boolean;\n mouseInteraction?: boolean;\n}\n\nfunction hexToVec4(hex: string): [number, number, number, number] {\n let hexStr = hex.replace('#', '');\n let r = 0,\n g = 0,\n b = 0,\n a = 1;\n if (hexStr.length === 6) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n } else if (hexStr.length === 8) {\n r = parseInt(hexStr.slice(0, 2), 16) / 255;\n g = parseInt(hexStr.slice(2, 4), 16) / 255;\n b = parseInt(hexStr.slice(4, 6), 16) / 255;\n a = parseInt(hexStr.slice(6, 8), 16) / 255;\n }\n return [r, g, b, a];\n}\n\nconst vertexShader = `\nattribute vec2 uv;\nattribute vec2 position;\nvarying vec2 vUv;\nvoid main() {\n vUv = uv;\n gl_Position = vec4(position, 0, 1);\n}\n`;\n\nconst fragmentShader = `\nprecision highp float;\n\n#define PI 3.14159265359\n\nuniform float iTime;\nuniform vec3 iResolution;\nuniform float uSpinRotation;\nuniform float uSpinSpeed;\nuniform vec2 uOffset;\nuniform vec4 uColor1;\nuniform vec4 uColor2;\nuniform vec4 uColor3;\nuniform float uContrast;\nuniform float uLighting;\nuniform float uSpinAmount;\nuniform float uPixelFilter;\nuniform float uSpinEase;\nuniform bool uIsRotate;\nuniform vec2 uMouse;\n\nvarying vec2 vUv;\n\nvec4 effect(vec2 screenSize, vec2 screen_coords) {\n float pixel_size = length(screenSize.xy) / uPixelFilter;\n vec2 uv = (floor(screen_coords.xy * (1.0 / pixel_size)) * pixel_size - 0.5 * screenSize.xy) / length(screenSize.xy) - uOffset;\n float uv_len = length(uv);\n \n float speed = (uSpinRotation * uSpinEase * 0.2);\n if(uIsRotate){\n speed = iTime * speed;\n }\n speed += 302.2;\n \n float mouseInfluence = (uMouse.x * 2.0 - 1.0);\n speed += mouseInfluence * 0.1;\n \n float new_pixel_angle = atan(uv.y, uv.x) + speed - uSpinEase * 20.0 * (uSpinAmount * uv_len + (1.0 - uSpinAmount));\n vec2 mid = (screenSize.xy / length(screenSize.xy)) / 2.0;\n uv = (vec2(uv_len * cos(new_pixel_angle) + mid.x, uv_len * sin(new_pixel_angle) + mid.y) - mid);\n \n uv *= 30.0;\n float baseSpeed = iTime * uSpinSpeed;\n speed = baseSpeed + mouseInfluence * 2.0;\n \n vec2 uv2 = vec2(uv.x + uv.y);\n \n for(int i = 0; i < 5; i++) {\n uv2 += sin(max(uv.x, uv.y)) + uv;\n uv += 0.5 * vec2(\n cos(5.1123314 + 0.353 * uv2.y + speed * 0.131121),\n sin(uv2.x - 0.113 * speed)\n );\n uv -= cos(uv.x + uv.y) - sin(uv.x * 0.711 - uv.y);\n }\n \n float contrast_mod = (0.25 * uContrast + 0.5 * uSpinAmount + 1.2);\n float paint_res = min(2.0, max(0.0, length(uv) * 0.035 * contrast_mod));\n float c1p = max(0.0, 1.0 - contrast_mod * abs(1.0 - paint_res));\n float c2p = max(0.0, 1.0 - contrast_mod * abs(paint_res));\n float c3p = 1.0 - min(1.0, c1p + c2p);\n float light = (uLighting - 0.2) * max(c1p * 5.0 - 4.0, 0.0) + uLighting * max(c2p * 5.0 - 4.0, 0.0);\n \n return (0.3 / uContrast) * uColor1 + (1.0 - 0.3 / uContrast) * (uColor1 * c1p + uColor2 * c2p + vec4(c3p * uColor3.rgb, c3p * uColor1.a)) + light;\n}\n\nvoid main() {\n vec2 uv = vUv * iResolution.xy;\n gl_FragColor = effect(iResolution.xy, uv);\n}\n`;\n\nexport default function Balatro({\n spinRotation = -2.0,\n spinSpeed = 7.0,\n offset = [0.0, 0.0],\n color1 = '#DE443B',\n color2 = '#006BB4',\n color3 = '#162325',\n contrast = 3.5,\n lighting = 0.4,\n spinAmount = 0.25,\n pixelFilter = 745.0,\n spinEase = 1.0,\n isRotate = false,\n mouseInteraction = true\n}: BalatroProps) {\n const containerRef = useRef(null);\n\n useEffect(() => {\n if (!containerRef.current) return;\n const container = containerRef.current;\n const renderer = new Renderer();\n const gl = renderer.gl;\n gl.clearColor(0, 0, 0, 1);\n\n let program: Program;\n\n function resize() {\n renderer.setSize(container.offsetWidth, container.offsetHeight);\n if (program) {\n program.uniforms.iResolution.value = [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height];\n }\n }\n window.addEventListener('resize', resize);\n resize();\n\n const geometry = new Triangle(gl);\n program = new Program(gl, {\n vertex: vertexShader,\n fragment: fragmentShader,\n uniforms: {\n iTime: { value: 0 },\n iResolution: {\n value: [gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height]\n },\n uSpinRotation: { value: spinRotation },\n uSpinSpeed: { value: spinSpeed },\n uOffset: { value: offset },\n uColor1: { value: hexToVec4(color1) },\n uColor2: { value: hexToVec4(color2) },\n uColor3: { value: hexToVec4(color3) },\n uContrast: { value: contrast },\n uLighting: { value: lighting },\n uSpinAmount: { value: spinAmount },\n uPixelFilter: { value: pixelFilter },\n uSpinEase: { value: spinEase },\n uIsRotate: { value: isRotate },\n uMouse: { value: [0.5, 0.5] }\n }\n });\n\n const mesh = new Mesh(gl, { geometry, program });\n let animationFrameId: number;\n\n function update(time: number) {\n animationFrameId = requestAnimationFrame(update);\n program.uniforms.iTime.value = time * 0.001;\n renderer.render({ scene: mesh });\n }\n animationFrameId = requestAnimationFrame(update);\n container.appendChild(gl.canvas);\n\n function handleMouseMove(e: MouseEvent) {\n if (!mouseInteraction) return;\n const rect = container.getBoundingClientRect();\n const x = (e.clientX - rect.left) / rect.width;\n const y = 1.0 - (e.clientY - rect.top) / rect.height;\n program.uniforms.uMouse.value = [x, y];\n }\n container.addEventListener('mousemove', handleMouseMove);\n\n return () => {\n cancelAnimationFrame(animationFrameId);\n window.removeEventListener('resize', resize);\n container.removeEventListener('mousemove', handleMouseMove);\n container.removeChild(gl.canvas);\n gl.getExtension('WEBGL_lose_context')?.loseContext();\n };\n }, [\n spinRotation,\n spinSpeed,\n offset,\n color1,\n color2,\n color3,\n contrast,\n lighting,\n spinAmount,\n pixelFilter,\n spinEase,\n isRotate,\n mouseInteraction\n ]);\n\n return
;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "ogl@^1.0.11" + ] } \ No newline at end of file diff --git a/public/r/Ballpit-JS-CSS.json b/public/r/Ballpit-JS-CSS.json index 119d6cf6..d68b6a21 100644 --- a/public/r/Ballpit-JS-CSS.json +++ b/public/r/Ballpit-JS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Ballpit-JS-CSS", - "type": "registry:block", - "title": "Ballpit", - "description": "Physics ball pit simulation with bouncing colorful spheres.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/default/src/content/Backgrounds/Ballpit/Ballpit.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.fragmentShader.replace(\n 'void main() {',\n '\\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\\n #ifdef USE_COLOR\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\\n #else\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\\n #endif\\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\\n }\\n\\n void main() {\\n '\n );\n const t = h.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n '\\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\\n '\n );\n e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Ballpit-JS-CSS", + "title": "Ballpit", + "description": "Physics ball pit simulation with bouncing colorful spheres.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Backgrounds/Ballpit/Ballpit.jsx", + "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.fragmentShader.replace(\n 'void main() {',\n '\\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\\n #ifdef USE_COLOR\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\\n #else\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\\n #endif\\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\\n }\\n\\n void main() {\\n '\n );\n const t = h.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n '\\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\\n '\n );\n e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/Ballpit-JS-TW.json b/public/r/Ballpit-JS-TW.json index e65cc6ab..0109040f 100644 --- a/public/r/Ballpit-JS-TW.json +++ b/public/r/Ballpit-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Ballpit-JS-TW", - "type": "registry:block", - "title": "Ballpit", - "description": "Physics ball pit simulation with bouncing colorful spheres.", - "dependencies": [ - "three" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Backgrounds/Ballpit/Ballpit.jsx", - "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.fragmentShader.replace(\n 'void main() {',\n '\\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\\n #ifdef USE_COLOR\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\\n #else\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\\n #endif\\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\\n }\\n\\n void main() {\\n '\n );\n const t = h.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n '\\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\\n '\n );\n e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Ballpit-JS-TW", + "title": "Ballpit", + "description": "Physics ball pit simulation with bouncing colorful spheres.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/Backgrounds/Ballpit/Ballpit.jsx", + "content": "import { useRef, useEffect } from 'react';\nimport {\n Clock as e,\n PerspectiveCamera as t,\n Scene as i,\n WebGLRenderer as s,\n SRGBColorSpace as n,\n MathUtils as o,\n Vector2 as r,\n Vector3 as a,\n MeshPhysicalMaterial as c,\n ShaderChunk as h,\n Color as l,\n Object3D as m,\n InstancedMesh as d,\n PMREMGenerator as p,\n SphereGeometry as g,\n AmbientLight as f,\n PointLight as u,\n ACESFilmicToneMapping as v,\n Raycaster as y,\n Plane as w\n} from 'three';\nimport { RoomEnvironment as z } from 'three/examples/jsm/environments/RoomEnvironment.js';\n\nclass x {\n #e;\n canvas;\n camera;\n cameraMinAspect;\n cameraMaxAspect;\n cameraFov;\n maxPixelRatio;\n minPixelRatio;\n scene;\n renderer;\n #t;\n size = { width: 0, height: 0, wWidth: 0, wHeight: 0, ratio: 0, pixelRatio: 0 };\n render = this.#i;\n onBeforeRender = () => {};\n onAfterRender = () => {};\n onAfterResize = () => {};\n #s = false;\n #n = false;\n isDisposed = false;\n #o;\n #r;\n #a;\n #c = new e();\n #h = { elapsed: 0, delta: 0 };\n #l;\n constructor(e) {\n this.#e = { ...e };\n this.#m();\n this.#d();\n this.#p();\n this.resize();\n this.#g();\n }\n #m() {\n this.camera = new t();\n this.cameraFov = this.camera.fov;\n }\n #d() {\n this.scene = new i();\n }\n #p() {\n if (this.#e.canvas) {\n this.canvas = this.#e.canvas;\n } else if (this.#e.id) {\n this.canvas = document.getElementById(this.#e.id);\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas.style.display = 'block';\n const e = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#e.rendererOptions ?? {})\n };\n this.renderer = new s(e);\n this.renderer.outputColorSpace = n;\n }\n #g() {\n if (!(this.#e.size instanceof Object)) {\n window.addEventListener('resize', this.#f.bind(this));\n if (this.#e.size === 'parent' && this.canvas.parentNode) {\n this.#r = new ResizeObserver(this.#f.bind(this));\n this.#r.observe(this.canvas.parentNode);\n }\n }\n this.#o = new IntersectionObserver(this.#u.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#o.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#v.bind(this));\n }\n #y() {\n window.removeEventListener('resize', this.#f.bind(this));\n this.#r?.disconnect();\n this.#o?.disconnect();\n document.removeEventListener('visibilitychange', this.#v.bind(this));\n }\n #u(e) {\n this.#s = e[0].isIntersecting;\n this.#s ? this.#w() : this.#z();\n }\n #v() {\n if (this.#s) {\n document.hidden ? this.#z() : this.#w();\n }\n }\n #f() {\n if (this.#a) clearTimeout(this.#a);\n this.#a = setTimeout(this.resize.bind(this), 100);\n }\n resize() {\n let e, t;\n if (this.#e.size instanceof Object) {\n e = this.#e.size.width;\n t = this.#e.size.height;\n } else if (this.#e.size === 'parent' && this.canvas.parentNode) {\n e = this.canvas.parentNode.offsetWidth;\n t = this.canvas.parentNode.offsetHeight;\n } else {\n e = window.innerWidth;\n t = window.innerHeight;\n }\n this.size.width = e;\n this.size.height = t;\n this.size.ratio = e / t;\n this.#x();\n this.#b();\n this.onAfterResize(this.size);\n }\n #x() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#A(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#A(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n #A(e) {\n const t = Math.tan(o.degToRad(this.cameraFov / 2)) / (this.camera.aspect / e);\n this.camera.fov = 2 * o.radToDeg(Math.atan(t));\n }\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const e = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(e / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if (this.camera.isOrthographicCamera) {\n this.size.wHeight = this.camera.top - this.camera.bottom;\n this.size.wWidth = this.camera.right - this.camera.left;\n }\n }\n #b() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#t?.setSize(this.size.width, this.size.height);\n let e = window.devicePixelRatio;\n if (this.maxPixelRatio && e > this.maxPixelRatio) {\n e = this.maxPixelRatio;\n } else if (this.minPixelRatio && e < this.minPixelRatio) {\n e = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(e);\n this.size.pixelRatio = e;\n }\n get postprocessing() {\n return this.#t;\n }\n set postprocessing(e) {\n this.#t = e;\n this.render = e.render.bind(e);\n }\n #w() {\n if (this.#n) return;\n const animate = () => {\n this.#l = requestAnimationFrame(animate);\n this.#h.delta = this.#c.getDelta();\n this.#h.elapsed += this.#h.delta;\n this.onBeforeRender(this.#h);\n this.render();\n this.onAfterRender(this.#h);\n };\n this.#n = true;\n this.#c.start();\n animate();\n }\n #z() {\n if (this.#n) {\n cancelAnimationFrame(this.#l);\n this.#n = false;\n this.#c.stop();\n }\n }\n #i() {\n this.renderer.render(this.scene, this.camera);\n }\n clear() {\n this.scene.traverse(e => {\n if (e.isMesh && typeof e.material === 'object' && e.material !== null) {\n Object.keys(e.material).forEach(t => {\n const i = e.material[t];\n if (i !== null && typeof i === 'object' && typeof i.dispose === 'function') {\n i.dispose();\n }\n });\n e.material.dispose();\n e.geometry.dispose();\n }\n });\n this.scene.clear();\n }\n dispose() {\n this.#y();\n this.#z();\n this.clear();\n this.#t?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n}\n\nconst b = new Map(),\n A = new r();\nlet R = false;\nfunction S(e) {\n const t = {\n position: new r(),\n nPosition: new r(),\n hover: false,\n touching: false,\n onEnter() {},\n onMove() {},\n onClick() {},\n onLeave() {},\n ...e\n };\n (function (e, t) {\n if (!b.has(e)) {\n b.set(e, t);\n if (!R) {\n document.body.addEventListener('pointermove', M);\n document.body.addEventListener('pointerleave', L);\n document.body.addEventListener('click', C);\n\n document.body.addEventListener('touchstart', TouchStart, { passive: false });\n document.body.addEventListener('touchmove', TouchMove, { passive: false });\n document.body.addEventListener('touchend', TouchEnd, { passive: false });\n document.body.addEventListener('touchcancel', TouchEnd, { passive: false });\n\n R = true;\n }\n }\n })(e.domElement, t);\n t.dispose = () => {\n const t = e.domElement;\n b.delete(t);\n if (b.size === 0) {\n document.body.removeEventListener('pointermove', M);\n document.body.removeEventListener('pointerleave', L);\n document.body.removeEventListener('click', C);\n\n document.body.removeEventListener('touchstart', TouchStart);\n document.body.removeEventListener('touchmove', TouchMove);\n document.body.removeEventListener('touchend', TouchEnd);\n document.body.removeEventListener('touchcancel', TouchEnd);\n\n R = false;\n }\n };\n return t;\n}\n\nfunction M(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n processInteraction();\n}\n\nfunction processInteraction() {\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n if (D(i)) {\n P(t, i);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && !t.touching) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction C(e) {\n A.x = e.clientX;\n A.y = e.clientY;\n for (const [elem, t] of b) {\n const i = elem.getBoundingClientRect();\n P(t, i);\n if (D(i)) t.onClick(t);\n }\n}\n\nfunction L() {\n for (const t of b.values()) {\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n}\n\nfunction TouchStart(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n if (D(rect)) {\n t.touching = true;\n P(t, rect);\n if (!t.hover) {\n t.hover = true;\n t.onEnter(t);\n }\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchMove(e) {\n if (e.touches.length > 0) {\n e.preventDefault();\n A.x = e.touches[0].clientX;\n A.y = e.touches[0].clientY;\n\n for (const [elem, t] of b) {\n const rect = elem.getBoundingClientRect();\n P(t, rect);\n\n if (D(rect)) {\n if (!t.hover) {\n t.hover = true;\n t.touching = true;\n t.onEnter(t);\n }\n t.onMove(t);\n } else if (t.hover && t.touching) {\n t.onMove(t);\n }\n }\n }\n}\n\nfunction TouchEnd() {\n for (const [, t] of b) {\n if (t.touching) {\n t.touching = false;\n if (t.hover) {\n t.hover = false;\n t.onLeave(t);\n }\n }\n }\n}\n\nfunction P(e, t) {\n const { position: i, nPosition: s } = e;\n i.x = A.x - t.left;\n i.y = A.y - t.top;\n s.x = (i.x / t.width) * 2 - 1;\n s.y = (-i.y / t.height) * 2 + 1;\n}\nfunction D(e) {\n const { x: t, y: i } = A;\n const { left: s, top: n, width: o, height: r } = e;\n return t >= s && t <= s + o && i >= n && i <= n + r;\n}\n\nconst { randFloat: k, randFloatSpread: E } = o;\nconst F = new a();\nconst I = new a();\nconst O = new a();\nconst V = new a();\nconst B = new a();\nconst N = new a();\nconst _ = new a();\nconst j = new a();\nconst H = new a();\nconst T = new a();\n\nclass W {\n constructor(e) {\n this.config = e;\n this.positionData = new Float32Array(3 * e.count).fill(0);\n this.velocityData = new Float32Array(3 * e.count).fill(0);\n this.sizeData = new Float32Array(e.count).fill(1);\n this.center = new a();\n this.#R();\n this.setSizes();\n }\n #R() {\n const { config: e, positionData: t } = this;\n this.center.toArray(t, 0);\n for (let i = 1; i < e.count; i++) {\n const s = 3 * i;\n t[s] = E(2 * e.maxX);\n t[s + 1] = E(2 * e.maxY);\n t[s + 2] = E(2 * e.maxZ);\n }\n }\n setSizes() {\n const { config: e, sizeData: t } = this;\n t[0] = e.size0;\n for (let i = 1; i < e.count; i++) {\n t[i] = k(e.minSize, e.maxSize);\n }\n }\n update(e) {\n const { config: t, center: i, positionData: s, sizeData: n, velocityData: o } = this;\n let r = 0;\n if (t.controlSphere0) {\n r = 1;\n F.fromArray(s, 0);\n F.lerp(i, 0.1).toArray(s, 0);\n V.set(0, 0, 0).toArray(o, 0);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n B.y -= e.delta * t.gravity * n[idx];\n B.multiplyScalar(t.friction);\n B.clampLength(0, t.maxVelocity);\n I.add(B);\n I.toArray(s, base);\n B.toArray(o, base);\n }\n for (let idx = r; idx < t.count; idx++) {\n const base = 3 * idx;\n I.fromArray(s, base);\n B.fromArray(o, base);\n const radius = n[idx];\n for (let jdx = idx + 1; jdx < t.count; jdx++) {\n const otherBase = 3 * jdx;\n O.fromArray(s, otherBase);\n N.fromArray(o, otherBase);\n const otherRadius = n[jdx];\n _.copy(O).sub(I);\n const dist = _.length();\n const sumRadius = radius + otherRadius;\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n j.copy(_)\n .normalize()\n .multiplyScalar(0.5 * overlap);\n H.copy(j).multiplyScalar(Math.max(B.length(), 1));\n T.copy(j).multiplyScalar(Math.max(N.length(), 1));\n I.sub(j);\n B.sub(H);\n I.toArray(s, base);\n B.toArray(o, base);\n O.add(j);\n N.add(T);\n O.toArray(s, otherBase);\n N.toArray(o, otherBase);\n }\n }\n if (t.controlSphere0) {\n _.copy(F).sub(I);\n const dist = _.length();\n const sumRadius0 = radius + n[0];\n if (dist < sumRadius0) {\n const diff = sumRadius0 - dist;\n j.copy(_.normalize()).multiplyScalar(diff);\n H.copy(j).multiplyScalar(Math.max(B.length(), 2));\n I.sub(j);\n B.sub(H);\n }\n }\n if (Math.abs(I.x) + radius > t.maxX) {\n I.x = Math.sign(I.x) * (t.maxX - radius);\n B.x = -B.x * t.wallBounce;\n }\n if (t.gravity === 0) {\n if (Math.abs(I.y) + radius > t.maxY) {\n I.y = Math.sign(I.y) * (t.maxY - radius);\n B.y = -B.y * t.wallBounce;\n }\n } else if (I.y - radius < -t.maxY) {\n I.y = -t.maxY + radius;\n B.y = -B.y * t.wallBounce;\n }\n const maxBoundary = Math.max(t.maxZ, t.maxSize);\n if (Math.abs(I.z) + radius > maxBoundary) {\n I.z = Math.sign(I.z) * (t.maxZ - radius);\n B.z = -B.z * t.wallBounce;\n }\n I.toArray(s, base);\n B.toArray(o, base);\n }\n }\n}\n\nclass Y extends c {\n constructor(e) {\n super(e);\n this.uniforms = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n this.defines.USE_UV = '';\n this.onBeforeCompile = e => {\n Object.assign(e.uniforms, this.uniforms);\n e.fragmentShader =\n '\\n uniform float thicknessPower;\\n uniform float thicknessScale;\\n uniform float thicknessDistortion;\\n uniform float thicknessAmbient;\\n uniform float thicknessAttenuation;\\n ' +\n e.fragmentShader;\n e.fragmentShader = e.fragmentShader.replace(\n 'void main() {',\n '\\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\\n #ifdef USE_COLOR\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\\n #else\\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\\n #endif\\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\\n }\\n\\n void main() {\\n '\n );\n const t = h.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n '\\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\\n '\n );\n e.fragmentShader = e.fragmentShader.replace('#include ', t);\n if (this.onBeforeCompile2) this.onBeforeCompile2(e);\n };\n }\n}\n\nconst X = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 16777215,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new m();\n\nclass Z extends d {\n constructor(e, t = {}) {\n const i = { ...X, ...t };\n const s = new z();\n const n = new p(e, 0.04).fromScene(s).texture;\n const o = new g();\n const r = new Y({ envMap: n, ...i.materialParams });\n r.envMapRotation.x = -Math.PI / 2;\n super(o, r, i.count);\n this.config = i;\n this.physics = new W(i);\n this.#S();\n this.setColors(i.colors);\n }\n #S() {\n this.ambientLight = new f(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new u(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n setColors(e) {\n if (Array.isArray(e) && e.length > 1) {\n const t = (function (e) {\n let t, i;\n function setColors(e) {\n t = e;\n i = [];\n t.forEach(col => {\n i.push(new l(col));\n });\n }\n setColors(e);\n return {\n setColors,\n getColorAt: function (ratio, out = new l()) {\n const scaled = Math.max(0, Math.min(1, ratio)) * (t.length - 1);\n const idx = Math.floor(scaled);\n const start = i[idx];\n if (idx >= t.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = i[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(e);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, t.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light.color.copy(t.getColorAt(idx / this.count));\n }\n }\n this.instanceColor.needsUpdate = true;\n }\n }\n update(e) {\n this.physics.update(e);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\nfunction createBallpit(e, t = {}) {\n const i = new x({\n canvas: e,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let s;\n i.renderer.toneMapping = v;\n i.camera.position.set(0, 0, 20);\n i.camera.lookAt(0, 0, 0);\n i.cameraMaxAspect = 1.5;\n i.resize();\n initialize(t);\n const n = new y();\n const o = new w(new a(0, 0, 1), 0);\n const r = new a();\n let c = false;\n\n e.style.touchAction = 'none';\n e.style.userSelect = 'none';\n e.style.webkitUserSelect = 'none';\n\n const h = S({\n domElement: e,\n onMove() {\n n.setFromCamera(h.nPosition, i.camera);\n i.camera.getWorldDirection(o.normal);\n n.ray.intersectPlane(o, r);\n s.physics.center.copy(r);\n s.config.controlSphere0 = true;\n },\n onLeave() {\n s.config.controlSphere0 = false;\n }\n });\n function initialize(e) {\n if (s) {\n i.clear();\n i.scene.remove(s);\n }\n s = new Z(i.renderer, e);\n i.scene.add(s);\n }\n i.onBeforeRender = e => {\n if (!c) s.update(e);\n };\n i.onAfterResize = e => {\n s.config.maxX = e.wWidth / 2;\n s.config.maxY = e.wHeight / 2;\n };\n return {\n three: i,\n get spheres() {\n return s;\n },\n setCount(e) {\n initialize({ ...s.config, count: e });\n },\n togglePause() {\n c = !c;\n },\n dispose() {\n h.dispose();\n i.dispose();\n }\n };\n}\n\nconst Ballpit = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, { followCursor, ...props });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1" + ] } \ No newline at end of file diff --git a/public/r/Ballpit-TS-CSS.json b/public/r/Ballpit-TS-CSS.json index 027f12a1..7d7e7937 100644 --- a/public/r/Ballpit-TS-CSS.json +++ b/public/r/Ballpit-TS-CSS.json @@ -1,18 +1,19 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Ballpit-TS-CSS", - "type": "registry:block", - "title": "Ballpit", - "description": "Physics ball pit simulation with bouncing colorful spheres.", - "dependencies": [ - "gsap", - "three" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Backgrounds/Ballpit/Ballpit.tsx", - "content": "import React, { useRef, useEffect } from 'react';\nimport {\n Clock,\n PerspectiveCamera,\n Scene,\n WebGLRenderer,\n WebGLRendererParameters,\n SRGBColorSpace,\n MathUtils,\n Vector2,\n Vector3,\n MeshPhysicalMaterial,\n ShaderChunk,\n Color,\n Object3D,\n InstancedMesh,\n PMREMGenerator,\n SphereGeometry,\n AmbientLight,\n PointLight,\n ACESFilmicToneMapping,\n Raycaster,\n Plane\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\nimport { Observer } from 'gsap/Observer';\nimport { gsap } from 'gsap';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, { passive: false });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, { passive: false });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, { passive: false });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, { passive: false });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nconst { randFloat, randFloatSpread } = MathUtils;\nconst F = new Vector3();\nconst I = new Vector3();\nconst O = new Vector3();\nconst V = new Vector3();\nconst B = new Vector3();\nconst N = new Vector3();\nconst _ = new Vector3();\nconst j = new Vector3();\nconst H = new Vector3();\nconst T = new Vector3();\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Ballpit-TS-CSS", + "title": "Ballpit", + "description": "Physics ball pit simulation with bouncing colorful spheres.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/Backgrounds/Ballpit/Ballpit.tsx", + "content": "import React, { useRef, useEffect } from 'react';\nimport {\n Clock,\n PerspectiveCamera,\n Scene,\n WebGLRenderer,\n WebGLRendererParameters,\n SRGBColorSpace,\n MathUtils,\n Vector2,\n Vector3,\n MeshPhysicalMaterial,\n ShaderChunk,\n Color,\n Object3D,\n InstancedMesh,\n PMREMGenerator,\n SphereGeometry,\n AmbientLight,\n PointLight,\n ACESFilmicToneMapping,\n Raycaster,\n Plane\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\nimport { Observer } from 'gsap/Observer';\nimport { gsap } from 'gsap';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, { passive: false });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, { passive: false });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, { passive: false });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, { passive: false });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nconst { randFloat, randFloatSpread } = MathUtils;\nconst F = new Vector3();\nconst I = new Vector3();\nconst O = new Vector3();\nconst V = new Vector3();\nconst B = new Vector3();\nconst N = new Vector3();\nconst _ = new Vector3();\nconst j = new Vector3();\nconst H = new Vector3();\nconst T = new Vector3();\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/Ballpit-TS-TW.json b/public/r/Ballpit-TS-TW.json index 1a7d448c..d019f99c 100644 --- a/public/r/Ballpit-TS-TW.json +++ b/public/r/Ballpit-TS-TW.json @@ -1,18 +1,19 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Ballpit-TS-TW", - "type": "registry:block", - "title": "Ballpit", - "description": "Physics ball pit simulation with bouncing colorful spheres.", - "dependencies": [ - "gsap", - "three" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Backgrounds/Ballpit/Ballpit.tsx", - "content": "import React, { useRef, useEffect } from 'react';\nimport {\n Clock,\n PerspectiveCamera,\n Scene,\n WebGLRenderer,\n WebGLRendererParameters,\n SRGBColorSpace,\n MathUtils,\n Vector2,\n Vector3,\n MeshPhysicalMaterial,\n ShaderChunk,\n Color,\n Object3D,\n InstancedMesh,\n PMREMGenerator,\n SphereGeometry,\n AmbientLight,\n PointLight,\n ACESFilmicToneMapping,\n Raycaster,\n Plane\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\nimport { Observer } from 'gsap/Observer';\nimport { gsap } from 'gsap';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, {\n passive: false\n });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Ballpit-TS-TW", + "title": "Ballpit", + "description": "Physics ball pit simulation with bouncing colorful spheres.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/Backgrounds/Ballpit/Ballpit.tsx", + "content": "import React, { useRef, useEffect } from 'react';\nimport {\n Clock,\n PerspectiveCamera,\n Scene,\n WebGLRenderer,\n WebGLRendererParameters,\n SRGBColorSpace,\n MathUtils,\n Vector2,\n Vector3,\n MeshPhysicalMaterial,\n ShaderChunk,\n Color,\n Object3D,\n InstancedMesh,\n PMREMGenerator,\n SphereGeometry,\n AmbientLight,\n PointLight,\n ACESFilmicToneMapping,\n Raycaster,\n Plane\n} from 'three';\nimport { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';\nimport { Observer } from 'gsap/Observer';\nimport { gsap } from 'gsap';\n\ngsap.registerPlugin(Observer);\n\ninterface XConfig {\n canvas?: HTMLCanvasElement;\n id?: string;\n rendererOptions?: Partial;\n size?: 'parent' | { width: number; height: number };\n}\n\ninterface SizeData {\n width: number;\n height: number;\n wWidth: number;\n wHeight: number;\n ratio: number;\n pixelRatio: number;\n}\n\nclass X {\n #config: XConfig;\n #postprocessing: any;\n #resizeObserver?: ResizeObserver;\n #intersectionObserver?: IntersectionObserver;\n #resizeTimer?: number;\n #animationFrameId: number = 0;\n #clock: Clock = new Clock();\n #animationState = { elapsed: 0, delta: 0 };\n #isAnimating: boolean = false;\n #isVisible: boolean = false;\n\n canvas!: HTMLCanvasElement;\n camera!: PerspectiveCamera;\n cameraMinAspect?: number;\n cameraMaxAspect?: number;\n cameraFov!: number;\n maxPixelRatio?: number;\n minPixelRatio?: number;\n scene!: Scene;\n renderer!: WebGLRenderer;\n size: SizeData = {\n width: 0,\n height: 0,\n wWidth: 0,\n wHeight: 0,\n ratio: 0,\n pixelRatio: 0\n };\n\n render: () => void = this.#render.bind(this);\n onBeforeRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterRender: (state: { elapsed: number; delta: number }) => void = () => {};\n onAfterResize: (size: SizeData) => void = () => {};\n isDisposed: boolean = false;\n\n constructor(config: XConfig) {\n this.#config = { ...config };\n this.#initCamera();\n this.#initScene();\n this.#initRenderer();\n this.resize();\n this.#initObservers();\n }\n\n #initCamera() {\n this.camera = new PerspectiveCamera();\n this.cameraFov = this.camera.fov;\n }\n\n #initScene() {\n this.scene = new Scene();\n }\n\n #initRenderer() {\n if (this.#config.canvas) {\n this.canvas = this.#config.canvas;\n } else if (this.#config.id) {\n const elem = document.getElementById(this.#config.id);\n if (elem instanceof HTMLCanvasElement) {\n this.canvas = elem;\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n } else {\n console.error('Three: Missing canvas or id parameter');\n }\n this.canvas!.style.display = 'block';\n const rendererOptions: WebGLRendererParameters = {\n canvas: this.canvas,\n powerPreference: 'high-performance',\n ...(this.#config.rendererOptions ?? {})\n };\n this.renderer = new WebGLRenderer(rendererOptions);\n this.renderer.outputColorSpace = SRGBColorSpace;\n }\n\n #initObservers() {\n if (!(this.#config.size instanceof Object)) {\n window.addEventListener('resize', this.#onResize.bind(this));\n if (this.#config.size === 'parent' && this.canvas.parentNode) {\n this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));\n this.#resizeObserver.observe(this.canvas.parentNode as Element);\n }\n }\n this.#intersectionObserver = new IntersectionObserver(this.#onIntersection.bind(this), {\n root: null,\n rootMargin: '0px',\n threshold: 0\n });\n this.#intersectionObserver.observe(this.canvas);\n document.addEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n\n #onResize() {\n if (this.#resizeTimer) clearTimeout(this.#resizeTimer);\n this.#resizeTimer = window.setTimeout(this.resize.bind(this), 100);\n }\n\n resize() {\n let w: number, h: number;\n if (this.#config.size instanceof Object) {\n w = this.#config.size.width;\n h = this.#config.size.height;\n } else if (this.#config.size === 'parent' && this.canvas.parentNode) {\n w = (this.canvas.parentNode as HTMLElement).offsetWidth;\n h = (this.canvas.parentNode as HTMLElement).offsetHeight;\n } else {\n w = window.innerWidth;\n h = window.innerHeight;\n }\n this.size.width = w;\n this.size.height = h;\n this.size.ratio = w / h;\n this.#updateCamera();\n this.#updateRenderer();\n this.onAfterResize(this.size);\n }\n\n #updateCamera() {\n this.camera.aspect = this.size.width / this.size.height;\n if (this.camera.isPerspectiveCamera && this.cameraFov) {\n if (this.cameraMinAspect && this.camera.aspect < this.cameraMinAspect) {\n this.#adjustFov(this.cameraMinAspect);\n } else if (this.cameraMaxAspect && this.camera.aspect > this.cameraMaxAspect) {\n this.#adjustFov(this.cameraMaxAspect);\n } else {\n this.camera.fov = this.cameraFov;\n }\n }\n this.camera.updateProjectionMatrix();\n this.updateWorldSize();\n }\n\n #adjustFov(aspect: number) {\n const tanFov = Math.tan(MathUtils.degToRad(this.cameraFov / 2));\n const newTan = tanFov / (this.camera.aspect / aspect);\n this.camera.fov = 2 * MathUtils.radToDeg(Math.atan(newTan));\n }\n\n updateWorldSize() {\n if (this.camera.isPerspectiveCamera) {\n const fovRad = (this.camera.fov * Math.PI) / 180;\n this.size.wHeight = 2 * Math.tan(fovRad / 2) * this.camera.position.length();\n this.size.wWidth = this.size.wHeight * this.camera.aspect;\n } else if ((this.camera as any).isOrthographicCamera) {\n const cam = this.camera as any;\n this.size.wHeight = cam.top - cam.bottom;\n this.size.wWidth = cam.right - cam.left;\n }\n }\n\n #updateRenderer() {\n this.renderer.setSize(this.size.width, this.size.height);\n this.#postprocessing?.setSize(this.size.width, this.size.height);\n let pr = window.devicePixelRatio;\n if (this.maxPixelRatio && pr > this.maxPixelRatio) {\n pr = this.maxPixelRatio;\n } else if (this.minPixelRatio && pr < this.minPixelRatio) {\n pr = this.minPixelRatio;\n }\n this.renderer.setPixelRatio(pr);\n this.size.pixelRatio = pr;\n }\n\n get postprocessing() {\n return this.#postprocessing;\n }\n set postprocessing(value: any) {\n this.#postprocessing = value;\n this.render = value.render.bind(value);\n }\n\n #onIntersection(entries: IntersectionObserverEntry[]) {\n this.#isAnimating = entries[0].isIntersecting;\n this.#isAnimating ? this.#startAnimation() : this.#stopAnimation();\n }\n\n #onVisibilityChange() {\n if (this.#isAnimating) {\n document.hidden ? this.#stopAnimation() : this.#startAnimation();\n }\n }\n\n #startAnimation() {\n if (this.#isVisible) return;\n const animateFrame = () => {\n this.#animationFrameId = requestAnimationFrame(animateFrame);\n this.#animationState.delta = this.#clock.getDelta();\n this.#animationState.elapsed += this.#animationState.delta;\n this.onBeforeRender(this.#animationState);\n this.render();\n this.onAfterRender(this.#animationState);\n };\n this.#isVisible = true;\n this.#clock.start();\n animateFrame();\n }\n\n #stopAnimation() {\n if (this.#isVisible) {\n cancelAnimationFrame(this.#animationFrameId);\n this.#isVisible = false;\n this.#clock.stop();\n }\n }\n\n #render() {\n this.renderer.render(this.scene, this.camera);\n }\n\n clear() {\n this.scene.traverse(obj => {\n if ((obj as any).isMesh && typeof (obj as any).material === 'object' && (obj as any).material !== null) {\n Object.keys((obj as any).material).forEach(key => {\n const matProp = (obj as any).material[key];\n if (matProp && typeof matProp === 'object' && typeof matProp.dispose === 'function') {\n matProp.dispose();\n }\n });\n (obj as any).material.dispose();\n (obj as any).geometry.dispose();\n }\n });\n this.scene.clear();\n }\n\n dispose() {\n this.#onResizeCleanup();\n this.#stopAnimation();\n this.clear();\n this.#postprocessing?.dispose();\n this.renderer.dispose();\n this.isDisposed = true;\n }\n\n #onResizeCleanup() {\n window.removeEventListener('resize', this.#onResize.bind(this));\n this.#resizeObserver?.disconnect();\n this.#intersectionObserver?.disconnect();\n document.removeEventListener('visibilitychange', this.#onVisibilityChange.bind(this));\n }\n}\n\ninterface WConfig {\n count: number;\n maxX: number;\n maxY: number;\n maxZ: number;\n maxSize: number;\n minSize: number;\n size0: number;\n gravity: number;\n friction: number;\n wallBounce: number;\n maxVelocity: number;\n controlSphere0?: boolean;\n followCursor?: boolean;\n}\n\nclass W {\n config: WConfig;\n positionData: Float32Array;\n velocityData: Float32Array;\n sizeData: Float32Array;\n center: Vector3 = new Vector3();\n\n constructor(config: WConfig) {\n this.config = config;\n this.positionData = new Float32Array(3 * config.count).fill(0);\n this.velocityData = new Float32Array(3 * config.count).fill(0);\n this.sizeData = new Float32Array(config.count).fill(1);\n this.center = new Vector3();\n this.#initializePositions();\n this.setSizes();\n }\n\n #initializePositions() {\n const { config, positionData } = this;\n this.center.toArray(positionData, 0);\n for (let i = 1; i < config.count; i++) {\n const idx = 3 * i;\n positionData[idx] = MathUtils.randFloatSpread(2 * config.maxX);\n positionData[idx + 1] = MathUtils.randFloatSpread(2 * config.maxY);\n positionData[idx + 2] = MathUtils.randFloatSpread(2 * config.maxZ);\n }\n }\n\n setSizes() {\n const { config, sizeData } = this;\n sizeData[0] = config.size0;\n for (let i = 1; i < config.count; i++) {\n sizeData[i] = MathUtils.randFloat(config.minSize, config.maxSize);\n }\n }\n\n update(deltaInfo: { delta: number }) {\n const { config, center, positionData, sizeData, velocityData } = this;\n let startIdx = 0;\n if (config.controlSphere0) {\n startIdx = 1;\n const firstVec = new Vector3().fromArray(positionData, 0);\n firstVec.lerp(center, 0.1).toArray(positionData, 0);\n new Vector3(0, 0, 0).toArray(velocityData, 0);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n vel.y -= deltaInfo.delta * config.gravity * sizeData[idx];\n vel.multiplyScalar(config.friction);\n vel.clampLength(0, config.maxVelocity);\n pos.add(vel);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n for (let idx = startIdx; idx < config.count; idx++) {\n const base = 3 * idx;\n const pos = new Vector3().fromArray(positionData, base);\n const vel = new Vector3().fromArray(velocityData, base);\n const radius = sizeData[idx];\n for (let jdx = idx + 1; jdx < config.count; jdx++) {\n const otherBase = 3 * jdx;\n const otherPos = new Vector3().fromArray(positionData, otherBase);\n const otherVel = new Vector3().fromArray(velocityData, otherBase);\n const diff = new Vector3().copy(otherPos).sub(pos);\n const dist = diff.length();\n const sumRadius = radius + sizeData[jdx];\n if (dist < sumRadius) {\n const overlap = sumRadius - dist;\n const correction = diff.normalize().multiplyScalar(0.5 * overlap);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 1));\n pos.sub(correction);\n vel.sub(velCorrection);\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n otherPos.add(correction);\n otherVel.add(correction.clone().multiplyScalar(Math.max(otherVel.length(), 1)));\n otherPos.toArray(positionData, otherBase);\n otherVel.toArray(velocityData, otherBase);\n }\n }\n if (config.controlSphere0) {\n const diff = new Vector3().copy(new Vector3().fromArray(positionData, 0)).sub(pos);\n const d = diff.length();\n const sumRadius0 = radius + sizeData[0];\n if (d < sumRadius0) {\n const correction = diff.normalize().multiplyScalar(sumRadius0 - d);\n const velCorrection = correction.clone().multiplyScalar(Math.max(vel.length(), 2));\n pos.sub(correction);\n vel.sub(velCorrection);\n }\n }\n if (Math.abs(pos.x) + radius > config.maxX) {\n pos.x = Math.sign(pos.x) * (config.maxX - radius);\n vel.x = -vel.x * config.wallBounce;\n }\n if (config.gravity === 0) {\n if (Math.abs(pos.y) + radius > config.maxY) {\n pos.y = Math.sign(pos.y) * (config.maxY - radius);\n vel.y = -vel.y * config.wallBounce;\n }\n } else if (pos.y - radius < -config.maxY) {\n pos.y = -config.maxY + radius;\n vel.y = -vel.y * config.wallBounce;\n }\n const maxBoundary = Math.max(config.maxZ, config.maxSize);\n if (Math.abs(pos.z) + radius > maxBoundary) {\n pos.z = Math.sign(pos.z) * (config.maxZ - radius);\n vel.z = -vel.z * config.wallBounce;\n }\n pos.toArray(positionData, base);\n vel.toArray(velocityData, base);\n }\n }\n}\n\nclass Y extends MeshPhysicalMaterial {\n uniforms: { [key: string]: { value: any } } = {\n thicknessDistortion: { value: 0.1 },\n thicknessAmbient: { value: 0 },\n thicknessAttenuation: { value: 0.1 },\n thicknessPower: { value: 2 },\n thicknessScale: { value: 10 }\n };\n\n constructor(params: any) {\n super(params);\n this.defines = { USE_UV: '' };\n this.onBeforeCompile = shader => {\n Object.assign(shader.uniforms, this.uniforms);\n shader.fragmentShader =\n `\n uniform float thicknessPower;\n uniform float thicknessScale;\n uniform float thicknessDistortion;\n uniform float thicknessAmbient;\n uniform float thicknessAttenuation;\n ` + shader.fragmentShader;\n shader.fragmentShader = shader.fragmentShader.replace(\n 'void main() {',\n `\n void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in vec3 geometryPosition, const in vec3 geometryNormal, const in vec3 geometryViewDir, const in vec3 geometryClearcoatNormal, inout ReflectedLight reflectedLight) {\n vec3 scatteringHalf = normalize(directLight.direction + (geometryNormal * thicknessDistortion));\n float scatteringDot = pow(saturate(dot(geometryViewDir, -scatteringHalf)), thicknessPower) * thicknessScale;\n #ifdef USE_COLOR\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * vColor;\n #else\n vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * diffuse;\n #endif\n reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;\n }\n\n void main() {\n `\n );\n const lightsChunk = ShaderChunk.lights_fragment_begin.replaceAll(\n 'RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );',\n `\n RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );\n RE_Direct_Scattering(directLight, vUv, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, reflectedLight);\n `\n );\n shader.fragmentShader = shader.fragmentShader.replace('#include ', lightsChunk);\n if (this.onBeforeCompile2) this.onBeforeCompile2(shader);\n };\n }\n onBeforeCompile2?: (shader: any) => void;\n}\n\nconst XConfig = {\n count: 200,\n colors: [0, 0, 0],\n ambientColor: 0xffffff,\n ambientIntensity: 1,\n lightIntensity: 200,\n materialParams: {\n metalness: 0.5,\n roughness: 0.5,\n clearcoat: 1,\n clearcoatRoughness: 0.15\n },\n minSize: 0.5,\n maxSize: 1,\n size0: 1,\n gravity: 0.5,\n friction: 0.9975,\n wallBounce: 0.95,\n maxVelocity: 0.15,\n maxX: 5,\n maxY: 5,\n maxZ: 2,\n controlSphere0: false,\n followCursor: true\n};\n\nconst U = new Object3D();\n\nlet globalPointerActive = false;\nconst pointerPosition = new Vector2();\n\ninterface PointerData {\n position: Vector2;\n nPosition: Vector2;\n hover: boolean;\n touching: boolean;\n onEnter: (data: PointerData) => void;\n onMove: (data: PointerData) => void;\n onClick: (data: PointerData) => void;\n onLeave: (data: PointerData) => void;\n dispose?: () => void;\n}\n\nconst pointerMap = new Map();\n\nfunction createPointerData(options: Partial & { domElement: HTMLElement }): PointerData {\n const defaultData: PointerData = {\n position: new Vector2(),\n nPosition: new Vector2(),\n hover: false,\n touching: false,\n onEnter: () => {},\n onMove: () => {},\n onClick: () => {},\n onLeave: () => {},\n ...options\n };\n if (!pointerMap.has(options.domElement)) {\n pointerMap.set(options.domElement, defaultData);\n if (!globalPointerActive) {\n document.body.addEventListener('pointermove', onPointerMove as EventListener);\n document.body.addEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.addEventListener('click', onPointerClick as EventListener);\n\n document.body.addEventListener('touchstart', onTouchStart as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchmove', onTouchMove as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchend', onTouchEnd as EventListener, {\n passive: false\n });\n document.body.addEventListener('touchcancel', onTouchEnd as EventListener, {\n passive: false\n });\n globalPointerActive = true;\n }\n }\n defaultData.dispose = () => {\n pointerMap.delete(options.domElement);\n if (pointerMap.size === 0) {\n document.body.removeEventListener('pointermove', onPointerMove as EventListener);\n document.body.removeEventListener('pointerleave', onPointerLeave as EventListener);\n document.body.removeEventListener('click', onPointerClick as EventListener);\n\n document.body.removeEventListener('touchstart', onTouchStart as EventListener);\n document.body.removeEventListener('touchmove', onTouchMove as EventListener);\n document.body.removeEventListener('touchend', onTouchEnd as EventListener);\n document.body.removeEventListener('touchcancel', onTouchEnd as EventListener);\n globalPointerActive = false;\n }\n };\n return defaultData;\n}\n\nfunction onPointerMove(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n processPointerInteraction();\n}\n\nfunction processPointerInteraction() {\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && !data.touching) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction onTouchStart(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n if (isInside(rect)) {\n data.touching = true;\n updatePointerData(data, rect);\n if (!data.hover) {\n data.hover = true;\n data.onEnter(data);\n }\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchMove(e: TouchEvent) {\n if (e.touches.length > 0) {\n e.preventDefault();\n pointerPosition.set(e.touches[0].clientX, e.touches[0].clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) {\n if (!data.hover) {\n data.hover = true;\n data.touching = true;\n data.onEnter(data);\n }\n data.onMove(data);\n } else if (data.hover && data.touching) {\n data.onMove(data);\n }\n }\n }\n}\n\nfunction onTouchEnd() {\n for (const [, data] of pointerMap) {\n if (data.touching) {\n data.touching = false;\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n }\n}\n\nfunction onPointerClick(e: PointerEvent) {\n pointerPosition.set(e.clientX, e.clientY);\n for (const [elem, data] of pointerMap) {\n const rect = elem.getBoundingClientRect();\n updatePointerData(data, rect);\n if (isInside(rect)) data.onClick(data);\n }\n}\n\nfunction onPointerLeave() {\n for (const data of pointerMap.values()) {\n if (data.hover) {\n data.hover = false;\n data.onLeave(data);\n }\n }\n}\n\nfunction updatePointerData(data: PointerData, rect: DOMRect) {\n data.position.set(pointerPosition.x - rect.left, pointerPosition.y - rect.top);\n data.nPosition.set((data.position.x / rect.width) * 2 - 1, (-data.position.y / rect.height) * 2 + 1);\n}\n\nfunction isInside(rect: DOMRect) {\n return (\n pointerPosition.x >= rect.left &&\n pointerPosition.x <= rect.left + rect.width &&\n pointerPosition.y >= rect.top &&\n pointerPosition.y <= rect.top + rect.height\n );\n}\n\nclass Z extends InstancedMesh {\n config: typeof XConfig;\n physics: W;\n ambientLight: AmbientLight | undefined;\n light: PointLight | undefined;\n\n constructor(renderer: WebGLRenderer, params: Partial = {}) {\n const config = { ...XConfig, ...params };\n const roomEnv = new RoomEnvironment();\n const pmrem = new PMREMGenerator(renderer);\n const envTexture = pmrem.fromScene(roomEnv).texture;\n const geometry = new SphereGeometry();\n const material = new Y({ envMap: envTexture, ...config.materialParams });\n material.envMapRotation.x = -Math.PI / 2;\n super(geometry, material, config.count);\n this.config = config;\n this.physics = new W(config);\n this.#setupLights();\n this.setColors(config.colors);\n }\n\n #setupLights() {\n this.ambientLight = new AmbientLight(this.config.ambientColor, this.config.ambientIntensity);\n this.add(this.ambientLight);\n this.light = new PointLight(this.config.colors[0], this.config.lightIntensity);\n this.add(this.light);\n }\n\n setColors(colors: number[]) {\n if (Array.isArray(colors) && colors.length > 1) {\n const colorUtils = (function (colorsArr: number[]) {\n let baseColors: number[] = colorsArr;\n let colorObjects: Color[] = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n return {\n setColors: (cols: number[]) => {\n baseColors = cols;\n colorObjects = [];\n baseColors.forEach(col => {\n colorObjects.push(new Color(col));\n });\n },\n getColorAt: (ratio: number, out: Color = new Color()) => {\n const clamped = Math.max(0, Math.min(1, ratio));\n const scaled = clamped * (baseColors.length - 1);\n const idx = Math.floor(scaled);\n const start = colorObjects[idx];\n if (idx >= baseColors.length - 1) return start.clone();\n const alpha = scaled - idx;\n const end = colorObjects[idx + 1];\n out.r = start.r + alpha * (end.r - start.r);\n out.g = start.g + alpha * (end.g - start.g);\n out.b = start.b + alpha * (end.b - start.b);\n return out;\n }\n };\n })(colors);\n for (let idx = 0; idx < this.count; idx++) {\n this.setColorAt(idx, colorUtils.getColorAt(idx / this.count));\n if (idx === 0) {\n this.light!.color.copy(colorUtils.getColorAt(idx / this.count));\n }\n }\n\n if (!this.instanceColor) return;\n this.instanceColor.needsUpdate = true;\n }\n }\n\n update(deltaInfo: { delta: number }) {\n this.physics.update(deltaInfo);\n for (let idx = 0; idx < this.count; idx++) {\n U.position.fromArray(this.physics.positionData, 3 * idx);\n if (idx === 0 && this.config.followCursor === false) {\n U.scale.setScalar(0);\n } else {\n U.scale.setScalar(this.physics.sizeData[idx]);\n }\n U.updateMatrix();\n this.setMatrixAt(idx, U.matrix);\n if (idx === 0) this.light!.position.copy(U.position);\n }\n this.instanceMatrix.needsUpdate = true;\n }\n}\n\ninterface CreateBallpitReturn {\n three: X;\n spheres: Z;\n setCount: (count: number) => void;\n togglePause: () => void;\n dispose: () => void;\n}\n\nfunction createBallpit(canvas: HTMLCanvasElement, config: any = {}): CreateBallpitReturn {\n const threeInstance = new X({\n canvas,\n size: 'parent',\n rendererOptions: { antialias: true, alpha: true }\n });\n let spheres: Z;\n threeInstance.renderer.toneMapping = ACESFilmicToneMapping;\n threeInstance.camera.position.set(0, 0, 20);\n threeInstance.camera.lookAt(0, 0, 0);\n threeInstance.cameraMaxAspect = 1.5;\n threeInstance.resize();\n initialize(config);\n const raycaster = new Raycaster();\n const plane = new Plane(new Vector3(0, 0, 1), 0);\n const intersectionPoint = new Vector3();\n let isPaused = false;\n\n canvas.style.touchAction = 'none';\n canvas.style.userSelect = 'none';\n (canvas.style as any).webkitUserSelect = 'none';\n\n const pointerData = createPointerData({\n domElement: canvas,\n onMove() {\n raycaster.setFromCamera(pointerData.nPosition, threeInstance.camera);\n threeInstance.camera.getWorldDirection(plane.normal);\n raycaster.ray.intersectPlane(plane, intersectionPoint);\n spheres.physics.center.copy(intersectionPoint);\n spheres.config.controlSphere0 = true;\n },\n onLeave() {\n spheres.config.controlSphere0 = false;\n }\n });\n function initialize(cfg: any) {\n if (spheres) {\n threeInstance.clear();\n threeInstance.scene.remove(spheres);\n }\n spheres = new Z(threeInstance.renderer, cfg);\n threeInstance.scene.add(spheres);\n }\n threeInstance.onBeforeRender = deltaInfo => {\n if (!isPaused) spheres.update(deltaInfo);\n };\n threeInstance.onAfterResize = size => {\n spheres.config.maxX = size.wWidth / 2;\n spheres.config.maxY = size.wHeight / 2;\n };\n return {\n three: threeInstance,\n get spheres() {\n return spheres;\n },\n setCount(count: number) {\n initialize({ ...spheres.config, count });\n },\n togglePause() {\n isPaused = !isPaused;\n },\n dispose() {\n pointerData.dispose?.();\n threeInstance.dispose();\n }\n };\n}\n\ninterface BallpitProps {\n className?: string;\n followCursor?: boolean;\n [key: string]: any;\n}\n\nconst Ballpit: React.FC = ({ className = '', followCursor = true, ...props }) => {\n const canvasRef = useRef(null);\n const spheresInstanceRef = useRef(null);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n spheresInstanceRef.current = createBallpit(canvas, {\n followCursor,\n ...props\n });\n\n return () => {\n if (spheresInstanceRef.current) {\n spheresInstanceRef.current.dispose();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return ;\n};\n\nexport default Ballpit;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/Beams-JS-CSS.json b/public/r/Beams-JS-CSS.json index 25c61584..453a1804 100644 --- a/public/r/Beams-JS-CSS.json +++ b/public/r/Beams-JS-CSS.json @@ -1,24 +1,25 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Beams-JS-CSS", - "type": "registry:block", - "title": "Beams", - "description": "Crossing animated ribbons with customizable properties.", - "dependencies": [ - "@react-three/drei", - "@react-three/fiber", - "three" - ], - "files": [ - { - "path": "public/default/src/content/Backgrounds/Beams/Beams.jsx", - "content": "/* eslint-disable react/no-unknown-property */\nimport { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\nimport './Beams.css';\n\nfunction extendMaterial(BaseMaterial, cfg) {\n const physical = THREE.ShaderLib.physical;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {});\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = hex => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\nconst Beams = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef(null);\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(n, width, height, spacing, heightSegments) {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef(({ material, width, count, height }, ref) => {\n const mesh = useRef(null);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight = ({ position, color }) => {\n const dir = useRef(null);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera;\n if (!cam) return;\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return ;\n};\n\nexport default Beams;\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Backgrounds/Beams/Beams.css", - "content": ".beams-container {\n position: relative;\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Beams-JS-CSS", + "title": "Beams", + "description": "Crossing animated ribbons with customizable properties.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Backgrounds/Beams/Beams.jsx", + "content": "/* eslint-disable react/no-unknown-property */\nimport { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\nimport './Beams.css';\n\nfunction extendMaterial(BaseMaterial, cfg) {\n const physical = THREE.ShaderLib.physical;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {});\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = hex => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\nconst Beams = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef(null);\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(n, width, height, spacing, heightSegments) {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef(({ material, width, count, height }, ref) => {\n const mesh = useRef(null);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight = ({ position, color }) => {\n const dir = useRef(null);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera;\n if (!cam) return;\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return ;\n};\n\nexport default Beams;\n" + }, + { + "type": "registry:item", + "path": "src/content/Backgrounds/Beams/Beams.css", + "content": ".beams-container {\n position: relative;\n width: 100%;\n height: 100%;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "@react-three/fiber@^9.3.0", + "@react-three/drei@^10.7.4" + ] } \ No newline at end of file diff --git a/public/r/Beams-JS-TW.json b/public/r/Beams-JS-TW.json index c0b5b39d..bba13fc5 100644 --- a/public/r/Beams-JS-TW.json +++ b/public/r/Beams-JS-TW.json @@ -1,19 +1,20 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Beams-JS-TW", - "type": "registry:block", - "title": "Beams", - "description": "Crossing animated ribbons with customizable properties.", - "dependencies": [ - "@react-three/drei", - "@react-three/fiber", - "three" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Backgrounds/Beams/Beams.jsx", - "content": "/* eslint-disable react/no-unknown-property */\nimport { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\nfunction extendMaterial(BaseMaterial, cfg) {\n const physical = THREE.ShaderLib.physical;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {});\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = hex => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\nconst Beams = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef(null);\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(n, width, height, spacing, heightSegments) {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef(({ material, width, count, height }, ref) => {\n const mesh = useRef(null);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight = ({ position, color }) => {\n const dir = useRef(null);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera;\n if (!cam) return;\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return ;\n};\n\nexport default Beams;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Beams-JS-TW", + "title": "Beams", + "description": "Crossing animated ribbons with customizable properties.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/Backgrounds/Beams/Beams.jsx", + "content": "/* eslint-disable react/no-unknown-property */\nimport { forwardRef, useImperativeHandle, useEffect, useRef, useMemo } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\nfunction extendMaterial(BaseMaterial, cfg) {\n const physical = THREE.ShaderLib.physical;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {});\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] = u !== null && typeof u === 'object' && 'value' in u ? u : { value: u };\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = hex => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\nconst Beams = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef(null);\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(n, width, height, spacing, heightSegments) {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef(({ material, width, count, height }, ref) => {\n const mesh = useRef(null);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight = ({ position, color }) => {\n const dir = useRef(null);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera;\n if (!cam) return;\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return ;\n};\n\nexport default Beams;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "@react-three/fiber@^9.3.0", + "@react-three/drei@^10.7.4" + ] } \ No newline at end of file diff --git a/public/r/Beams-TS-CSS.json b/public/r/Beams-TS-CSS.json index c189ef98..d95ff65b 100644 --- a/public/r/Beams-TS-CSS.json +++ b/public/r/Beams-TS-CSS.json @@ -1,24 +1,25 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Beams-TS-CSS", - "type": "registry:block", - "title": "Beams", - "description": "Crossing animated ribbons with customizable properties.", - "dependencies": [ - "@react-three/drei", - "@react-three/fiber", - "three" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Backgrounds/Beams/Beams.tsx", - "content": "import { forwardRef, useImperativeHandle, useEffect, useRef, useMemo, FC, ReactNode } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\nimport './Beams.css';\n\ntype UniformValue = THREE.IUniform | unknown;\n\ninterface ExtendMaterialConfig {\n header: string;\n vertexHeader?: string;\n fragmentHeader?: string;\n material?: THREE.MeshPhysicalMaterialParameters & { fog?: boolean };\n uniforms?: Record;\n vertex?: Record;\n fragment?: Record;\n}\n\ntype ShaderWithDefines = THREE.ShaderLibShader & {\n defines?: Record;\n};\n\nfunction extendMaterial(\n BaseMaterial: new (params?: THREE.MaterialParameters) => T,\n cfg: ExtendMaterialConfig\n): THREE.ShaderMaterial {\n const physical = THREE.ShaderLib.physical as ShaderWithDefines;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms: Record = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {}) as T & {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n envMap?: THREE.Texture;\n envMapIntensity?: number;\n };\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] =\n u !== null && typeof u === 'object' && 'value' in u\n ? (u as THREE.IUniform)\n : ({ value: u } as THREE.IUniform);\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper: FC<{ children: ReactNode }> = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = (hex: string): [number, number, number] => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\ninterface BeamsProps {\n beamWidth?: number;\n beamHeight?: number;\n beamNumber?: number;\n lightColor?: string;\n speed?: number;\n noiseIntensity?: number;\n scale?: number;\n rotation?: number;\n}\n\nconst Beams: FC = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef>(null!);\n\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(\n n: number,\n width: number,\n height: number,\n spacing: number,\n heightSegments: number\n): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>(({ material, width, count, height }, ref) => {\n const mesh = useRef>(null!);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight: FC<{ position: [number, number, number]; color: string }> = ({ position, color }) => {\n const dir = useRef(null!);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera as THREE.Camera & {\n top: number;\n bottom: number;\n left: number;\n right: number;\n far: number;\n };\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n }, []);\n return ;\n};\n\nexport default Beams;\n", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Backgrounds/Beams/Beams.css", - "content": ".beams-container {\n position: relative;\n width: 100%;\n height: 100%;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Beams-TS-CSS", + "title": "Beams", + "description": "Crossing animated ribbons with customizable properties.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/Backgrounds/Beams/Beams.tsx", + "content": "import { forwardRef, useImperativeHandle, useEffect, useRef, useMemo, FC, ReactNode } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\nimport './Beams.css';\n\ntype UniformValue = THREE.IUniform | unknown;\n\ninterface ExtendMaterialConfig {\n header: string;\n vertexHeader?: string;\n fragmentHeader?: string;\n material?: THREE.MeshPhysicalMaterialParameters & { fog?: boolean };\n uniforms?: Record;\n vertex?: Record;\n fragment?: Record;\n}\n\ntype ShaderWithDefines = THREE.ShaderLibShader & {\n defines?: Record;\n};\n\nfunction extendMaterial(\n BaseMaterial: new (params?: THREE.MaterialParameters) => T,\n cfg: ExtendMaterialConfig\n): THREE.ShaderMaterial {\n const physical = THREE.ShaderLib.physical as ShaderWithDefines;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms: Record = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {}) as T & {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n envMap?: THREE.Texture;\n envMapIntensity?: number;\n };\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] =\n u !== null && typeof u === 'object' && 'value' in u\n ? (u as THREE.IUniform)\n : ({ value: u } as THREE.IUniform);\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper: FC<{ children: ReactNode }> = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = (hex: string): [number, number, number] => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\ninterface BeamsProps {\n beamWidth?: number;\n beamHeight?: number;\n beamNumber?: number;\n lightColor?: string;\n speed?: number;\n noiseIntensity?: number;\n scale?: number;\n rotation?: number;\n}\n\nconst Beams: FC = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef>(null!);\n\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(\n n: number,\n width: number,\n height: number,\n spacing: number,\n heightSegments: number\n): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>(({ material, width, count, height }, ref) => {\n const mesh = useRef>(null!);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight: FC<{ position: [number, number, number]; color: string }> = ({ position, color }) => {\n const dir = useRef(null!);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera as THREE.Camera & {\n top: number;\n bottom: number;\n left: number;\n right: number;\n far: number;\n };\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n }, []);\n return ;\n};\n\nexport default Beams;\n" + }, + { + "type": "registry:item", + "path": "src/ts-default/Backgrounds/Beams/Beams.css", + "content": ".beams-container {\n position: relative;\n width: 100%;\n height: 100%;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "@react-three/fiber@^9.3.0", + "@react-three/drei@^10.7.4" + ] } \ No newline at end of file diff --git a/public/r/Beams-TS-TW.json b/public/r/Beams-TS-TW.json index 13772c8b..1255c112 100644 --- a/public/r/Beams-TS-TW.json +++ b/public/r/Beams-TS-TW.json @@ -1,19 +1,20 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "Beams-TS-TW", - "type": "registry:block", - "title": "Beams", - "description": "Crossing animated ribbons with customizable properties.", - "dependencies": [ - "@react-three/drei", - "@react-three/fiber", - "three" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Backgrounds/Beams/Beams.tsx", - "content": "import { forwardRef, useImperativeHandle, useEffect, useRef, useMemo, FC, ReactNode } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\ntype UniformValue = THREE.IUniform | unknown;\n\ninterface ExtendMaterialConfig {\n header: string;\n vertexHeader?: string;\n fragmentHeader?: string;\n material?: THREE.MeshPhysicalMaterialParameters & { fog?: boolean };\n uniforms?: Record;\n vertex?: Record;\n fragment?: Record;\n}\n\ntype ShaderWithDefines = THREE.ShaderLibShader & {\n defines?: Record;\n};\n\nfunction extendMaterial(\n BaseMaterial: new (params?: THREE.MaterialParameters) => T,\n cfg: ExtendMaterialConfig\n): THREE.ShaderMaterial {\n const physical = THREE.ShaderLib.physical as ShaderWithDefines;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms: Record = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {}) as T & {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n envMap?: THREE.Texture;\n envMapIntensity?: number;\n };\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] =\n u !== null && typeof u === 'object' && 'value' in u\n ? (u as THREE.IUniform)\n : ({ value: u } as THREE.IUniform);\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper: FC<{ children: ReactNode }> = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = (hex: string): [number, number, number] => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\ninterface BeamsProps {\n beamWidth?: number;\n beamHeight?: number;\n beamNumber?: number;\n lightColor?: string;\n speed?: number;\n noiseIntensity?: number;\n scale?: number;\n rotation?: number;\n}\n\nconst Beams: FC = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef>(null!);\n\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(\n n: number,\n width: number,\n height: number,\n spacing: number,\n heightSegments: number\n): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>(({ material, width, count, height }, ref) => {\n const mesh = useRef>(null!);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight: FC<{ position: [number, number, number]; color: string }> = ({ position, color }) => {\n const dir = useRef(null!);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera as THREE.Camera & {\n top: number;\n bottom: number;\n left: number;\n right: number;\n far: number;\n };\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n }, []);\n return ;\n};\n\nexport default Beams;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "Beams-TS-TW", + "title": "Beams", + "description": "Crossing animated ribbons with customizable properties.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/Backgrounds/Beams/Beams.tsx", + "content": "import { forwardRef, useImperativeHandle, useEffect, useRef, useMemo, FC, ReactNode } from 'react';\n\nimport * as THREE from 'three';\n\nimport { Canvas, useFrame } from '@react-three/fiber';\nimport { PerspectiveCamera } from '@react-three/drei';\nimport { degToRad } from 'three/src/math/MathUtils.js';\n\ntype UniformValue = THREE.IUniform | unknown;\n\ninterface ExtendMaterialConfig {\n header: string;\n vertexHeader?: string;\n fragmentHeader?: string;\n material?: THREE.MeshPhysicalMaterialParameters & { fog?: boolean };\n uniforms?: Record;\n vertex?: Record;\n fragment?: Record;\n}\n\ntype ShaderWithDefines = THREE.ShaderLibShader & {\n defines?: Record;\n};\n\nfunction extendMaterial(\n BaseMaterial: new (params?: THREE.MaterialParameters) => T,\n cfg: ExtendMaterialConfig\n): THREE.ShaderMaterial {\n const physical = THREE.ShaderLib.physical as ShaderWithDefines;\n const { vertexShader: baseVert, fragmentShader: baseFrag, uniforms: baseUniforms } = physical;\n const baseDefines = physical.defines ?? {};\n\n const uniforms: Record = THREE.UniformsUtils.clone(baseUniforms);\n\n const defaults = new BaseMaterial(cfg.material || {}) as T & {\n color?: THREE.Color;\n roughness?: number;\n metalness?: number;\n envMap?: THREE.Texture;\n envMapIntensity?: number;\n };\n\n if (defaults.color) uniforms.diffuse.value = defaults.color;\n if ('roughness' in defaults) uniforms.roughness.value = defaults.roughness;\n if ('metalness' in defaults) uniforms.metalness.value = defaults.metalness;\n if ('envMap' in defaults) uniforms.envMap.value = defaults.envMap;\n if ('envMapIntensity' in defaults) uniforms.envMapIntensity.value = defaults.envMapIntensity;\n\n Object.entries(cfg.uniforms ?? {}).forEach(([key, u]) => {\n uniforms[key] =\n u !== null && typeof u === 'object' && 'value' in u\n ? (u as THREE.IUniform)\n : ({ value: u } as THREE.IUniform);\n });\n\n let vert = `${cfg.header}\\n${cfg.vertexHeader ?? ''}\\n${baseVert}`;\n let frag = `${cfg.header}\\n${cfg.fragmentHeader ?? ''}\\n${baseFrag}`;\n\n for (const [inc, code] of Object.entries(cfg.vertex ?? {})) {\n vert = vert.replace(inc, `${inc}\\n${code}`);\n }\n for (const [inc, code] of Object.entries(cfg.fragment ?? {})) {\n frag = frag.replace(inc, `${inc}\\n${code}`);\n }\n\n const mat = new THREE.ShaderMaterial({\n defines: { ...baseDefines },\n uniforms,\n vertexShader: vert,\n fragmentShader: frag,\n lights: true,\n fog: !!cfg.material?.fog\n });\n\n return mat;\n}\n\nconst CanvasWrapper: FC<{ children: ReactNode }> = ({ children }) => (\n \n {children}\n \n);\n\nconst hexToNormalizedRGB = (hex: string): [number, number, number] => {\n const clean = hex.replace('#', '');\n const r = parseInt(clean.substring(0, 2), 16);\n const g = parseInt(clean.substring(2, 4), 16);\n const b = parseInt(clean.substring(4, 6), 16);\n return [r / 255, g / 255, b / 255];\n};\n\nconst noise = `\nfloat random (in vec2 st) {\n return fract(sin(dot(st.xy,\n vec2(12.9898,78.233)))*\n 43758.5453123);\n}\nfloat noise (in vec2 st) {\n vec2 i = floor(st);\n vec2 f = fract(st);\n float a = random(i);\n float b = random(i + vec2(1.0, 0.0));\n float c = random(i + vec2(0.0, 1.0));\n float d = random(i + vec2(1.0, 1.0));\n vec2 u = f * f * (3.0 - 2.0 * f);\n return mix(a, b, u.x) +\n (c - a)* u.y * (1.0 - u.x) +\n (d - b) * u.x * u.y;\n}\nvec4 permute(vec4 x){return mod(((x*34.0)+1.0)*x, 289.0);}\nvec4 taylorInvSqrt(vec4 r){return 1.79284291400159 - 0.85373472095314 * r;}\nvec3 fade(vec3 t) {return t*t*t*(t*(t*6.0-15.0)+10.0);}\nfloat cnoise(vec3 P){\n vec3 Pi0 = floor(P);\n vec3 Pi1 = Pi0 + vec3(1.0);\n Pi0 = mod(Pi0, 289.0);\n Pi1 = mod(Pi1, 289.0);\n vec3 Pf0 = fract(P);\n vec3 Pf1 = Pf0 - vec3(1.0);\n vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n vec4 iy = vec4(Pi0.yy, Pi1.yy);\n vec4 iz0 = Pi0.zzzz;\n vec4 iz1 = Pi1.zzzz;\n vec4 ixy = permute(permute(ix) + iy);\n vec4 ixy0 = permute(ixy + iz0);\n vec4 ixy1 = permute(ixy + iz1);\n vec4 gx0 = ixy0 / 7.0;\n vec4 gy0 = fract(floor(gx0) / 7.0) - 0.5;\n gx0 = fract(gx0);\n vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n vec4 sz0 = step(gz0, vec4(0.0));\n gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n vec4 gx1 = ixy1 / 7.0;\n vec4 gy1 = fract(floor(gx1) / 7.0) - 0.5;\n gx1 = fract(gx1);\n vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n vec4 sz1 = step(gz1, vec4(0.0));\n gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n vec4 norm0 = taylorInvSqrt(vec4(dot(g000,g000),dot(g010,g010),dot(g100,g100),dot(g110,g110)));\n g000 *= norm0.x; g010 *= norm0.y; g100 *= norm0.z; g110 *= norm0.w;\n vec4 norm1 = taylorInvSqrt(vec4(dot(g001,g001),dot(g011,g011),dot(g101,g101),dot(g111,g111)));\n g001 *= norm1.x; g011 *= norm1.y; g101 *= norm1.z; g111 *= norm1.w;\n float n000 = dot(g000, Pf0);\n float n100 = dot(g100, vec3(Pf1.x,Pf0.yz));\n float n010 = dot(g010, vec3(Pf0.x,Pf1.y,Pf0.z));\n float n110 = dot(g110, vec3(Pf1.xy,Pf0.z));\n float n001 = dot(g001, vec3(Pf0.xy,Pf1.z));\n float n101 = dot(g101, vec3(Pf1.x,Pf0.y,Pf1.z));\n float n011 = dot(g011, vec3(Pf0.x,Pf1.yz));\n float n111 = dot(g111, Pf1);\n vec3 fade_xyz = fade(Pf0);\n vec4 n_z = mix(vec4(n000,n100,n010,n110),vec4(n001,n101,n011,n111),fade_xyz.z);\n vec2 n_yz = mix(n_z.xy,n_z.zw,fade_xyz.y);\n float n_xyz = mix(n_yz.x,n_yz.y,fade_xyz.x);\n return 2.2 * n_xyz;\n}\n`;\n\ninterface BeamsProps {\n beamWidth?: number;\n beamHeight?: number;\n beamNumber?: number;\n lightColor?: string;\n speed?: number;\n noiseIntensity?: number;\n scale?: number;\n rotation?: number;\n}\n\nconst Beams: FC = ({\n beamWidth = 2,\n beamHeight = 15,\n beamNumber = 12,\n lightColor = '#ffffff',\n speed = 2,\n noiseIntensity = 1.75,\n scale = 0.2,\n rotation = 0\n}) => {\n const meshRef = useRef>(null!);\n\n const beamMaterial = useMemo(\n () =>\n extendMaterial(THREE.MeshStandardMaterial, {\n header: `\n varying vec3 vEye;\n varying float vNoise;\n varying vec2 vUv;\n varying vec3 vPosition;\n uniform float time;\n uniform float uSpeed;\n uniform float uNoiseIntensity;\n uniform float uScale;\n ${noise}`,\n vertexHeader: `\n float getPos(vec3 pos) {\n vec3 noisePos =\n vec3(pos.x * 0., pos.y - uv.y, pos.z + time * uSpeed * 3.) * uScale;\n return cnoise(noisePos);\n }\n vec3 getCurrentPos(vec3 pos) {\n vec3 newpos = pos;\n newpos.z += getPos(pos);\n return newpos;\n }\n vec3 getNormal(vec3 pos) {\n vec3 curpos = getCurrentPos(pos);\n vec3 nextposX = getCurrentPos(pos + vec3(0.01, 0.0, 0.0));\n vec3 nextposZ = getCurrentPos(pos + vec3(0.0, -0.01, 0.0));\n vec3 tangentX = normalize(nextposX - curpos);\n vec3 tangentZ = normalize(nextposZ - curpos);\n return normalize(cross(tangentZ, tangentX));\n }`,\n fragmentHeader: '',\n vertex: {\n '#include ': `transformed.z += getPos(transformed.xyz);`,\n '#include ': `objectNormal = getNormal(position.xyz);`\n },\n fragment: {\n '#include ': `\n float randomNoise = noise(gl_FragCoord.xy);\n gl_FragColor.rgb -= randomNoise / 15. * uNoiseIntensity;`\n },\n material: { fog: true },\n uniforms: {\n diffuse: new THREE.Color(...hexToNormalizedRGB('#000000')),\n time: { shared: true, mixed: true, linked: true, value: 0 },\n roughness: 0.3,\n metalness: 0.3,\n uSpeed: { shared: true, mixed: true, linked: true, value: speed },\n envMapIntensity: 10,\n uNoiseIntensity: noiseIntensity,\n uScale: scale\n }\n }),\n [speed, noiseIntensity, scale]\n );\n\n return (\n \n \n \n \n \n \n \n \n \n );\n};\n\nfunction createStackedPlanesBufferGeometry(\n n: number,\n width: number,\n height: number,\n spacing: number,\n heightSegments: number\n): THREE.BufferGeometry {\n const geometry = new THREE.BufferGeometry();\n const numVertices = n * (heightSegments + 1) * 2;\n const numFaces = n * heightSegments * 2;\n const positions = new Float32Array(numVertices * 3);\n const indices = new Uint32Array(numFaces * 3);\n const uvs = new Float32Array(numVertices * 2);\n\n let vertexOffset = 0;\n let indexOffset = 0;\n let uvOffset = 0;\n const totalWidth = n * width + (n - 1) * spacing;\n const xOffsetBase = -totalWidth / 2;\n\n for (let i = 0; i < n; i++) {\n const xOffset = xOffsetBase + i * (width + spacing);\n const uvXOffset = Math.random() * 300;\n const uvYOffset = Math.random() * 300;\n\n for (let j = 0; j <= heightSegments; j++) {\n const y = height * (j / heightSegments - 0.5);\n const v0 = [xOffset, y, 0];\n const v1 = [xOffset + width, y, 0];\n positions.set([...v0, ...v1], vertexOffset * 3);\n\n const uvY = j / heightSegments;\n uvs.set([uvXOffset, uvY + uvYOffset, uvXOffset + 1, uvY + uvYOffset], uvOffset);\n\n if (j < heightSegments) {\n const a = vertexOffset,\n b = vertexOffset + 1,\n c = vertexOffset + 2,\n d = vertexOffset + 3;\n indices.set([a, b, c, c, b, d], indexOffset);\n indexOffset += 6;\n }\n vertexOffset += 2;\n uvOffset += 4;\n }\n }\n\n geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));\n geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));\n geometry.setIndex(new THREE.BufferAttribute(indices, 1));\n geometry.computeVertexNormals();\n return geometry;\n}\n\nconst MergedPlanes = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>(({ material, width, count, height }, ref) => {\n const mesh = useRef>(null!);\n useImperativeHandle(ref, () => mesh.current);\n const geometry = useMemo(\n () => createStackedPlanesBufferGeometry(count, width, height, 0, 100),\n [count, width, height]\n );\n useFrame((_, delta) => {\n mesh.current.material.uniforms.time.value += 0.1 * delta;\n });\n return ;\n});\nMergedPlanes.displayName = 'MergedPlanes';\n\nconst PlaneNoise = forwardRef<\n THREE.Mesh,\n {\n material: THREE.ShaderMaterial;\n width: number;\n count: number;\n height: number;\n }\n>((props, ref) => (\n \n));\nPlaneNoise.displayName = 'PlaneNoise';\n\nconst DirLight: FC<{ position: [number, number, number]; color: string }> = ({ position, color }) => {\n const dir = useRef(null!);\n useEffect(() => {\n if (!dir.current) return;\n const cam = dir.current.shadow.camera as THREE.Camera & {\n top: number;\n bottom: number;\n left: number;\n right: number;\n far: number;\n };\n cam.top = 24;\n cam.bottom = -24;\n cam.left = -24;\n cam.right = 24;\n cam.far = 64;\n dir.current.shadow.bias = -0.004;\n }, []);\n return ;\n};\n\nexport default Beams;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "three@^0.167.1", + "@react-three/fiber@^9.3.0", + "@react-three/drei@^10.7.4" + ] } \ No newline at end of file diff --git a/public/r/BlobCursor-JS-CSS.json b/public/r/BlobCursor-JS-CSS.json index d7416ade..0256a221 100644 --- a/public/r/BlobCursor-JS-CSS.json +++ b/public/r/BlobCursor-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlobCursor-JS-CSS", - "type": "registry:block", - "title": "BlobCursor", - "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Animations/BlobCursor/BlobCursor.jsx", - "content": "'use client';\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport './BlobCursor.css';\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}) {\n const containerRef = useRef(null);\n const blobsRef = useRef([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n e => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n
\n {Array.from({ length: trailCount }).map((_, i) => (\n (blobsRef.current[i] = el)}\n className=\"blob\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0%',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n
\n ))}\n
\n
\n );\n}\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Animations/BlobCursor/BlobCursor.css", - "content": ".blob-container {\n position: relative;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.blob-main {\n pointer-events: none;\n position: absolute;\n width: 100%;\n height: 100%;\n overflow: hidden;\n background: transparent;\n user-select: none;\n cursor: default;\n}\n\n.blob {\n position: absolute;\n will-change: transform;\n transform: translate(-50%, -50%);\n}\n\n.inner-dot {\n position: absolute;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlobCursor-JS-CSS", + "title": "BlobCursor", + "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Animations/BlobCursor/BlobCursor.jsx", + "content": "'use client';\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport './BlobCursor.css';\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}) {\n const containerRef = useRef(null);\n const blobsRef = useRef([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n e => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n
\n {Array.from({ length: trailCount }).map((_, i) => (\n (blobsRef.current[i] = el)}\n className=\"blob\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0%',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n
\n ))}\n
\n
\n );\n}\n" + }, + { + "type": "registry:item", + "path": "src/content/Animations/BlobCursor/BlobCursor.css", + "content": ".blob-container {\n position: relative;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.blob-main {\n pointer-events: none;\n position: absolute;\n width: 100%;\n height: 100%;\n overflow: hidden;\n background: transparent;\n user-select: none;\n cursor: default;\n}\n\n.blob {\n position: absolute;\n will-change: transform;\n transform: translate(-50%, -50%);\n}\n\n.inner-dot {\n position: absolute;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BlobCursor-JS-TW.json b/public/r/BlobCursor-JS-TW.json index 3e9e387c..18fb37a7 100644 --- a/public/r/BlobCursor-JS-TW.json +++ b/public/r/BlobCursor-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlobCursor-JS-TW", - "type": "registry:block", - "title": "BlobCursor", - "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Animations/BlobCursor/BlobCursor.jsx", - "content": "'use client';\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}) {\n const containerRef = useRef(null);\n const blobsRef = useRef([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n e => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n \n {Array.from({ length: trailCount }).map((_, i) => (\n (blobsRef.current[i] = el)}\n className=\"absolute will-change-transform transform -translate-x-1/2 -translate-y-1/2\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n
\n ))}\n
\n
\n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlobCursor-JS-TW", + "title": "BlobCursor", + "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/Animations/BlobCursor/BlobCursor.jsx", + "content": "'use client';\n\nimport { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}) {\n const containerRef = useRef(null);\n const blobsRef = useRef([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n e => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n \n {Array.from({ length: trailCount }).map((_, i) => (\n (blobsRef.current[i] = el)}\n className=\"absolute will-change-transform transform -translate-x-1/2 -translate-y-1/2\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n
\n ))}\n
\n
\n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BlobCursor-TS-CSS.json b/public/r/BlobCursor-TS-CSS.json index c00d3e24..aff5a314 100644 --- a/public/r/BlobCursor-TS-CSS.json +++ b/public/r/BlobCursor-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlobCursor-TS-CSS", - "type": "registry:block", - "title": "BlobCursor", - "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Animations/BlobCursor/BlobCursor.tsx", - "content": "'use client';\n\nimport React, { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport './BlobCursor.css';\n\nexport interface BlobCursorProps {\n blobType?: 'circle' | 'square';\n fillColor?: string;\n trailCount?: number;\n sizes?: number[];\n innerSizes?: number[];\n innerColor?: string;\n opacities?: number[];\n shadowColor?: string;\n shadowBlur?: number;\n shadowOffsetX?: number;\n shadowOffsetY?: number;\n filterId?: string;\n filterStdDeviation?: number;\n filterColorMatrixValues?: string;\n useFilter?: boolean;\n fastDuration?: number;\n slowDuration?: number;\n fastEase?: string;\n slowEase?: string;\n zIndex?: number;\n}\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}: BlobCursorProps) {\n const containerRef = useRef(null);\n const blobsRef = useRef<(HTMLDivElement | null)[]>([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n (e: React.MouseEvent | React.TouchEvent) => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n
\n {Array.from({ length: trailCount }).map((_, i) => (\n {\n blobsRef.current[i] = el;\n }}\n className=\"blob\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0%',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n
\n ))}\n
\n
\n );\n}\n", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Animations/BlobCursor/BlobCursor.css", - "content": ".blob-container {\n position: relative;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.blob-main {\n pointer-events: none;\n position: absolute;\n width: 100%;\n height: 100%;\n overflow: hidden;\n background: transparent;\n user-select: none;\n cursor: default;\n}\n\n.blob {\n position: absolute;\n will-change: transform;\n transform: translate(-50%, -50%);\n}\n\n.inner-dot {\n position: absolute;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlobCursor-TS-CSS", + "title": "BlobCursor", + "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/Animations/BlobCursor/BlobCursor.tsx", + "content": "'use client';\n\nimport React, { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport './BlobCursor.css';\n\nexport interface BlobCursorProps {\n blobType?: 'circle' | 'square';\n fillColor?: string;\n trailCount?: number;\n sizes?: number[];\n innerSizes?: number[];\n innerColor?: string;\n opacities?: number[];\n shadowColor?: string;\n shadowBlur?: number;\n shadowOffsetX?: number;\n shadowOffsetY?: number;\n filterId?: string;\n filterStdDeviation?: number;\n filterColorMatrixValues?: string;\n useFilter?: boolean;\n fastDuration?: number;\n slowDuration?: number;\n fastEase?: string;\n slowEase?: string;\n zIndex?: number;\n}\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}: BlobCursorProps) {\n const containerRef = useRef(null);\n const blobsRef = useRef<(HTMLDivElement | null)[]>([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n (e: React.MouseEvent | React.TouchEvent) => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n
\n {Array.from({ length: trailCount }).map((_, i) => (\n {\n blobsRef.current[i] = el;\n }}\n className=\"blob\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0%',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n
\n ))}\n
\n
\n );\n}\n" + }, + { + "type": "registry:item", + "path": "src/ts-default/Animations/BlobCursor/BlobCursor.css", + "content": ".blob-container {\n position: relative;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n}\n\n.blob-main {\n pointer-events: none;\n position: absolute;\n width: 100%;\n height: 100%;\n overflow: hidden;\n background: transparent;\n user-select: none;\n cursor: default;\n}\n\n.blob {\n position: absolute;\n will-change: transform;\n transform: translate(-50%, -50%);\n}\n\n.inner-dot {\n position: absolute;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BlobCursor-TS-TW.json b/public/r/BlobCursor-TS-TW.json index cf0c1ab8..81e9ff34 100644 --- a/public/r/BlobCursor-TS-TW.json +++ b/public/r/BlobCursor-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlobCursor-TS-TW", - "type": "registry:block", - "title": "BlobCursor", - "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Animations/BlobCursor/BlobCursor.tsx", - "content": "'use client';\n\nimport React, { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\n\nexport interface BlobCursorProps {\n blobType?: 'circle' | 'square';\n fillColor?: string;\n trailCount?: number;\n sizes?: number[];\n innerSizes?: number[];\n innerColor?: string;\n opacities?: number[];\n shadowColor?: string;\n shadowBlur?: number;\n shadowOffsetX?: number;\n shadowOffsetY?: number;\n filterId?: string;\n filterStdDeviation?: number;\n filterColorMatrixValues?: string;\n useFilter?: boolean;\n fastDuration?: number;\n slowDuration?: number;\n fastEase?: string;\n slowEase?: string;\n zIndex?: number;\n}\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}: BlobCursorProps) {\n const containerRef = useRef(null);\n const blobsRef = useRef<(HTMLDivElement | null)[]>([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n (e: React.MouseEvent | React.TouchEvent) => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n \n {Array.from({ length: trailCount }).map((_, i) => (\n {\n blobsRef.current[i] = el;\n }}\n className=\"absolute will-change-transform transform -translate-x-1/2 -translate-y-1/2\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n
\n ))}\n
\n \n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlobCursor-TS-TW", + "title": "BlobCursor", + "description": "Organic blob cursor that smoothly follows the pointer with inertia and elastic morphing.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/Animations/BlobCursor/BlobCursor.tsx", + "content": "'use client';\n\nimport React, { useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\n\nexport interface BlobCursorProps {\n blobType?: 'circle' | 'square';\n fillColor?: string;\n trailCount?: number;\n sizes?: number[];\n innerSizes?: number[];\n innerColor?: string;\n opacities?: number[];\n shadowColor?: string;\n shadowBlur?: number;\n shadowOffsetX?: number;\n shadowOffsetY?: number;\n filterId?: string;\n filterStdDeviation?: number;\n filterColorMatrixValues?: string;\n useFilter?: boolean;\n fastDuration?: number;\n slowDuration?: number;\n fastEase?: string;\n slowEase?: string;\n zIndex?: number;\n}\n\nexport default function BlobCursor({\n blobType = 'circle',\n fillColor = '#5227FF',\n trailCount = 3,\n sizes = [60, 125, 75],\n innerSizes = [20, 35, 25],\n innerColor = 'rgba(255,255,255,0.8)',\n opacities = [0.6, 0.6, 0.6],\n shadowColor = 'rgba(0,0,0,0.75)',\n shadowBlur = 5,\n shadowOffsetX = 10,\n shadowOffsetY = 10,\n filterId = 'blob',\n filterStdDeviation = 30,\n filterColorMatrixValues = '1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 35 -10',\n useFilter = true,\n fastDuration = 0.1,\n slowDuration = 0.5,\n fastEase = 'power3.out',\n slowEase = 'power1.out',\n zIndex = 100\n}: BlobCursorProps) {\n const containerRef = useRef(null);\n const blobsRef = useRef<(HTMLDivElement | null)[]>([]);\n\n const updateOffset = useCallback(() => {\n if (!containerRef.current) return { left: 0, top: 0 };\n const rect = containerRef.current.getBoundingClientRect();\n return { left: rect.left, top: rect.top };\n }, []);\n\n const handleMove = useCallback(\n (e: React.MouseEvent | React.TouchEvent) => {\n const { left, top } = updateOffset();\n const x = 'clientX' in e ? e.clientX : e.touches[0].clientX;\n const y = 'clientY' in e ? e.clientY : e.touches[0].clientY;\n\n blobsRef.current.forEach((el, i) => {\n if (!el) return;\n const isLead = i === 0;\n gsap.to(el, {\n x: x - left,\n y: y - top,\n duration: isLead ? fastDuration : slowDuration,\n ease: isLead ? fastEase : slowEase\n });\n });\n },\n [updateOffset, fastDuration, slowDuration, fastEase, slowEase]\n );\n\n useEffect(() => {\n const onResize = () => updateOffset();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }, [updateOffset]);\n\n return (\n \n {useFilter && (\n \n \n \n \n \n \n )}\n\n \n {Array.from({ length: trailCount }).map((_, i) => (\n {\n blobsRef.current[i] = el;\n }}\n className=\"absolute will-change-transform transform -translate-x-1/2 -translate-y-1/2\"\n style={{\n width: sizes[i],\n height: sizes[i],\n borderRadius: blobType === 'circle' ? '50%' : '0',\n backgroundColor: fillColor,\n opacity: opacities[i],\n boxShadow: `${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px 0 ${shadowColor}`\n }}\n >\n \n \n ))}\n \n \n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BlurText-JS-CSS.json b/public/r/BlurText-JS-CSS.json index 26c75098..773340a6 100644 --- a/public/r/BlurText-JS-CSS.json +++ b/public/r/BlurText-JS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlurText-JS-CSS", - "type": "registry:block", - "title": "BlurText", - "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/default/src/content/TextAnimations/BlurText/BlurText.jsx", - "content": "import { motion } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\nconst buildKeyframes = (from, steps) => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = t => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000\n };\n spanTransition.ease = easing;\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlurText-JS-CSS", + "title": "BlurText", + "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/TextAnimations/BlurText/BlurText.jsx", + "content": "import { motion } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\nconst buildKeyframes = (from, steps) => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = t => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000\n };\n spanTransition.ease = easing;\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/BlurText-JS-TW.json b/public/r/BlurText-JS-TW.json index 82eecaea..d3812b1b 100644 --- a/public/r/BlurText-JS-TW.json +++ b/public/r/BlurText-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlurText-JS-TW", - "type": "registry:block", - "title": "BlurText", - "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/TextAnimations/BlurText/BlurText.jsx", - "content": "import { motion } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\nconst buildKeyframes = (from, steps) => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = t => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000\n };\n spanTransition.ease = easing;\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlurText-JS-TW", + "title": "BlurText", + "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/TextAnimations/BlurText/BlurText.jsx", + "content": "import { motion } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\nconst buildKeyframes = (from, steps) => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = t => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000\n };\n spanTransition.ease = easing;\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/BlurText-TS-CSS.json b/public/r/BlurText-TS-CSS.json index 4a763fd9..b7175e62 100644 --- a/public/r/BlurText-TS-CSS.json +++ b/public/r/BlurText-TS-CSS.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlurText-TS-CSS", - "type": "registry:block", - "title": "BlurText", - "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/TextAnimations/BlurText/BlurText.tsx", - "content": "import { motion, Transition } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\ntype BlurTextProps = {\n text?: string;\n delay?: number;\n className?: string;\n animateBy?: 'words' | 'letters';\n direction?: 'top' | 'bottom';\n threshold?: number;\n rootMargin?: string;\n animationFrom?: Record;\n animationTo?: Array>;\n easing?: (t: number) => number;\n onAnimationComplete?: () => void;\n stepDuration?: number;\n};\n\nconst buildKeyframes = (\n from: Record,\n steps: Array>\n): Record> => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes: Record> = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText: React.FC = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = (t: number) => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current as Element);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition: Transition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000,\n ease: easing\n };\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlurText-TS-CSS", + "title": "BlurText", + "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/TextAnimations/BlurText/BlurText.tsx", + "content": "import { motion, Transition } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\ntype BlurTextProps = {\n text?: string;\n delay?: number;\n className?: string;\n animateBy?: 'words' | 'letters';\n direction?: 'top' | 'bottom';\n threshold?: number;\n rootMargin?: string;\n animationFrom?: Record;\n animationTo?: Array>;\n easing?: (t: number) => number;\n onAnimationComplete?: () => void;\n stepDuration?: number;\n};\n\nconst buildKeyframes = (\n from: Record,\n steps: Array>\n): Record> => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes: Record> = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText: React.FC = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = (t: number) => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current as Element);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition: Transition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000,\n ease: easing\n };\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/BlurText-TS-TW.json b/public/r/BlurText-TS-TW.json index f1e64bab..218f2f3b 100644 --- a/public/r/BlurText-TS-TW.json +++ b/public/r/BlurText-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BlurText-TS-TW", - "type": "registry:block", - "title": "BlurText", - "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", - "dependencies": [ - "motion" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/TextAnimations/BlurText/BlurText.tsx", - "content": "import { motion, Transition, Easing } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\ntype BlurTextProps = {\n text?: string;\n delay?: number;\n className?: string;\n animateBy?: 'words' | 'letters';\n direction?: 'top' | 'bottom';\n threshold?: number;\n rootMargin?: string;\n animationFrom?: Record;\n animationTo?: Array>;\n easing?: Easing | Easing[];\n onAnimationComplete?: () => void;\n stepDuration?: number;\n};\n\nconst buildKeyframes = (\n from: Record,\n steps: Array>\n): Record> => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes: Record> = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText: React.FC = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = (t: number) => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current as Element);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition: Transition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000,\n ease: easing\n };\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BlurText-TS-TW", + "title": "BlurText", + "description": "Text starts blurred then crisply resolves for a soft-focus reveal effect.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/TextAnimations/BlurText/BlurText.tsx", + "content": "import { motion, Transition, Easing } from 'motion/react';\nimport { useEffect, useRef, useState, useMemo } from 'react';\n\ntype BlurTextProps = {\n text?: string;\n delay?: number;\n className?: string;\n animateBy?: 'words' | 'letters';\n direction?: 'top' | 'bottom';\n threshold?: number;\n rootMargin?: string;\n animationFrom?: Record;\n animationTo?: Array>;\n easing?: Easing | Easing[];\n onAnimationComplete?: () => void;\n stepDuration?: number;\n};\n\nconst buildKeyframes = (\n from: Record,\n steps: Array>\n): Record> => {\n const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]);\n\n const keyframes: Record> = {};\n keys.forEach(k => {\n keyframes[k] = [from[k], ...steps.map(s => s[k])];\n });\n return keyframes;\n};\n\nconst BlurText: React.FC = ({\n text = '',\n delay = 200,\n className = '',\n animateBy = 'words',\n direction = 'top',\n threshold = 0.1,\n rootMargin = '0px',\n animationFrom,\n animationTo,\n easing = (t: number) => t,\n onAnimationComplete,\n stepDuration = 0.35\n}) => {\n const elements = animateBy === 'words' ? text.split(' ') : text.split('');\n const [inView, setInView] = useState(false);\n const ref = useRef(null);\n\n useEffect(() => {\n if (!ref.current) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setInView(true);\n observer.unobserve(ref.current as Element);\n }\n },\n { threshold, rootMargin }\n );\n observer.observe(ref.current);\n return () => observer.disconnect();\n }, [threshold, rootMargin]);\n\n const defaultFrom = useMemo(\n () =>\n direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 },\n [direction]\n );\n\n const defaultTo = useMemo(\n () => [\n {\n filter: 'blur(5px)',\n opacity: 0.5,\n y: direction === 'top' ? 5 : -5\n },\n { filter: 'blur(0px)', opacity: 1, y: 0 }\n ],\n [direction]\n );\n\n const fromSnapshot = animationFrom ?? defaultFrom;\n const toSnapshots = animationTo ?? defaultTo;\n\n const stepCount = toSnapshots.length + 1;\n const totalDuration = stepDuration * (stepCount - 1);\n const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1)));\n\n return (\n

\n {elements.map((segment, index) => {\n const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots);\n\n const spanTransition: Transition = {\n duration: totalDuration,\n times,\n delay: (index * delay) / 1000,\n ease: easing\n };\n\n return (\n \n {segment === ' ' ? '\\u00A0' : segment}\n {animateBy === 'words' && index < elements.length - 1 && '\\u00A0'}\n \n );\n })}\n

\n );\n};\n\nexport default BlurText;\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "motion@^12.23.12" + ] } \ No newline at end of file diff --git a/public/r/BounceCards-JS-CSS.json b/public/r/BounceCards-JS-CSS.json index cb81b291..9fddc0d3 100644 --- a/public/r/BounceCards-JS-CSS.json +++ b/public/r/BounceCards-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BounceCards-JS-CSS", - "type": "registry:block", - "title": "BounceCards", - "description": "Cards bounce that bounce in on mount.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Components/BounceCards/BounceCards.jsx", - "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport './BounceCards.css';\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = true\n}) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationStagger, easeType, animationDelay]);\n\n const getNoRotationTransform = transformStr => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform, offsetX) => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = hoveredIdx => {\n if (!enableHover) return;\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotationTransform = getNoRotationTransform(baseTransform);\n gsap.to(`.card-${i}`, {\n transform: noRotationTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(`.card-${i}`, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(`.card-${i}`, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Components/BounceCards/BounceCards.css", - "content": ".bounceCardsContainer {\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 400px;\n height: 400px;\n}\n\n.card {\n position: absolute;\n width: 200px;\n aspect-ratio: 1;\n border: 5px solid #fff;\n border-radius: 25px;\n overflow: hidden;\n box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);\n}\n\n.card .image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BounceCards-JS-CSS", + "title": "BounceCards", + "description": "Cards bounce that bounce in on mount.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Components/BounceCards/BounceCards.jsx", + "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport './BounceCards.css';\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = true\n}) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationStagger, easeType, animationDelay]);\n\n const getNoRotationTransform = transformStr => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform, offsetX) => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = hoveredIdx => {\n if (!enableHover) return;\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotationTransform = getNoRotationTransform(baseTransform);\n gsap.to(`.card-${i}`, {\n transform: noRotationTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(`.card-${i}`, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(`.card-${i}`, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n" + }, + { + "type": "registry:item", + "path": "src/content/Components/BounceCards/BounceCards.css", + "content": ".bounceCardsContainer {\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 400px;\n height: 400px;\n}\n\n.card {\n position: absolute;\n width: 200px;\n aspect-ratio: 1;\n border: 5px solid #fff;\n border-radius: 25px;\n overflow: hidden;\n box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);\n}\n\n.card .image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BounceCards-JS-TW.json b/public/r/BounceCards-JS-TW.json index b7e4eefe..2b05a83c 100644 --- a/public/r/BounceCards-JS-TW.json +++ b/public/r/BounceCards-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BounceCards-JS-TW", - "type": "registry:block", - "title": "BounceCards", - "description": "Cards bounce that bounce in on mount.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Components/BounceCards/BounceCards.jsx", - "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = false\n}) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationDelay, animationStagger, easeType]);\n\n const getNoRotationTransform = transformStr => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform, offsetX) => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = hoveredIdx => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotation = getNoRotationTransform(baseTransform);\n gsap.to(selector, {\n transform: noRotation,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(selector, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(selector, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BounceCards-JS-TW", + "title": "BounceCards", + "description": "Cards bounce that bounce in on mount.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/Components/BounceCards/BounceCards.jsx", + "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = false\n}) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationDelay, animationStagger, easeType]);\n\n const getNoRotationTransform = transformStr => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform, offsetX) => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = hoveredIdx => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotation = getNoRotationTransform(baseTransform);\n gsap.to(selector, {\n transform: noRotation,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(selector, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(selector, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BounceCards-TS-CSS.json b/public/r/BounceCards-TS-CSS.json index c281bb47..5883209c 100644 --- a/public/r/BounceCards-TS-CSS.json +++ b/public/r/BounceCards-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BounceCards-TS-CSS", - "type": "registry:block", - "title": "BounceCards", - "description": "Cards bounce that bounce in on mount.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Components/BounceCards/BounceCards.tsx", - "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport './BounceCards.css';\n\ninterface BounceCardsProps {\n className?: string;\n images?: string[];\n containerWidth?: number;\n containerHeight?: number;\n animationDelay?: number;\n animationStagger?: number;\n easeType?: string;\n transformStyles?: string[];\n enableHover?: boolean;\n}\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = false\n}: BounceCardsProps) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationStagger, easeType, animationDelay]);\n\n const getNoRotationTransform = (transformStr: string): string => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform: string, offsetX: number): string => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = (hoveredIdx: number) => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotation = getNoRotationTransform(baseTransform);\n gsap.to(`.card-${i}`, {\n transform: noRotation,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(`.card-${i}`, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(`.card-${i}`, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Components/BounceCards/BounceCards.css", - "content": ".bounceCardsContainer {\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 400px;\n height: 400px;\n}\n\n.card {\n position: absolute;\n width: 200px;\n aspect-ratio: 1;\n border: 5px solid #fff;\n border-radius: 25px;\n overflow: hidden;\n box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);\n}\n\n.card .image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BounceCards-TS-CSS", + "title": "BounceCards", + "description": "Cards bounce that bounce in on mount.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/Components/BounceCards/BounceCards.tsx", + "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\nimport './BounceCards.css';\n\ninterface BounceCardsProps {\n className?: string;\n images?: string[];\n containerWidth?: number;\n containerHeight?: number;\n animationDelay?: number;\n animationStagger?: number;\n easeType?: string;\n transformStyles?: string[];\n enableHover?: boolean;\n}\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = false\n}: BounceCardsProps) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationStagger, easeType, animationDelay]);\n\n const getNoRotationTransform = (transformStr: string): string => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform: string, offsetX: number): string => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = (hoveredIdx: number) => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotation = getNoRotationTransform(baseTransform);\n gsap.to(`.card-${i}`, {\n transform: noRotation,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(`.card-${i}`, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n gsap.killTweensOf(`.card-${i}`);\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(`.card-${i}`, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n" + }, + { + "type": "registry:item", + "path": "src/ts-default/Components/BounceCards/BounceCards.css", + "content": ".bounceCardsContainer {\n position: relative;\n display: flex;\n justify-content: center;\n align-items: center;\n width: 400px;\n height: 400px;\n}\n\n.card {\n position: absolute;\n width: 200px;\n aspect-ratio: 1;\n border: 5px solid #fff;\n border-radius: 25px;\n overflow: hidden;\n box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);\n}\n\n.card .image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BounceCards-TS-TW.json b/public/r/BounceCards-TS-TW.json index 713d13dd..5a354d99 100644 --- a/public/r/BounceCards-TS-TW.json +++ b/public/r/BounceCards-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BounceCards-TS-TW", - "type": "registry:block", - "title": "BounceCards", - "description": "Cards bounce that bounce in on mount.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Components/BounceCards/BounceCards.tsx", - "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\n\ninterface BounceCardsProps {\n className?: string;\n images?: string[];\n containerWidth?: number;\n containerHeight?: number;\n animationDelay?: number;\n animationStagger?: number;\n easeType?: string;\n transformStyles?: string[];\n enableHover?: boolean;\n}\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = false\n}: BounceCardsProps) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationDelay, animationStagger, easeType]);\n\n const getNoRotationTransform = (transformStr: string): string => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform: string, offsetX: number): string => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = (hoveredIdx: number) => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotation = getNoRotationTransform(baseTransform);\n gsap.to(selector, {\n transform: noRotation,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(selector, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(selector, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BounceCards-TS-TW", + "title": "BounceCards", + "description": "Cards bounce that bounce in on mount.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/Components/BounceCards/BounceCards.tsx", + "content": "import { useEffect } from 'react';\nimport { gsap } from 'gsap';\n\ninterface BounceCardsProps {\n className?: string;\n images?: string[];\n containerWidth?: number;\n containerHeight?: number;\n animationDelay?: number;\n animationStagger?: number;\n easeType?: string;\n transformStyles?: string[];\n enableHover?: boolean;\n}\n\nexport default function BounceCards({\n className = '',\n images = [],\n containerWidth = 400,\n containerHeight = 400,\n animationDelay = 0.5,\n animationStagger = 0.06,\n easeType = 'elastic.out(1, 0.8)',\n transformStyles = [\n 'rotate(10deg) translate(-170px)',\n 'rotate(5deg) translate(-85px)',\n 'rotate(-3deg)',\n 'rotate(-10deg) translate(85px)',\n 'rotate(2deg) translate(170px)'\n ],\n enableHover = false\n}: BounceCardsProps) {\n useEffect(() => {\n gsap.fromTo(\n '.card',\n { scale: 0 },\n {\n scale: 1,\n stagger: animationStagger,\n ease: easeType,\n delay: animationDelay\n }\n );\n }, [animationDelay, animationStagger, easeType]);\n\n const getNoRotationTransform = (transformStr: string): string => {\n const hasRotate = /rotate\\([\\s\\S]*?\\)/.test(transformStr);\n if (hasRotate) {\n return transformStr.replace(/rotate\\([\\s\\S]*?\\)/, 'rotate(0deg)');\n } else if (transformStr === 'none') {\n return 'rotate(0deg)';\n } else {\n return `${transformStr} rotate(0deg)`;\n }\n };\n\n const getPushedTransform = (baseTransform: string, offsetX: number): string => {\n const translateRegex = /translate\\(([-0-9.]+)px\\)/;\n const match = baseTransform.match(translateRegex);\n if (match) {\n const currentX = parseFloat(match[1]);\n const newX = currentX + offsetX;\n return baseTransform.replace(translateRegex, `translate(${newX}px)`);\n } else {\n return baseTransform === 'none' ? `translate(${offsetX}px)` : `${baseTransform} translate(${offsetX}px)`;\n }\n };\n\n const pushSiblings = (hoveredIdx: number) => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n\n if (i === hoveredIdx) {\n const noRotation = getNoRotationTransform(baseTransform);\n gsap.to(selector, {\n transform: noRotation,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n } else {\n const offsetX = i < hoveredIdx ? -160 : 160;\n const pushedTransform = getPushedTransform(baseTransform, offsetX);\n\n const distance = Math.abs(hoveredIdx - i);\n const delay = distance * 0.05;\n\n gsap.to(selector, {\n transform: pushedTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n delay,\n overwrite: 'auto'\n });\n }\n });\n };\n\n const resetSiblings = () => {\n if (!enableHover) return;\n\n images.forEach((_, i) => {\n const selector = `.card-${i}`;\n gsap.killTweensOf(selector);\n\n const baseTransform = transformStyles[i] || 'none';\n gsap.to(selector, {\n transform: baseTransform,\n duration: 0.4,\n ease: 'back.out(1.4)',\n overwrite: 'auto'\n });\n });\n };\n\n return (\n \n {images.map((src, idx) => (\n pushSiblings(idx)}\n onMouseLeave={resetSiblings}\n >\n {`card-${idx}`}\n \n ))}\n \n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BubbleMenu-JS-CSS.json b/public/r/BubbleMenu-JS-CSS.json index 0f8f6d1b..13bd65e7 100644 --- a/public/r/BubbleMenu-JS-CSS.json +++ b/public/r/BubbleMenu-JS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BubbleMenu-JS-CSS", - "type": "registry:block", - "title": "BubbleMenu", - "description": "Floating circular expanding menu with staggered item reveal.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Components/BubbleMenu/BubbleMenu.jsx", - "content": "import { useState, useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\n\nimport './BubbleMenu.css';\n\nconst DEFAULT_ITEMS = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n const containerClassName = ['bubble-menu', useFixedPosition ? 'fixed' : 'absolute', className]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n `-=${animationDuration * 0.9}`\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n \n {showOverlay && (\n \n
    \n {menuItems.map((item, idx) => (\n
  • \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n
  • \n ))}\n
\n \n )}\n \n );\n}\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Components/BubbleMenu/BubbleMenu.css", - "content": ".bubble-menu {\n left: 0;\n right: 0;\n top: 2em;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 0 2em;\n pointer-events: none;\n z-index: 99;\n}\n\n.bubble-menu.fixed {\n position: fixed;\n}\n\n.bubble-menu.absolute {\n position: absolute;\n}\n\n.bubble-menu .bubble {\n --bubble-size: 48px;\n width: var(--bubble-size);\n height: var(--bubble-size);\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n pointer-events: auto;\n}\n\n.bubble-menu .logo-bubble,\n.bubble-menu .toggle-bubble {\n will-change: transform;\n}\n\n.bubble-menu .logo-bubble {\n width: auto;\n min-height: var(--bubble-size);\n height: var(--bubble-size);\n padding: 0 16px;\n border-radius: calc(var(--bubble-size) / 2);\n gap: 8px;\n}\n\n.bubble-menu .toggle-bubble {\n width: var(--bubble-size);\n height: var(--bubble-size);\n}\n\n.bubble-menu .bubble-logo {\n max-height: 60%;\n max-width: 100%;\n object-fit: contain;\n display: block;\n}\n\n.bubble-menu .logo-content {\n --logo-max-height: 60%;\n --logo-max-width: 100%;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 120px;\n height: 100%;\n}\n\n.bubble-menu .logo-content > .bubble-logo,\n.bubble-menu .logo-content > img,\n.bubble-menu .logo-content > svg {\n max-height: var(--logo-max-height);\n max-width: var(--logo-max-width);\n}\n\n.bubble-menu .menu-btn {\n border: none;\n background: #fff;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 0;\n}\n\n.bubble-menu .menu-line {\n width: 26px;\n height: 2px;\n background: #111;\n border-radius: 2px;\n display: block;\n margin: 0 auto;\n transition:\n transform 0.3s ease,\n opacity 0.3s ease;\n transform-origin: center;\n}\n\n.bubble-menu .menu-line + .menu-line {\n margin-top: 6px;\n}\n\n.bubble-menu .menu-btn.open .menu-line:first-child {\n transform: translateY(4px) rotate(45deg);\n}\n\n.bubble-menu .menu-btn.open .menu-line:last-child {\n transform: translateY(-4px) rotate(-45deg);\n}\n\n@media (min-width: 768px) {\n .bubble-menu .bubble {\n --bubble-size: 56px;\n }\n\n .bubble-menu .logo-bubble {\n padding: 0 16px;\n }\n}\n\n.bubble-menu-items {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n z-index: 98;\n}\n\n.bubble-menu-items.fixed {\n position: fixed;\n}\n\n.bubble-menu-items.absolute {\n position: absolute;\n}\n\n.bubble-menu-items .pill-list {\n list-style: none;\n margin: 0;\n padding: 0 24px;\n display: flex;\n flex-wrap: wrap;\n gap: 0;\n row-gap: 4px;\n width: 100%;\n max-width: 1600px;\n margin-left: auto;\n margin-right: auto;\n pointer-events: auto;\n justify-content: stretch;\n}\n\n.bubble-menu-items .pill-list .pill-spacer {\n width: 100%;\n height: 0;\n pointer-events: none;\n}\n\n.bubble-menu-items .pill-list .pill-col {\n display: flex;\n justify-content: center;\n align-items: stretch;\n flex: 0 0 calc(100% / 3);\n box-sizing: border-box;\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):nth-last-child(2) {\n margin-left: calc(100% / 6);\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):last-child {\n margin-left: calc(100% / 3);\n}\n\n.bubble-menu-items .pill-link {\n --pill-bg: #ffffff;\n --pill-color: #111;\n --pill-border: rgba(0, 0, 0, 0.12);\n --item-rot: 0deg;\n --pill-min-h: 160px;\n --hover-bg: #f3f4f6;\n --hover-color: #111;\n width: 100%;\n min-height: var(--pill-min-h);\n padding: clamp(1.5rem, 3vw, 8rem) 0;\n font-size: clamp(1.5rem, 4vw, 4rem);\n font-weight: 400;\n line-height: 0;\n border-radius: 999px;\n background: var(--pill-bg);\n color: var(--pill-color);\n text-decoration: none;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n transition:\n background 0.3s ease,\n color 0.3s ease;\n will-change: transform;\n box-sizing: border-box;\n white-space: nowrap;\n overflow: hidden;\n height: 10px;\n}\n\n@media (min-width: 900px) {\n .bubble-menu-items .pill-link {\n transform: rotate(var(--item-rot));\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: rotate(var(--item-rot)) scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: rotate(var(--item-rot)) scale(0.94);\n }\n}\n\n.bubble-menu-items .pill-link .pill-label {\n display: inline-block;\n will-change: transform, opacity;\n height: 1.2em;\n line-height: 1.2;\n}\n\n@media (max-width: 899px) {\n .bubble-menu-items {\n padding-top: 0px;\n align-items: flex-start;\n padding-top: 120px;\n }\n\n .bubble-menu-items .pill-list {\n row-gap: 16px;\n }\n\n .bubble-menu-items .pill-list .pill-col {\n flex: 0 0 100%;\n margin-left: 0 !important;\n overflow: visible;\n }\n\n .bubble-menu-items .pill-link {\n font-size: clamp(1.2rem, 3vw, 4rem);\n padding: clamp(1rem, 2vw, 2rem) 0;\n min-height: 80px;\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: scale(0.94);\n }\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BubbleMenu-JS-CSS", + "title": "BubbleMenu", + "description": "Floating circular expanding menu with staggered item reveal.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Components/BubbleMenu/BubbleMenu.jsx", + "content": "import { useState, useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\n\nimport './BubbleMenu.css';\n\nconst DEFAULT_ITEMS = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n const containerClassName = ['bubble-menu', useFixedPosition ? 'fixed' : 'absolute', className]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n `-=${animationDuration * 0.9}`\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n \n {showOverlay && (\n \n
    \n {menuItems.map((item, idx) => (\n
  • \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n
  • \n ))}\n
\n \n )}\n \n );\n}\n" + }, + { + "type": "registry:item", + "path": "src/content/Components/BubbleMenu/BubbleMenu.css", + "content": ".bubble-menu {\n left: 0;\n right: 0;\n top: 2em;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 0 2em;\n pointer-events: none;\n z-index: 99;\n}\n\n.bubble-menu.fixed {\n position: fixed;\n}\n\n.bubble-menu.absolute {\n position: absolute;\n}\n\n.bubble-menu .bubble {\n --bubble-size: 48px;\n width: var(--bubble-size);\n height: var(--bubble-size);\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n pointer-events: auto;\n}\n\n.bubble-menu .logo-bubble,\n.bubble-menu .toggle-bubble {\n will-change: transform;\n}\n\n.bubble-menu .logo-bubble {\n width: auto;\n min-height: var(--bubble-size);\n height: var(--bubble-size);\n padding: 0 16px;\n border-radius: calc(var(--bubble-size) / 2);\n gap: 8px;\n}\n\n.bubble-menu .toggle-bubble {\n width: var(--bubble-size);\n height: var(--bubble-size);\n}\n\n.bubble-menu .bubble-logo {\n max-height: 60%;\n max-width: 100%;\n object-fit: contain;\n display: block;\n}\n\n.bubble-menu .logo-content {\n --logo-max-height: 60%;\n --logo-max-width: 100%;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 120px;\n height: 100%;\n}\n\n.bubble-menu .logo-content > .bubble-logo,\n.bubble-menu .logo-content > img,\n.bubble-menu .logo-content > svg {\n max-height: var(--logo-max-height);\n max-width: var(--logo-max-width);\n}\n\n.bubble-menu .menu-btn {\n border: none;\n background: #fff;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 0;\n}\n\n.bubble-menu .menu-line {\n width: 26px;\n height: 2px;\n background: #111;\n border-radius: 2px;\n display: block;\n margin: 0 auto;\n transition:\n transform 0.3s ease,\n opacity 0.3s ease;\n transform-origin: center;\n}\n\n.bubble-menu .menu-line + .menu-line {\n margin-top: 6px;\n}\n\n.bubble-menu .menu-btn.open .menu-line:first-child {\n transform: translateY(4px) rotate(45deg);\n}\n\n.bubble-menu .menu-btn.open .menu-line:last-child {\n transform: translateY(-4px) rotate(-45deg);\n}\n\n@media (min-width: 768px) {\n .bubble-menu .bubble {\n --bubble-size: 56px;\n }\n\n .bubble-menu .logo-bubble {\n padding: 0 16px;\n }\n}\n\n.bubble-menu-items {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n z-index: 98;\n}\n\n.bubble-menu-items.fixed {\n position: fixed;\n}\n\n.bubble-menu-items.absolute {\n position: absolute;\n}\n\n.bubble-menu-items .pill-list {\n list-style: none;\n margin: 0;\n padding: 0 24px;\n display: flex;\n flex-wrap: wrap;\n gap: 0;\n row-gap: 4px;\n width: 100%;\n max-width: 1600px;\n margin-left: auto;\n margin-right: auto;\n pointer-events: auto;\n justify-content: stretch;\n}\n\n.bubble-menu-items .pill-list .pill-spacer {\n width: 100%;\n height: 0;\n pointer-events: none;\n}\n\n.bubble-menu-items .pill-list .pill-col {\n display: flex;\n justify-content: center;\n align-items: stretch;\n flex: 0 0 calc(100% / 3);\n box-sizing: border-box;\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):nth-last-child(2) {\n margin-left: calc(100% / 6);\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):last-child {\n margin-left: calc(100% / 3);\n}\n\n.bubble-menu-items .pill-link {\n --pill-bg: #ffffff;\n --pill-color: #111;\n --pill-border: rgba(0, 0, 0, 0.12);\n --item-rot: 0deg;\n --pill-min-h: 160px;\n --hover-bg: #f3f4f6;\n --hover-color: #111;\n width: 100%;\n min-height: var(--pill-min-h);\n padding: clamp(1.5rem, 3vw, 8rem) 0;\n font-size: clamp(1.5rem, 4vw, 4rem);\n font-weight: 400;\n line-height: 0;\n border-radius: 999px;\n background: var(--pill-bg);\n color: var(--pill-color);\n text-decoration: none;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n transition:\n background 0.3s ease,\n color 0.3s ease;\n will-change: transform;\n box-sizing: border-box;\n white-space: nowrap;\n overflow: hidden;\n height: 10px;\n}\n\n@media (min-width: 900px) {\n .bubble-menu-items .pill-link {\n transform: rotate(var(--item-rot));\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: rotate(var(--item-rot)) scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: rotate(var(--item-rot)) scale(0.94);\n }\n}\n\n.bubble-menu-items .pill-link .pill-label {\n display: inline-block;\n will-change: transform, opacity;\n height: 1.2em;\n line-height: 1.2;\n}\n\n@media (max-width: 899px) {\n .bubble-menu-items {\n padding-top: 0px;\n align-items: flex-start;\n padding-top: 120px;\n }\n\n .bubble-menu-items .pill-list {\n row-gap: 16px;\n }\n\n .bubble-menu-items .pill-list .pill-col {\n flex: 0 0 100%;\n margin-left: 0 !important;\n overflow: visible;\n }\n\n .bubble-menu-items .pill-link {\n font-size: clamp(1.2rem, 3vw, 4rem);\n padding: clamp(1rem, 2vw, 2rem) 0;\n min-height: 80px;\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: scale(0.94);\n }\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BubbleMenu-JS-TW.json b/public/r/BubbleMenu-JS-TW.json index 3fce1b2e..1020d1c8 100644 --- a/public/r/BubbleMenu-JS-TW.json +++ b/public/r/BubbleMenu-JS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BubbleMenu-JS-TW", - "type": "registry:block", - "title": "BubbleMenu", - "description": "Floating circular expanding menu with staggered item reveal.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Components/BubbleMenu/BubbleMenu.jsx", - "content": "import { useEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n\nconst DEFAULT_ITEMS = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n\n const containerClassName = [\n 'bubble-menu',\n useFixedPosition ? 'fixed' : 'absolute',\n 'left-0 right-0 top-8',\n 'flex items-center justify-between',\n 'gap-4 px-8',\n 'pointer-events-none',\n 'z-[1001]',\n className\n ]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n '-=' + animationDuration * 0.9\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n {/* Workaround for silly Tailwind capabilities */}\n \n\n \n\n {showOverlay && (\n \n \n {menuItems.map((item, idx) => (\n \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n \n ))}\n \n \n )}\n \n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BubbleMenu-JS-TW", + "title": "BubbleMenu", + "description": "Floating circular expanding menu with staggered item reveal.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/tailwind/Components/BubbleMenu/BubbleMenu.jsx", + "content": "import { useEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n\nconst DEFAULT_ITEMS = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n\n const containerClassName = [\n 'bubble-menu',\n useFixedPosition ? 'fixed' : 'absolute',\n 'left-0 right-0 top-8',\n 'flex items-center justify-between',\n 'gap-4 px-8',\n 'pointer-events-none',\n 'z-[1001]',\n className\n ]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n '-=' + animationDuration * 0.9\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n {/* Workaround for silly Tailwind capabilities */}\n \n\n \n\n {showOverlay && (\n \n \n {menuItems.map((item, idx) => (\n \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n \n ))}\n \n \n )}\n \n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BubbleMenu-TS-CSS.json b/public/r/BubbleMenu-TS-CSS.json index b2d42510..64e8a8a6 100644 --- a/public/r/BubbleMenu-TS-CSS.json +++ b/public/r/BubbleMenu-TS-CSS.json @@ -1,22 +1,23 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BubbleMenu-TS-CSS", - "type": "registry:block", - "title": "BubbleMenu", - "description": "Floating circular expanding menu with staggered item reveal.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/default/src/ts-default/Components/BubbleMenu/BubbleMenu.tsx", - "content": "import type { CSSProperties, ReactNode } from 'react';\nimport { useState, useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\n\nimport './BubbleMenu.css';\n\ntype MenuItem = {\n label: string;\n href: string;\n ariaLabel?: string;\n rotation?: number;\n hoverStyles?: {\n bgColor?: string;\n textColor?: string;\n };\n};\n\nexport type BubbleMenuProps = {\n logo: ReactNode | string;\n onMenuClick?: (open: boolean) => void;\n className?: string;\n style?: CSSProperties;\n menuAriaLabel?: string;\n menuBg?: string;\n menuContentColor?: string;\n useFixedPosition?: boolean;\n items?: MenuItem[];\n animationEase?: string;\n animationDuration?: number;\n staggerDelay?: number;\n};\n\nconst DEFAULT_ITEMS: MenuItem[] = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}: BubbleMenuProps) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n const containerClassName = ['bubble-menu', useFixedPosition ? 'fixed' : 'absolute', className]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n `-=${animationDuration * 0.9}`\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n \n {showOverlay && (\n \n
    \n {menuItems.map((item, idx) => (\n
  • \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n
  • \n ))}\n
\n \n )}\n \n );\n}\n", - "type": "registry:component" - }, - { - "path": "public/ts/default/src/ts-default/Components/BubbleMenu/BubbleMenu.css", - "content": ".bubble-menu {\n left: 0;\n right: 0;\n top: 2em;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 0 2em;\n pointer-events: none;\n z-index: 99;\n}\n\n.bubble-menu.fixed {\n position: fixed;\n}\n\n.bubble-menu.absolute {\n position: absolute;\n}\n\n.bubble-menu .bubble {\n --bubble-size: 48px;\n width: var(--bubble-size);\n height: var(--bubble-size);\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n pointer-events: auto;\n}\n\n.bubble-menu .logo-bubble,\n.bubble-menu .toggle-bubble {\n will-change: transform;\n}\n\n.bubble-menu .logo-bubble {\n width: auto;\n min-height: var(--bubble-size);\n height: var(--bubble-size);\n padding: 0 16px;\n border-radius: calc(var(--bubble-size) / 2);\n gap: 8px;\n}\n\n.bubble-menu .toggle-bubble {\n width: var(--bubble-size);\n height: var(--bubble-size);\n}\n\n.bubble-menu .bubble-logo {\n max-height: 60%;\n max-width: 100%;\n object-fit: contain;\n display: block;\n}\n\n.bubble-menu .logo-content {\n --logo-max-height: 60%;\n --logo-max-width: 100%;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 120px;\n height: 100%;\n}\n\n.bubble-menu .logo-content > .bubble-logo,\n.bubble-menu .logo-content > img,\n.bubble-menu .logo-content > svg {\n max-height: var(--logo-max-height);\n max-width: var(--logo-max-width);\n}\n\n.bubble-menu .menu-btn {\n border: none;\n background: #fff;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 0;\n}\n\n.bubble-menu .menu-line {\n width: 26px;\n height: 2px;\n background: #111;\n border-radius: 2px;\n display: block;\n margin: 0 auto;\n transition:\n transform 0.3s ease,\n opacity 0.3s ease;\n transform-origin: center;\n}\n\n.bubble-menu .menu-line + .menu-line {\n margin-top: 6px;\n}\n\n.bubble-menu .menu-btn.open .menu-line:first-child {\n transform: translateY(4px) rotate(45deg);\n}\n\n.bubble-menu .menu-btn.open .menu-line:last-child {\n transform: translateY(-4px) rotate(-45deg);\n}\n\n@media (min-width: 768px) {\n .bubble-menu .bubble {\n --bubble-size: 56px;\n }\n\n .bubble-menu .logo-bubble {\n padding: 0 16px;\n }\n}\n\n.bubble-menu-items {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n z-index: 98;\n}\n\n.bubble-menu-items.fixed {\n position: fixed;\n}\n\n.bubble-menu-items.absolute {\n position: absolute;\n}\n\n.bubble-menu-items .pill-list {\n list-style: none;\n margin: 0;\n padding: 0 24px;\n display: flex;\n flex-wrap: wrap;\n gap: 0;\n row-gap: 4px;\n width: 100%;\n max-width: 1600px;\n margin-left: auto;\n margin-right: auto;\n pointer-events: auto;\n justify-content: stretch;\n}\n\n.bubble-menu-items .pill-list .pill-spacer {\n width: 100%;\n height: 0;\n pointer-events: none;\n}\n\n.bubble-menu-items .pill-list .pill-col {\n display: flex;\n justify-content: center;\n align-items: stretch;\n flex: 0 0 calc(100% / 3);\n box-sizing: border-box;\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):nth-last-child(2) {\n margin-left: calc(100% / 6);\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):last-child {\n margin-left: calc(100% / 3);\n}\n\n.bubble-menu-items .pill-link {\n --pill-bg: #ffffff;\n --pill-color: #111;\n --pill-border: rgba(0, 0, 0, 0.12);\n --item-rot: 0deg;\n --pill-min-h: 160px;\n --hover-bg: #f3f4f6;\n --hover-color: #111;\n width: 100%;\n min-height: var(--pill-min-h);\n padding: clamp(1.5rem, 3vw, 8rem) 0;\n font-size: clamp(1.5rem, 4vw, 4rem);\n font-weight: 400;\n line-height: 0;\n border-radius: 999px;\n background: var(--pill-bg);\n color: var(--pill-color);\n text-decoration: none;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n transition:\n background 0.3s ease,\n color 0.3s ease;\n will-change: transform;\n box-sizing: border-box;\n white-space: nowrap;\n overflow: hidden;\n height: 10px;\n}\n\n@media (min-width: 900px) {\n .bubble-menu-items .pill-link {\n transform: rotate(var(--item-rot));\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: rotate(var(--item-rot)) scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: rotate(var(--item-rot)) scale(0.94);\n }\n}\n\n.bubble-menu-items .pill-link .pill-label {\n display: inline-block;\n will-change: transform, opacity;\n height: 1.2em;\n line-height: 1.2;\n}\n\n@media (max-width: 899px) {\n .bubble-menu-items {\n padding-top: 0px;\n align-items: flex-start;\n padding-top: 120px;\n }\n\n .bubble-menu-items .pill-list {\n row-gap: 16px;\n }\n\n .bubble-menu-items .pill-list .pill-col {\n flex: 0 0 100%;\n margin-left: 0 !important;\n overflow: visible;\n }\n\n .bubble-menu-items .pill-link {\n font-size: clamp(1.2rem, 3vw, 4rem);\n padding: clamp(1rem, 2vw, 2rem) 0;\n min-height: 80px;\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: scale(0.94);\n }\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BubbleMenu-TS-CSS", + "title": "BubbleMenu", + "description": "Floating circular expanding menu with staggered item reveal.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-default/Components/BubbleMenu/BubbleMenu.tsx", + "content": "import type { CSSProperties, ReactNode } from 'react';\nimport { useState, useRef, useEffect } from 'react';\nimport { gsap } from 'gsap';\n\nimport './BubbleMenu.css';\n\ntype MenuItem = {\n label: string;\n href: string;\n ariaLabel?: string;\n rotation?: number;\n hoverStyles?: {\n bgColor?: string;\n textColor?: string;\n };\n};\n\nexport type BubbleMenuProps = {\n logo: ReactNode | string;\n onMenuClick?: (open: boolean) => void;\n className?: string;\n style?: CSSProperties;\n menuAriaLabel?: string;\n menuBg?: string;\n menuContentColor?: string;\n useFixedPosition?: boolean;\n items?: MenuItem[];\n animationEase?: string;\n animationDuration?: number;\n staggerDelay?: number;\n};\n\nconst DEFAULT_ITEMS: MenuItem[] = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}: BubbleMenuProps) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n const containerClassName = ['bubble-menu', useFixedPosition ? 'fixed' : 'absolute', className]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n `-=${animationDuration * 0.9}`\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n \n {showOverlay && (\n \n
    \n {menuItems.map((item, idx) => (\n
  • \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n
  • \n ))}\n
\n \n )}\n \n );\n}\n" + }, + { + "type": "registry:item", + "path": "src/ts-default/Components/BubbleMenu/BubbleMenu.css", + "content": ".bubble-menu {\n left: 0;\n right: 0;\n top: 2em;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 0 2em;\n pointer-events: none;\n z-index: 99;\n}\n\n.bubble-menu.fixed {\n position: fixed;\n}\n\n.bubble-menu.absolute {\n position: absolute;\n}\n\n.bubble-menu .bubble {\n --bubble-size: 48px;\n width: var(--bubble-size);\n height: var(--bubble-size);\n border-radius: 50%;\n background: #fff;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n pointer-events: auto;\n}\n\n.bubble-menu .logo-bubble,\n.bubble-menu .toggle-bubble {\n will-change: transform;\n}\n\n.bubble-menu .logo-bubble {\n width: auto;\n min-height: var(--bubble-size);\n height: var(--bubble-size);\n padding: 0 16px;\n border-radius: calc(var(--bubble-size) / 2);\n gap: 8px;\n}\n\n.bubble-menu .toggle-bubble {\n width: var(--bubble-size);\n height: var(--bubble-size);\n}\n\n.bubble-menu .bubble-logo {\n max-height: 60%;\n max-width: 100%;\n object-fit: contain;\n display: block;\n}\n\n.bubble-menu .logo-content {\n --logo-max-height: 60%;\n --logo-max-width: 100%;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 120px;\n height: 100%;\n}\n\n.bubble-menu .logo-content > .bubble-logo,\n.bubble-menu .logo-content > img,\n.bubble-menu .logo-content > svg {\n max-height: var(--logo-max-height);\n max-width: var(--logo-max-width);\n}\n\n.bubble-menu .menu-btn {\n border: none;\n background: #fff;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 0;\n}\n\n.bubble-menu .menu-line {\n width: 26px;\n height: 2px;\n background: #111;\n border-radius: 2px;\n display: block;\n margin: 0 auto;\n transition:\n transform 0.3s ease,\n opacity 0.3s ease;\n transform-origin: center;\n}\n\n.bubble-menu .menu-line + .menu-line {\n margin-top: 6px;\n}\n\n.bubble-menu .menu-btn.open .menu-line:first-child {\n transform: translateY(4px) rotate(45deg);\n}\n\n.bubble-menu .menu-btn.open .menu-line:last-child {\n transform: translateY(-4px) rotate(-45deg);\n}\n\n@media (min-width: 768px) {\n .bubble-menu .bubble {\n --bubble-size: 56px;\n }\n\n .bubble-menu .logo-bubble {\n padding: 0 16px;\n }\n}\n\n.bubble-menu-items {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n z-index: 98;\n}\n\n.bubble-menu-items.fixed {\n position: fixed;\n}\n\n.bubble-menu-items.absolute {\n position: absolute;\n}\n\n.bubble-menu-items .pill-list {\n list-style: none;\n margin: 0;\n padding: 0 24px;\n display: flex;\n flex-wrap: wrap;\n gap: 0;\n row-gap: 4px;\n width: 100%;\n max-width: 1600px;\n margin-left: auto;\n margin-right: auto;\n pointer-events: auto;\n justify-content: stretch;\n}\n\n.bubble-menu-items .pill-list .pill-spacer {\n width: 100%;\n height: 0;\n pointer-events: none;\n}\n\n.bubble-menu-items .pill-list .pill-col {\n display: flex;\n justify-content: center;\n align-items: stretch;\n flex: 0 0 calc(100% / 3);\n box-sizing: border-box;\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):nth-last-child(2) {\n margin-left: calc(100% / 6);\n}\n\n.bubble-menu-items .pill-list .pill-col:nth-child(4):last-child {\n margin-left: calc(100% / 3);\n}\n\n.bubble-menu-items .pill-link {\n --pill-bg: #ffffff;\n --pill-color: #111;\n --pill-border: rgba(0, 0, 0, 0.12);\n --item-rot: 0deg;\n --pill-min-h: 160px;\n --hover-bg: #f3f4f6;\n --hover-color: #111;\n width: 100%;\n min-height: var(--pill-min-h);\n padding: clamp(1.5rem, 3vw, 8rem) 0;\n font-size: clamp(1.5rem, 4vw, 4rem);\n font-weight: 400;\n line-height: 0;\n border-radius: 999px;\n background: var(--pill-bg);\n color: var(--pill-color);\n text-decoration: none;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.1);\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n transition:\n background 0.3s ease,\n color 0.3s ease;\n will-change: transform;\n box-sizing: border-box;\n white-space: nowrap;\n overflow: hidden;\n height: 10px;\n}\n\n@media (min-width: 900px) {\n .bubble-menu-items .pill-link {\n transform: rotate(var(--item-rot));\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: rotate(var(--item-rot)) scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: rotate(var(--item-rot)) scale(0.94);\n }\n}\n\n.bubble-menu-items .pill-link .pill-label {\n display: inline-block;\n will-change: transform, opacity;\n height: 1.2em;\n line-height: 1.2;\n}\n\n@media (max-width: 899px) {\n .bubble-menu-items {\n padding-top: 0px;\n align-items: flex-start;\n padding-top: 120px;\n }\n\n .bubble-menu-items .pill-list {\n row-gap: 16px;\n }\n\n .bubble-menu-items .pill-list .pill-col {\n flex: 0 0 100%;\n margin-left: 0 !important;\n overflow: visible;\n }\n\n .bubble-menu-items .pill-link {\n font-size: clamp(1.2rem, 3vw, 4rem);\n padding: clamp(1rem, 2vw, 2rem) 0;\n min-height: 80px;\n }\n\n .bubble-menu-items .pill-link:hover {\n transform: scale(1.06);\n background: var(--hover-bg);\n color: var(--hover-color);\n }\n\n .bubble-menu-items .pill-link:active {\n transform: scale(0.94);\n }\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/BubbleMenu-TS-TW.json b/public/r/BubbleMenu-TS-TW.json index 9231174b..2c87adda 100644 --- a/public/r/BubbleMenu-TS-TW.json +++ b/public/r/BubbleMenu-TS-TW.json @@ -1,17 +1,18 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "BubbleMenu-TS-TW", - "type": "registry:block", - "title": "BubbleMenu", - "description": "Floating circular expanding menu with staggered item reveal.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/ts/tailwind/src/ts-tailwind/Components/BubbleMenu/BubbleMenu.tsx", - "content": "import type { CSSProperties, ReactNode } from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n\ntype MenuItem = {\n label: string;\n href: string;\n ariaLabel?: string;\n rotation?: number;\n hoverStyles?: {\n bgColor?: string;\n textColor?: string;\n };\n};\n\nexport type BubbleMenuProps = {\n logo: ReactNode | string;\n onMenuClick?: (open: boolean) => void;\n className?: string;\n style?: CSSProperties;\n menuAriaLabel?: string;\n menuBg?: string;\n menuContentColor?: string;\n useFixedPosition?: boolean;\n items?: MenuItem[];\n animationEase?: string;\n animationDuration?: number;\n staggerDelay?: number;\n};\n\nconst DEFAULT_ITEMS: MenuItem[] = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}: BubbleMenuProps) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n\n const containerClassName = [\n 'bubble-menu',\n useFixedPosition ? 'fixed' : 'absolute',\n 'left-0 right-0 top-8',\n 'flex items-center justify-between',\n 'gap-4 px-8',\n 'pointer-events-none',\n 'z-[1001]',\n className\n ]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n '-=' + animationDuration * 0.9\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n {/* Workaround for silly Tailwind capabilities */}\n \n\n \n\n {showOverlay && (\n \n \n {menuItems.map((item, idx) => (\n \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n \n ))}\n \n \n )}\n \n );\n}\n", - "type": "registry:component" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "BubbleMenu-TS-TW", + "title": "BubbleMenu", + "description": "Floating circular expanding menu with staggered item reveal.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/ts-tailwind/Components/BubbleMenu/BubbleMenu.tsx", + "content": "import type { CSSProperties, ReactNode } from 'react';\nimport { useEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n\ntype MenuItem = {\n label: string;\n href: string;\n ariaLabel?: string;\n rotation?: number;\n hoverStyles?: {\n bgColor?: string;\n textColor?: string;\n };\n};\n\nexport type BubbleMenuProps = {\n logo: ReactNode | string;\n onMenuClick?: (open: boolean) => void;\n className?: string;\n style?: CSSProperties;\n menuAriaLabel?: string;\n menuBg?: string;\n menuContentColor?: string;\n useFixedPosition?: boolean;\n items?: MenuItem[];\n animationEase?: string;\n animationDuration?: number;\n staggerDelay?: number;\n};\n\nconst DEFAULT_ITEMS: MenuItem[] = [\n {\n label: 'home',\n href: '#',\n ariaLabel: 'Home',\n rotation: -8,\n hoverStyles: { bgColor: '#3b82f6', textColor: '#ffffff' }\n },\n {\n label: 'about',\n href: '#',\n ariaLabel: 'About',\n rotation: 8,\n hoverStyles: { bgColor: '#10b981', textColor: '#ffffff' }\n },\n {\n label: 'projects',\n href: '#',\n ariaLabel: 'Documentation',\n rotation: 8,\n hoverStyles: { bgColor: '#f59e0b', textColor: '#ffffff' }\n },\n {\n label: 'blog',\n href: '#',\n ariaLabel: 'Blog',\n rotation: 8,\n hoverStyles: { bgColor: '#ef4444', textColor: '#ffffff' }\n },\n {\n label: 'contact',\n href: '#',\n ariaLabel: 'Contact',\n rotation: -8,\n hoverStyles: { bgColor: '#8b5cf6', textColor: '#ffffff' }\n }\n];\n\nexport default function BubbleMenu({\n logo,\n onMenuClick,\n className,\n style,\n menuAriaLabel = 'Toggle menu',\n menuBg = '#fff',\n menuContentColor = '#111',\n useFixedPosition = false,\n items,\n animationEase = 'back.out(1.5)',\n animationDuration = 0.5,\n staggerDelay = 0.12\n}: BubbleMenuProps) {\n const [isMenuOpen, setIsMenuOpen] = useState(false);\n const [showOverlay, setShowOverlay] = useState(false);\n\n const overlayRef = useRef(null);\n const bubblesRef = useRef([]);\n const labelRefs = useRef([]);\n\n const menuItems = items?.length ? items : DEFAULT_ITEMS;\n\n const containerClassName = [\n 'bubble-menu',\n useFixedPosition ? 'fixed' : 'absolute',\n 'left-0 right-0 top-8',\n 'flex items-center justify-between',\n 'gap-4 px-8',\n 'pointer-events-none',\n 'z-[1001]',\n className\n ]\n .filter(Boolean)\n .join(' ');\n\n const handleToggle = () => {\n const nextState = !isMenuOpen;\n if (nextState) setShowOverlay(true);\n setIsMenuOpen(nextState);\n onMenuClick?.(nextState);\n };\n\n useEffect(() => {\n const overlay = overlayRef.current;\n const bubbles = bubblesRef.current.filter(Boolean);\n const labels = labelRefs.current.filter(Boolean);\n if (!overlay || !bubbles.length) return;\n\n if (isMenuOpen) {\n gsap.set(overlay, { display: 'flex' });\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.set(bubbles, { scale: 0, transformOrigin: '50% 50%' });\n gsap.set(labels, { y: 24, autoAlpha: 0 });\n\n bubbles.forEach((bubble, i) => {\n const delay = i * staggerDelay + gsap.utils.random(-0.05, 0.05);\n const tl = gsap.timeline({ delay });\n tl.to(bubble, {\n scale: 1,\n duration: animationDuration,\n ease: animationEase\n });\n if (labels[i]) {\n tl.to(\n labels[i],\n {\n y: 0,\n autoAlpha: 1,\n duration: animationDuration,\n ease: 'power3.out'\n },\n '-=' + animationDuration * 0.9\n );\n }\n });\n } else if (showOverlay) {\n gsap.killTweensOf([...bubbles, ...labels]);\n gsap.to(labels, {\n y: 24,\n autoAlpha: 0,\n duration: 0.2,\n ease: 'power3.in'\n });\n gsap.to(bubbles, {\n scale: 0,\n duration: 0.2,\n ease: 'power3.in',\n onComplete: () => {\n gsap.set(overlay, { display: 'none' });\n setShowOverlay(false);\n }\n });\n }\n }, [isMenuOpen, showOverlay, animationEase, animationDuration, staggerDelay]);\n\n useEffect(() => {\n const handleResize = () => {\n if (isMenuOpen) {\n const bubbles = bubblesRef.current.filter(Boolean);\n const isDesktop = window.innerWidth >= 900;\n bubbles.forEach((bubble, i) => {\n const item = menuItems[i];\n if (bubble && item) {\n const rotation = isDesktop ? (item.rotation ?? 0) : 0;\n gsap.set(bubble, { rotation });\n }\n });\n }\n };\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n }, [isMenuOpen, menuItems]);\n\n return (\n <>\n {/* Workaround for silly Tailwind capabilities */}\n \n\n \n\n {showOverlay && (\n \n \n {menuItems.map((item, idx) => (\n \n {\n if (el) bubblesRef.current[idx] = el;\n }}\n >\n {\n if (el) labelRefs.current[idx] = el;\n }}\n >\n {item.label}\n \n \n \n ))}\n \n \n )}\n \n );\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0" + ] } \ No newline at end of file diff --git a/public/r/CardNav-JS-CSS.json b/public/r/CardNav-JS-CSS.json index b0153f2e..8e9b7e7c 100644 --- a/public/r/CardNav-JS-CSS.json +++ b/public/r/CardNav-JS-CSS.json @@ -1,22 +1,24 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "CardNav-JS-CSS", - "type": "registry:block", - "title": "CardNav", - "description": "Expandable navigation bar with card panels revealing nested links.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/default/src/content/Components/CardNav/CardNav.jsx", - "content": "import { useLayoutEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n// use your own icon import if react-icons is not available\nimport { GoArrowUpRight } from 'react-icons/go';\nimport './CardNav.css';\n\nconst CardNav = ({\n logo,\n logoAlt = 'Logo',\n items,\n className = '',\n ease = 'power3.out',\n baseColor = '#fff',\n menuColor,\n buttonBgColor,\n buttonTextColor\n}) => {\n const [isHamburgerOpen, setIsHamburgerOpen] = useState(false);\n const [isExpanded, setIsExpanded] = useState(false);\n const navRef = useRef(null);\n const cardsRef = useRef([]);\n const tlRef = useRef(null);\n\n const calculateHeight = () => {\n const navEl = navRef.current;\n if (!navEl) return 260;\n\n const isMobile = window.matchMedia('(max-width: 768px)').matches;\n if (isMobile) {\n const contentEl = navEl.querySelector('.card-nav-content');\n if (contentEl) {\n const wasVisible = contentEl.style.visibility;\n const wasPointerEvents = contentEl.style.pointerEvents;\n const wasPosition = contentEl.style.position;\n const wasHeight = contentEl.style.height;\n\n contentEl.style.visibility = 'visible';\n contentEl.style.pointerEvents = 'auto';\n contentEl.style.position = 'static';\n contentEl.style.height = 'auto';\n\n contentEl.offsetHeight;\n\n const topBar = 60;\n const padding = 16;\n const contentHeight = contentEl.scrollHeight;\n\n contentEl.style.visibility = wasVisible;\n contentEl.style.pointerEvents = wasPointerEvents;\n contentEl.style.position = wasPosition;\n contentEl.style.height = wasHeight;\n\n return topBar + contentHeight + padding;\n }\n }\n return 260;\n };\n\n const createTimeline = () => {\n const navEl = navRef.current;\n if (!navEl) return null;\n\n gsap.set(navEl, { height: 60, overflow: 'hidden' });\n gsap.set(cardsRef.current, { y: 50, opacity: 0 });\n\n const tl = gsap.timeline({ paused: true });\n\n tl.to(navEl, {\n height: calculateHeight,\n duration: 0.4,\n ease\n });\n\n tl.to(cardsRef.current, { y: 0, opacity: 1, duration: 0.4, ease, stagger: 0.08 }, '-=0.1');\n\n return tl;\n };\n\n useLayoutEffect(() => {\n const tl = createTimeline();\n tlRef.current = tl;\n\n return () => {\n tl?.kill();\n tlRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ease, items]);\n\n useLayoutEffect(() => {\n const handleResize = () => {\n if (!tlRef.current) return;\n\n if (isExpanded) {\n const newHeight = calculateHeight();\n gsap.set(navRef.current, { height: newHeight });\n\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n newTl.progress(1);\n tlRef.current = newTl;\n }\n } else {\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n tlRef.current = newTl;\n }\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isExpanded]);\n\n const toggleMenu = () => {\n const tl = tlRef.current;\n if (!tl) return;\n if (!isExpanded) {\n setIsHamburgerOpen(true);\n setIsExpanded(true);\n tl.play(0);\n } else {\n setIsHamburgerOpen(false);\n tl.eventCallback('onReverseComplete', () => setIsExpanded(false));\n tl.reverse();\n }\n };\n\n const setCardRef = i => el => {\n if (el) cardsRef.current[i] = el;\n };\n\n return (\n
\n \n
\n );\n};\n\nexport default CardNav;\n", - "type": "registry:component" - }, - { - "path": "public/default/src/content/Components/CardNav/CardNav.css", - "content": ".card-nav-container {\n position: absolute;\n top: 2em;\n left: 50%;\n transform: translateX(-50%);\n width: 90%;\n max-width: 800px;\n z-index: 99;\n box-sizing: border-box;\n}\n\n.card-nav {\n display: block;\n height: 60px;\n padding: 0;\n background-color: white;\n border: 0.5px solid rgba(255, 255, 255, 0.1);\n border-radius: 0.75rem;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n position: relative;\n overflow: hidden;\n will-change: height;\n}\n\n.card-nav-top {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 60px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 0.45rem 0.55rem 1.1rem;\n z-index: 2;\n}\n\n.hamburger-menu {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n gap: 6px;\n}\n\n.hamburger-menu:hover .hamburger-line {\n opacity: 0.75;\n}\n\n.hamburger-line {\n width: 30px;\n height: 2px;\n background-color: currentColor;\n transition:\n transform 0.25s ease,\n opacity 0.2s ease,\n margin 0.3s ease;\n transform-origin: 50% 50%;\n}\n\n.hamburger-menu.open .hamburger-line:first-child {\n transform: translateY(4px) rotate(45deg);\n}\n\n.hamburger-menu.open .hamburger-line:last-child {\n transform: translateY(-4px) rotate(-45deg);\n}\n\n.logo-container {\n display: flex;\n align-items: center;\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.logo {\n height: 28px;\n}\n\n.card-nav-cta-button {\n background-color: #111;\n color: white;\n border: none;\n border-radius: calc(0.75rem - 0.35rem);\n padding: 0 1rem;\n height: 100%;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 0.3s ease;\n align-items: center;\n}\n\n.card-nav-cta-button:hover {\n background-color: #333;\n}\n\n.card-nav-content {\n position: absolute;\n left: 0;\n right: 0;\n top: 60px;\n bottom: 0;\n padding: 0.5rem;\n display: flex;\n align-items: flex-end;\n gap: 12px;\n visibility: hidden;\n pointer-events: none;\n z-index: 1;\n}\n\n.card-nav.open .card-nav-content {\n visibility: visible;\n pointer-events: auto;\n}\n\n.nav-card {\n height: 100%;\n flex: 1 1 0;\n min-width: 0;\n border-radius: calc(0.75rem - 0.2rem);\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 16px;\n gap: 8px;\n user-select: none;\n}\n\n.nav-card-label {\n font-weight: 400;\n font-size: 22px;\n letter-spacing: -0.5px;\n}\n\n.nav-card-links {\n margin-top: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.nav-card-link {\n font-size: 16px;\n cursor: pointer;\n text-decoration: none;\n transition: opacity 0.3s ease;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.nav-card-link:hover {\n opacity: 0.75;\n}\n\n@media (max-width: 768px) {\n .card-nav-container {\n width: 90%;\n top: 1.2em;\n }\n\n .card-nav-top {\n padding: 0.5rem 1rem;\n justify-content: space-between;\n }\n\n .hamburger-menu {\n order: 2;\n }\n\n .logo-container {\n position: static;\n transform: none;\n order: 1;\n }\n\n .card-nav-cta-button {\n display: none;\n }\n\n .card-nav-content {\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 0.5rem;\n bottom: 0;\n justify-content: flex-start;\n }\n\n .nav-card {\n height: auto;\n min-height: 60px;\n flex: 1 1 auto;\n max-height: none;\n }\n\n .nav-card-label {\n font-size: 18px;\n }\n\n .nav-card-link {\n font-size: 15px;\n }\n}\n", - "type": "registry:item" - } - ] + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "CardNav-JS-CSS", + "title": "CardNav", + "description": "Expandable navigation bar with card panels revealing nested links.", + "type": "registry:block", + "files": [ + { + "type": "registry:component", + "path": "src/content/Components/CardNav/CardNav.jsx", + "content": "import { useLayoutEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n// use your own icon import if react-icons is not available\nimport { GoArrowUpRight } from 'react-icons/go';\nimport './CardNav.css';\n\nconst CardNav = ({\n logo,\n logoAlt = 'Logo',\n items,\n className = '',\n ease = 'power3.out',\n baseColor = '#fff',\n menuColor,\n buttonBgColor,\n buttonTextColor\n}) => {\n const [isHamburgerOpen, setIsHamburgerOpen] = useState(false);\n const [isExpanded, setIsExpanded] = useState(false);\n const navRef = useRef(null);\n const cardsRef = useRef([]);\n const tlRef = useRef(null);\n\n const calculateHeight = () => {\n const navEl = navRef.current;\n if (!navEl) return 260;\n\n const isMobile = window.matchMedia('(max-width: 768px)').matches;\n if (isMobile) {\n const contentEl = navEl.querySelector('.card-nav-content');\n if (contentEl) {\n const wasVisible = contentEl.style.visibility;\n const wasPointerEvents = contentEl.style.pointerEvents;\n const wasPosition = contentEl.style.position;\n const wasHeight = contentEl.style.height;\n\n contentEl.style.visibility = 'visible';\n contentEl.style.pointerEvents = 'auto';\n contentEl.style.position = 'static';\n contentEl.style.height = 'auto';\n\n contentEl.offsetHeight;\n\n const topBar = 60;\n const padding = 16;\n const contentHeight = contentEl.scrollHeight;\n\n contentEl.style.visibility = wasVisible;\n contentEl.style.pointerEvents = wasPointerEvents;\n contentEl.style.position = wasPosition;\n contentEl.style.height = wasHeight;\n\n return topBar + contentHeight + padding;\n }\n }\n return 260;\n };\n\n const createTimeline = () => {\n const navEl = navRef.current;\n if (!navEl) return null;\n\n gsap.set(navEl, { height: 60, overflow: 'hidden' });\n gsap.set(cardsRef.current, { y: 50, opacity: 0 });\n\n const tl = gsap.timeline({ paused: true });\n\n tl.to(navEl, {\n height: calculateHeight,\n duration: 0.4,\n ease\n });\n\n tl.to(cardsRef.current, { y: 0, opacity: 1, duration: 0.4, ease, stagger: 0.08 }, '-=0.1');\n\n return tl;\n };\n\n useLayoutEffect(() => {\n const tl = createTimeline();\n tlRef.current = tl;\n\n return () => {\n tl?.kill();\n tlRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ease, items]);\n\n useLayoutEffect(() => {\n const handleResize = () => {\n if (!tlRef.current) return;\n\n if (isExpanded) {\n const newHeight = calculateHeight();\n gsap.set(navRef.current, { height: newHeight });\n\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n newTl.progress(1);\n tlRef.current = newTl;\n }\n } else {\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n tlRef.current = newTl;\n }\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isExpanded]);\n\n const toggleMenu = () => {\n const tl = tlRef.current;\n if (!tl) return;\n if (!isExpanded) {\n setIsHamburgerOpen(true);\n setIsExpanded(true);\n tl.play(0);\n } else {\n setIsHamburgerOpen(false);\n tl.eventCallback('onReverseComplete', () => setIsExpanded(false));\n tl.reverse();\n }\n };\n\n const setCardRef = i => el => {\n if (el) cardsRef.current[i] = el;\n };\n\n return (\n
\n \n
\n );\n};\n\nexport default CardNav;\n" + }, + { + "type": "registry:item", + "path": "src/content/Components/CardNav/CardNav.css", + "content": ".card-nav-container {\n position: absolute;\n top: 2em;\n left: 50%;\n transform: translateX(-50%);\n width: 90%;\n max-width: 800px;\n z-index: 99;\n box-sizing: border-box;\n}\n\n.card-nav {\n display: block;\n height: 60px;\n padding: 0;\n background-color: white;\n border: 0.5px solid rgba(255, 255, 255, 0.1);\n border-radius: 0.75rem;\n box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);\n position: relative;\n overflow: hidden;\n will-change: height;\n}\n\n.card-nav-top {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 60px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 0.45rem 0.55rem 1.1rem;\n z-index: 2;\n}\n\n.hamburger-menu {\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n gap: 6px;\n}\n\n.hamburger-menu:hover .hamburger-line {\n opacity: 0.75;\n}\n\n.hamburger-line {\n width: 30px;\n height: 2px;\n background-color: currentColor;\n transition:\n transform 0.25s ease,\n opacity 0.2s ease,\n margin 0.3s ease;\n transform-origin: 50% 50%;\n}\n\n.hamburger-menu.open .hamburger-line:first-child {\n transform: translateY(4px) rotate(45deg);\n}\n\n.hamburger-menu.open .hamburger-line:last-child {\n transform: translateY(-4px) rotate(-45deg);\n}\n\n.logo-container {\n display: flex;\n align-items: center;\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n}\n\n.logo {\n height: 28px;\n}\n\n.card-nav-cta-button {\n background-color: #111;\n color: white;\n border: none;\n border-radius: calc(0.75rem - 0.35rem);\n padding: 0 1rem;\n height: 100%;\n font-weight: 500;\n cursor: pointer;\n transition: background-color 0.3s ease;\n align-items: center;\n}\n\n.card-nav-cta-button:hover {\n background-color: #333;\n}\n\n.card-nav-content {\n position: absolute;\n left: 0;\n right: 0;\n top: 60px;\n bottom: 0;\n padding: 0.5rem;\n display: flex;\n align-items: flex-end;\n gap: 12px;\n visibility: hidden;\n pointer-events: none;\n z-index: 1;\n}\n\n.card-nav.open .card-nav-content {\n visibility: visible;\n pointer-events: auto;\n}\n\n.nav-card {\n height: 100%;\n flex: 1 1 0;\n min-width: 0;\n border-radius: calc(0.75rem - 0.2rem);\n position: relative;\n display: flex;\n flex-direction: column;\n padding: 12px 16px;\n gap: 8px;\n user-select: none;\n}\n\n.nav-card-label {\n font-weight: 400;\n font-size: 22px;\n letter-spacing: -0.5px;\n}\n\n.nav-card-links {\n margin-top: auto;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.nav-card-link {\n font-size: 16px;\n cursor: pointer;\n text-decoration: none;\n transition: opacity 0.3s ease;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.nav-card-link:hover {\n opacity: 0.75;\n}\n\n@media (max-width: 768px) {\n .card-nav-container {\n width: 90%;\n top: 1.2em;\n }\n\n .card-nav-top {\n padding: 0.5rem 1rem;\n justify-content: space-between;\n }\n\n .hamburger-menu {\n order: 2;\n }\n\n .logo-container {\n position: static;\n transform: none;\n order: 1;\n }\n\n .card-nav-cta-button {\n display: none;\n }\n\n .card-nav-content {\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 0.5rem;\n bottom: 0;\n justify-content: flex-start;\n }\n\n .nav-card {\n height: auto;\n min-height: 60px;\n flex: 1 1 auto;\n max-height: none;\n }\n\n .nav-card-label {\n font-size: 18px;\n }\n\n .nav-card-link {\n font-size: 15px;\n }\n}\n" + } + ], + "registryDependencies": [], + "dependencies": [ + "gsap@^3.13.0", + "react-icons@^5.5.0" + ] } \ No newline at end of file diff --git a/public/r/CardNav-JS-TW.json b/public/r/CardNav-JS-TW.json index 4ad24442..7b62a7fd 100644 --- a/public/r/CardNav-JS-TW.json +++ b/public/r/CardNav-JS-TW.json @@ -1,17 +1,19 @@ { - "$schema": "https://ui.shadcn.com/schema/registry-item.json", - "name": "CardNav-JS-TW", - "type": "registry:block", - "title": "CardNav", - "description": "Expandable navigation bar with card panels revealing nested links.", - "dependencies": [ - "gsap" - ], - "files": [ - { - "path": "public/tailwind/src/tailwind/Components/CardNav/CardNav.jsx", - "content": "import { useLayoutEffect, useRef, useState } from 'react';\nimport { gsap } from 'gsap';\n// use your own icon import if react-icons is not available\nimport { GoArrowUpRight } from 'react-icons/go';\n\nconst CardNav = ({\n logo,\n logoAlt = 'Logo',\n items,\n className = '',\n ease = 'power3.out',\n baseColor = '#fff',\n menuColor,\n buttonBgColor,\n buttonTextColor\n}) => {\n const [isHamburgerOpen, setIsHamburgerOpen] = useState(false);\n const [isExpanded, setIsExpanded] = useState(false);\n const navRef = useRef(null);\n const cardsRef = useRef([]);\n const tlRef = useRef(null);\n\n const calculateHeight = () => {\n const navEl = navRef.current;\n if (!navEl) return 260;\n\n const isMobile = window.matchMedia('(max-width: 768px)').matches;\n if (isMobile) {\n const contentEl = navEl.querySelector('.card-nav-content');\n if (contentEl) {\n const wasVisible = contentEl.style.visibility;\n const wasPointerEvents = contentEl.style.pointerEvents;\n const wasPosition = contentEl.style.position;\n const wasHeight = contentEl.style.height;\n\n contentEl.style.visibility = 'visible';\n contentEl.style.pointerEvents = 'auto';\n contentEl.style.position = 'static';\n contentEl.style.height = 'auto';\n\n contentEl.offsetHeight;\n\n const topBar = 60;\n const padding = 16;\n const contentHeight = contentEl.scrollHeight;\n\n contentEl.style.visibility = wasVisible;\n contentEl.style.pointerEvents = wasPointerEvents;\n contentEl.style.position = wasPosition;\n contentEl.style.height = wasHeight;\n\n return topBar + contentHeight + padding;\n }\n }\n return 260;\n };\n\n const createTimeline = () => {\n const navEl = navRef.current;\n if (!navEl) return null;\n\n gsap.set(navEl, { height: 60, overflow: 'hidden' });\n gsap.set(cardsRef.current, { y: 50, opacity: 0 });\n\n const tl = gsap.timeline({ paused: true });\n\n tl.to(navEl, {\n height: calculateHeight,\n duration: 0.4,\n ease\n });\n\n tl.to(cardsRef.current, { y: 0, opacity: 1, duration: 0.4, ease, stagger: 0.08 }, '-=0.1');\n\n return tl;\n };\n\n useLayoutEffect(() => {\n const tl = createTimeline();\n tlRef.current = tl;\n\n return () => {\n tl?.kill();\n tlRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ease, items]);\n\n useLayoutEffect(() => {\n const handleResize = () => {\n if (!tlRef.current) return;\n\n if (isExpanded) {\n const newHeight = calculateHeight();\n gsap.set(navRef.current, { height: newHeight });\n\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n newTl.progress(1);\n tlRef.current = newTl;\n }\n } else {\n tlRef.current.kill();\n const newTl = createTimeline();\n if (newTl) {\n tlRef.current = newTl;\n }\n }\n };\n\n window.addEventListener('resize', handleResize);\n return () => window.removeEventListener('resize', handleResize);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isExpanded]);\n\n const toggleMenu = () => {\n const tl = tlRef.current;\n if (!tl) return;\n if (!isExpanded) {\n setIsHamburgerOpen(true);\n setIsExpanded(true);\n tl.play(0);\n } else {\n setIsHamburgerOpen(false);\n tl.eventCallback('onReverseComplete', () => setIsExpanded(false));\n tl.reverse();\n }\n };\n\n const setCardRef = i => el => {\n if (el) cardsRef.current[i] = el;\n };\n\n return (\n \n \n
\n \n \n \n
\n\n
\n {logoAlt}\n
\n\n