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,209 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useInsertionEffect, useRef } from "react";
4
+ const STYLE_ID = "bl-jelly-blob";
5
+ const CSS = `
6
+ .bl-jelly { position: relative; display: inline-grid; place-items: center; box-sizing: border-box; width: var(--bl-w); height: var(--bl-h); margin: 0; padding: 0 24px; border: 0; background: transparent; color: var(--bl-ink); font: inherit; font-family: var(--bl-font); font-size: var(--bl-size); font-weight: 700; cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
7
+ .bl-jelly:focus-visible { outline: none; }
8
+ .bl-jelly:focus-visible .bl-jelly__ring { opacity: 1; }
9
+ .bl-jelly:disabled { cursor: not-allowed; opacity: 0.5; }
10
+ .bl-jelly__blob { position: absolute; inset: -16px; width: calc(100% + 32px); height: calc(100% + 32px); overflow: visible; pointer-events: none; }
11
+ .bl-jelly__fill { fill: var(--bl-color); transition: fill 150ms; }
12
+ .bl-jelly__shade { fill: rgba(0,0,0,0.28); }
13
+ .bl-jelly__ring { fill: none; stroke: var(--bl-accent); stroke-width: 3; opacity: 0; transition: opacity 150ms; }
14
+ .bl-jelly[data-pressed] .bl-jelly__fill { fill: var(--bl-pressed); }
15
+ .bl-jelly__label { position: relative; display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; pointer-events: none; }
16
+ `;
17
+ function useStyles() {
18
+ useInsertionEffect(() => {
19
+ if (typeof document === "undefined") return;
20
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
21
+ style.textContent = CSS;
22
+ if (!style.isConnected) document.head.appendChild(style);
23
+ }, []);
24
+ }
25
+ function smoothPath(pts) {
26
+ const n = pts.length;
27
+ let d = `M${pts[0][0].toFixed(2)} ${pts[0][1].toFixed(2)}`;
28
+ for (let i = 0; i < n; i++) {
29
+ const [p0, p1, p2, p3] = [pts[(i - 1 + n) % n], pts[i], pts[(i + 1) % n], pts[(i + 2) % n]];
30
+ const c1 = [p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6];
31
+ const c2 = [p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6];
32
+ d += `C${c1[0].toFixed(2)} ${c1[1].toFixed(2)} ${c2[0].toFixed(2)} ${c2[1].toFixed(2)} ${p2[0].toFixed(2)} ${p2[1].toFixed(2)}`;
33
+ }
34
+ return `${d}Z`;
35
+ }
36
+ function JellyBlob({
37
+ children,
38
+ onClick,
39
+ width = 220,
40
+ height = 72,
41
+ points = 18,
42
+ bulge = 10,
43
+ squash = 9,
44
+ stiffness = 320,
45
+ damping = 9,
46
+ idle = true,
47
+ color = "#b13e53",
48
+ pressedColor = "#8a2f43",
49
+ textColor = "#f4f4f4",
50
+ accentColor = "#73eff7",
51
+ fontSize = 18,
52
+ fontFamily = "ui-sans-serif, system-ui, sans-serif",
53
+ type = "button",
54
+ disabled = false,
55
+ className = "",
56
+ style,
57
+ ...rest
58
+ }) {
59
+ useStyles();
60
+ const buttonRef = useRef(null);
61
+ const fillRef = useRef(null);
62
+ const shadeRef = useRef(null);
63
+ const ringRef = useRef(null);
64
+ useEffect(() => {
65
+ const button = buttonRef.current;
66
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
67
+ const pad = 16;
68
+ const cx = width / 2 + pad;
69
+ const cy = height / 2 + pad;
70
+ const a = width / 2;
71
+ const b = height / 2;
72
+ const angles = Array.from({ length: points }, (_, i) => i / points * Math.PI * 2);
73
+ const base = angles.map((t) => Math.pow(Math.pow(Math.abs(Math.cos(t) / a), 4) + Math.pow(Math.abs(Math.sin(t) / b), 4), -1 / 4));
74
+ const offset = angles.map(() => 0);
75
+ const velocity = angles.map(() => 0);
76
+ const pointer = { inside: false, angle: 0, near: 0 };
77
+ let pressed = false;
78
+ let visible = true;
79
+ let frame = 0;
80
+ let last = 0;
81
+ let clock = 0;
82
+ const draw = () => {
83
+ const pts = angles.map((t, i) => {
84
+ const r = base[i] + offset[i];
85
+ return [cx + Math.cos(t) * r, cy + Math.sin(t) * r];
86
+ });
87
+ const d = smoothPath(pts);
88
+ fillRef.current.setAttribute("d", d);
89
+ ringRef.current.setAttribute("d", smoothPath(angles.map((t, i) => [cx + Math.cos(t) * (base[i] + offset[i] + 5), cy + Math.sin(t) * (base[i] + offset[i] + 5)])));
90
+ shadeRef.current.setAttribute("d", d);
91
+ };
92
+ const target = (i) => {
93
+ const t = angles[i];
94
+ let goal = 0;
95
+ if (pressed) goal += squash * (0.7 * Math.cos(t) ** 2 - Math.sin(t) ** 2);
96
+ if (pointer.inside) {
97
+ const delta = Math.abs((t - pointer.angle + Math.PI * 3) % (Math.PI * 2) - Math.PI);
98
+ goal += bulge * pointer.near * Math.max(0, Math.cos(delta)) ** 6;
99
+ }
100
+ if (idle && !pressed) goal += 1.6 * Math.sin(clock * 2.2 + t * 3);
101
+ return goal;
102
+ };
103
+ const tick = (now) => {
104
+ const dt = Math.min(0.05, (now - last) / 1e3 || 0.016);
105
+ last = now;
106
+ clock += dt;
107
+ let moving = false;
108
+ for (let i = 0; i < points; i++) {
109
+ const goal = target(i);
110
+ for (let t = 0; t < dt; t += 1 / 240) {
111
+ velocity[i] += (stiffness * (goal - offset[i]) - damping * velocity[i]) / 240;
112
+ offset[i] += velocity[i] / 240;
113
+ }
114
+ if (Math.abs(goal - offset[i]) > 0.02 || Math.abs(velocity[i]) > 0.2) moving = true;
115
+ }
116
+ draw();
117
+ frame = visible && (moving || idle) ? requestAnimationFrame(tick) : 0;
118
+ };
119
+ const wake = () => {
120
+ if (reduced || frame || !visible) return;
121
+ last = performance.now();
122
+ frame = requestAnimationFrame(tick);
123
+ };
124
+ const setPressed = (value) => {
125
+ if (disabled) return;
126
+ pressed = value;
127
+ if (value) button.dataset.pressed = "";
128
+ else {
129
+ delete button.dataset.pressed;
130
+ angles.forEach((t, i) => {
131
+ velocity[i] += squash * 14 * (Math.sin(t) ** 2 - 0.5);
132
+ });
133
+ }
134
+ wake();
135
+ };
136
+ const onMove = (event) => {
137
+ const rect = button.getBoundingClientRect();
138
+ const x = event.clientX - rect.left - rect.width / 2;
139
+ const y = event.clientY - rect.top - rect.height / 2;
140
+ pointer.inside = true;
141
+ pointer.angle = Math.atan2(y, x);
142
+ pointer.near = Math.min(1, Math.hypot(x / (rect.width / 2), y / (rect.height / 2)));
143
+ wake();
144
+ };
145
+ const onLeave = () => {
146
+ pointer.inside = false;
147
+ if (pressed) setPressed(false);
148
+ wake();
149
+ };
150
+ const onDown = (event) => {
151
+ if (event.button === 0) setPressed(true);
152
+ };
153
+ const onUp = () => pressed && setPressed(false);
154
+ const onKeyDown = (event) => {
155
+ if ((event.key === " " || event.key === "Enter") && !event.repeat) setPressed(true);
156
+ };
157
+ const onKeyUp = (event) => {
158
+ if (event.key === " " || event.key === "Enter") setPressed(false);
159
+ };
160
+ const observer = new IntersectionObserver(([entry]) => {
161
+ visible = entry.isIntersecting;
162
+ if (visible) wake();
163
+ });
164
+ observer.observe(button);
165
+ button.addEventListener("pointermove", onMove);
166
+ button.addEventListener("pointerleave", onLeave);
167
+ button.addEventListener("pointerdown", onDown);
168
+ window.addEventListener("pointerup", onUp);
169
+ button.addEventListener("keydown", onKeyDown);
170
+ button.addEventListener("keyup", onKeyUp);
171
+ button.addEventListener("blur", onUp);
172
+ draw();
173
+ wake();
174
+ return () => {
175
+ cancelAnimationFrame(frame);
176
+ observer.disconnect();
177
+ button.removeEventListener("pointermove", onMove);
178
+ button.removeEventListener("pointerleave", onLeave);
179
+ button.removeEventListener("pointerdown", onDown);
180
+ window.removeEventListener("pointerup", onUp);
181
+ button.removeEventListener("keydown", onKeyDown);
182
+ button.removeEventListener("keyup", onKeyUp);
183
+ button.removeEventListener("blur", onUp);
184
+ };
185
+ }, [width, height, points, bulge, squash, stiffness, damping, idle, disabled]);
186
+ return /* @__PURE__ */ jsxs(
187
+ "button",
188
+ {
189
+ ...rest,
190
+ ref: buttonRef,
191
+ type,
192
+ disabled,
193
+ onClick,
194
+ className: `bl-jelly ${className}`,
195
+ style: { "--bl-w": `${width}px`, "--bl-h": `${height}px`, "--bl-color": color, "--bl-pressed": pressedColor, "--bl-ink": textColor, "--bl-accent": accentColor, "--bl-size": `${fontSize}px`, "--bl-font": fontFamily, ...style },
196
+ children: [
197
+ /* @__PURE__ */ jsxs("svg", { className: "bl-jelly__blob", viewBox: `0 0 ${width + 32} ${height + 32}`, "aria-hidden": "true", children: [
198
+ /* @__PURE__ */ jsx("path", { ref: shadeRef, className: "bl-jelly__shade", transform: "translate(0 5)" }),
199
+ /* @__PURE__ */ jsx("path", { ref: fillRef, className: "bl-jelly__fill" }),
200
+ /* @__PURE__ */ jsx("path", { ref: ringRef, className: "bl-jelly__ring" })
201
+ ] }),
202
+ /* @__PURE__ */ jsx("span", { className: "bl-jelly__label", children })
203
+ ]
204
+ }
205
+ );
206
+ }
207
+ export {
208
+ JellyBlob as default
209
+ };
@@ -0,0 +1,30 @@
1
+ import { type CSSProperties, type ElementType } from 'react';
2
+ export interface KineticHeadlineProps {
3
+ /** The headline; screen readers get it once as plain text. */
4
+ text?: string;
5
+ as?: ElementType;
6
+ /** A rolling wave, a pulse on every beat, or a spotlight driven by scroll position. */
7
+ mode?: 'breathe' | 'beat' | 'scroll';
8
+ /** Breathe: cycles per second. */
9
+ speed?: number;
10
+ /** Breathe: letters per wave. */
11
+ wavelength?: number;
12
+ /** Beat: tempo. */
13
+ bpm?: number;
14
+ /** Beat and scroll: pulse width in letters. */
15
+ spread?: number;
16
+ minWeight?: number;
17
+ maxWeight?: number;
18
+ /** Width axis range in % (needs a font with wdth, e.g. Roboto Flex). */
19
+ minWidth?: number;
20
+ maxWidth?: number;
21
+ /** How far heavy letters shift toward the accent (0–1). */
22
+ tint?: number;
23
+ fontSize?: number | string;
24
+ textColor?: string;
25
+ accentColor?: string;
26
+ fontFamily?: string;
27
+ className?: string;
28
+ style?: CSSProperties;
29
+ }
30
+ export default function KineticHeadline({ text, as: Tag, mode, speed, wavelength, bpm, spread, minWeight, maxWeight, minWidth, maxWidth, tint, fontSize, textColor, accentColor, fontFamily, className, style }: KineticHeadlineProps): import("react").JSX.Element;
@@ -0,0 +1,133 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useInsertionEffect, useRef } from "react";
4
+ const STYLE_ID = "bl-kinetic-headline";
5
+ const CSS = `
6
+ .bl-kinetic { margin: 0; font-family: var(--bl-font); font-size: var(--bl-size); line-height: 1.05; color: var(--bl-ink); letter-spacing: -0.01em; }
7
+ .bl-kinetic__letter { --k: 0.5; display: inline-block; white-space: pre; font-weight: calc(var(--bl-wmin) + var(--k) * (var(--bl-wmax) - var(--bl-wmin))); font-stretch: calc(var(--bl-dmin) + var(--k) * (var(--bl-dmax) - var(--bl-dmin))); color: color-mix(in srgb, var(--bl-accent) calc(var(--k) * var(--bl-tint)), var(--bl-ink)); }
8
+ .bl-kinetic__word { display: inline-block; white-space: nowrap; }
9
+ .bl-kinetic__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
10
+ `;
11
+ function useStyles() {
12
+ useInsertionEffect(() => {
13
+ if (typeof document === "undefined") return;
14
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
15
+ style.textContent = CSS;
16
+ if (!style.isConnected) document.head.appendChild(style);
17
+ }, []);
18
+ }
19
+ function KineticHeadline({
20
+ text = "",
21
+ as: Tag = "h2",
22
+ mode = "breathe",
23
+ speed = 0.5,
24
+ wavelength = 8,
25
+ bpm = 100,
26
+ spread = 3,
27
+ minWeight = 200,
28
+ maxWeight = 900,
29
+ minWidth = 100,
30
+ maxWidth = 100,
31
+ tint = 0,
32
+ fontSize = 72,
33
+ textColor = "#f4f4f4",
34
+ accentColor = "#ffcd75",
35
+ fontFamily = '"Roboto Flex", ui-sans-serif, system-ui, sans-serif',
36
+ className = "",
37
+ style
38
+ }) {
39
+ useStyles();
40
+ const rootRef = useRef(null);
41
+ const words = text.split(/(\s+)/).filter(Boolean);
42
+ useEffect(() => {
43
+ const root = rootRef.current;
44
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
45
+ const letters = [...root.querySelectorAll(".bl-kinetic__letter")];
46
+ const n = letters.length;
47
+ if (reduced || !n) return void 0;
48
+ let visible = true;
49
+ let raf = 0;
50
+ let clock = 0;
51
+ let last = 0;
52
+ const progress = () => {
53
+ const rect = root.getBoundingClientRect();
54
+ const vh = window.innerHeight || 1;
55
+ return Math.max(0, Math.min(1, (vh - rect.top) / (vh + rect.height)));
56
+ };
57
+ const level = (i, p) => {
58
+ if (mode === "scroll") {
59
+ const x = p * (n + spread * 4) - spread * 2;
60
+ return Math.exp(-((x - i) ** 2) / (2 * spread * spread));
61
+ }
62
+ if (mode === "beat") {
63
+ const beat = 60 / bpm;
64
+ const head = clock % beat / beat * (n + spread * 4) - spread * 2;
65
+ return 0.15 + 0.85 * Math.exp(-((head - i) ** 2) / (2 * spread * spread));
66
+ }
67
+ return (Math.sin(2 * Math.PI * (clock * speed - i / wavelength)) + 1) / 2;
68
+ };
69
+ const paint = () => {
70
+ const p = mode === "scroll" ? progress() : 0;
71
+ letters.forEach((node, i) => node.style.setProperty("--k", level(i, p).toFixed(3)));
72
+ };
73
+ const tick = (now) => {
74
+ clock += Math.max(0, Math.min(0.05, (now - last) / 1e3));
75
+ last = now;
76
+ paint();
77
+ raf = mode === "scroll" ? 0 : requestAnimationFrame(tick);
78
+ };
79
+ const start = () => {
80
+ if (raf || !visible || document.hidden) return;
81
+ last = performance.now();
82
+ raf = requestAnimationFrame(tick);
83
+ };
84
+ const stop = () => {
85
+ cancelAnimationFrame(raf);
86
+ raf = 0;
87
+ };
88
+ const observer = new IntersectionObserver(([entry]) => {
89
+ visible = entry.isIntersecting;
90
+ if (visible) start();
91
+ else stop();
92
+ });
93
+ observer.observe(root);
94
+ const onVisibility = () => document.hidden ? stop() : start();
95
+ document.addEventListener("visibilitychange", onVisibility);
96
+ const onScroll = () => start();
97
+ if (mode === "scroll") {
98
+ window.addEventListener("scroll", onScroll, { passive: true, capture: true });
99
+ window.addEventListener("resize", onScroll);
100
+ }
101
+ paint();
102
+ start();
103
+ return () => {
104
+ stop();
105
+ observer.disconnect();
106
+ document.removeEventListener("visibilitychange", onVisibility);
107
+ window.removeEventListener("scroll", onScroll, { capture: true });
108
+ window.removeEventListener("resize", onScroll);
109
+ letters.forEach((node) => node.style.removeProperty("--k"));
110
+ };
111
+ }, [text, mode, speed, wavelength, bpm, spread]);
112
+ let index = 0;
113
+ return /* @__PURE__ */ jsxs(
114
+ Tag,
115
+ {
116
+ ref: rootRef,
117
+ className: `bl-kinetic ${className}`,
118
+ style: { "--bl-size": typeof fontSize === "number" ? `${fontSize}px` : fontSize, "--bl-wmin": minWeight, "--bl-wmax": maxWeight, "--bl-dmin": `${minWidth}%`, "--bl-dmax": `${maxWidth}%`, "--bl-tint": `${tint * 100}%`, "--bl-ink": textColor, "--bl-accent": accentColor, "--bl-font": fontFamily, ...style },
119
+ children: [
120
+ /* @__PURE__ */ jsx("span", { className: "bl-kinetic__sr", children: text }),
121
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: words.map(
122
+ (word, w) => /^\s+$/.test(word) ? " " : (
123
+ // Letters of a word stay together so the line only breaks between words.
124
+ /* @__PURE__ */ jsx("span", { className: "bl-kinetic__word", children: [...word].map((char) => /* @__PURE__ */ jsx("span", { className: "bl-kinetic__letter", children: char }, index++)) }, w)
125
+ )
126
+ ) })
127
+ ]
128
+ }
129
+ );
130
+ }
131
+ export {
132
+ KineticHeadline as default
133
+ };
@@ -0,0 +1,21 @@
1
+ import { type CSSProperties } from 'react';
2
+ export interface LavaLampProps {
3
+ blobs?: number;
4
+ /** How fast blobs rise and sink. */
5
+ speed?: number;
6
+ /** Blob size multiplier. */
7
+ size?: number;
8
+ /** The pointer becomes a blob of its own. */
9
+ pointer?: boolean;
10
+ pixelSize?: number;
11
+ /** Wax colour at the top. */
12
+ topColor?: string;
13
+ /** Wax colour at the bottom. */
14
+ bottomColor?: string;
15
+ rimColor?: string;
16
+ backgroundTop?: string;
17
+ backgroundBottom?: string;
18
+ className?: string;
19
+ style?: CSSProperties;
20
+ }
21
+ export default function LavaLamp({ blobs, speed, size, pointer, pixelSize, topColor, bottomColor, rimColor, backgroundTop, backgroundBottom, className, style }: LavaLampProps): import("react").JSX.Element;
@@ -0,0 +1,133 @@
1
+ 'use client';
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ const hexToRgb = (hex) => {
5
+ const n = parseInt(hex.replace("#", "").replace(/^(.)(.)(.)$/, "$1$1$2$2$3$3"), 16);
6
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
7
+ };
8
+ const mix = (a, b, t) => a.map((v, i) => v + (b[i] - v) * t);
9
+ function LavaLamp({
10
+ blobs = 7,
11
+ speed = 1,
12
+ size = 1,
13
+ pointer = true,
14
+ pixelSize = 5,
15
+ topColor = "#ffcd75",
16
+ bottomColor = "#b13e53",
17
+ rimColor = "#f4f4f4",
18
+ backgroundTop = "#29366f",
19
+ backgroundBottom = "#1a1c2c",
20
+ className = "",
21
+ style
22
+ }) {
23
+ const canvasRef = useRef(null);
24
+ useEffect(() => {
25
+ const canvas = canvasRef.current;
26
+ const ctx = canvas.getContext("2d");
27
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
28
+ const hot = hexToRgb(bottomColor);
29
+ const cool = hexToRgb(topColor);
30
+ const rim = hexToRgb(rimColor);
31
+ const bgTop = hexToRgb(backgroundTop);
32
+ const bgBottom = hexToRgb(backgroundBottom);
33
+ const wax = Array.from({ length: blobs }, (_, i) => ({ x: 0.15 + i * 41 % 70 / 100, r: (0.07 + i * 13 % 6 / 100) * size, period: 9 + i * 7 % 8, phase: i * 1.37, sway: 0.04 + i * 3 % 5 / 100 }));
34
+ const hand = { x: -10, y: -10, on: false };
35
+ let w = 0;
36
+ let h = 0;
37
+ let img = null;
38
+ let t = 0;
39
+ let raf = 0;
40
+ let last = 0;
41
+ let visible = true;
42
+ const fit = () => {
43
+ w = Math.max(1, Math.ceil(canvas.clientWidth / pixelSize));
44
+ h = Math.max(1, Math.ceil(canvas.clientHeight / pixelSize));
45
+ canvas.width = w;
46
+ canvas.height = h;
47
+ img = ctx.createImageData(w, h);
48
+ };
49
+ const frame = () => {
50
+ const d = img.data;
51
+ const aspect = w / h;
52
+ const live = wax.map((b) => {
53
+ const cycle = (Math.sin(t / b.period * Math.PI * 2 + b.phase) + 1) / 2;
54
+ return { x: (b.x + Math.sin(t * 0.3 + b.phase) * b.sway) * aspect, y: 0.12 + cycle * 0.8, r2: b.r * b.r };
55
+ });
56
+ if (hand.on) live.push({ x: hand.x * aspect, y: hand.y, r2: 6e-3 * size });
57
+ for (let py = 0; py < h; py++) {
58
+ const y = py / h;
59
+ const back = mix(bgTop, bgBottom, y);
60
+ for (let px = 0; px < w; px++) {
61
+ const x = px / w * aspect;
62
+ let f = Math.max(0, (y - 0.9) * 9);
63
+ for (let k = 0; k < live.length; k++) {
64
+ const b = live[k];
65
+ const dx = x - b.x;
66
+ const dy = y - b.y;
67
+ f += b.r2 / (dx * dx + dy * dy + 1e-5);
68
+ }
69
+ const i = (py * w + px) * 4;
70
+ let c = back;
71
+ if (f > 1) c = mix(cool, hot, y);
72
+ else if (f > 0.82) c = mix(rim, mix(cool, hot, y), 0.35);
73
+ d[i] = c[0];
74
+ d[i + 1] = c[1];
75
+ d[i + 2] = c[2];
76
+ d[i + 3] = 255;
77
+ }
78
+ }
79
+ ctx.putImageData(img, 0, 0);
80
+ };
81
+ const tick = (now) => {
82
+ t += Math.max(0, Math.min(0.05, (now - last) / 1e3)) * speed;
83
+ last = now;
84
+ frame();
85
+ raf = visible && !document.hidden ? requestAnimationFrame(tick) : 0;
86
+ };
87
+ const start = () => {
88
+ if (raf || reduced || !visible || document.hidden) return;
89
+ last = performance.now();
90
+ raf = requestAnimationFrame(tick);
91
+ };
92
+ const onMove = (event) => {
93
+ if (!pointer) return;
94
+ const r = canvas.getBoundingClientRect();
95
+ hand.x = (event.clientX - r.left) / r.width;
96
+ hand.y = (event.clientY - r.top) / r.height;
97
+ hand.on = true;
98
+ if (reduced) frame();
99
+ };
100
+ const onLeave = () => {
101
+ hand.on = false;
102
+ if (reduced) frame();
103
+ };
104
+ const resize = new ResizeObserver(() => {
105
+ fit();
106
+ frame();
107
+ });
108
+ resize.observe(canvas);
109
+ const observer = new IntersectionObserver(([entry]) => {
110
+ visible = entry.isIntersecting;
111
+ start();
112
+ });
113
+ observer.observe(canvas);
114
+ document.addEventListener("visibilitychange", start);
115
+ canvas.addEventListener("pointermove", onMove);
116
+ canvas.addEventListener("pointerleave", onLeave);
117
+ fit();
118
+ frame();
119
+ start();
120
+ return () => {
121
+ cancelAnimationFrame(raf);
122
+ resize.disconnect();
123
+ observer.disconnect();
124
+ document.removeEventListener("visibilitychange", start);
125
+ canvas.removeEventListener("pointermove", onMove);
126
+ canvas.removeEventListener("pointerleave", onLeave);
127
+ };
128
+ }, [blobs, speed, size, pointer, pixelSize, topColor, bottomColor, rimColor, backgroundTop, backgroundBottom]);
129
+ return /* @__PURE__ */ jsx("canvas", { ref: canvasRef, "aria-hidden": "true", className, style: { display: "block", width: "100%", height: "100%", background: backgroundBottom, imageRendering: "pixelated", touchAction: "pan-y", ...style } });
130
+ }
131
+ export {
132
+ LavaLamp as default
133
+ };
@@ -0,0 +1,20 @@
1
+ import { type HTMLAttributes, type ElementType } from 'react';
2
+ export interface LetterSwapProps extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
3
+ text?: string;
4
+ /** The face that rolls into view; defaults to the same word. */
5
+ swapText?: string;
6
+ /** Element to render. */
7
+ as?: ElementType;
8
+ /** Makes it a link. */
9
+ href?: string;
10
+ /** Where the wave starts. */
11
+ from?: 'start' | 'center' | 'end' | 'random';
12
+ /** Delay between letters, in ms. */
13
+ stagger?: number;
14
+ duration?: number;
15
+ /** Hold it rolled. */
16
+ active?: boolean;
17
+ altColor?: string;
18
+ accentColor?: string;
19
+ }
20
+ export default function LetterSwap({ text, swapText, as, href, from, stagger, duration, active, altColor, accentColor, className, style, ...rest }: LetterSwapProps): import("react").JSX.Element;
@@ -0,0 +1,78 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useInsertionEffect } from "react";
4
+ const STYLE_ID = "bl-letter-swap";
5
+ const CSS = `
6
+ .bl-swap { display: inline-flex; flex-wrap: wrap; color: inherit; font: inherit; text-decoration: none; perspective: 600px; cursor: pointer; }
7
+ a.bl-swap, button.bl-swap { border: 0; background: none; padding: 0; }
8
+ .bl-swap:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 4px; border-radius: 4px; }
9
+ .bl-swap__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
10
+ .bl-swap { position: relative; }
11
+ .bl-swap__char { position: relative; display: inline-block; height: 1.15em; line-height: 1.15em; transform-style: preserve-3d; transition: transform var(--bl-dur) cubic-bezier(.3,1.15,.5,1); transition-delay: calc(var(--d) * var(--bl-stagger)); }
12
+ .bl-swap__face { display: block; backface-visibility: hidden; -webkit-backface-visibility: hidden; transform: translateZ(0.575em); white-space: pre; }
13
+ .bl-swap__face + .bl-swap__face { position: absolute; inset: 0; color: var(--bl-alt); transform: rotateX(-90deg) translateZ(0.575em); }
14
+ .bl-swap:is(:hover, :focus-visible) .bl-swap__char, .bl-swap[data-active] .bl-swap__char { transform: rotateX(90deg); }
15
+ @media (prefers-reduced-motion: reduce) {
16
+ .bl-swap__char { transition: color 200ms; transform: none !important; }
17
+ .bl-swap__face + .bl-swap__face { display: none; }
18
+ .bl-swap:is(:hover, :focus-visible) .bl-swap__face, .bl-swap[data-active] .bl-swap__face { color: var(--bl-alt); }
19
+ }
20
+ `;
21
+ function useStyles() {
22
+ useInsertionEffect(() => {
23
+ if (typeof document === "undefined") return;
24
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
25
+ style.textContent = CSS;
26
+ if (!style.isConnected) document.head.appendChild(style);
27
+ }, []);
28
+ }
29
+ function delays(count, from) {
30
+ if (from === "end") return Array.from({ length: count }, (_, i) => count - 1 - i);
31
+ if (from === "center") return Array.from({ length: count }, (_, i) => Math.abs(i - (count - 1) / 2));
32
+ if (from === "random") {
33
+ return Array.from({ length: count }, (_, i) => (i * 7 + 3) % count * 0.8);
34
+ }
35
+ return Array.from({ length: count }, (_, i) => i);
36
+ }
37
+ function LetterSwap({
38
+ text = "Hover me",
39
+ swapText,
40
+ as = "span",
41
+ href,
42
+ from = "start",
43
+ stagger = 30,
44
+ duration = 500,
45
+ active,
46
+ altColor = "currentColor",
47
+ accentColor = "#73eff7",
48
+ className = "",
49
+ style,
50
+ ...rest
51
+ }) {
52
+ useStyles();
53
+ const front = [...text];
54
+ const back = [...swapText ?? text];
55
+ const count = Math.max(front.length, back.length);
56
+ const order = delays(count, from);
57
+ const Tag = href ? "a" : as;
58
+ return /* @__PURE__ */ jsxs(
59
+ Tag,
60
+ {
61
+ className: `bl-swap ${className}`,
62
+ href,
63
+ "data-active": active || void 0,
64
+ style: { "--bl-stagger": `${stagger}ms`, "--bl-dur": `${duration}ms`, "--bl-alt": altColor, "--bl-accent": accentColor, ...style },
65
+ ...rest,
66
+ children: [
67
+ /* @__PURE__ */ jsx("span", { className: "bl-swap__sr", children: text }),
68
+ Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsxs("span", { className: "bl-swap__char", "aria-hidden": "true", style: { "--d": order[i] }, children: [
69
+ /* @__PURE__ */ jsx("span", { className: "bl-swap__face", children: front[i] ?? " " }),
70
+ /* @__PURE__ */ jsx("span", { className: "bl-swap__face", children: back[i] ?? " " })
71
+ ] }, i))
72
+ ]
73
+ }
74
+ );
75
+ }
76
+ export {
77
+ LetterSwap as default
78
+ };
@@ -0,0 +1,35 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ export type LevelStatus = 'cleared' | 'current' | 'available' | 'locked';
3
+ export interface Level {
4
+ id?: string | number;
5
+ name?: string;
6
+ status: LevelStatus;
7
+ /** 0–3 stars, shown under the name. */
8
+ stars?: number;
9
+ /** Text on the node instead of its number. */
10
+ label?: ReactNode;
11
+ }
12
+ export interface LevelMapProps {
13
+ levels?: Level[];
14
+ /** Node diameter, in px. */
15
+ nodeSize?: number;
16
+ /** Minimum horizontal spacing between nodes, in px. */
17
+ minSpacing?: number;
18
+ /** Vertical distance between rows, in px. */
19
+ rowGap?: number;
20
+ padding?: number;
21
+ pathColor?: string;
22
+ /** Color of the path already traveled. */
23
+ clearedColor?: string;
24
+ textColor?: string;
25
+ /** Custom "you are here" marker over the current level. */
26
+ marker?: ReactNode;
27
+ /** Path draw-in length, in ms. */
28
+ drawDuration?: number;
29
+ fontFamily?: string;
30
+ /** Called when an unlocked level is chosen. */
31
+ onSelect?: (level: Level, index: number) => void;
32
+ className?: string;
33
+ style?: CSSProperties;
34
+ }
35
+ export default function LevelMap({ levels, nodeSize, minSpacing, rowGap, padding, pathColor, clearedColor, textColor, marker, drawDuration, fontFamily, onSelect, className, style }: LevelMapProps): import("react").JSX.Element;