bits-lab 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -2
  3. package/dist/Accordion.d.ts +52 -0
  4. package/dist/Accordion.js +192 -0
  5. package/dist/AchievementToast.d.ts +36 -0
  6. package/dist/AchievementToast.js +162 -0
  7. package/dist/AgentCard.d.ts +41 -0
  8. package/dist/AgentCard.js +168 -0
  9. package/dist/ArcadeButtons.d.ts +23 -0
  10. package/dist/ArcadeButtons.js +169 -0
  11. package/dist/AuroraBands.d.ts +19 -0
  12. package/dist/AuroraBands.js +126 -0
  13. package/dist/AvatarTooltips.d.ts +32 -0
  14. package/dist/AvatarTooltips.js +121 -0
  15. package/dist/BarRace.d.ts +39 -0
  16. package/dist/BarRace.js +227 -0
  17. package/dist/BentoGrid.d.ts +50 -0
  18. package/dist/BentoGrid.js +145 -0
  19. package/dist/BootSequence.d.ts +48 -0
  20. package/dist/BootSequence.js +234 -0
  21. package/dist/BreadcrumbTrail.d.ts +31 -0
  22. package/dist/BreadcrumbTrail.js +181 -0
  23. package/dist/CRTPower.d.ts +30 -0
  24. package/dist/CRTPower.js +142 -0
  25. package/dist/CardDeck.d.ts +29 -0
  26. package/dist/CardDeck.js +154 -0
  27. package/dist/ChangelogTimeline.d.ts +35 -0
  28. package/dist/ChangelogTimeline.js +137 -0
  29. package/dist/ChapterProgress.d.ts +23 -0
  30. package/dist/ChapterProgress.js +211 -0
  31. package/dist/ChatBubbles.d.ts +42 -0
  32. package/dist/ChatBubbles.js +198 -0
  33. package/dist/ChiptuneKit.d.ts +158 -0
  34. package/dist/ChiptuneKit.js +207 -0
  35. package/dist/CipherCard.d.ts +22 -0
  36. package/dist/CipherCard.js +91 -0
  37. package/dist/CloudSky.d.ts +28 -0
  38. package/dist/CloudSky.js +222 -0
  39. package/dist/CodeRain.d.ts +24 -0
  40. package/dist/CodeRain.js +158 -0
  41. package/dist/CodeShowcase.d.ts +50 -0
  42. package/dist/CodeShowcase.js +307 -0
  43. package/dist/ColorDial.d.ts +42 -0
  44. package/dist/ColorDial.js +288 -0
  45. package/dist/CommandPalette.d.ts +59 -0
  46. package/dist/CommandPalette.js +349 -0
  47. package/dist/CompareSlider.d.ts +33 -0
  48. package/dist/CompareSlider.js +152 -0
  49. package/dist/Confetti.d.ts +61 -0
  50. package/dist/Confetti.js +176 -0
  51. package/dist/ConnectBeams.d.ts +39 -0
  52. package/dist/ConnectBeams.js +132 -0
  53. package/dist/ContextOrb.d.ts +34 -0
  54. package/dist/ContextOrb.js +269 -0
  55. package/dist/ContourLines.d.ts +20 -0
  56. package/dist/ContourLines.js +159 -0
  57. package/dist/Coverflow.d.ts +27 -0
  58. package/dist/Coverflow.js +167 -0
  59. package/dist/CursorAttractor.d.ts +21 -0
  60. package/dist/CursorAttractor.js +136 -0
  61. package/dist/CutReveal.d.ts +21 -0
  62. package/dist/CutReveal.js +116 -0
  63. package/dist/DateRange.d.ts +45 -0
  64. package/dist/DateRange.js +327 -0
  65. package/dist/DeviceFrame.d.ts +30 -0
  66. package/dist/DeviceFrame.js +145 -0
  67. package/dist/DiffReveal.d.ts +36 -0
  68. package/dist/DiffReveal.js +247 -0
  69. package/dist/DirectionHover.d.ts +15 -0
  70. package/dist/DirectionHover.js +90 -0
  71. package/dist/Discard.d.ts +18 -0
  72. package/dist/Discard.js +61 -0
  73. package/dist/DitherField.d.ts +15 -0
  74. package/dist/DitherField.js +157 -0
  75. package/dist/DotGlobe.d.ts +48 -0
  76. package/dist/DotGlobe.js +312 -0
  77. package/dist/DotMap.d.ts +47 -0
  78. package/dist/DotMap.js +172 -0
  79. package/dist/DropZone.d.ts +57 -0
  80. package/dist/DropZone.js +304 -0
  81. package/dist/DynamicIsland.d.ts +32 -0
  82. package/dist/DynamicIsland.js +92 -0
  83. package/dist/EdgeFX.d.ts +27 -0
  84. package/dist/EdgeFX.js +98 -0
  85. package/dist/ElasticString.d.ts +22 -0
  86. package/dist/ElasticString.js +127 -0
  87. package/dist/ExpandCard.d.ts +44 -0
  88. package/dist/ExpandCard.js +182 -0
  89. package/dist/FeatureTabs.d.ts +34 -0
  90. package/dist/FeatureTabs.js +169 -0
  91. package/dist/FeedList.d.ts +37 -0
  92. package/dist/FeedList.js +163 -0
  93. package/dist/FlickerGrid.d.ts +29 -0
  94. package/dist/FlickerGrid.js +156 -0
  95. package/dist/FocusGrid.d.ts +20 -0
  96. package/dist/FocusGrid.js +48 -0
  97. package/dist/FocusPull.d.ts +26 -0
  98. package/dist/FocusPull.js +106 -0
  99. package/dist/Footprints.d.ts +24 -0
  100. package/dist/Footprints.js +126 -0
  101. package/dist/FunnelFlow.d.ts +34 -0
  102. package/dist/FunnelFlow.js +267 -0
  103. package/dist/GameOver.d.ts +46 -0
  104. package/dist/GameOver.js +191 -0
  105. package/dist/GaugeMeter.d.ts +38 -0
  106. package/dist/GaugeMeter.js +195 -0
  107. package/dist/GlassPanel.d.ts +27 -0
  108. package/dist/GlassPanel.js +80 -0
  109. package/dist/GrainFX.d.ts +34 -0
  110. package/dist/GrainFX.js +122 -0
  111. package/dist/GravityBox.d.ts +30 -0
  112. package/dist/GravityBox.js +220 -0
  113. package/dist/HealthBar.d.ts +33 -0
  114. package/dist/HealthBar.js +140 -0
  115. package/dist/Heatmap.d.ts +42 -0
  116. package/dist/Heatmap.js +232 -0
  117. package/dist/HexField.d.ts +29 -0
  118. package/dist/HexField.js +188 -0
  119. package/dist/HoldToConfirm.d.ts +29 -0
  120. package/dist/HoldToConfirm.js +163 -0
  121. package/dist/HorizontalTrack.d.ts +28 -0
  122. package/dist/HorizontalTrack.js +195 -0
  123. package/dist/HotbarDock.d.ts +40 -0
  124. package/dist/HotbarDock.js +148 -0
  125. package/dist/InkText.d.ts +27 -0
  126. package/dist/InkText.js +65 -0
  127. package/dist/InlineEdit.d.ts +30 -0
  128. package/dist/InlineEdit.js +198 -0
  129. package/dist/InteractiveGrid.d.ts +25 -0
  130. package/dist/InteractiveGrid.js +194 -0
  131. package/dist/InventoryGrid.d.ts +45 -0
  132. package/dist/InventoryGrid.js +272 -0
  133. package/dist/JellyBlob.d.ts +30 -0
  134. package/dist/JellyBlob.js +209 -0
  135. package/dist/KineticHeadline.d.ts +30 -0
  136. package/dist/KineticHeadline.js +133 -0
  137. package/dist/LavaLamp.d.ts +21 -0
  138. package/dist/LavaLamp.js +133 -0
  139. package/dist/LetterSwap.d.ts +20 -0
  140. package/dist/LetterSwap.js +78 -0
  141. package/dist/LevelMap.d.ts +35 -0
  142. package/dist/LevelMap.js +189 -0
  143. package/dist/LightShafts.d.ts +22 -0
  144. package/dist/LightShafts.js +170 -0
  145. package/dist/LightboxGallery.d.ts +26 -0
  146. package/dist/LightboxGallery.js +327 -0
  147. package/dist/LinkPeek.d.ts +36 -0
  148. package/dist/LinkPeek.js +194 -0
  149. package/dist/LogoOrbit.d.ts +37 -0
  150. package/dist/LogoOrbit.js +117 -0
  151. package/dist/Marquee.d.ts +33 -0
  152. package/dist/Marquee.js +148 -0
  153. package/dist/MarqueeLine.d.ts +33 -0
  154. package/dist/MarqueeLine.js +167 -0
  155. package/dist/MasonryWall.d.ts +13 -0
  156. package/dist/MasonryWall.js +90 -0
  157. package/dist/MegaMenu.d.ts +32 -0
  158. package/dist/MegaMenu.js +280 -0
  159. package/dist/MeteorShower.d.ts +24 -0
  160. package/dist/MeteorShower.js +155 -0
  161. package/dist/Minimap.d.ts +21 -0
  162. package/dist/Minimap.js +236 -0
  163. package/dist/MobileTabBar.d.ts +38 -0
  164. package/dist/MobileTabBar.js +148 -0
  165. package/dist/MorphDialog.d.ts +34 -0
  166. package/dist/MorphDialog.js +199 -0
  167. package/dist/MorphPopover.d.ts +33 -0
  168. package/dist/MorphPopover.js +168 -0
  169. package/dist/MorphWords.d.ts +18 -0
  170. package/dist/MorphWords.js +136 -0
  171. package/dist/OTPInput.d.ts +43 -0
  172. package/dist/OTPInput.js +174 -0
  173. package/dist/OverlayKit.d.ts +97 -0
  174. package/dist/OverlayKit.js +466 -0
  175. package/dist/OverlayMenu.d.ts +32 -0
  176. package/dist/OverlayMenu.js +154 -0
  177. package/dist/ParallaxCollage.d.ts +41 -0
  178. package/dist/ParallaxCollage.js +143 -0
  179. package/dist/PasswordMeter.d.ts +44 -0
  180. package/dist/PasswordMeter.js +165 -0
  181. package/dist/PegBoard.d.ts +26 -0
  182. package/dist/PegBoard.js +184 -0
  183. package/dist/PinCard.d.ts +21 -0
  184. package/dist/PinCard.js +84 -0
  185. package/dist/PixelAvatar.d.ts +33 -0
  186. package/dist/PixelAvatar.js +144 -0
  187. package/dist/PixelButton.d.ts +20 -0
  188. package/dist/PixelButton.js +128 -0
  189. package/dist/PixelExplode.d.ts +31 -0
  190. package/dist/PixelExplode.js +164 -0
  191. package/dist/PixelLoader.d.ts +32 -0
  192. package/dist/PixelLoader.js +142 -0
  193. package/dist/PixelParallax.d.ts +41 -0
  194. package/dist/PixelParallax.js +151 -0
  195. package/dist/PixelSpinners.d.ts +21 -0
  196. package/dist/PixelSpinners.js +110 -0
  197. package/dist/PixelType.d.ts +26 -0
  198. package/dist/PixelType.js +220 -0
  199. package/dist/PointerKit.d.ts +23 -0
  200. package/dist/PointerKit.js +222 -0
  201. package/dist/Polaroids.d.ts +41 -0
  202. package/dist/Polaroids.js +293 -0
  203. package/dist/PressStart.d.ts +28 -0
  204. package/dist/PressStart.js +99 -0
  205. package/dist/PricingFlip.d.ts +52 -0
  206. package/dist/PricingFlip.js +190 -0
  207. package/dist/ProximityType.d.ts +26 -0
  208. package/dist/ProximityType.js +135 -0
  209. package/dist/QuestDialog.d.ts +50 -0
  210. package/dist/QuestDialog.js +204 -0
  211. package/dist/QuestLog.d.ts +39 -0
  212. package/dist/QuestLog.js +214 -0
  213. package/dist/RangeDual.d.ts +34 -0
  214. package/dist/RangeDual.js +238 -0
  215. package/dist/RatingPips.d.ts +25 -0
  216. package/dist/RatingPips.js +129 -0
  217. package/dist/RetroHorizon.d.ts +27 -0
  218. package/dist/RetroHorizon.js +169 -0
  219. package/dist/Reveal.d.ts +30 -0
  220. package/dist/Reveal.js +92 -0
  221. package/dist/RingProgress.d.ts +45 -0
  222. package/dist/RingProgress.js +180 -0
  223. package/dist/RollingNumber.d.ts +28 -0
  224. package/dist/RollingNumber.js +108 -0
  225. package/dist/ScanScope.d.ts +24 -0
  226. package/dist/ScanScope.js +164 -0
  227. package/dist/ScoreTicker.d.ts +32 -0
  228. package/dist/ScoreTicker.js +176 -0
  229. package/dist/ScreenScroller.d.ts +24 -0
  230. package/dist/ScreenScroller.js +138 -0
  231. package/dist/Screensaver.d.ts +31 -0
  232. package/dist/Screensaver.js +221 -0
  233. package/dist/ScrollTilt.d.ts +27 -0
  234. package/dist/ScrollTilt.js +100 -0
  235. package/dist/ScrollTypography.d.ts +26 -0
  236. package/dist/ScrollTypography.js +124 -0
  237. package/dist/ScrollZoom.d.ts +31 -0
  238. package/dist/ScrollZoom.js +123 -0
  239. package/dist/ScrubNumber.d.ts +29 -0
  240. package/dist/ScrubNumber.js +152 -0
  241. package/dist/ShadowStack.d.ts +28 -0
  242. package/dist/ShadowStack.js +116 -0
  243. package/dist/SideRail.d.ts +43 -0
  244. package/dist/SideRail.js +253 -0
  245. package/dist/SignalText.d.ts +25 -0
  246. package/dist/SignalText.js +178 -0
  247. package/dist/SkeletonShimmer.d.ts +18 -0
  248. package/dist/SkeletonShimmer.js +111 -0
  249. package/dist/SketchHighlight.d.ts +27 -0
  250. package/dist/SketchHighlight.js +180 -0
  251. package/dist/SlideTabs.d.ts +40 -0
  252. package/dist/SlideTabs.js +190 -0
  253. package/dist/SmartNavbar.d.ts +48 -0
  254. package/dist/SmartNavbar.js +188 -0
  255. package/dist/SparkChart.d.ts +41 -0
  256. package/dist/SparkChart.js +188 -0
  257. package/dist/Spoiler.d.ts +22 -0
  258. package/dist/Spoiler.js +97 -0
  259. package/dist/SpotlightTour.d.ts +38 -0
  260. package/dist/SpotlightTour.js +231 -0
  261. package/dist/SpringDrawer.d.ts +39 -0
  262. package/dist/SpringDrawer.js +360 -0
  263. package/dist/SpriteAnimator.d.ts +41 -0
  264. package/dist/SpriteAnimator.js +124 -0
  265. package/dist/StatCard.d.ts +43 -0
  266. package/dist/StatCard.js +168 -0
  267. package/dist/StepLoader.d.ts +41 -0
  268. package/dist/StepLoader.js +191 -0
  269. package/dist/StepperForm.d.ts +41 -0
  270. package/dist/StepperForm.js +206 -0
  271. package/dist/StickerBoard.d.ts +39 -0
  272. package/dist/StickerBoard.js +209 -0
  273. package/dist/StickyReveal.d.ts +42 -0
  274. package/dist/StickyReveal.js +181 -0
  275. package/dist/StoryViewer.d.ts +36 -0
  276. package/dist/StoryViewer.js +175 -0
  277. package/dist/StreamingText.d.ts +29 -0
  278. package/dist/StreamingText.js +271 -0
  279. package/dist/SuggestionChips.d.ts +32 -0
  280. package/dist/SuggestionChips.js +96 -0
  281. package/dist/SwipeActions.d.ts +30 -0
  282. package/dist/SwipeActions.js +141 -0
  283. package/dist/TagInput.d.ts +30 -0
  284. package/dist/TagInput.js +284 -0
  285. package/dist/TallyUp.d.ts +38 -0
  286. package/dist/TallyUp.js +157 -0
  287. package/dist/TerminalDemo.d.ts +63 -0
  288. package/dist/TerminalDemo.js +214 -0
  289. package/dist/TestimonialWall.d.ts +35 -0
  290. package/dist/TestimonialWall.js +131 -0
  291. package/dist/TextOnPath.d.ts +36 -0
  292. package/dist/TextOnPath.js +130 -0
  293. package/dist/ThinkingShimmer.d.ts +36 -0
  294. package/dist/ThinkingShimmer.js +132 -0
  295. package/dist/ToastStack.d.ts +62 -0
  296. package/dist/ToastStack.js +289 -0
  297. package/dist/ToggleGroup.d.ts +39 -0
  298. package/dist/ToggleGroup.js +190 -0
  299. package/dist/ToggleKit.d.ts +70 -0
  300. package/dist/ToggleKit.js +229 -0
  301. package/dist/TracingLine.d.ts +24 -0
  302. package/dist/TracingLine.js +139 -0
  303. package/dist/TradingCard.d.ts +30 -0
  304. package/dist/TradingCard.js +147 -0
  305. package/dist/VanishInput.d.ts +34 -0
  306. package/dist/VanishInput.js +190 -0
  307. package/dist/VideoHero.d.ts +32 -0
  308. package/dist/VideoHero.js +207 -0
  309. package/dist/VoiceOrb.d.ts +23 -0
  310. package/dist/VoiceOrb.js +182 -0
  311. package/dist/VoxelTiles.d.ts +38 -0
  312. package/dist/VoxelTiles.js +201 -0
  313. package/dist/WarpField.d.ts +18 -0
  314. package/dist/WarpField.js +160 -0
  315. package/dist/WebcamPixels.d.ts +38 -0
  316. package/dist/WebcamPixels.js +292 -0
  317. package/dist/WheelPicker.d.ts +29 -0
  318. package/dist/WheelPicker.js +199 -0
  319. package/dist/WobbleCard.d.ts +23 -0
  320. package/dist/WobbleCard.js +112 -0
  321. package/dist/index.d.ts +158 -0
  322. package/dist/index.js +159 -0
  323. package/package.json +58 -4
@@ -0,0 +1,304 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useInsertionEffect, useRef, useState } from "react";
4
+ const STYLE_ID = "bl-dropzone";
5
+ const CSS = `
6
+ .bl-drop { display: grid; gap: 14px; width: 100%; max-width: var(--bl-w); font-family: var(--bl-font); color: var(--bl-ink); }
7
+ .bl-drop__zone { position: relative; display: block; width: 100%; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: center; cursor: pointer; perspective: 800px; }
8
+ .bl-drop__zone:focus-visible { outline: none; }
9
+ .bl-drop__zone:focus-visible .bl-drop__card { border-color: var(--bl-accent); box-shadow: 0 0 0 4px var(--bl-ring); }
10
+ .bl-drop__card { display: grid; justify-items: center; gap: 10px; padding: 34px 20px; border-radius: var(--bl-radius); background: var(--bl-bg); border: 1.5px dashed var(--bl-line); transform: translate(var(--bl-mx, 0px), var(--bl-my, 0px)) rotateX(var(--bl-rx, 0deg)) rotateY(var(--bl-ry, 0deg)); transition: transform 220ms cubic-bezier(.2,.9,.25,1), box-shadow 200ms, border-color 200ms, background-color 200ms; }
11
+ .bl-drop[data-dragging] .bl-drop__card { background: var(--bl-over); border-color: var(--bl-accent); border-style: solid; box-shadow: 0 18px 40px -18px var(--bl-accent); transition-duration: 120ms; }
12
+ .bl-drop__icon { display: grid; place-items: center; width: 52px; height: 52px; border-radius: 50%; background: var(--bl-chip); color: var(--bl-accent); transition: transform 300ms cubic-bezier(.2,.9,.25,1.4); }
13
+ .bl-drop[data-dragging] .bl-drop__icon { transform: translateY(-6px) scale(1.12); }
14
+ .bl-drop__title { margin: 0; font-weight: 700; font-size: 16px; }
15
+ .bl-drop__hint { margin: 0; color: var(--bl-muted); font-size: 13px; }
16
+ .bl-drop__list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
17
+ .bl-drop__file { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; align-items: center; gap: 12px; padding: 10px 12px; border-radius: calc(var(--bl-radius) * 0.7); background: var(--bl-bg); box-shadow: inset 0 0 0 1px var(--bl-line); animation: bl-drop-in 260ms cubic-bezier(.2,.9,.25,1); }
18
+ .bl-drop__thumb { display: grid; place-items: center; width: 44px; height: 44px; overflow: hidden; border-radius: 8px; background: var(--bl-chip); color: var(--bl-accent); font-size: 10px; font-weight: 800; text-transform: uppercase; }
19
+ .bl-drop__thumb img { width: 100%; height: 100%; object-fit: cover; }
20
+ .bl-drop__name { overflow: hidden; font-size: 14px; font-weight: 600; white-space: nowrap; text-overflow: ellipsis; }
21
+ .bl-drop__meta { margin-top: 2px; color: var(--bl-muted); font-size: 12px; }
22
+ .bl-drop__file[data-status="error"] .bl-drop__meta { color: var(--bl-error); }
23
+ .bl-drop__file[data-status="done"] .bl-drop__meta { color: var(--bl-success); }
24
+ .bl-drop__bar { height: 4px; margin-top: 6px; border-radius: 2px; background: var(--bl-chip); overflow: hidden; }
25
+ .bl-drop__fill { height: 100%; border-radius: 2px; background: var(--bl-accent); transform-origin: left; transition: transform 200ms linear, background-color 200ms; }
26
+ .bl-drop__file[data-status="done"] .bl-drop__fill { background: var(--bl-success); }
27
+ .bl-drop__file[data-status="error"] .bl-drop__fill { background: var(--bl-error); }
28
+ .bl-drop__remove { display: grid; place-items: center; width: 30px; height: 30px; border: 0; border-radius: 50%; background: transparent; color: var(--bl-muted); cursor: pointer; }
29
+ .bl-drop__remove:hover { background: var(--bl-chip); color: var(--bl-ink); }
30
+ .bl-drop__remove:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 1px; }
31
+ .bl-drop__rejects { margin: 0; padding: 10px 12px; border-radius: 10px; background: color-mix(in srgb, var(--bl-error) 14%, transparent); color: var(--bl-error); font-size: 13px; list-style: none; }
32
+ .bl-drop__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
33
+ @keyframes bl-drop-in { from { opacity: 0; transform: translateY(-6px); } }
34
+ @media (prefers-reduced-motion: reduce) { .bl-drop__card, .bl-drop__icon, .bl-drop__fill { transition: none; transform: none !important; } .bl-drop__file { animation: none; } }
35
+ `;
36
+ function useStyles() {
37
+ useInsertionEffect(() => {
38
+ if (typeof document === "undefined") return;
39
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
40
+ style.textContent = CSS;
41
+ if (!style.isConnected) document.head.appendChild(style);
42
+ }, []);
43
+ }
44
+ function formatBytes(bytes) {
45
+ if (bytes < 1024) return `${bytes} B`;
46
+ const units = ["KB", "MB", "GB"];
47
+ let value = bytes / 1024;
48
+ let unit = 0;
49
+ while (value >= 1024 && unit < units.length - 1) {
50
+ value /= 1024;
51
+ unit++;
52
+ }
53
+ return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
54
+ }
55
+ function accepts(file, accept) {
56
+ if (!accept) return true;
57
+ const name = file.name.toLowerCase();
58
+ const type = (file.type || "").toLowerCase();
59
+ return accept.split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean).some((rule) => rule.startsWith(".") ? name.endsWith(rule) : rule.endsWith("/*") ? type.startsWith(rule.slice(0, -1)) : type === rule);
60
+ }
61
+ const extension = (name) => name.includes(".") ? name.split(".").pop().slice(0, 4) : "file";
62
+ let nextId = 1;
63
+ function DropZone({
64
+ accept,
65
+ multiple = true,
66
+ maxFiles = 10,
67
+ maxSize,
68
+ upload,
69
+ onFilesChange,
70
+ onReject,
71
+ title = "Drop files here",
72
+ hint,
73
+ disabled = false,
74
+ magnetic = true,
75
+ width = 520,
76
+ radius = 16,
77
+ backgroundColor = "#1a1c2c",
78
+ overColor = "#202a4a",
79
+ textColor = "#f4f4f4",
80
+ mutedColor = "#94b0c2",
81
+ lineColor = "rgba(255,255,255,0.22)",
82
+ chipColor = "rgba(255,255,255,0.07)",
83
+ accentColor = "#73eff7",
84
+ successColor = "#38b764",
85
+ errorColor = "#ef7d57",
86
+ fontFamily = "ui-sans-serif, system-ui, sans-serif",
87
+ className = "",
88
+ style
89
+ }) {
90
+ useStyles();
91
+ const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
92
+ const inputRef = useRef(null);
93
+ const cardRef = useRef(null);
94
+ const depth = useRef(0);
95
+ const controllers = useRef(/* @__PURE__ */ new Map());
96
+ const [files, setFiles] = useState([]);
97
+ const [rejected, setRejected] = useState([]);
98
+ const [dragging, setDragging] = useState(false);
99
+ const [announcement, setAnnouncement] = useState("");
100
+ const filesRef = useRef(files);
101
+ filesRef.current = files;
102
+ const patch = (id, change) => setFiles((prev) => prev.map((f) => f.id === id ? { ...f, ...change } : f));
103
+ useEffect(() => {
104
+ onFilesChange?.(files);
105
+ }, [files]);
106
+ useEffect(
107
+ () => () => {
108
+ filesRef.current.forEach((f) => f.preview && URL.revokeObjectURL(f.preview));
109
+ controllers.current.forEach((controller) => controller.abort());
110
+ },
111
+ []
112
+ );
113
+ const start = (entry) => {
114
+ if (!upload) {
115
+ patch(entry.id, { status: "done", progress: 1 });
116
+ return;
117
+ }
118
+ const controller = new AbortController();
119
+ controllers.current.set(entry.id, controller);
120
+ const onProgress = (fraction) => patch(entry.id, { progress: Math.max(0, Math.min(1, fraction)) });
121
+ Promise.resolve().then(() => upload(entry.file, { onProgress, signal: controller.signal })).then(
122
+ () => {
123
+ if (controller.signal.aborted) return;
124
+ patch(entry.id, { status: "done", progress: 1 });
125
+ setAnnouncement(`${entry.name} uploaded.`);
126
+ },
127
+ (error) => {
128
+ if (controller.signal.aborted) return;
129
+ patch(entry.id, { status: "error", error: error?.message ?? "Upload failed" });
130
+ setAnnouncement(`${entry.name} failed to upload.`);
131
+ }
132
+ ).finally(() => controllers.current.delete(entry.id));
133
+ };
134
+ const addFiles = (list) => {
135
+ const incoming = Array.from(list ?? []);
136
+ const room = Math.max(0, (multiple ? maxFiles : 1) - filesRef.current.length);
137
+ const accepted = [];
138
+ const rejects = [];
139
+ for (const file of incoming) {
140
+ if (!accepts(file, accept)) rejects.push({ file, reason: "File type not allowed" });
141
+ else if (maxSize && file.size > maxSize) rejects.push({ file, reason: `Larger than ${formatBytes(maxSize)}` });
142
+ else if (accepted.length >= room) rejects.push({ file, reason: multiple ? `Limit of ${maxFiles} files` : "Only one file allowed" });
143
+ else accepted.push(file);
144
+ }
145
+ const entries = accepted.map((file) => ({
146
+ id: nextId++,
147
+ file,
148
+ name: file.name,
149
+ size: file.size,
150
+ type: file.type,
151
+ preview: file.type.startsWith("image/") ? URL.createObjectURL(file) : null,
152
+ progress: 0,
153
+ status: upload ? "uploading" : "done",
154
+ error: null
155
+ }));
156
+ setRejected(rejects);
157
+ if (rejects.length) onReject?.(rejects);
158
+ if (entries.length) {
159
+ setFiles((prev) => [...prev, ...entries]);
160
+ entries.forEach(start);
161
+ }
162
+ setAnnouncement(
163
+ [entries.length ? `${entries.length} file${entries.length === 1 ? "" : "s"} added.` : "", rejects.length ? `${rejects.length} rejected.` : ""].filter(Boolean).join(" ")
164
+ );
165
+ };
166
+ const remove = (id) => {
167
+ const entry = filesRef.current.find((f) => f.id === id);
168
+ controllers.current.get(id)?.abort();
169
+ controllers.current.delete(id);
170
+ if (entry?.preview) URL.revokeObjectURL(entry.preview);
171
+ setFiles((prev) => prev.filter((f) => f.id !== id));
172
+ if (entry) setAnnouncement(`${entry.name} removed.`);
173
+ };
174
+ const lean = (event) => {
175
+ const card = cardRef.current;
176
+ if (!magnetic || !card || window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
177
+ const rect = card.getBoundingClientRect();
178
+ const x = (event.clientX - rect.left) / rect.width - 0.5;
179
+ const y = (event.clientY - rect.top) / rect.height - 0.5;
180
+ card.style.setProperty("--bl-mx", `${x * 14}px`);
181
+ card.style.setProperty("--bl-my", `${y * 10}px`);
182
+ card.style.setProperty("--bl-rx", `${-y * 6}deg`);
183
+ card.style.setProperty("--bl-ry", `${x * 8}deg`);
184
+ };
185
+ const settle = () => {
186
+ for (const name of ["--bl-mx", "--bl-my", "--bl-rx", "--bl-ry"]) cardRef.current?.style.removeProperty(name);
187
+ };
188
+ const hasFiles = (event) => Array.from(event.dataTransfer?.types ?? []).includes("Files");
189
+ return /* @__PURE__ */ jsxs(
190
+ "div",
191
+ {
192
+ className: `bl-drop ${className}`,
193
+ "data-dragging": dragging || void 0,
194
+ style: {
195
+ "--bl-w": `${width}px`,
196
+ "--bl-radius": `${radius}px`,
197
+ "--bl-bg": backgroundColor,
198
+ "--bl-over": overColor,
199
+ "--bl-ink": textColor,
200
+ "--bl-muted": mutedColor,
201
+ "--bl-line": lineColor,
202
+ "--bl-chip": chipColor,
203
+ "--bl-accent": accentColor,
204
+ "--bl-ring": `color-mix(in srgb, ${accentColor} 22%, transparent)`,
205
+ "--bl-success": successColor,
206
+ "--bl-error": errorColor,
207
+ "--bl-font": fontFamily,
208
+ ...style
209
+ },
210
+ children: [
211
+ /* @__PURE__ */ jsx(
212
+ "button",
213
+ {
214
+ type: "button",
215
+ className: "bl-drop__zone",
216
+ disabled,
217
+ "aria-describedby": `${uid}-hint`,
218
+ onClick: () => inputRef.current?.click(),
219
+ onDragEnter: (event) => {
220
+ if (!hasFiles(event) || disabled) return;
221
+ event.preventDefault();
222
+ depth.current += 1;
223
+ setDragging(true);
224
+ },
225
+ onDragOver: (event) => {
226
+ if (!hasFiles(event) || disabled) return;
227
+ event.preventDefault();
228
+ event.dataTransfer.dropEffect = "copy";
229
+ lean(event);
230
+ },
231
+ onDragLeave: () => {
232
+ depth.current = Math.max(0, depth.current - 1);
233
+ if (!depth.current) {
234
+ setDragging(false);
235
+ settle();
236
+ }
237
+ },
238
+ onDrop: (event) => {
239
+ event.preventDefault();
240
+ depth.current = 0;
241
+ setDragging(false);
242
+ settle();
243
+ if (!disabled) addFiles(event.dataTransfer.files);
244
+ },
245
+ children: /* @__PURE__ */ jsxs("span", { ref: cardRef, className: "bl-drop__card", children: [
246
+ /* @__PURE__ */ jsx("span", { className: "bl-drop__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M12 16V4M7 9l5-5 5 5M4 16v3a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-3" }) }) }),
247
+ /* @__PURE__ */ jsx("span", { className: "bl-drop__title", children: dragging ? "Release to add" : title }),
248
+ /* @__PURE__ */ jsx("span", { id: `${uid}-hint`, className: "bl-drop__hint", children: hint ?? [`Click or press Enter to browse`, accept && `\xB7 ${accept}`, maxSize && `\xB7 up to ${formatBytes(maxSize)}`].filter(Boolean).join(" ") })
249
+ ] })
250
+ }
251
+ ),
252
+ /* @__PURE__ */ jsx(
253
+ "input",
254
+ {
255
+ ref: inputRef,
256
+ type: "file",
257
+ hidden: true,
258
+ accept,
259
+ multiple,
260
+ onChange: (event) => {
261
+ addFiles(event.target.files);
262
+ event.target.value = "";
263
+ }
264
+ }
265
+ ),
266
+ rejected.length > 0 && /* @__PURE__ */ jsx("ul", { className: "bl-drop__rejects", children: rejected.map(({ file, reason }, i) => /* @__PURE__ */ jsxs("li", { children: [
267
+ file.name,
268
+ ": ",
269
+ reason
270
+ ] }, `${file.name}-${i}`)) }),
271
+ files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "bl-drop__list", "aria-label": "Files", children: files.map((entry) => /* @__PURE__ */ jsxs("li", { className: "bl-drop__file", "data-status": entry.status, children: [
272
+ /* @__PURE__ */ jsx("span", { className: "bl-drop__thumb", "aria-hidden": "true", children: entry.preview ? /* @__PURE__ */ jsx("img", { src: entry.preview, alt: "" }) : extension(entry.name) }),
273
+ /* @__PURE__ */ jsxs("span", { style: { minWidth: 0 }, children: [
274
+ /* @__PURE__ */ jsx("span", { className: "bl-drop__name", style: { display: "block" }, children: entry.name }),
275
+ /* @__PURE__ */ jsxs("span", { className: "bl-drop__meta", style: { display: "block" }, children: [
276
+ formatBytes(entry.size),
277
+ " \xB7 ",
278
+ entry.status === "uploading" ? `${Math.round(entry.progress * 100)}%` : entry.status === "done" ? "Done" : entry.error
279
+ ] }),
280
+ upload && /* @__PURE__ */ jsx(
281
+ "span",
282
+ {
283
+ className: "bl-drop__bar",
284
+ style: { display: "block" },
285
+ role: "progressbar",
286
+ "aria-label": `${entry.name} upload`,
287
+ "aria-valuemin": 0,
288
+ "aria-valuemax": 100,
289
+ "aria-valuenow": Math.round(entry.progress * 100),
290
+ children: /* @__PURE__ */ jsx("span", { className: "bl-drop__fill", style: { display: "block", transform: `scaleX(${entry.progress})` } })
291
+ }
292
+ )
293
+ ] }),
294
+ /* @__PURE__ */ jsx("button", { type: "button", className: "bl-drop__remove", "aria-label": `Remove ${entry.name}`, onClick: () => remove(entry.id), children: /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2 2l8 8M10 2 2 10" }) }) })
295
+ ] }, entry.id)) }),
296
+ /* @__PURE__ */ jsx("span", { className: "bl-drop__sr", "aria-live": "polite", children: announcement })
297
+ ]
298
+ }
299
+ );
300
+ }
301
+ export {
302
+ DropZone as default,
303
+ formatBytes
304
+ };
@@ -0,0 +1,32 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ export interface IslandView {
3
+ /** Width of the island in this view, in px. */
4
+ width: number;
5
+ /** Height of the island in this view, in px. */
6
+ height: number;
7
+ /** Corner radius; defaults to a pill (half the height, capped at 44). */
8
+ radius?: number;
9
+ /** Announced politely to screen readers when this view appears. */
10
+ label?: string;
11
+ content?: ReactNode;
12
+ }
13
+ export interface DynamicIslandProps {
14
+ /** Every state the island can take. */
15
+ views?: Record<string, IslandView>;
16
+ /** The current view's key. Change it and the island morphs. */
17
+ view?: string;
18
+ /** Makes the island pressable (Enter/Space too). Leave it off when the view has its own buttons. */
19
+ onPress?: (view: string) => void;
20
+ /** Morph length, in ms. */
21
+ duration?: number;
22
+ /** Overshoot: 0 is a plain ease-out, 1 is very springy. */
23
+ bounce?: number;
24
+ backgroundColor?: string;
25
+ textColor?: string;
26
+ lineColor?: string;
27
+ accentColor?: string;
28
+ fontFamily?: string;
29
+ className?: string;
30
+ style?: CSSProperties;
31
+ }
32
+ export default function DynamicIsland({ views, view, onPress, duration, bounce, backgroundColor, textColor, lineColor, accentColor, fontFamily, className, style }: DynamicIslandProps): import("react").JSX.Element;
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useInsertionEffect, useRef, useState } from "react";
4
+ const STYLE_ID = "bl-dynamic-island";
5
+ const CSS = `
6
+ .bl-island { position: relative; display: block; margin: 0 auto; overflow: hidden; width: var(--bl-w); height: var(--bl-h); max-width: 100%; border-radius: var(--bl-r); background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); box-shadow: 0 0 0 1px var(--bl-line), 0 12px 30px -12px rgba(0,0,0,0.6); transition: width var(--bl-dur) var(--bl-ease), height var(--bl-dur) var(--bl-ease), border-radius var(--bl-dur) var(--bl-ease); }
7
+ [role="button"].bl-island { cursor: pointer; }
8
+ .bl-island:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 3px; }
9
+ .bl-island__layer { position: absolute; inset: 0; display: flex; align-items: center; transition: opacity calc(var(--bl-dur) * 0.6) ease, filter calc(var(--bl-dur) * 0.6) ease, transform var(--bl-dur) var(--bl-ease); }
10
+ .bl-island__layer[data-state="in"] { animation: bl-island-in calc(var(--bl-dur) * 0.75) ease calc(var(--bl-dur) * 0.15) both; }
11
+ @keyframes bl-island-in { from { opacity: 0; filter: blur(6px); transform: scale(0.92); } }
12
+ .bl-island__layer[data-state="out"] { opacity: 0; filter: blur(6px); transform: scale(1.04); pointer-events: none; }
13
+ .bl-island__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
14
+ @media (prefers-reduced-motion: reduce) { .bl-island, .bl-island__layer { transition: none !important; animation: none !important; } }
15
+ `;
16
+ function useStyles() {
17
+ useInsertionEffect(() => {
18
+ if (typeof document === "undefined") return;
19
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
20
+ style.textContent = CSS;
21
+ if (!style.isConnected) document.head.appendChild(style);
22
+ }, []);
23
+ }
24
+ function DynamicIsland({
25
+ views = {},
26
+ view = "idle",
27
+ onPress,
28
+ duration = 520,
29
+ bounce = 0.35,
30
+ backgroundColor = "#000000",
31
+ textColor = "#f4f4f4",
32
+ lineColor = "rgba(255,255,255,0.08)",
33
+ accentColor = "#73eff7",
34
+ fontFamily = "ui-sans-serif, system-ui, sans-serif",
35
+ className = "",
36
+ style
37
+ }) {
38
+ useStyles();
39
+ const current = views[view] ?? { width: 126, height: 36 };
40
+ const [layers, setLayers] = useState(() => [{ key: view, state: "in" }]);
41
+ const timer = useRef(0);
42
+ useEffect(() => {
43
+ setLayers((prev) => {
44
+ if (prev.some((layer) => layer.key === view && layer.state === "in")) return prev;
45
+ return [...prev.filter((layer) => layer.key !== view).map((layer) => ({ ...layer, state: "out" })), { key: view, state: "in" }];
46
+ });
47
+ clearTimeout(timer.current);
48
+ timer.current = setTimeout(() => setLayers((prev) => prev.filter((layer) => layer.state !== "out")), duration);
49
+ }, [view, duration]);
50
+ useEffect(() => () => clearTimeout(timer.current), []);
51
+ const radius = current.radius ?? Math.min(current.height / 2, 44);
52
+ const ease = `cubic-bezier(.3, ${(1 + bounce * 1.2).toFixed(2)}, .5, 1)`;
53
+ return (
54
+ // Always a div, so switching onPress on and off never remounts it mid-animation.
55
+ /* @__PURE__ */ jsxs(
56
+ "div",
57
+ {
58
+ className: `bl-island ${className}`,
59
+ role: onPress ? "button" : void 0,
60
+ tabIndex: onPress ? 0 : void 0,
61
+ onClick: onPress ? () => onPress(view) : void 0,
62
+ onKeyDown: onPress ? (event) => {
63
+ if (event.key === "Enter" || event.key === " ") {
64
+ event.preventDefault();
65
+ onPress(view);
66
+ }
67
+ } : void 0,
68
+ "aria-label": onPress ? current.label ?? view : void 0,
69
+ style: {
70
+ "--bl-w": `${current.width}px`,
71
+ "--bl-h": `${current.height}px`,
72
+ "--bl-r": `${radius}px`,
73
+ "--bl-dur": `${duration}ms`,
74
+ "--bl-ease": ease,
75
+ "--bl-bg": backgroundColor,
76
+ "--bl-ink": textColor,
77
+ "--bl-line": lineColor,
78
+ "--bl-accent": accentColor,
79
+ "--bl-font": fontFamily,
80
+ ...style
81
+ },
82
+ children: [
83
+ layers.map((layer) => /* @__PURE__ */ jsx("span", { className: "bl-island__layer", "data-state": layer.state, "aria-hidden": layer.state === "out" || void 0, inert: layer.state === "out" ? true : void 0, children: views[layer.key]?.content }, layer.key)),
84
+ /* @__PURE__ */ jsx("span", { className: "bl-island__sr", role: "status", "aria-live": "polite", children: current.label ?? "" })
85
+ ]
86
+ }
87
+ )
88
+ );
89
+ }
90
+ export {
91
+ DynamicIsland as default
92
+ };
@@ -0,0 +1,27 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ export interface EdgeFXProps {
3
+ /** The card inside the frame. */
4
+ children?: ReactNode;
5
+ /** Marching pixel dashes, a band circling the frame, a pointer-following light, or a flash across the surface. */
6
+ mode?: 'ants' | 'energy' | 'glow' | 'glare';
7
+ /** Hover also lights it while focus is inside. */
8
+ active?: 'always' | 'hover';
9
+ /** Edge thickness in px. */
10
+ width?: number;
11
+ radius?: number;
12
+ /** Dash length for ants, in px. */
13
+ dash?: number;
14
+ /** Cycle speed multiplier. */
15
+ speed?: number;
16
+ /** How far from the card the glow notices the pointer, in px. */
17
+ reach?: number;
18
+ color?: string;
19
+ secondColor?: string;
20
+ /** The resting edge. */
21
+ lineColor?: string;
22
+ backgroundColor?: string;
23
+ glareColor?: string;
24
+ className?: string;
25
+ style?: CSSProperties;
26
+ }
27
+ export default function EdgeFX({ children, mode, active, width, radius, dash, speed, reach, color, secondColor, lineColor, backgroundColor, glareColor, className, style }: EdgeFXProps): import("react").JSX.Element;
package/dist/EdgeFX.js ADDED
@@ -0,0 +1,98 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useInsertionEffect, useRef } from "react";
4
+ const STYLE_ID = "bl-edge-fx";
5
+ const CSS = `
6
+ @property --bl-edge-turn { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
7
+ .bl-edge { --bl-on: 1; position: relative; display: block; box-sizing: border-box; padding: var(--bl-w); border-radius: var(--bl-radius); background: var(--bl-line); isolation: isolate; }
8
+ .bl-edge__body { position: relative; z-index: 1; height: 100%; overflow: hidden; border-radius: calc(var(--bl-radius) - var(--bl-w)); background: var(--bl-bg); }
9
+ .bl-edge__ring { position: absolute; inset: 0; z-index: 0; border-radius: inherit; opacity: var(--bl-on); transition: opacity 200ms steps(3); pointer-events: none; }
10
+ .bl-edge[data-active="hover"] { --bl-on: 0; }
11
+ .bl-edge[data-active="hover"]:hover, .bl-edge[data-active="hover"]:focus-within { --bl-on: 1; }
12
+ .bl-edge[data-mode="ants"] .bl-edge__ring { background:
13
+ linear-gradient(90deg, var(--bl-a) 50%, var(--bl-b) 0) 0 0 / var(--bl-dash) var(--bl-w) repeat-x,
14
+ linear-gradient(90deg, var(--bl-a) 50%, var(--bl-b) 0) 0 100% / var(--bl-dash) var(--bl-w) repeat-x,
15
+ linear-gradient(0deg, var(--bl-a) 50%, var(--bl-b) 0) 0 0 / var(--bl-w) var(--bl-dash) repeat-y,
16
+ linear-gradient(0deg, var(--bl-a) 50%, var(--bl-b) 0) 100% 0 / var(--bl-w) var(--bl-dash) repeat-y;
17
+ animation: bl-edge-march var(--bl-speed) steps(4) infinite; }
18
+ .bl-edge[data-mode="energy"] .bl-edge__ring { background: conic-gradient(from var(--bl-edge-turn), transparent 0 55%, var(--bl-b) 70%, var(--bl-a) 80%, transparent 92%); animation: bl-edge-spin var(--bl-speed) steps(36) infinite; }
19
+ .bl-edge[data-mode="glow"] .bl-edge__ring { background: radial-gradient(circle var(--bl-reach) at var(--bl-x, 50%) var(--bl-y, -40%), var(--bl-a), var(--bl-b) 45%, transparent 75%); }
20
+ .bl-edge[data-mode="glow"][data-active="hover"] { --bl-on: 0; }
21
+ .bl-edge[data-mode="glow"][data-lit] { --bl-on: 1; }
22
+ .bl-edge__glare { position: absolute; inset: 0; z-index: 2; border-radius: inherit; overflow: hidden; pointer-events: none; }
23
+ .bl-edge__glare::before { content: ""; position: absolute; top: -50%; bottom: -50%; left: 0; width: 40%; background: linear-gradient(100deg, transparent, var(--bl-glare), transparent); transform: translateX(-150%) skewX(-12deg); }
24
+ .bl-edge[data-mode="glare"]:hover .bl-edge__glare::before, .bl-edge[data-mode="glare"]:focus-within .bl-edge__glare::before, .bl-edge[data-mode="glare"][data-active="always"] .bl-edge__glare::before { animation: bl-edge-glare var(--bl-speed) steps(12) infinite; }
25
+ @keyframes bl-edge-march { to { background-position: var(--bl-dash) 0, calc(var(--bl-dash) * -1) 100%, 0 calc(var(--bl-dash) * -1), 100% var(--bl-dash); } }
26
+ @keyframes bl-edge-spin { to { --bl-edge-turn: 360deg; } }
27
+ @keyframes bl-edge-glare { 0% { transform: translateX(-150%) skewX(-12deg); } 60%, 100% { transform: translateX(350%) skewX(-12deg); } }
28
+ @media (prefers-reduced-motion: reduce) { .bl-edge .bl-edge__ring, .bl-edge .bl-edge__glare::before { animation: none !important; transition: none; } }
29
+ `;
30
+ function useStyles() {
31
+ useInsertionEffect(() => {
32
+ if (typeof document === "undefined") return;
33
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
34
+ style.textContent = CSS;
35
+ if (!style.isConnected) document.head.appendChild(style);
36
+ }, []);
37
+ }
38
+ function EdgeFX({
39
+ children,
40
+ mode = "ants",
41
+ active = "always",
42
+ width = 3,
43
+ radius = 14,
44
+ dash = 12,
45
+ speed = 0.8,
46
+ reach = 160,
47
+ color = "#73eff7",
48
+ secondColor = "#41a6f6",
49
+ lineColor = "#333c57",
50
+ backgroundColor = "#1a1c2c",
51
+ glareColor = "rgba(244,244,244,0.22)",
52
+ className = "",
53
+ style
54
+ }) {
55
+ useStyles();
56
+ const rootRef = useRef(null);
57
+ useEffect(() => {
58
+ if (mode !== "glow") return void 0;
59
+ const root = rootRef.current;
60
+ let raf = 0;
61
+ const onMove = (event) => {
62
+ if (raf) return;
63
+ raf = requestAnimationFrame(() => {
64
+ raf = 0;
65
+ const r = root.getBoundingClientRect();
66
+ const x = event.clientX - r.left;
67
+ const y = event.clientY - r.top;
68
+ const near = x > -reach && y > -reach && x < r.width + reach && y < r.height + reach;
69
+ root.style.setProperty("--bl-x", `${x.toFixed(0)}px`);
70
+ root.style.setProperty("--bl-y", `${y.toFixed(0)}px`);
71
+ root.toggleAttribute("data-lit", near);
72
+ });
73
+ };
74
+ window.addEventListener("pointermove", onMove, { passive: true });
75
+ return () => {
76
+ cancelAnimationFrame(raf);
77
+ window.removeEventListener("pointermove", onMove);
78
+ };
79
+ }, [mode, reach]);
80
+ return /* @__PURE__ */ jsxs(
81
+ "div",
82
+ {
83
+ ref: rootRef,
84
+ className: `bl-edge ${className}`,
85
+ "data-mode": mode,
86
+ "data-active": active,
87
+ style: { "--bl-w": `${width}px`, "--bl-radius": `${radius}px`, "--bl-dash": `${dash}px`, "--bl-speed": `${speed * (mode === "energy" ? 4 : mode === "glare" ? 3 : 1)}s`, "--bl-reach": `${reach}px`, "--bl-a": color, "--bl-b": mode === "ants" ? lineColor : secondColor, "--bl-line": lineColor, "--bl-bg": backgroundColor, "--bl-glare": glareColor, ...style },
88
+ children: [
89
+ /* @__PURE__ */ jsx("span", { className: "bl-edge__ring", "aria-hidden": "true" }),
90
+ /* @__PURE__ */ jsx("div", { className: "bl-edge__body", children }),
91
+ mode === "glare" && /* @__PURE__ */ jsx("span", { className: "bl-edge__glare", "aria-hidden": "true" })
92
+ ]
93
+ }
94
+ );
95
+ }
96
+ export {
97
+ EdgeFX as default
98
+ };
@@ -0,0 +1,22 @@
1
+ import { type CSSProperties } from 'react';
2
+ export interface ElasticStringProps {
3
+ /** How many strings. */
4
+ strings?: number;
5
+ height?: number;
6
+ /** Fixed spacing between strings in px (default: spread evenly). */
7
+ gap?: number;
8
+ thickness?: number;
9
+ /** Colours, cycled per string. */
10
+ colors?: string[];
11
+ /** How close (px) the pointer must come to catch a string. */
12
+ grab?: number;
13
+ /** How far (px) a string bends before it slips free. */
14
+ maxPull?: number;
15
+ stiffness?: number;
16
+ damping?: number;
17
+ /** Called on release with the string index and a 0–1 strength. */
18
+ onPluck?: (index: number, strength: number) => void;
19
+ className?: string;
20
+ style?: CSSProperties;
21
+ }
22
+ export default function ElasticString({ strings, height, gap, thickness, colors, grab, maxPull, stiffness, damping, onPluck, className, style }: ElasticStringProps): import("react").JSX.Element;