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,26 @@
1
+ import { type CSSProperties, type ElementType } from 'react';
2
+ export interface ProximityTypeProps {
3
+ text?: string;
4
+ as?: ElementType;
5
+ /** How far the pointer reaches, in px. */
6
+ radius?: number;
7
+ falloff?: 'smooth' | 'linear' | 'sharp';
8
+ minWeight?: number;
9
+ maxWeight?: number;
10
+ /** Width axis range in % (needs a font with wdth, e.g. Roboto Flex). */
11
+ minWidth?: number;
12
+ maxWidth?: number;
13
+ /** Raise near letters by up to this many px. */
14
+ lift?: number;
15
+ /** How far near letters shift toward the accent (0–1). */
16
+ tint?: number;
17
+ /** How quickly letters follow, 0–1 (1 is instant). */
18
+ ease?: number;
19
+ fontSize?: number | string;
20
+ textColor?: string;
21
+ accentColor?: string;
22
+ fontFamily?: string;
23
+ className?: string;
24
+ style?: CSSProperties;
25
+ }
26
+ export default function ProximityType({ text, as: Tag, radius, falloff, minWeight, maxWeight, minWidth, maxWidth, lift, tint, ease, fontSize, textColor, accentColor, fontFamily, className, style }: ProximityTypeProps): import("react").JSX.Element;
@@ -0,0 +1,135 @@
1
+ 'use client';
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useInsertionEffect, useRef } from "react";
4
+ const STYLE_ID = "bl-proximity-type";
5
+ const CSS = `
6
+ .bl-prox { margin: 0; font-family: var(--bl-font); font-size: var(--bl-size); line-height: 1.1; color: var(--bl-ink); touch-action: pan-y; }
7
+ .bl-prox__word { display: inline-block; white-space: nowrap; }
8
+ .bl-prox__ch { --k: 0; 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)); transform: translateY(calc(var(--k) * var(--bl-lift) * -1)); transform-origin: 50% 100%; }
9
+ `;
10
+ function useStyles() {
11
+ useInsertionEffect(() => {
12
+ if (typeof document === "undefined") return;
13
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
14
+ style.textContent = CSS;
15
+ if (!style.isConnected) document.head.appendChild(style);
16
+ }, []);
17
+ }
18
+ function ProximityType({
19
+ text = "",
20
+ as: Tag = "p",
21
+ radius = 160,
22
+ falloff = "smooth",
23
+ minWeight = 300,
24
+ maxWeight = 900,
25
+ minWidth = 100,
26
+ maxWidth = 100,
27
+ lift = 0,
28
+ tint = 0,
29
+ ease = 0.2,
30
+ fontSize = 56,
31
+ textColor = "#f4f4f4",
32
+ accentColor = "#73eff7",
33
+ fontFamily = '"Roboto Flex", ui-sans-serif, system-ui, sans-serif',
34
+ className = "",
35
+ style
36
+ }) {
37
+ useStyles();
38
+ const rootRef = useRef(null);
39
+ useEffect(() => {
40
+ const root = rootRef.current;
41
+ if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return void 0;
42
+ const letters = [...root.querySelectorAll(".bl-prox__ch")];
43
+ const level = letters.map(() => 0);
44
+ let centres = [];
45
+ const pointer = { x: -1e4, y: -1e4 };
46
+ let raf = 0;
47
+ const measure = () => {
48
+ centres = letters.map((node) => {
49
+ const r = node.getBoundingClientRect();
50
+ const k = Number(node.style.getPropertyValue("--k")) || 0;
51
+ return { x: r.left + r.width / 2 + window.scrollX, y: r.top + r.height / 2 + window.scrollY + k * lift };
52
+ });
53
+ };
54
+ const shape = (t) => falloff === "linear" ? t : falloff === "sharp" ? t * t * t : t * t * (3 - 2 * t);
55
+ const tick = () => {
56
+ let moving = false;
57
+ letters.forEach((node, i) => {
58
+ const c = centres[i];
59
+ const d = Math.hypot(c.x - pointer.x, c.y - pointer.y);
60
+ const goal = d < radius ? shape(1 - d / radius) : 0;
61
+ level[i] += (goal - level[i]) * ease;
62
+ if (Math.abs(goal - level[i]) > 2e-3) moving = true;
63
+ else level[i] = goal;
64
+ node.style.setProperty("--k", level[i].toFixed(3));
65
+ });
66
+ raf = moving ? requestAnimationFrame(tick) : 0;
67
+ };
68
+ const wake = () => {
69
+ if (!raf) raf = requestAnimationFrame(tick);
70
+ };
71
+ const onMove = (event) => {
72
+ if (!centres.length) measure();
73
+ pointer.x = event.clientX + window.scrollX;
74
+ pointer.y = event.clientY + window.scrollY;
75
+ wake();
76
+ };
77
+ const onLeave = () => {
78
+ pointer.x = -1e4;
79
+ pointer.y = -1e4;
80
+ wake();
81
+ };
82
+ const onLayout = () => {
83
+ centres = [];
84
+ };
85
+ let listening = false;
86
+ const listen = (on) => {
87
+ if (on === listening) return;
88
+ listening = on;
89
+ if (on) {
90
+ window.addEventListener("pointermove", onMove, { passive: true });
91
+ document.documentElement.addEventListener("pointerleave", onLeave);
92
+ } else {
93
+ window.removeEventListener("pointermove", onMove);
94
+ document.documentElement.removeEventListener("pointerleave", onLeave);
95
+ }
96
+ };
97
+ const observer = new IntersectionObserver(([entry]) => {
98
+ listen(entry.isIntersecting);
99
+ if (!entry.isIntersecting) onLeave();
100
+ });
101
+ observer.observe(root);
102
+ const resize = new ResizeObserver(onLayout);
103
+ resize.observe(root);
104
+ window.addEventListener("scroll", onLayout, { passive: true, capture: true });
105
+ document.fonts?.ready.then(onLayout);
106
+ return () => {
107
+ cancelAnimationFrame(raf);
108
+ listen(false);
109
+ observer.disconnect();
110
+ resize.disconnect();
111
+ window.removeEventListener("scroll", onLayout, { capture: true });
112
+ letters.forEach((node) => node.style.removeProperty("--k"));
113
+ };
114
+ }, [text, radius, falloff, lift, ease]);
115
+ let index = 0;
116
+ return /* @__PURE__ */ jsx(
117
+ Tag,
118
+ {
119
+ ref: rootRef,
120
+ className: `bl-prox ${className}`,
121
+ style: { "--bl-size": typeof fontSize === "number" ? `${fontSize}px` : fontSize, "--bl-wmin": minWeight, "--bl-wmax": maxWeight, "--bl-dmin": `${minWidth}%`, "--bl-dmax": `${maxWidth}%`, "--bl-lift": `${lift}px`, "--bl-tint": `${tint * 100}%`, "--bl-ink": textColor, "--bl-accent": accentColor, "--bl-font": fontFamily, ...style },
122
+ children: text.split(/(\s+)/).map((part, w) => {
123
+ if (!part) return null;
124
+ if (/^\s+$/.test(part)) return " ";
125
+ return (
126
+ // Letters of a word stay together so lines only break between words.
127
+ /* @__PURE__ */ jsx("span", { className: "bl-prox__word", children: [...part].map((ch) => /* @__PURE__ */ jsx("span", { className: "bl-prox__ch", children: ch }, index++)) }, w)
128
+ );
129
+ })
130
+ }
131
+ );
132
+ }
133
+ export {
134
+ ProximityType as default
135
+ };
@@ -0,0 +1,50 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ export interface DialogueChoice {
3
+ label: string;
4
+ /** Passed to onChoice; defaults to the label. */
5
+ value?: string;
6
+ }
7
+ export interface DialogueLine {
8
+ /** Name shown in the tab above the box. */
9
+ speaker?: string;
10
+ text: string;
11
+ /** Image or sprite shown at the left of the box. */
12
+ portrait?: ReactNode;
13
+ /** Choices shown once the line finishes typing. */
14
+ choices?: DialogueChoice[];
15
+ }
16
+ export interface QuestDialogProps {
17
+ /** The conversation, in order. */
18
+ lines?: DialogueLine[];
19
+ /** Characters per second. */
20
+ speed?: number;
21
+ /** Max width, in px. */
22
+ width?: number;
23
+ fontSize?: number;
24
+ /** Minimum text lines the box reserves. */
25
+ minLines?: number;
26
+ portraitSize?: number;
27
+ boxColor?: string;
28
+ borderColor?: string;
29
+ textColor?: string;
30
+ nameColor?: string;
31
+ nameBackground?: string;
32
+ accentColor?: string;
33
+ /** Font for the name tab and choices. */
34
+ fontFamily?: string;
35
+ /** Font for the dialogue text. */
36
+ bodyFont?: string;
37
+ /** Start over after the last line. */
38
+ loop?: boolean;
39
+ /** Called for each typed (non-space) character, e.g. to play a blip. */
40
+ onChar?: (char: string) => void;
41
+ /** Called with the new line index after advancing. */
42
+ onAdvance?: (index: number) => void;
43
+ /** Called with the chosen value and its index. */
44
+ onChoice?: (value: string, index: number) => void;
45
+ /** Called after the last line. */
46
+ onComplete?: () => void;
47
+ className?: string;
48
+ style?: CSSProperties;
49
+ }
50
+ export default function QuestDialog({ lines, speed, width, fontSize, minLines, portraitSize, boxColor, borderColor, textColor, nameColor, nameBackground, accentColor, fontFamily, bodyFont, loop, onChar, onAdvance, onChoice, onComplete, className, style }: QuestDialogProps): import("react").JSX.Element;
@@ -0,0 +1,204 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useId, useInsertionEffect, useRef, useState } from "react";
4
+ const STYLE_ID = "bl-quest-dialog";
5
+ const CSS = `
6
+ .bl-quest { position: relative; width: min(var(--bl-width), 100%); margin: 4px; padding: 22px 22px 20px; background: var(--bl-box); color: var(--bl-text); cursor: pointer; user-select: none;
7
+ box-shadow: 0 -4px 0 0 var(--bl-border), 0 4px 0 0 var(--bl-border), -4px 0 0 0 var(--bl-border), 4px 0 0 0 var(--bl-border), inset 0 0 0 4px #0b0b14, 8px 8px 0 4px rgba(11,11,20,0.45); }
8
+ .bl-quest:focus-visible { outline: 4px solid #41a6f6; outline-offset: 8px; }
9
+ .bl-quest__name { position: absolute; top: -16px; left: 18px; padding: 6px 10px; background: var(--bl-name-bg); color: var(--bl-name); font: 10px/1 var(--bl-font, "Press Start 2P", ui-monospace, monospace); text-transform: uppercase;
10
+ box-shadow: 0 -3px 0 0 #0b0b14, 0 3px 0 0 #0b0b14, -3px 0 0 0 #0b0b14, 3px 0 0 0 #0b0b14; }
11
+ .bl-quest__body { display: flex; gap: 18px; align-items: flex-start; }
12
+ .bl-quest__portrait { flex: none; display: grid; place-items: center; width: var(--bl-portrait); height: var(--bl-portrait); background: #0b0b14; box-shadow: inset 0 0 0 3px var(--bl-border); overflow: hidden; }
13
+ .bl-quest__text { flex: 1; min-height: calc(var(--bl-lines) * 1.6em); margin: 4px 0 0; font: var(--bl-size)/1.6 var(--bl-body, var(--bl-font, "Press Start 2P", ui-monospace, monospace)); white-space: pre-wrap; overflow-wrap: anywhere; }
14
+ .bl-quest__ghost { visibility: hidden; }
15
+ .bl-quest__next { position: absolute; right: 16px; bottom: 8px; color: var(--bl-accent); font: 12px/1 monospace; animation: bl-quest-bob 0.7s steps(2) infinite; }
16
+ .bl-quest__choices { display: grid; gap: 8px; margin: 14px 0 0; padding: 0 0 0 calc(var(--bl-portrait-gap)); list-style: none; }
17
+ .bl-quest__choice { display: flex; gap: 10px; width: 100%; padding: 6px 8px; border: 0; background: none; color: var(--bl-text); font: var(--bl-size)/1.3 var(--bl-body, var(--bl-font, "Press Start 2P", ui-monospace, monospace)); text-align: left; cursor: pointer; }
18
+ .bl-quest__choice::before { content: "\u25BA"; visibility: hidden; color: var(--bl-accent); }
19
+ .bl-quest__choice[data-active]::before { visibility: visible; animation: bl-quest-blink 0.8s steps(1) infinite; }
20
+ .bl-quest__choice[data-active] { color: var(--bl-accent); }
21
+ .bl-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
22
+ @keyframes bl-quest-bob { 50% { transform: translateY(4px); } }
23
+ @keyframes bl-quest-blink { 50% { opacity: 0; } }
24
+ @media (prefers-reduced-motion: reduce) { .bl-quest__next, .bl-quest__choice[data-active]::before { animation: 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 useReducedMotion() {
35
+ const [reduced, setReduced] = useState(false);
36
+ useEffect(() => {
37
+ const query = window.matchMedia?.("(prefers-reduced-motion: reduce)");
38
+ if (!query) return void 0;
39
+ setReduced(query.matches);
40
+ const onChange = (event) => setReduced(event.matches);
41
+ query.addEventListener("change", onChange);
42
+ return () => query.removeEventListener("change", onChange);
43
+ }, []);
44
+ return reduced;
45
+ }
46
+ const DEFAULT_LINES = [
47
+ { speaker: "Old Miner", text: "Ah, a new face! These tunnels run deeper than anyone remembers." },
48
+ { speaker: "Old Miner", text: "Legend says a diamond the size of your fist waits at the bottom... Will you go?", choices: [{ label: "I'm in. Hand me a pickaxe.", value: "yes" }, { label: "Maybe after lunch.", value: "no" }] }
49
+ ];
50
+ const PAUSES = { ".": 8, "!": 8, "?": 8, ",": 4, ";": 4, ":": 4, "\u2014": 4 };
51
+ function QuestDialog({
52
+ lines = DEFAULT_LINES,
53
+ speed = 40,
54
+ width = 620,
55
+ fontSize = 13,
56
+ minLines = 3,
57
+ portraitSize = 72,
58
+ boxColor = "#1a1c2c",
59
+ borderColor = "#f4f4f4",
60
+ textColor = "#f4f4f4",
61
+ nameColor = "#0b0b14",
62
+ nameBackground = "#ffcd75",
63
+ accentColor = "#ffcd75",
64
+ fontFamily,
65
+ bodyFont,
66
+ loop = false,
67
+ onChar,
68
+ onAdvance,
69
+ onChoice,
70
+ onComplete,
71
+ className = "",
72
+ style
73
+ }) {
74
+ useStyles();
75
+ const reduced = useReducedMotion();
76
+ const uid = useId();
77
+ const [index, setIndex] = useState(0);
78
+ const [shown, setShown] = useState(0);
79
+ const [active, setActive] = useState(0);
80
+ const [finished, setFinished] = useState(false);
81
+ const callbacks = useRef({ onChar });
82
+ callbacks.current = { onChar };
83
+ const line = lines[Math.min(index, lines.length - 1)] ?? { text: "" };
84
+ const text = line.text ?? "";
85
+ const typing = shown < text.length;
86
+ const last = index >= lines.length - 1;
87
+ const choices = !typing && line.choices?.length ? line.choices : null;
88
+ useEffect(() => {
89
+ setShown(reduced ? text.length : 0);
90
+ setActive(0);
91
+ }, [index, text, reduced]);
92
+ useEffect(() => {
93
+ if (!typing || reduced) return void 0;
94
+ const char = text[shown];
95
+ const delay = 1e3 / Math.max(1, speed) * (PAUSES[text[shown - 1]] ?? 1);
96
+ const timer = setTimeout(() => {
97
+ setShown((count) => count + 1);
98
+ if (char && char.trim()) callbacks.current.onChar?.(char);
99
+ }, delay);
100
+ return () => clearTimeout(timer);
101
+ }, [typing, shown, text, speed, reduced]);
102
+ const advance = useCallback(() => {
103
+ if (finished) return;
104
+ if (typing) {
105
+ setShown(text.length);
106
+ return;
107
+ }
108
+ if (choices) return;
109
+ if (last) {
110
+ onComplete?.();
111
+ if (loop) setIndex(0);
112
+ else setFinished(true);
113
+ return;
114
+ }
115
+ setIndex((current) => current + 1);
116
+ onAdvance?.(index + 1);
117
+ }, [finished, typing, text.length, choices, last, loop, onComplete, onAdvance, index]);
118
+ const choose = useCallback(
119
+ (choiceIndex) => {
120
+ const choice = choices?.[choiceIndex];
121
+ if (!choice) return;
122
+ onChoice?.(choice.value ?? choice.label, choiceIndex);
123
+ if (last) {
124
+ onComplete?.();
125
+ if (loop) setIndex(0);
126
+ else setFinished(true);
127
+ } else setIndex((current) => current + 1);
128
+ },
129
+ [choices, onChoice, last, loop, onComplete]
130
+ );
131
+ const onKeyDown = (event) => {
132
+ if (choices && (event.key === "ArrowDown" || event.key === "ArrowUp")) {
133
+ event.preventDefault();
134
+ setActive((current) => (current + (event.key === "ArrowDown" ? 1 : -1) + choices.length) % choices.length);
135
+ return;
136
+ }
137
+ if (event.key === "Enter" || event.key === " ") {
138
+ event.preventDefault();
139
+ if (choices) choose(active);
140
+ else advance();
141
+ }
142
+ };
143
+ const portrait = line.portrait;
144
+ return /* @__PURE__ */ jsxs(
145
+ "div",
146
+ {
147
+ className: `bl-quest ${className}`,
148
+ role: "group",
149
+ "aria-roledescription": "dialogue",
150
+ "aria-label": line.speaker ? `${line.speaker} says` : "Dialogue",
151
+ tabIndex: 0,
152
+ onClick: () => !choices && advance(),
153
+ onKeyDown,
154
+ style: {
155
+ "--bl-width": `${width}px`,
156
+ "--bl-box": boxColor,
157
+ "--bl-border": borderColor,
158
+ "--bl-text": textColor,
159
+ "--bl-name": nameColor,
160
+ "--bl-name-bg": nameBackground,
161
+ "--bl-accent": accentColor,
162
+ "--bl-size": `${fontSize}px`,
163
+ "--bl-lines": minLines,
164
+ "--bl-portrait": `${portraitSize}px`,
165
+ "--bl-portrait-gap": portrait ? `${portraitSize + 18}px` : "0px",
166
+ ...fontFamily ? { "--bl-font": fontFamily } : null,
167
+ ...bodyFont ? { "--bl-body": bodyFont } : null,
168
+ ...style
169
+ },
170
+ children: [
171
+ line.speaker && /* @__PURE__ */ jsx("span", { className: "bl-quest__name", children: line.speaker }),
172
+ /* @__PURE__ */ jsxs("div", { className: "bl-quest__body", children: [
173
+ portrait && /* @__PURE__ */ jsx("div", { className: "bl-quest__portrait", "aria-hidden": "true", children: portrait }),
174
+ /* @__PURE__ */ jsxs("p", { className: "bl-quest__text", "aria-hidden": "true", children: [
175
+ text.slice(0, shown),
176
+ /* @__PURE__ */ jsx("span", { className: "bl-quest__ghost", children: text.slice(shown) })
177
+ ] }),
178
+ /* @__PURE__ */ jsx("span", { className: "bl-sr", "aria-live": "polite", children: text })
179
+ ] }),
180
+ choices && /* @__PURE__ */ jsx("ul", { className: "bl-quest__choices", role: "listbox", "aria-label": "Choices", "aria-activedescendant": `${uid}-choice-${active}`, children: choices.map((choice, choiceIndex) => /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
181
+ "button",
182
+ {
183
+ id: `${uid}-choice-${choiceIndex}`,
184
+ type: "button",
185
+ role: "option",
186
+ "aria-selected": choiceIndex === active,
187
+ className: "bl-quest__choice",
188
+ "data-active": choiceIndex === active || void 0,
189
+ onMouseEnter: () => setActive(choiceIndex),
190
+ onClick: (event) => {
191
+ event.stopPropagation();
192
+ choose(choiceIndex);
193
+ },
194
+ children: choice.label
195
+ }
196
+ ) }, choice.label)) }),
197
+ !typing && !choices && !finished && /* @__PURE__ */ jsx("span", { className: "bl-quest__next", "aria-hidden": "true", children: last ? "\u25A0" : "\u25BC" })
198
+ ]
199
+ }
200
+ );
201
+ }
202
+ export {
203
+ QuestDialog as default
204
+ };
@@ -0,0 +1,39 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ export interface Quest {
3
+ id: string;
4
+ title: string;
5
+ description?: ReactNode;
6
+ /** XP paid into the bar when done. */
7
+ xp?: number;
8
+ /** Quest ids that must be done first; locked until then. */
9
+ requires?: string[];
10
+ }
11
+ export interface QuestLogProps {
12
+ quests?: Quest[];
13
+ /** Controlled list of done quest ids. */
14
+ completed?: string[];
15
+ defaultCompleted?: string[];
16
+ onCompletedChange?: (completed: string[]) => void;
17
+ /** Fires when a quest is ticked (hook up a sound or analytics). */
18
+ onQuestComplete?: (quest: Quest) => void;
19
+ /** Unticking also unticks quests that depend on it. */
20
+ allowUndo?: boolean;
21
+ title?: string;
22
+ finishedMessage?: string;
23
+ /** Max width in px. */
24
+ width?: number;
25
+ backgroundColor?: string;
26
+ rowColor?: string;
27
+ hoverColor?: string;
28
+ textColor?: string;
29
+ mutedColor?: string;
30
+ lineColor?: string;
31
+ trackColor?: string;
32
+ accentColor?: string;
33
+ doneColor?: string;
34
+ fontFamily?: string;
35
+ titleFontFamily?: string;
36
+ className?: string;
37
+ style?: CSSProperties;
38
+ }
39
+ export default function QuestLog({ quests, completed, defaultCompleted, onCompletedChange, onQuestComplete, allowUndo, title, finishedMessage, width, backgroundColor, rowColor, hoverColor, textColor, mutedColor, lineColor, trackColor, accentColor, doneColor, fontFamily, titleFontFamily, className, style }: QuestLogProps): import("react").JSX.Element;
@@ -0,0 +1,214 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useId, useInsertionEffect, useRef, useState } from "react";
4
+ const STYLE_ID = "bl-quest-log";
5
+ const CSS = `
6
+ .bl-ql { position: relative; width: 100%; max-width: var(--bl-w); padding: 18px; box-sizing: border-box; background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); box-shadow: 0 0 0 3px var(--bl-line), 6px 6px 0 3px rgba(0,0,0,0.45); }
7
+ .bl-ql__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 0 0 10px; }
8
+ .bl-ql__title { margin: 0; font-family: var(--bl-title-font); font-size: 13px; font-weight: 400; color: var(--bl-accent); letter-spacing: 0.04em; }
9
+ .bl-ql__xp { color: var(--bl-muted); font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; }
10
+ .bl-ql__bar { position: relative; height: 12px; margin-bottom: 16px; background: var(--bl-track); box-shadow: inset 0 0 0 2px rgba(0,0,0,0.35); }
11
+ .bl-ql__fill { position: absolute; inset: 0 auto 0 0; width: calc(var(--bl-p) * 100%); background: repeating-linear-gradient(90deg, var(--bl-accent) 0 8px, color-mix(in srgb, var(--bl-accent) 75%, #000) 8px 10px); transition: width 600ms steps(12); }
12
+ .bl-ql__list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
13
+ .bl-ql__quest { position: relative; display: grid; grid-template-columns: 22px 1fr auto; align-items: start; gap: 12px; padding: 10px 12px; background: var(--bl-row); cursor: pointer; }
14
+ .bl-ql__quest:hover { background: var(--bl-hover); }
15
+ .bl-ql__quest[data-state="locked"] { cursor: not-allowed; opacity: 0.55; }
16
+ .bl-ql__quest[data-state="done"] .bl-ql__name { color: var(--bl-muted); text-decoration: line-through; text-decoration-thickness: 2px; }
17
+ .bl-ql__quest[data-flash] { animation: bl-ql-flash 600ms steps(4); }
18
+ .bl-ql__input { position: absolute; width: 1px; height: 1px; margin: 0; overflow: hidden; clip: rect(0 0 0 0); opacity: 0; }
19
+ .bl-ql__box { position: relative; width: 22px; height: 22px; background: var(--bl-track); box-shadow: inset 0 0 0 3px var(--bl-line); color: var(--bl-muted); }
20
+ .bl-ql__input:focus-visible ~ .bl-ql__box { outline: 3px solid var(--bl-accent); outline-offset: 2px; }
21
+ .bl-ql__quest[data-state="done"] .bl-ql__box { color: var(--bl-bg); background: var(--bl-done); box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--bl-done) 60%, #000); }
22
+ .bl-ql__box svg { position: absolute; inset: 3px; width: 16px; height: 16px; }
23
+ .bl-ql__mark { position: absolute; inset: 0; display: grid; place-items: center; color: var(--bl-accent); font-family: var(--bl-title-font); font-size: 11px; animation: bl-ql-bob 1.2s steps(2) infinite; }
24
+ .bl-ql__body { display: grid; gap: 3px; min-width: 0; }
25
+ .bl-ql__name { font-size: 15px; font-weight: 600; line-height: 1.3; }
26
+ .bl-ql__desc { color: var(--bl-muted); font-size: 13px; line-height: 1.4; }
27
+ .bl-ql__reward { position: relative; padding: 3px 7px; background: var(--bl-track); color: var(--bl-accent); font-family: var(--bl-title-font); font-size: 9px; line-height: 1.4; white-space: nowrap; }
28
+ .bl-ql__quest[data-state="done"] .bl-ql__reward { background: var(--bl-accent); color: var(--bl-bg); }
29
+ .bl-ql__pop { position: absolute; right: 0; bottom: 100%; color: var(--bl-accent); font-family: var(--bl-title-font); font-size: 10px; white-space: nowrap; text-shadow: 2px 2px 0 rgba(0,0,0,0.6); pointer-events: none; animation: bl-ql-pop 1000ms steps(10) forwards; }
30
+ .bl-ql__banner { position: relative; margin-top: 14px; padding: 12px; background: var(--bl-accent); color: var(--bl-bg); font-family: var(--bl-title-font); font-size: 11px; line-height: 1.5; text-align: center; animation: bl-ql-banner 500ms cubic-bezier(.3,1.5,.5,1) both; }
31
+ .bl-ql__spark { position: absolute; left: 50%; top: 50%; width: 6px; height: 6px; background: var(--c); animation: bl-ql-spark 900ms cubic-bezier(.2,.8,.3,1) forwards; }
32
+ .bl-ql__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
33
+ @keyframes bl-ql-flash { 25%, 75% { background: color-mix(in srgb, var(--bl-done) 35%, var(--bl-row)); } }
34
+ @keyframes bl-ql-bob { 50% { transform: translateY(-3px); } }
35
+ @keyframes bl-ql-pop { from { transform: translateY(6px); opacity: 0; } 15% { opacity: 1; } to { transform: translateY(-26px); opacity: 0; } }
36
+ @keyframes bl-ql-banner { from { transform: scale(0.6); opacity: 0; } }
37
+ @keyframes bl-ql-spark { from { transform: translate(-50%, -50%); opacity: 1; } to { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))); opacity: 0; } }
38
+ @media (prefers-reduced-motion: reduce) {
39
+ .bl-ql__fill { transition: none; }
40
+ .bl-ql__quest[data-flash], .bl-ql__mark, .bl-ql__banner, .bl-ql__spark { animation: none; }
41
+ .bl-ql__spark { display: none; }
42
+ }
43
+ `;
44
+ function useStyles() {
45
+ useInsertionEffect(() => {
46
+ if (typeof document === "undefined") return;
47
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
48
+ style.textContent = CSS;
49
+ if (!style.isConnected) document.head.appendChild(style);
50
+ }, []);
51
+ }
52
+ const SPARK_COLORS = ["#ffcd75", "#ef7d57", "#a7f070", "#73eff7", "#f4f4f4", "#b13e53"];
53
+ const SPARKS = Array.from({ length: 14 }, (_, i) => {
54
+ const angle = i / 14 * Math.PI * 2;
55
+ const dist = 60 + i % 3 * 24;
56
+ return { dx: `${Math.round(Math.cos(angle) * dist * 1.6)}px`, dy: `${Math.round(Math.sin(angle) * dist * 0.7)}px`, c: SPARK_COLORS[i % SPARK_COLORS.length] };
57
+ });
58
+ function QuestLog({
59
+ quests = [],
60
+ completed,
61
+ defaultCompleted = [],
62
+ onCompletedChange,
63
+ onQuestComplete,
64
+ allowUndo = true,
65
+ title = "Quest log",
66
+ finishedMessage = "All quests complete!",
67
+ width = 520,
68
+ backgroundColor = "#1a1c2c",
69
+ rowColor = "#262b44",
70
+ hoverColor = "#2e3552",
71
+ textColor = "#f4f4f4",
72
+ mutedColor = "#94b0c2",
73
+ lineColor = "#333c57",
74
+ trackColor = "#0b0b14",
75
+ accentColor = "#ffcd75",
76
+ doneColor = "#38b764",
77
+ fontFamily = '"Pixelify Sans", ui-sans-serif, system-ui, sans-serif',
78
+ titleFontFamily = '"Press Start 2P", ui-monospace, monospace',
79
+ className = "",
80
+ style
81
+ }) {
82
+ useStyles();
83
+ const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
84
+ const [inner, setInner] = useState(defaultCompleted);
85
+ const done = completed ?? inner;
86
+ const [flash, setFlash] = useState(null);
87
+ const [pops, setPops] = useState([]);
88
+ const [announce, setAnnounce] = useState("");
89
+ const popId = useRef(0);
90
+ const byId = Object.fromEntries(quests.map((q) => [q.id, q]));
91
+ const isLocked = (quest) => (quest.requires ?? []).some((id) => !done.includes(id));
92
+ const earned = quests.reduce((sum, q) => sum + (done.includes(q.id) ? q.xp ?? 0 : 0), 0);
93
+ const totalXp = quests.reduce((sum, q) => sum + (q.xp ?? 0), 0);
94
+ const allDone = quests.length > 0 && quests.every((q) => done.includes(q.id));
95
+ const commit = (next) => {
96
+ if (completed === void 0) setInner(next);
97
+ onCompletedChange?.(next);
98
+ };
99
+ const toggle = (quest) => {
100
+ if (isLocked(quest)) return;
101
+ if (done.includes(quest.id)) {
102
+ if (!allowUndo) return;
103
+ const removed = /* @__PURE__ */ new Set([quest.id]);
104
+ let grew = true;
105
+ while (grew) {
106
+ grew = false;
107
+ for (const q of quests) {
108
+ if (!removed.has(q.id) && (q.requires ?? []).some((id) => removed.has(id))) {
109
+ removed.add(q.id);
110
+ grew = true;
111
+ }
112
+ }
113
+ }
114
+ commit(done.filter((id) => !removed.has(id)));
115
+ setAnnounce(`${quest.title} marked not done.`);
116
+ return;
117
+ }
118
+ const next = [...done, quest.id];
119
+ commit(next);
120
+ onQuestComplete?.(quest);
121
+ setFlash(quest.id);
122
+ popId.current += 1;
123
+ setPops((list) => [...list, { id: popId.current, quest: quest.id, xp: quest.xp ?? 0 }]);
124
+ const finishing = quests.every((q) => next.includes(q.id));
125
+ setAnnounce(`Quest complete: ${quest.title}.${quest.xp ? ` Plus ${quest.xp} XP.` : ""}${finishing ? ` ${finishedMessage}` : ""}`);
126
+ };
127
+ return /* @__PURE__ */ jsxs(
128
+ "section",
129
+ {
130
+ className: `bl-ql ${className}`,
131
+ "aria-labelledby": `${uid}-title`,
132
+ style: {
133
+ "--bl-w": `${width}px`,
134
+ "--bl-bg": backgroundColor,
135
+ "--bl-row": rowColor,
136
+ "--bl-hover": hoverColor,
137
+ "--bl-ink": textColor,
138
+ "--bl-muted": mutedColor,
139
+ "--bl-line": lineColor,
140
+ "--bl-track": trackColor,
141
+ "--bl-accent": accentColor,
142
+ "--bl-done": doneColor,
143
+ "--bl-p": totalXp ? earned / totalXp : 0,
144
+ "--bl-font": fontFamily,
145
+ "--bl-title-font": titleFontFamily,
146
+ ...style
147
+ },
148
+ children: [
149
+ /* @__PURE__ */ jsxs("div", { className: "bl-ql__head", children: [
150
+ /* @__PURE__ */ jsx("h3", { id: `${uid}-title`, className: "bl-ql__title", children: title }),
151
+ /* @__PURE__ */ jsxs("span", { className: "bl-ql__xp", children: [
152
+ earned,
153
+ " / ",
154
+ totalXp,
155
+ " XP"
156
+ ] })
157
+ ] }),
158
+ /* @__PURE__ */ jsx("div", { className: "bl-ql__bar", role: "progressbar", "aria-label": "Experience", "aria-valuemin": 0, "aria-valuemax": totalXp, "aria-valuenow": earned, children: /* @__PURE__ */ jsx("div", { className: "bl-ql__fill" }) }),
159
+ /* @__PURE__ */ jsx("ul", { className: "bl-ql__list", children: quests.map((quest) => {
160
+ const locked = isLocked(quest);
161
+ const complete = done.includes(quest.id);
162
+ const state = complete ? "done" : locked ? "locked" : "active";
163
+ const needs = (quest.requires ?? []).filter((id) => !done.includes(id)).map((id) => byId[id]?.title ?? id);
164
+ const descId = `${uid}-${quest.id}-desc`;
165
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", { className: "bl-ql__quest", "data-state": state, "data-flash": flash === quest.id || void 0, onAnimationEnd: (event) => event.target === event.currentTarget && setFlash(null), children: [
166
+ /* @__PURE__ */ jsx(
167
+ "input",
168
+ {
169
+ type: "checkbox",
170
+ className: "bl-ql__input",
171
+ checked: complete,
172
+ disabled: locked || complete && !allowUndo,
173
+ "aria-describedby": descId,
174
+ onChange: () => toggle(quest)
175
+ }
176
+ ),
177
+ /* @__PURE__ */ jsxs("span", { className: "bl-ql__box", "aria-hidden": "true", children: [
178
+ complete && /* @__PURE__ */ jsx("svg", { viewBox: "0 0 8 8", shapeRendering: "crispEdges", children: /* @__PURE__ */ jsx("path", { d: "M1 4 3 6 7 2", fill: "none", stroke: "currentColor", strokeWidth: "1.6" }) }),
179
+ state === "active" && /* @__PURE__ */ jsx("span", { className: "bl-ql__mark", children: "!" }),
180
+ locked && /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 8 8", shapeRendering: "crispEdges", fill: "currentColor", children: [
181
+ /* @__PURE__ */ jsx("rect", { x: 2, y: 1, width: 4, height: 1 }),
182
+ /* @__PURE__ */ jsx("rect", { x: 1, y: 2, width: 1, height: 2 }),
183
+ /* @__PURE__ */ jsx("rect", { x: 6, y: 2, width: 1, height: 2 }),
184
+ /* @__PURE__ */ jsx("rect", { x: 1, y: 4, width: 6, height: 4 })
185
+ ] })
186
+ ] }),
187
+ /* @__PURE__ */ jsxs("span", { className: "bl-ql__body", children: [
188
+ /* @__PURE__ */ jsx("span", { className: "bl-ql__name", children: quest.title }),
189
+ /* @__PURE__ */ jsx("span", { id: descId, className: "bl-ql__desc", children: locked ? `Locked. Requires: ${needs.join(", ")}.` : quest.description })
190
+ ] }),
191
+ quest.xp ? /* @__PURE__ */ jsxs("span", { className: "bl-ql__reward", "aria-label": `${quest.xp} XP`, children: [
192
+ "+",
193
+ quest.xp,
194
+ " XP",
195
+ pops.filter((pop) => pop.quest === quest.id).map((pop) => /* @__PURE__ */ jsxs("span", { className: "bl-ql__pop", "aria-hidden": "true", onAnimationEnd: () => setPops((list) => list.filter((p) => p.id !== pop.id)), children: [
196
+ "+",
197
+ pop.xp,
198
+ " XP"
199
+ ] }, pop.id))
200
+ ] }) : /* @__PURE__ */ jsx("span", {})
201
+ ] }) }, quest.id);
202
+ }) }),
203
+ allDone && /* @__PURE__ */ jsxs("div", { className: "bl-ql__banner", "aria-hidden": "true", children: [
204
+ finishedMessage,
205
+ SPARKS.map((spark, i) => /* @__PURE__ */ jsx("i", { className: "bl-ql__spark", style: { "--dx": spark.dx, "--dy": spark.dy, "--c": spark.c } }, i))
206
+ ] }),
207
+ /* @__PURE__ */ jsx("p", { className: "bl-ql__sr", "aria-live": "polite", children: announce })
208
+ ]
209
+ }
210
+ );
211
+ }
212
+ export {
213
+ QuestLog as default
214
+ };