bits-lab 0.0.0-stage → 0.1.0

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 +51 -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 +34 -4
@@ -0,0 +1,236 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useInsertionEffect, useRef, useState } from "react";
4
+ const STYLE_ID = "bl-minimap";
5
+ const CSS = `
6
+ .bl-mini { position: relative; flex: 0 0 auto; overflow: hidden; background: var(--bl-bg); box-shadow: 0 0 0 2px var(--bl-edge); cursor: pointer; touch-action: none; user-select: none; -webkit-user-select: none; }
7
+ .bl-mini:focus-visible { outline: 2px solid var(--bl-view); outline-offset: 3px; }
8
+ .bl-mini canvas { position: absolute; top: 0; left: 0; image-rendering: pixelated; }
9
+ .bl-mini__view { position: absolute; left: 0; right: 0; top: 0; box-sizing: border-box; border: 2px solid var(--bl-view); background: color-mix(in srgb, var(--bl-view) 14%, transparent); cursor: grab; will-change: transform; }
10
+ .bl-mini[data-dragging] .bl-mini__view { cursor: grabbing; background: color-mix(in srgb, var(--bl-view) 24%, transparent); }
11
+ `;
12
+ function useStyles() {
13
+ useInsertionEffect(() => {
14
+ if (typeof document === "undefined") return;
15
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
16
+ style.textContent = CSS;
17
+ if (!style.isConnected) document.head.appendChild(style);
18
+ }, []);
19
+ }
20
+ const KINDS = {
21
+ heading: /^H[1-6]$/,
22
+ media: /^(IMG|VIDEO|CANVAS|SVG|PICTURE|FIGURE|IFRAME)$/,
23
+ code: /^(PRE|CODE|TABLE)$/,
24
+ control: /^(BUTTON|INPUT|SELECT|TEXTAREA)$/
25
+ };
26
+ const DEFAULT_COLORS = { heading: "#ffcd75", text: "#566c86", media: "#41a6f6", code: "#a7f070", control: "#ef7d57" };
27
+ const kindOf = (node) => Object.keys(KINDS).find((kind) => KINDS[kind].test(node.tagName.toUpperCase())) ?? "text";
28
+ function Minimap({
29
+ target,
30
+ selector = "h1, h2, h3, h4, h5, h6, p, li, blockquote, img, video, canvas, svg, picture, pre, table, button, input, textarea",
31
+ width = 96,
32
+ height = 320,
33
+ pixelSize = 2,
34
+ colors,
35
+ backgroundColor = "#0b0b14",
36
+ edgeColor = "#333c57",
37
+ viewportColor = "#73eff7",
38
+ label = "Page overview",
39
+ className = "",
40
+ style
41
+ }) {
42
+ useStyles();
43
+ const rootRef = useRef(null);
44
+ const canvasRef = useRef(null);
45
+ const viewRef = useRef(null);
46
+ const [progress, setProgress] = useState(0);
47
+ const [mapHeight, setMapHeight] = useState(height);
48
+ const geometry = useRef({ scale: 1, contentH: 1, viewH: 1, offset: 0, maxScroll: 0, blocks: [] });
49
+ const palette = { ...DEFAULT_COLORS, ...colors };
50
+ const paletteKey = JSON.stringify(palette);
51
+ useEffect(() => {
52
+ const root = rootRef.current;
53
+ const canvas = canvasRef.current;
54
+ const ctx = canvas.getContext("2d");
55
+ const isWindow = !target;
56
+ const scroller = () => target?.current ?? document.scrollingElement ?? document.documentElement;
57
+ const scrollTop = () => isWindow ? window.scrollY : scroller().scrollTop;
58
+ const colorsNow = JSON.parse(paletteKey);
59
+ let frame = 0;
60
+ const measure = () => {
61
+ const el2 = scroller();
62
+ if (!el2) return;
63
+ const g = geometry.current;
64
+ const box = isWindow ? { top: 0, left: 0 } : el2.getBoundingClientRect();
65
+ const originTop = box.top + (isWindow ? 0 : el2.clientTop) - scrollTop();
66
+ const originLeft = box.left + (isWindow ? 0 : el2.clientLeft) - (isWindow ? window.scrollX : el2.scrollLeft);
67
+ const contentW = isWindow ? document.documentElement.clientWidth : el2.clientWidth;
68
+ g.viewH = isWindow ? window.innerHeight : el2.clientHeight;
69
+ g.contentH = Math.max(el2.scrollHeight, g.viewH);
70
+ g.maxScroll = Math.max(0, g.contentH - g.viewH);
71
+ g.scale = width / Math.max(1, contentW);
72
+ g.blocks = [];
73
+ for (const node of el2.querySelectorAll(selector)) {
74
+ if (root.contains(node)) continue;
75
+ const r = node.getBoundingClientRect();
76
+ if (!r.width || !r.height) continue;
77
+ const kind = kindOf(node);
78
+ const line = kind === "text" || kind === "heading" ? parseFloat(getComputedStyle(node).lineHeight) || r.height : 0;
79
+ g.blocks.push({ x: r.left - originLeft, y: r.top - originTop, w: r.width, h: r.height, kind, line });
80
+ }
81
+ setMapHeight(Math.min(height, Math.ceil(g.contentH * g.scale)));
82
+ draw();
83
+ };
84
+ const draw = () => {
85
+ const g = geometry.current;
86
+ const fullH = g.contentH * g.scale;
87
+ const visibleH = Math.min(height, fullH);
88
+ const p = g.maxScroll ? Math.min(1, Math.max(0, scrollTop() / g.maxScroll)) : 0;
89
+ g.offset = p * Math.max(0, fullH - visibleH);
90
+ const px = Math.max(1, pixelSize);
91
+ canvas.width = Math.ceil(width / px);
92
+ canvas.height = Math.ceil(visibleH / px);
93
+ canvas.style.width = `${canvas.width * px}px`;
94
+ canvas.style.height = `${canvas.height * px}px`;
95
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
96
+ const s = g.scale / px;
97
+ const off = g.offset / px;
98
+ for (const b of g.blocks) {
99
+ const y = b.y * s - off;
100
+ const h = Math.max(1, b.h * s);
101
+ if (y + h < 0 || y > canvas.height) continue;
102
+ ctx.fillStyle = colorsNow[b.kind] ?? colorsNow.text;
103
+ const x = Math.round(b.x * s);
104
+ const w = Math.max(1, Math.round(b.w * s));
105
+ const pitch = b.line * s;
106
+ if (pitch >= 2) {
107
+ const count = Math.max(1, Math.round(b.h / b.line));
108
+ for (let i = 0; i < count; i++) {
109
+ const lineW = i === count - 1 && count > 1 ? Math.max(1, Math.round(w * 0.6)) : w;
110
+ ctx.fillRect(x, Math.round(y + i * pitch), lineW, Math.max(1, Math.round(pitch * 0.55)));
111
+ }
112
+ } else {
113
+ ctx.fillRect(x, Math.round(y), w, Math.round(h));
114
+ }
115
+ }
116
+ const view = viewRef.current;
117
+ if (view) {
118
+ view.style.height = `${Math.max(8, g.viewH * g.scale)}px`;
119
+ view.style.transform = `translateY(${scrollTop() * g.scale - g.offset}px)`;
120
+ }
121
+ setProgress(Math.round(p * 100));
122
+ };
123
+ const onScroll = () => {
124
+ if (frame) return;
125
+ frame = requestAnimationFrame(() => {
126
+ frame = 0;
127
+ draw();
128
+ });
129
+ };
130
+ let timer = 0;
131
+ const remeasure = () => {
132
+ clearTimeout(timer);
133
+ timer = setTimeout(measure, 120);
134
+ };
135
+ const scrollSource = isWindow ? window : scroller();
136
+ scrollSource?.addEventListener("scroll", onScroll, { passive: true });
137
+ window.addEventListener("resize", remeasure);
138
+ const resize = new ResizeObserver(remeasure);
139
+ const el = scroller();
140
+ if (el) {
141
+ resize.observe(el);
142
+ for (const child of el.children) resize.observe(child);
143
+ }
144
+ const mutations = new MutationObserver(remeasure);
145
+ if (el) mutations.observe(el, { childList: true, subtree: true, characterData: true });
146
+ measure();
147
+ return () => {
148
+ cancelAnimationFrame(frame);
149
+ clearTimeout(timer);
150
+ scrollSource?.removeEventListener("scroll", onScroll);
151
+ window.removeEventListener("resize", remeasure);
152
+ resize.disconnect();
153
+ mutations.disconnect();
154
+ };
155
+ }, [target, selector, width, height, pixelSize, paletteKey]);
156
+ const scrollTo = (top, smooth = false) => {
157
+ const g = geometry.current;
158
+ const value = Math.max(0, Math.min(g.maxScroll, top));
159
+ const behavior = smooth && !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? "smooth" : "instant";
160
+ if (target) target.current?.scrollTo({ top: value, behavior });
161
+ else window.scrollTo({ top: value, behavior });
162
+ };
163
+ const currentScroll = () => target ? target.current?.scrollTop ?? 0 : window.scrollY;
164
+ const onPointerDown = (event) => {
165
+ if (event.button !== 0) return;
166
+ const root = rootRef.current;
167
+ const g = geometry.current;
168
+ const rect = root.getBoundingClientRect();
169
+ const onBox = viewRef.current?.contains(event.target);
170
+ if (!onBox) {
171
+ scrollTo((event.clientY - rect.top + g.offset) / g.scale - g.viewH / 2, true);
172
+ return;
173
+ }
174
+ event.preventDefault();
175
+ try {
176
+ root.setPointerCapture(event.pointerId);
177
+ } catch {
178
+ }
179
+ root.setAttribute("data-dragging", "");
180
+ const startY = event.clientY;
181
+ const startScroll = currentScroll();
182
+ const travel = Math.max(1, Math.min(height, g.contentH * g.scale) - g.viewH * g.scale);
183
+ const ratio = g.maxScroll / travel;
184
+ const onMove = (move) => scrollTo(startScroll + (move.clientY - startY) * ratio);
185
+ const onUp = () => {
186
+ root.removeAttribute("data-dragging");
187
+ root.removeEventListener("pointermove", onMove);
188
+ root.removeEventListener("pointerup", onUp);
189
+ root.removeEventListener("pointercancel", onUp);
190
+ };
191
+ root.addEventListener("pointermove", onMove);
192
+ root.addEventListener("pointerup", onUp);
193
+ root.addEventListener("pointercancel", onUp);
194
+ };
195
+ const onKeyDown = (event) => {
196
+ const g = geometry.current;
197
+ const now = currentScroll();
198
+ const moves = {
199
+ ArrowDown: now + g.viewH * 0.15,
200
+ ArrowUp: now - g.viewH * 0.15,
201
+ PageDown: now + g.viewH * 0.9,
202
+ PageUp: now - g.viewH * 0.9,
203
+ Home: 0,
204
+ End: g.maxScroll
205
+ };
206
+ if (!(event.key in moves)) return;
207
+ event.preventDefault();
208
+ scrollTo(moves[event.key], event.key === "Home" || event.key === "End" || event.key.startsWith("Page"));
209
+ };
210
+ return /* @__PURE__ */ jsxs(
211
+ "div",
212
+ {
213
+ ref: rootRef,
214
+ className: `bl-mini ${className}`,
215
+ role: "scrollbar",
216
+ "aria-label": label,
217
+ "aria-orientation": "vertical",
218
+ "aria-controls": target?.current?.id || void 0,
219
+ "aria-valuemin": 0,
220
+ "aria-valuemax": 100,
221
+ "aria-valuenow": progress,
222
+ "aria-valuetext": `${progress}%`,
223
+ tabIndex: 0,
224
+ onPointerDown,
225
+ onKeyDown,
226
+ style: { width, height: mapHeight, "--bl-bg": backgroundColor, "--bl-edge": edgeColor, "--bl-view": viewportColor, ...style },
227
+ children: [
228
+ /* @__PURE__ */ jsx("canvas", { ref: canvasRef, "aria-hidden": "true" }),
229
+ /* @__PURE__ */ jsx("div", { ref: viewRef, className: "bl-mini__view", "aria-hidden": "true" })
230
+ ]
231
+ }
232
+ );
233
+ }
234
+ export {
235
+ Minimap as default
236
+ };
@@ -0,0 +1,38 @@
1
+ import { type CSSProperties, type ReactNode, type MouseEvent as ReactMouseEvent } from 'react';
2
+ export interface MobileTab {
3
+ id: string;
4
+ label: string;
5
+ icon?: ReactNode;
6
+ /** Unread count or marker. */
7
+ badge?: ReactNode;
8
+ /** Fires when the active tab is tapped again (e.g. scroll to top). */
9
+ onReselect?: (event: ReactMouseEvent<HTMLButtonElement>) => void;
10
+ }
11
+ export interface MobileTabBarProps {
12
+ tabs?: MobileTab[];
13
+ value?: string;
14
+ defaultValue?: string;
15
+ onValueChange?: (id: string, tab: MobileTab) => void;
16
+ /** Show every label, or only the active one. */
17
+ labels?: 'active' | 'always';
18
+ /** A floating pill instead of a full-width bar. */
19
+ floating?: boolean;
20
+ /** Fixed to the bottom of the viewport. */
21
+ fixed?: boolean;
22
+ /** A short vibration on change, where supported. */
23
+ haptics?: boolean;
24
+ /** How much the blob stretches between tabs. */
25
+ stretch?: number;
26
+ label?: string;
27
+ backgroundColor?: string;
28
+ textColor?: string;
29
+ activeColor?: string;
30
+ blobColor?: string;
31
+ lineColor?: string;
32
+ accentColor?: string;
33
+ badgeColor?: string;
34
+ fontFamily?: string;
35
+ className?: string;
36
+ style?: CSSProperties;
37
+ }
38
+ export default function MobileTabBar({ tabs, value, defaultValue, onValueChange, labels, floating, fixed, haptics, stretch, label, backgroundColor, textColor, activeColor, blobColor, lineColor, accentColor, badgeColor, fontFamily, className, style }: MobileTabBarProps): import("react").JSX.Element;
@@ -0,0 +1,148 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
4
+ const STYLE_ID = "bl-mobile-tab-bar";
5
+ const CSS = `
6
+ .bl-tabbar { position: relative; display: flex; width: 100%; box-sizing: border-box; padding: 8px 8px calc(8px + env(safe-area-inset-bottom, 0px)); background: var(--bl-bg); color: var(--bl-muted); font-family: var(--bl-font); box-shadow: 0 -1px 0 var(--bl-line); }
7
+ .bl-tabbar[data-floating] { width: auto; margin: 0 12px 12px; padding-bottom: 8px; border-radius: 999px; box-shadow: 0 0 0 1px var(--bl-line), 0 16px 40px -16px rgba(0,0,0,0.8); }
8
+ .bl-tabbar[data-fixed] { position: fixed; left: 0; right: 0; bottom: 0; z-index: 30; }
9
+ .bl-tabbar__blob { position: absolute; top: 8px; bottom: calc(8px + env(safe-area-inset-bottom, 0px)); left: var(--bl-l, 0px); right: var(--bl-r, 0px); border-radius: 999px; background: var(--bl-blob); transition: left var(--bl-lead-l) cubic-bezier(.3,1.2,.5,1) var(--bl-delay-l), right var(--bl-lead-r) cubic-bezier(.3,1.2,.5,1) var(--bl-delay-r); pointer-events: none; }
10
+ .bl-tabbar[data-floating] .bl-tabbar__blob { bottom: 8px; }
11
+ .bl-tabbar__tab { position: relative; z-index: 1; flex: 1 1 0; display: grid; justify-items: center; align-content: center; gap: 2px; min-width: 0; min-height: 48px; padding: 6px 4px; border: 0; border-radius: 999px; background: transparent; color: inherit; font: inherit; font-size: 11px; font-weight: 600; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: color 200ms; }
12
+ .bl-tabbar__tab[aria-current="page"] { color: var(--bl-active); }
13
+ .bl-tabbar__tab:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: -4px; }
14
+ .bl-tabbar__icon { position: relative; display: grid; place-items: center; width: 26px; height: 26px; }
15
+ .bl-tabbar__icon > * { max-width: 100%; max-height: 100%; }
16
+ .bl-tabbar__tab[data-bounce] .bl-tabbar__icon { animation: bl-tabbar-bounce 520ms cubic-bezier(.3,1.6,.5,1); }
17
+ .bl-tabbar__label { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
18
+ .bl-tabbar[data-labels="active"] .bl-tabbar__label { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows 260ms cubic-bezier(.3,1.2,.5,1), opacity 200ms; }
19
+ .bl-tabbar[data-labels="active"] .bl-tabbar__label > span { min-height: 0; overflow: hidden; }
20
+ .bl-tabbar[data-labels="active"] .bl-tabbar__tab[aria-current="page"] .bl-tabbar__label { grid-template-rows: 1fr; opacity: 1; }
21
+ .bl-tabbar__badge { position: absolute; top: -4px; right: -9px; min-width: 16px; height: 16px; padding: 0 4px; box-sizing: border-box; border-radius: 999px; background: var(--bl-badge); color: #fff; font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; box-shadow: 0 0 0 2px var(--bl-bg); }
22
+ .bl-tabbar__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
23
+ @keyframes bl-tabbar-bounce { 0% { transform: scale(1); } 30% { transform: scale(0.8) translateY(2px); } 60% { transform: scale(1.18) translateY(-3px); } 100% { transform: none; } }
24
+ @media (prefers-reduced-motion: reduce) { .bl-tabbar__blob { transition: none; } .bl-tabbar__tab[data-bounce] .bl-tabbar__icon { animation: none; } .bl-tabbar[data-labels="active"] .bl-tabbar__label { transition: none; } }
25
+ `;
26
+ function useStyles() {
27
+ useInsertionEffect(() => {
28
+ if (typeof document === "undefined") return;
29
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
30
+ style.textContent = CSS;
31
+ if (!style.isConnected) document.head.appendChild(style);
32
+ }, []);
33
+ }
34
+ function MobileTabBar({
35
+ tabs = [],
36
+ value,
37
+ defaultValue,
38
+ onValueChange,
39
+ labels = "active",
40
+ floating = false,
41
+ fixed = false,
42
+ haptics = false,
43
+ stretch = 1,
44
+ label = "Main",
45
+ backgroundColor = "#1a1c2c",
46
+ textColor = "#94b0c2",
47
+ activeColor = "#0b0b14",
48
+ blobColor = "#73eff7",
49
+ lineColor = "#333c57",
50
+ accentColor = "#f4f4f4",
51
+ badgeColor = "#b13e53",
52
+ fontFamily = "ui-sans-serif, system-ui, sans-serif",
53
+ className = "",
54
+ style
55
+ }) {
56
+ useStyles();
57
+ const [inner, setInner] = useState(defaultValue ?? tabs[0]?.id);
58
+ const current = value !== void 0 ? value : inner;
59
+ const [bounce, setBounce] = useState(null);
60
+ const rootRef = useRef(null);
61
+ const tabRefs = useRef({});
62
+ const last = useRef(null);
63
+ useLayoutEffect(() => {
64
+ const root = rootRef.current;
65
+ const place = (animate = true) => {
66
+ const tab = tabRefs.current[current];
67
+ if (!root || !tab) return;
68
+ const width = root.clientWidth;
69
+ const left = tab.offsetLeft;
70
+ const right = width - (tab.offsetLeft + tab.offsetWidth);
71
+ const previous = last.current;
72
+ const goingRight = previous != null && left > previous;
73
+ const lead = `${Math.round(260 / stretch)}ms`;
74
+ const tail = `${Math.round(420 * stretch)}ms`;
75
+ const delay = `${Math.round(70 * stretch)}ms`;
76
+ root.style.setProperty("--bl-lead-l", animate ? goingRight ? tail : lead : "0ms");
77
+ root.style.setProperty("--bl-lead-r", animate ? goingRight ? lead : tail : "0ms");
78
+ root.style.setProperty("--bl-delay-l", animate && goingRight ? delay : "0ms");
79
+ root.style.setProperty("--bl-delay-r", animate && !goingRight ? delay : "0ms");
80
+ root.style.setProperty("--bl-l", `${left}px`);
81
+ root.style.setProperty("--bl-r", `${right}px`);
82
+ last.current = left;
83
+ };
84
+ place(last.current !== null);
85
+ const resize = new ResizeObserver(() => place(false));
86
+ resize.observe(root);
87
+ return () => resize.disconnect();
88
+ }, [current, tabs.length, labels, stretch]);
89
+ const choose = (tab, event) => {
90
+ if (tab.id === current) {
91
+ tab.onReselect?.(event);
92
+ return;
93
+ }
94
+ if (value === void 0) setInner(tab.id);
95
+ onValueChange?.(tab.id, tab);
96
+ setBounce(tab.id);
97
+ if (haptics && navigator.userActivation?.hasBeenActive !== false && !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) navigator.vibrate?.(8);
98
+ };
99
+ const onKeyDown = (event, index) => {
100
+ const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
101
+ if (!step) return;
102
+ event.preventDefault();
103
+ const next = tabs[(index + step + tabs.length) % tabs.length];
104
+ tabRefs.current[next.id]?.focus();
105
+ };
106
+ return /* @__PURE__ */ jsxs(
107
+ "nav",
108
+ {
109
+ ref: rootRef,
110
+ className: `bl-tabbar ${className}`,
111
+ "aria-label": label,
112
+ "data-labels": labels,
113
+ "data-floating": floating || void 0,
114
+ "data-fixed": fixed || void 0,
115
+ style: { "--bl-bg": backgroundColor, "--bl-active": activeColor, "--bl-blob": blobColor, "--bl-line": lineColor, "--bl-accent": accentColor, "--bl-badge": badgeColor, "--bl-font": fontFamily, color: textColor, ...style },
116
+ children: [
117
+ /* @__PURE__ */ jsx("span", { className: "bl-tabbar__blob", "aria-hidden": "true" }),
118
+ tabs.map((tab, i) => /* @__PURE__ */ jsxs(
119
+ "button",
120
+ {
121
+ ref: (node) => {
122
+ tabRefs.current[tab.id] = node;
123
+ },
124
+ type: "button",
125
+ className: "bl-tabbar__tab",
126
+ "aria-current": tab.id === current ? "page" : void 0,
127
+ "aria-label": tab.badge ? `${tab.label}, ${tab.badge} new` : void 0,
128
+ "data-bounce": bounce === tab.id || void 0,
129
+ onAnimationEnd: () => bounce === tab.id && setBounce(null),
130
+ onClick: (event) => choose(tab, event),
131
+ onKeyDown: (event) => onKeyDown(event, i),
132
+ children: [
133
+ /* @__PURE__ */ jsxs("span", { className: "bl-tabbar__icon", "aria-hidden": "true", children: [
134
+ tab.icon,
135
+ tab.badge ? /* @__PURE__ */ jsx("span", { className: "bl-tabbar__badge", children: tab.badge }) : null
136
+ ] }),
137
+ /* @__PURE__ */ jsx("span", { className: "bl-tabbar__label", children: /* @__PURE__ */ jsx("span", { children: tab.label }) })
138
+ ]
139
+ },
140
+ tab.id
141
+ ))
142
+ ]
143
+ }
144
+ );
145
+ }
146
+ export {
147
+ MobileTabBar as default
148
+ };
@@ -0,0 +1,34 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ export interface MorphDialogProps {
3
+ /** Card content below the shared media. The card is a button. */
4
+ card?: ReactNode;
5
+ /** Dialog content. Mark an element with data-autofocus to focus it on open. */
6
+ children?: ReactNode;
7
+ /** Dialog heading; also its accessible name. */
8
+ title?: ReactNode;
9
+ /** Shared image or video at the top of both the card and the dialog. */
10
+ media?: ReactNode;
11
+ cardMediaHeight?: number;
12
+ dialogMediaHeight?: number;
13
+ /** Dialog width, in px. */
14
+ width?: number;
15
+ /** Dialog max height (any CSS length); the body scrolls beyond it. */
16
+ maxHeight?: string;
17
+ radius?: number;
18
+ /** Morph length, in ms. */
19
+ duration?: number;
20
+ /** Controlled open state. */
21
+ open?: boolean;
22
+ onOpenChange?: (open: boolean) => void;
23
+ backgroundColor?: string;
24
+ textColor?: string;
25
+ lineColor?: string;
26
+ accentColor?: string;
27
+ backdropColor?: string;
28
+ /** Backdrop blur, in px. */
29
+ backdropBlur?: number;
30
+ fontFamily?: string;
31
+ className?: string;
32
+ style?: CSSProperties;
33
+ }
34
+ export default function MorphDialog({ card, children, title, media, cardMediaHeight, dialogMediaHeight, width, maxHeight, radius, duration, open: controlledOpen, onOpenChange, backgroundColor, textColor, lineColor, accentColor, backdropColor, backdropBlur, fontFamily, className, style }: MorphDialogProps): import("react").JSX.Element;
@@ -0,0 +1,199 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+ const STYLE_ID = "bl-morph-dialog";
6
+ const CSS = `
7
+ .bl-md__card { display: block; width: 100%; padding: 0; border: 0; overflow: hidden; border-radius: var(--bl-radius); background: var(--bl-bg); color: var(--bl-ink); font: inherit; font-family: var(--bl-font, inherit); text-align: left; cursor: pointer; box-shadow: inset 0 0 0 1px var(--bl-line); transition: transform 200ms, box-shadow 200ms; }
8
+ .bl-md__card:hover { transform: translateY(-2px); box-shadow: inset 0 0 0 1px var(--bl-line), 0 12px 30px -16px rgba(0,0,0,0.6); }
9
+ .bl-md__card:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 3px; }
10
+ .bl-md__card[data-hidden] { visibility: hidden; }
11
+ .bl-md__media { overflow: hidden; }
12
+ .bl-md__media > img, .bl-md__media > video { display: block; width: 100%; height: 100%; object-fit: cover; }
13
+ .bl-md__card .bl-md__media { height: var(--bl-card-media); }
14
+ .bl-md__backdrop { position: fixed; inset: 0; z-index: 1000; background: var(--bl-backdrop); backdrop-filter: blur(var(--bl-blur)); -webkit-backdrop-filter: blur(var(--bl-blur)); }
15
+ .bl-md__shell { font-family: var(--bl-font, inherit); position: fixed; z-index: 1001; display: flex; flex-direction: column; overflow: hidden; background: var(--bl-bg); color: var(--bl-ink); box-shadow: 0 0 0 1px var(--bl-line), 0 40px 80px -30px rgba(0,0,0,0.7); outline: none; }
16
+ .bl-md__shell .bl-md__media { flex: 0 0 auto; height: var(--bl-dialog-media); }
17
+ .bl-md__body { flex: 1; min-height: 0; overflow-y: auto; padding: 24px; }
18
+ .bl-md__close { position: absolute; top: 12px; right: 12px; z-index: 2; display: grid; place-items: center; width: 34px; height: 34px; border: 0; border-radius: var(--bl-close-radius); background: rgba(0,0,0,0.55); color: #fff; cursor: pointer; }
19
+ .bl-md__close:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
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
+ const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
30
+ function lockScroll() {
31
+ const { body, documentElement } = document;
32
+ const previous = { overflow: body.style.overflow, paddingRight: body.style.paddingRight };
33
+ const gap = window.innerWidth - documentElement.clientWidth;
34
+ body.style.overflow = "hidden";
35
+ if (gap > 0) body.style.paddingRight = `${gap}px`;
36
+ return () => {
37
+ Object.assign(body.style, previous);
38
+ };
39
+ }
40
+ function MorphDialog({
41
+ card,
42
+ children,
43
+ title,
44
+ media,
45
+ cardMediaHeight = 160,
46
+ dialogMediaHeight = 260,
47
+ width = 560,
48
+ maxHeight = "85vh",
49
+ radius = 18,
50
+ duration = 480,
51
+ open: controlledOpen,
52
+ onOpenChange,
53
+ backgroundColor = "#1a1c2c",
54
+ textColor = "#f4f4f4",
55
+ lineColor = "rgba(255,255,255,0.1)",
56
+ accentColor = "#73eff7",
57
+ backdropColor = "rgba(10,10,20,0.6)",
58
+ backdropBlur = 6,
59
+ fontFamily,
60
+ className = "",
61
+ style
62
+ }) {
63
+ useStyles();
64
+ const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
65
+ const cardRef = useRef(null);
66
+ const shellRef = useRef(null);
67
+ const backdropRef = useRef(null);
68
+ const bodyRef = useRef(null);
69
+ const [innerOpen, setInnerOpen] = useState(false);
70
+ const [mounted, setMounted] = useState(false);
71
+ const open = controlledOpen ?? innerOpen;
72
+ const closing = useRef(false);
73
+ const setOpen = (next) => {
74
+ if (controlledOpen === void 0) setInnerOpen(next);
75
+ onOpenChange?.(next);
76
+ };
77
+ const vars = {
78
+ "--bl-radius": `${radius}px`,
79
+ "--bl-close-radius": radius ? "50%" : "0",
80
+ "--bl-bg": backgroundColor,
81
+ "--bl-ink": textColor,
82
+ "--bl-line": lineColor,
83
+ "--bl-accent": accentColor,
84
+ "--bl-backdrop": backdropColor,
85
+ "--bl-blur": `${backdropBlur}px`,
86
+ "--bl-card-media": `${cardMediaHeight}px`,
87
+ "--bl-dialog-media": `${dialogMediaHeight}px`,
88
+ ...fontFamily ? { "--bl-font": fontFamily } : null
89
+ };
90
+ useEffect(() => {
91
+ if (open) setMounted(true);
92
+ }, [open]);
93
+ const reduced = () => window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
94
+ const frames = () => {
95
+ const from = cardRef.current.getBoundingClientRect();
96
+ const w = Math.min(width, window.innerWidth - 32);
97
+ const shell = shellRef.current;
98
+ shell.style.width = `${w}px`;
99
+ shell.style.height = "auto";
100
+ const h = shell.offsetHeight;
101
+ const to = { left: (window.innerWidth - w) / 2, top: Math.max(16, (window.innerHeight - h) / 2), width: w, height: h };
102
+ return { from, to };
103
+ };
104
+ const box = (r, rad) => ({ left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px`, borderRadius: `${rad}px` });
105
+ useLayoutEffect(() => {
106
+ if (!mounted || !open) return;
107
+ const shell = shellRef.current;
108
+ const { from, to } = frames();
109
+ Object.assign(shell.style, box(to, radius));
110
+ const unlock = lockScroll();
111
+ const easing = "cubic-bezier(.2,.9,.25,1)";
112
+ if (reduced()) {
113
+ shell.animate({ opacity: [0, 1] }, { duration: 160 });
114
+ } else {
115
+ shell.animate([box(from, radius), box(to, radius)], { duration, easing });
116
+ bodyRef.current.animate({ opacity: [0, 0, 1], transform: ["translateY(12px)", "translateY(12px)", "none"] }, { duration: duration * 1.1, easing });
117
+ }
118
+ backdropRef.current.animate({ opacity: [0, 1] }, { duration: duration * 0.7, fill: "both" });
119
+ cardRef.current.setAttribute("data-hidden", "");
120
+ (shell.querySelector("[data-autofocus]") ?? shell).focus({ preventScroll: true });
121
+ return unlock;
122
+ }, [mounted, open]);
123
+ useEffect(() => {
124
+ if (open || !mounted || closing.current) return;
125
+ closing.current = true;
126
+ const shell = shellRef.current;
127
+ const finish = () => {
128
+ closing.current = false;
129
+ cardRef.current?.removeAttribute("data-hidden");
130
+ setMounted(false);
131
+ cardRef.current?.focus({ preventScroll: true });
132
+ };
133
+ if (!shell) return finish();
134
+ const current = shell.getBoundingClientRect();
135
+ const target = cardRef.current.getBoundingClientRect();
136
+ backdropRef.current.animate({ opacity: [1, 0] }, { duration: duration * 0.7, fill: "forwards" });
137
+ const animation = reduced() ? shell.animate({ opacity: [1, 0] }, { duration: 140, fill: "forwards" }) : shell.animate([box(current, radius), box(target, radius)], { duration: duration * 0.85, easing: "cubic-bezier(.4,0,.2,1)", fill: "forwards" });
138
+ if (!reduced()) bodyRef.current.animate({ opacity: [1, 0] }, { duration: duration * 0.3, fill: "forwards" });
139
+ animation.onfinish = finish;
140
+ }, [open, mounted]);
141
+ const onKeyDown = (event) => {
142
+ if (event.key === "Escape") {
143
+ event.stopPropagation();
144
+ setOpen(false);
145
+ return;
146
+ }
147
+ if (event.key !== "Tab") return;
148
+ const items = [...shellRef.current.querySelectorAll(FOCUSABLE)].filter((el) => el.offsetParent !== null || el === document.activeElement);
149
+ if (!items.length) {
150
+ event.preventDefault();
151
+ return;
152
+ }
153
+ const first = items[0];
154
+ const last = items[items.length - 1];
155
+ if (event.shiftKey && (document.activeElement === first || document.activeElement === shellRef.current)) {
156
+ event.preventDefault();
157
+ last.focus();
158
+ } else if (!event.shiftKey && document.activeElement === last) {
159
+ event.preventDefault();
160
+ first.focus();
161
+ }
162
+ };
163
+ return /* @__PURE__ */ jsxs("div", { className, style: { ...vars, ...style }, children: [
164
+ /* @__PURE__ */ jsxs("button", { ref: cardRef, type: "button", className: "bl-md__card", "aria-haspopup": "dialog", "aria-expanded": open, onClick: () => setOpen(true), children: [
165
+ media && /* @__PURE__ */ jsx("div", { className: "bl-md__media", children: media }),
166
+ card
167
+ ] }),
168
+ mounted && createPortal(
169
+ /* @__PURE__ */ jsxs("div", { style: vars, children: [
170
+ /* @__PURE__ */ jsx("div", { ref: backdropRef, className: "bl-md__backdrop", onClick: () => setOpen(false) }),
171
+ /* @__PURE__ */ jsxs(
172
+ "div",
173
+ {
174
+ ref: shellRef,
175
+ className: "bl-md__shell",
176
+ role: "dialog",
177
+ "aria-modal": "true",
178
+ "aria-labelledby": title ? `${uid}-title` : void 0,
179
+ tabIndex: -1,
180
+ onKeyDown,
181
+ style: { maxHeight },
182
+ children: [
183
+ /* @__PURE__ */ jsx("button", { type: "button", className: "bl-md__close", "aria-label": "Close", onClick: () => setOpen(false), children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2 2l10 10M12 2 2 12" }) }) }),
184
+ media && /* @__PURE__ */ jsx("div", { className: "bl-md__media", children: media }),
185
+ /* @__PURE__ */ jsxs("div", { ref: bodyRef, className: "bl-md__body", children: [
186
+ title && /* @__PURE__ */ jsx("h2", { id: `${uid}-title`, style: { margin: "0 0 12px", font: "inherit", fontSize: "1.4em", fontWeight: 700 }, children: title }),
187
+ children
188
+ ] })
189
+ ]
190
+ }
191
+ )
192
+ ] }),
193
+ document.body
194
+ )
195
+ ] });
196
+ }
197
+ export {
198
+ MorphDialog as default
199
+ };