bits-lab 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -2
  3. package/dist/Accordion.d.ts +52 -0
  4. package/dist/Accordion.js +192 -0
  5. package/dist/AchievementToast.d.ts +36 -0
  6. package/dist/AchievementToast.js +162 -0
  7. package/dist/AgentCard.d.ts +41 -0
  8. package/dist/AgentCard.js +168 -0
  9. package/dist/ArcadeButtons.d.ts +23 -0
  10. package/dist/ArcadeButtons.js +169 -0
  11. package/dist/AuroraBands.d.ts +19 -0
  12. package/dist/AuroraBands.js +126 -0
  13. package/dist/AvatarTooltips.d.ts +32 -0
  14. package/dist/AvatarTooltips.js +121 -0
  15. package/dist/BarRace.d.ts +39 -0
  16. package/dist/BarRace.js +227 -0
  17. package/dist/BentoGrid.d.ts +50 -0
  18. package/dist/BentoGrid.js +145 -0
  19. package/dist/BootSequence.d.ts +48 -0
  20. package/dist/BootSequence.js +234 -0
  21. package/dist/BreadcrumbTrail.d.ts +31 -0
  22. package/dist/BreadcrumbTrail.js +181 -0
  23. package/dist/CRTPower.d.ts +30 -0
  24. package/dist/CRTPower.js +142 -0
  25. package/dist/CardDeck.d.ts +29 -0
  26. package/dist/CardDeck.js +154 -0
  27. package/dist/ChangelogTimeline.d.ts +35 -0
  28. package/dist/ChangelogTimeline.js +137 -0
  29. package/dist/ChapterProgress.d.ts +23 -0
  30. package/dist/ChapterProgress.js +211 -0
  31. package/dist/ChatBubbles.d.ts +42 -0
  32. package/dist/ChatBubbles.js +198 -0
  33. package/dist/ChiptuneKit.d.ts +158 -0
  34. package/dist/ChiptuneKit.js +207 -0
  35. package/dist/CipherCard.d.ts +22 -0
  36. package/dist/CipherCard.js +91 -0
  37. package/dist/CloudSky.d.ts +28 -0
  38. package/dist/CloudSky.js +222 -0
  39. package/dist/CodeRain.d.ts +24 -0
  40. package/dist/CodeRain.js +158 -0
  41. package/dist/CodeShowcase.d.ts +50 -0
  42. package/dist/CodeShowcase.js +307 -0
  43. package/dist/ColorDial.d.ts +42 -0
  44. package/dist/ColorDial.js +288 -0
  45. package/dist/CommandPalette.d.ts +59 -0
  46. package/dist/CommandPalette.js +349 -0
  47. package/dist/CompareSlider.d.ts +33 -0
  48. package/dist/CompareSlider.js +152 -0
  49. package/dist/Confetti.d.ts +61 -0
  50. package/dist/Confetti.js +176 -0
  51. package/dist/ConnectBeams.d.ts +39 -0
  52. package/dist/ConnectBeams.js +132 -0
  53. package/dist/ContextOrb.d.ts +34 -0
  54. package/dist/ContextOrb.js +269 -0
  55. package/dist/ContourLines.d.ts +20 -0
  56. package/dist/ContourLines.js +159 -0
  57. package/dist/Coverflow.d.ts +27 -0
  58. package/dist/Coverflow.js +167 -0
  59. package/dist/CursorAttractor.d.ts +21 -0
  60. package/dist/CursorAttractor.js +136 -0
  61. package/dist/CutReveal.d.ts +21 -0
  62. package/dist/CutReveal.js +116 -0
  63. package/dist/DateRange.d.ts +45 -0
  64. package/dist/DateRange.js +327 -0
  65. package/dist/DeviceFrame.d.ts +30 -0
  66. package/dist/DeviceFrame.js +145 -0
  67. package/dist/DiffReveal.d.ts +36 -0
  68. package/dist/DiffReveal.js +247 -0
  69. package/dist/DirectionHover.d.ts +15 -0
  70. package/dist/DirectionHover.js +90 -0
  71. package/dist/Discard.d.ts +18 -0
  72. package/dist/Discard.js +61 -0
  73. package/dist/DitherField.d.ts +15 -0
  74. package/dist/DitherField.js +157 -0
  75. package/dist/DotGlobe.d.ts +48 -0
  76. package/dist/DotGlobe.js +312 -0
  77. package/dist/DotMap.d.ts +47 -0
  78. package/dist/DotMap.js +172 -0
  79. package/dist/DropZone.d.ts +57 -0
  80. package/dist/DropZone.js +304 -0
  81. package/dist/DynamicIsland.d.ts +32 -0
  82. package/dist/DynamicIsland.js +92 -0
  83. package/dist/EdgeFX.d.ts +27 -0
  84. package/dist/EdgeFX.js +98 -0
  85. package/dist/ElasticString.d.ts +22 -0
  86. package/dist/ElasticString.js +127 -0
  87. package/dist/ExpandCard.d.ts +44 -0
  88. package/dist/ExpandCard.js +182 -0
  89. package/dist/FeatureTabs.d.ts +34 -0
  90. package/dist/FeatureTabs.js +169 -0
  91. package/dist/FeedList.d.ts +37 -0
  92. package/dist/FeedList.js +163 -0
  93. package/dist/FlickerGrid.d.ts +29 -0
  94. package/dist/FlickerGrid.js +156 -0
  95. package/dist/FocusGrid.d.ts +20 -0
  96. package/dist/FocusGrid.js +48 -0
  97. package/dist/FocusPull.d.ts +26 -0
  98. package/dist/FocusPull.js +106 -0
  99. package/dist/Footprints.d.ts +24 -0
  100. package/dist/Footprints.js +126 -0
  101. package/dist/FunnelFlow.d.ts +34 -0
  102. package/dist/FunnelFlow.js +267 -0
  103. package/dist/GameOver.d.ts +46 -0
  104. package/dist/GameOver.js +191 -0
  105. package/dist/GaugeMeter.d.ts +38 -0
  106. package/dist/GaugeMeter.js +195 -0
  107. package/dist/GlassPanel.d.ts +27 -0
  108. package/dist/GlassPanel.js +80 -0
  109. package/dist/GrainFX.d.ts +34 -0
  110. package/dist/GrainFX.js +122 -0
  111. package/dist/GravityBox.d.ts +30 -0
  112. package/dist/GravityBox.js +220 -0
  113. package/dist/HealthBar.d.ts +33 -0
  114. package/dist/HealthBar.js +140 -0
  115. package/dist/Heatmap.d.ts +42 -0
  116. package/dist/Heatmap.js +232 -0
  117. package/dist/HexField.d.ts +29 -0
  118. package/dist/HexField.js +188 -0
  119. package/dist/HoldToConfirm.d.ts +29 -0
  120. package/dist/HoldToConfirm.js +163 -0
  121. package/dist/HorizontalTrack.d.ts +28 -0
  122. package/dist/HorizontalTrack.js +195 -0
  123. package/dist/HotbarDock.d.ts +40 -0
  124. package/dist/HotbarDock.js +148 -0
  125. package/dist/InkText.d.ts +27 -0
  126. package/dist/InkText.js +65 -0
  127. package/dist/InlineEdit.d.ts +30 -0
  128. package/dist/InlineEdit.js +198 -0
  129. package/dist/InteractiveGrid.d.ts +25 -0
  130. package/dist/InteractiveGrid.js +194 -0
  131. package/dist/InventoryGrid.d.ts +45 -0
  132. package/dist/InventoryGrid.js +272 -0
  133. package/dist/JellyBlob.d.ts +30 -0
  134. package/dist/JellyBlob.js +209 -0
  135. package/dist/KineticHeadline.d.ts +30 -0
  136. package/dist/KineticHeadline.js +133 -0
  137. package/dist/LavaLamp.d.ts +21 -0
  138. package/dist/LavaLamp.js +133 -0
  139. package/dist/LetterSwap.d.ts +20 -0
  140. package/dist/LetterSwap.js +78 -0
  141. package/dist/LevelMap.d.ts +35 -0
  142. package/dist/LevelMap.js +189 -0
  143. package/dist/LightShafts.d.ts +22 -0
  144. package/dist/LightShafts.js +170 -0
  145. package/dist/LightboxGallery.d.ts +26 -0
  146. package/dist/LightboxGallery.js +327 -0
  147. package/dist/LinkPeek.d.ts +36 -0
  148. package/dist/LinkPeek.js +194 -0
  149. package/dist/LogoOrbit.d.ts +37 -0
  150. package/dist/LogoOrbit.js +117 -0
  151. package/dist/Marquee.d.ts +33 -0
  152. package/dist/Marquee.js +148 -0
  153. package/dist/MarqueeLine.d.ts +33 -0
  154. package/dist/MarqueeLine.js +167 -0
  155. package/dist/MasonryWall.d.ts +13 -0
  156. package/dist/MasonryWall.js +90 -0
  157. package/dist/MegaMenu.d.ts +32 -0
  158. package/dist/MegaMenu.js +280 -0
  159. package/dist/MeteorShower.d.ts +24 -0
  160. package/dist/MeteorShower.js +155 -0
  161. package/dist/Minimap.d.ts +21 -0
  162. package/dist/Minimap.js +236 -0
  163. package/dist/MobileTabBar.d.ts +38 -0
  164. package/dist/MobileTabBar.js +148 -0
  165. package/dist/MorphDialog.d.ts +34 -0
  166. package/dist/MorphDialog.js +199 -0
  167. package/dist/MorphPopover.d.ts +33 -0
  168. package/dist/MorphPopover.js +168 -0
  169. package/dist/MorphWords.d.ts +18 -0
  170. package/dist/MorphWords.js +136 -0
  171. package/dist/OTPInput.d.ts +43 -0
  172. package/dist/OTPInput.js +174 -0
  173. package/dist/OverlayKit.d.ts +97 -0
  174. package/dist/OverlayKit.js +466 -0
  175. package/dist/OverlayMenu.d.ts +32 -0
  176. package/dist/OverlayMenu.js +154 -0
  177. package/dist/ParallaxCollage.d.ts +41 -0
  178. package/dist/ParallaxCollage.js +143 -0
  179. package/dist/PasswordMeter.d.ts +44 -0
  180. package/dist/PasswordMeter.js +165 -0
  181. package/dist/PegBoard.d.ts +26 -0
  182. package/dist/PegBoard.js +184 -0
  183. package/dist/PinCard.d.ts +21 -0
  184. package/dist/PinCard.js +84 -0
  185. package/dist/PixelAvatar.d.ts +33 -0
  186. package/dist/PixelAvatar.js +144 -0
  187. package/dist/PixelButton.d.ts +20 -0
  188. package/dist/PixelButton.js +128 -0
  189. package/dist/PixelExplode.d.ts +31 -0
  190. package/dist/PixelExplode.js +164 -0
  191. package/dist/PixelLoader.d.ts +32 -0
  192. package/dist/PixelLoader.js +142 -0
  193. package/dist/PixelParallax.d.ts +41 -0
  194. package/dist/PixelParallax.js +151 -0
  195. package/dist/PixelSpinners.d.ts +21 -0
  196. package/dist/PixelSpinners.js +110 -0
  197. package/dist/PixelType.d.ts +26 -0
  198. package/dist/PixelType.js +220 -0
  199. package/dist/PointerKit.d.ts +23 -0
  200. package/dist/PointerKit.js +222 -0
  201. package/dist/Polaroids.d.ts +41 -0
  202. package/dist/Polaroids.js +293 -0
  203. package/dist/PressStart.d.ts +28 -0
  204. package/dist/PressStart.js +99 -0
  205. package/dist/PricingFlip.d.ts +52 -0
  206. package/dist/PricingFlip.js +190 -0
  207. package/dist/ProximityType.d.ts +26 -0
  208. package/dist/ProximityType.js +135 -0
  209. package/dist/QuestDialog.d.ts +50 -0
  210. package/dist/QuestDialog.js +204 -0
  211. package/dist/QuestLog.d.ts +39 -0
  212. package/dist/QuestLog.js +214 -0
  213. package/dist/RangeDual.d.ts +34 -0
  214. package/dist/RangeDual.js +238 -0
  215. package/dist/RatingPips.d.ts +25 -0
  216. package/dist/RatingPips.js +129 -0
  217. package/dist/RetroHorizon.d.ts +27 -0
  218. package/dist/RetroHorizon.js +169 -0
  219. package/dist/Reveal.d.ts +30 -0
  220. package/dist/Reveal.js +92 -0
  221. package/dist/RingProgress.d.ts +45 -0
  222. package/dist/RingProgress.js +180 -0
  223. package/dist/RollingNumber.d.ts +28 -0
  224. package/dist/RollingNumber.js +108 -0
  225. package/dist/ScanScope.d.ts +24 -0
  226. package/dist/ScanScope.js +164 -0
  227. package/dist/ScoreTicker.d.ts +32 -0
  228. package/dist/ScoreTicker.js +176 -0
  229. package/dist/ScreenScroller.d.ts +24 -0
  230. package/dist/ScreenScroller.js +138 -0
  231. package/dist/Screensaver.d.ts +31 -0
  232. package/dist/Screensaver.js +221 -0
  233. package/dist/ScrollTilt.d.ts +27 -0
  234. package/dist/ScrollTilt.js +100 -0
  235. package/dist/ScrollTypography.d.ts +26 -0
  236. package/dist/ScrollTypography.js +124 -0
  237. package/dist/ScrollZoom.d.ts +31 -0
  238. package/dist/ScrollZoom.js +123 -0
  239. package/dist/ScrubNumber.d.ts +29 -0
  240. package/dist/ScrubNumber.js +152 -0
  241. package/dist/ShadowStack.d.ts +28 -0
  242. package/dist/ShadowStack.js +116 -0
  243. package/dist/SideRail.d.ts +43 -0
  244. package/dist/SideRail.js +253 -0
  245. package/dist/SignalText.d.ts +25 -0
  246. package/dist/SignalText.js +178 -0
  247. package/dist/SkeletonShimmer.d.ts +18 -0
  248. package/dist/SkeletonShimmer.js +111 -0
  249. package/dist/SketchHighlight.d.ts +27 -0
  250. package/dist/SketchHighlight.js +180 -0
  251. package/dist/SlideTabs.d.ts +40 -0
  252. package/dist/SlideTabs.js +190 -0
  253. package/dist/SmartNavbar.d.ts +48 -0
  254. package/dist/SmartNavbar.js +188 -0
  255. package/dist/SparkChart.d.ts +41 -0
  256. package/dist/SparkChart.js +188 -0
  257. package/dist/Spoiler.d.ts +22 -0
  258. package/dist/Spoiler.js +97 -0
  259. package/dist/SpotlightTour.d.ts +38 -0
  260. package/dist/SpotlightTour.js +231 -0
  261. package/dist/SpringDrawer.d.ts +39 -0
  262. package/dist/SpringDrawer.js +360 -0
  263. package/dist/SpriteAnimator.d.ts +41 -0
  264. package/dist/SpriteAnimator.js +124 -0
  265. package/dist/StatCard.d.ts +43 -0
  266. package/dist/StatCard.js +168 -0
  267. package/dist/StepLoader.d.ts +41 -0
  268. package/dist/StepLoader.js +191 -0
  269. package/dist/StepperForm.d.ts +41 -0
  270. package/dist/StepperForm.js +206 -0
  271. package/dist/StickerBoard.d.ts +39 -0
  272. package/dist/StickerBoard.js +209 -0
  273. package/dist/StickyReveal.d.ts +42 -0
  274. package/dist/StickyReveal.js +181 -0
  275. package/dist/StoryViewer.d.ts +36 -0
  276. package/dist/StoryViewer.js +175 -0
  277. package/dist/StreamingText.d.ts +29 -0
  278. package/dist/StreamingText.js +271 -0
  279. package/dist/SuggestionChips.d.ts +32 -0
  280. package/dist/SuggestionChips.js +96 -0
  281. package/dist/SwipeActions.d.ts +30 -0
  282. package/dist/SwipeActions.js +141 -0
  283. package/dist/TagInput.d.ts +30 -0
  284. package/dist/TagInput.js +284 -0
  285. package/dist/TallyUp.d.ts +38 -0
  286. package/dist/TallyUp.js +157 -0
  287. package/dist/TerminalDemo.d.ts +63 -0
  288. package/dist/TerminalDemo.js +214 -0
  289. package/dist/TestimonialWall.d.ts +35 -0
  290. package/dist/TestimonialWall.js +131 -0
  291. package/dist/TextOnPath.d.ts +36 -0
  292. package/dist/TextOnPath.js +130 -0
  293. package/dist/ThinkingShimmer.d.ts +36 -0
  294. package/dist/ThinkingShimmer.js +132 -0
  295. package/dist/ToastStack.d.ts +62 -0
  296. package/dist/ToastStack.js +289 -0
  297. package/dist/ToggleGroup.d.ts +39 -0
  298. package/dist/ToggleGroup.js +190 -0
  299. package/dist/ToggleKit.d.ts +70 -0
  300. package/dist/ToggleKit.js +229 -0
  301. package/dist/TracingLine.d.ts +24 -0
  302. package/dist/TracingLine.js +139 -0
  303. package/dist/TradingCard.d.ts +30 -0
  304. package/dist/TradingCard.js +147 -0
  305. package/dist/VanishInput.d.ts +34 -0
  306. package/dist/VanishInput.js +190 -0
  307. package/dist/VideoHero.d.ts +32 -0
  308. package/dist/VideoHero.js +207 -0
  309. package/dist/VoiceOrb.d.ts +23 -0
  310. package/dist/VoiceOrb.js +182 -0
  311. package/dist/VoxelTiles.d.ts +38 -0
  312. package/dist/VoxelTiles.js +201 -0
  313. package/dist/WarpField.d.ts +18 -0
  314. package/dist/WarpField.js +160 -0
  315. package/dist/WebcamPixels.d.ts +38 -0
  316. package/dist/WebcamPixels.js +292 -0
  317. package/dist/WheelPicker.d.ts +29 -0
  318. package/dist/WheelPicker.js +199 -0
  319. package/dist/WobbleCard.d.ts +23 -0
  320. package/dist/WobbleCard.js +112 -0
  321. package/dist/index.d.ts +158 -0
  322. package/dist/index.js +159 -0
  323. package/package.json +58 -4
@@ -0,0 +1,30 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ export interface TradingCardProps {
3
+ name?: string;
4
+ image?: string;
5
+ imageAlt?: string;
6
+ /** Frame colour and how strong the holo foil is (common has none). */
7
+ rarity?: 'common' | 'rare' | 'epic' | 'legendary';
8
+ stats?: {
9
+ label: string;
10
+ value: ReactNode;
11
+ }[];
12
+ /** With a back, the card is a button that flips. */
13
+ back?: ReactNode;
14
+ flipped?: boolean;
15
+ onFlip?: (flipped: boolean) => void;
16
+ /** Maximum tilt toward the pointer, in degrees. */
17
+ tilt?: number;
18
+ width?: number;
19
+ radius?: number;
20
+ cardColor?: string;
21
+ backColor?: string;
22
+ textColor?: string;
23
+ mutedColor?: string;
24
+ accentColor?: string;
25
+ frameColor?: string;
26
+ fontFamily?: string;
27
+ className?: string;
28
+ style?: CSSProperties;
29
+ }
30
+ export default function TradingCard({ name, image, imageAlt, rarity, stats, back, flipped: controlled, onFlip, tilt, width, radius, cardColor, backColor, textColor, mutedColor, accentColor, frameColor, fontFamily, className, style }: TradingCardProps): import("react").JSX.Element;
@@ -0,0 +1,147 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useInsertionEffect, useRef, useState } from "react";
4
+ const STYLE_ID = "bl-trading-card";
5
+ const CSS = `
6
+ .bl-tcard { --rx: 0deg; --ry: 0deg; --gx: 50%; --gy: 50%; --shift: 0.5; display: inline-block; width: var(--bl-w); perspective: 900px; font-family: var(--bl-font); color: var(--bl-ink); }
7
+ .bl-tcard__flip { position: relative; display: block; width: 100%; aspect-ratio: 5 / 7; margin: 0; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: left; transform: rotateX(var(--rx)) rotateY(calc(var(--ry) + var(--turn))); transform-style: preserve-3d; transition: transform 500ms cubic-bezier(.3,1.2,.4,1); cursor: default; }
8
+ .bl-tcard[data-flippable] .bl-tcard__flip { cursor: pointer; }
9
+ .bl-tcard[data-tilting] .bl-tcard__flip { transition: transform 80ms linear; }
10
+ .bl-tcard__flip:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 6px; border-radius: var(--bl-radius); }
11
+ .bl-tcard__face { position: absolute; inset: 0; display: grid; grid-template-rows: auto 1fr auto; gap: 8px; overflow: hidden; padding: 10px; border-radius: var(--bl-radius); background: var(--bl-card); box-shadow: inset 0 0 0 4px var(--bl-frame), inset 0 0 0 6px rgba(0,0,0,0.35), 0 18px 34px -16px rgba(0,0,0,0.9); backface-visibility: hidden; }
12
+ .bl-tcard__back { transform: rotateY(180deg); place-items: center; grid-template-rows: 1fr; background: var(--bl-back); }
13
+ .bl-tcard__head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; padding: 4px 6px 0; }
14
+ .bl-tcard__name { font-size: 15px; font-weight: 700; line-height: 1.2; }
15
+ .bl-tcard__rarity { color: var(--bl-frame); font-size: 10px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; }
16
+ .bl-tcard__art { position: relative; overflow: hidden; border-radius: 6px; background: #0b0b14; box-shadow: inset 0 0 0 2px rgba(0,0,0,0.5); }
17
+ .bl-tcard__art > img { display: block; width: 100%; height: 100%; object-fit: cover; image-rendering: pixelated; }
18
+ .bl-tcard__stats { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 4px; padding: 0 2px 2px; }
19
+ .bl-tcard__stat { display: grid; justify-items: center; padding: 4px 2px; border-radius: 4px; background: rgba(0,0,0,0.3); }
20
+ .bl-tcard__stat-label { color: var(--bl-muted); font-size: 9px; text-transform: uppercase; letter-spacing: 0.06em; }
21
+ .bl-tcard__stat-value { font-size: 14px; font-weight: 700; font-variant-numeric: tabular-nums; }
22
+ .bl-tcard__foil { position: absolute; inset: 0; border-radius: inherit; background: repeating-linear-gradient(115deg, #ef7d57 0 8%, #ffcd75 8% 16%, #a7f070 16% 24%, #73eff7 24% 32%, #41a6f6 32% 40%, #b13e53 40% 48%); background-size: 260% 260%; background-position: calc(var(--shift) * 100%) calc(var(--shift) * 60%); mix-blend-mode: color-dodge; opacity: var(--bl-foil); pointer-events: none; }
23
+ .bl-tcard__glare { position: absolute; inset: 0; border-radius: inherit; background: radial-gradient(circle at var(--gx) var(--gy), rgba(255,255,255,0.45), transparent 45%); mix-blend-mode: overlay; opacity: 0; transition: opacity 200ms; pointer-events: none; }
24
+ .bl-tcard[data-tilting] .bl-tcard__glare { opacity: 1; }
25
+ @media (prefers-reduced-motion: reduce) { .bl-tcard__flip { transition: none; transform: rotateY(var(--turn)); } .bl-tcard__glare { display: none; } }
26
+ `;
27
+ function useStyles() {
28
+ useInsertionEffect(() => {
29
+ if (typeof document === "undefined") return;
30
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
31
+ style.textContent = CSS;
32
+ if (!style.isConnected) document.head.appendChild(style);
33
+ }, []);
34
+ }
35
+ const RARITY = {
36
+ common: { frame: "#94b0c2", foil: 0 },
37
+ rare: { frame: "#41a6f6", foil: 0.25 },
38
+ epic: { frame: "#b13e53", foil: 0.4 },
39
+ legendary: { frame: "#ffcd75", foil: 0.55 }
40
+ };
41
+ function TradingCard({
42
+ name = "",
43
+ image,
44
+ imageAlt = "",
45
+ rarity = "rare",
46
+ stats = [],
47
+ back,
48
+ flipped: controlled,
49
+ onFlip,
50
+ tilt = 14,
51
+ width = 240,
52
+ radius = 14,
53
+ cardColor = "#262b44",
54
+ backColor = "#29366f",
55
+ textColor = "#f4f4f4",
56
+ mutedColor = "#94b0c2",
57
+ accentColor = "#73eff7",
58
+ frameColor,
59
+ fontFamily = "inherit",
60
+ className = "",
61
+ style
62
+ }) {
63
+ useStyles();
64
+ const rootRef = useRef(null);
65
+ const [inner, setInner] = useState(false);
66
+ const flipped = controlled ?? inner;
67
+ const tier = RARITY[rarity] ?? RARITY.rare;
68
+ useEffect(() => {
69
+ const root = rootRef.current;
70
+ if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return void 0;
71
+ let raf = 0;
72
+ const onMove = (event) => {
73
+ if (raf) return;
74
+ raf = requestAnimationFrame(() => {
75
+ raf = 0;
76
+ const r = root.getBoundingClientRect();
77
+ const x = (event.clientX - r.left) / r.width;
78
+ const y = (event.clientY - r.top) / r.height;
79
+ root.style.setProperty("--ry", `${((x - 0.5) * tilt * 2).toFixed(2)}deg`);
80
+ root.style.setProperty("--rx", `${((0.5 - y) * tilt * 2).toFixed(2)}deg`);
81
+ root.style.setProperty("--gx", `${(x * 100).toFixed(1)}%`);
82
+ root.style.setProperty("--gy", `${(y * 100).toFixed(1)}%`);
83
+ root.style.setProperty("--shift", ((x + y) / 2).toFixed(3));
84
+ root.dataset.tilting = "";
85
+ });
86
+ };
87
+ const onLeave = () => {
88
+ cancelAnimationFrame(raf);
89
+ raf = 0;
90
+ ["--rx", "--ry", "--gx", "--gy", "--shift"].forEach((p) => root.style.removeProperty(p));
91
+ delete root.dataset.tilting;
92
+ };
93
+ root.addEventListener("pointermove", onMove);
94
+ root.addEventListener("pointerleave", onLeave);
95
+ return () => {
96
+ cancelAnimationFrame(raf);
97
+ root.removeEventListener("pointermove", onMove);
98
+ root.removeEventListener("pointerleave", onLeave);
99
+ };
100
+ }, [tilt]);
101
+ const flip = () => {
102
+ if (!back) return;
103
+ if (controlled === void 0) setInner((f) => !f);
104
+ onFlip?.(!flipped);
105
+ };
106
+ const face = /* @__PURE__ */ jsxs("span", { className: "bl-tcard__face", children: [
107
+ /* @__PURE__ */ jsxs("span", { className: "bl-tcard__head", children: [
108
+ /* @__PURE__ */ jsx("span", { className: "bl-tcard__name", children: name }),
109
+ /* @__PURE__ */ jsx("span", { className: "bl-tcard__rarity", children: rarity })
110
+ ] }),
111
+ /* @__PURE__ */ jsx("span", { className: "bl-tcard__art", children: image && /* @__PURE__ */ jsx("img", { src: image, alt: imageAlt, draggable: false }) }),
112
+ stats.length > 0 && // Spans, not a <dl>: the card can be a button, which only takes inline content.
113
+ /* @__PURE__ */ jsx("span", { className: "bl-tcard__stats", role: "list", children: stats.map((s) => /* @__PURE__ */ jsxs("span", { className: "bl-tcard__stat", role: "listitem", children: [
114
+ /* @__PURE__ */ jsx("span", { className: "bl-tcard__stat-label", children: s.label }),
115
+ /* @__PURE__ */ jsx("span", { className: "bl-tcard__stat-value", children: s.value })
116
+ ] }, s.label)) }),
117
+ tier.foil > 0 && /* @__PURE__ */ jsx("span", { className: "bl-tcard__foil", "aria-hidden": "true" }),
118
+ /* @__PURE__ */ jsx("span", { className: "bl-tcard__glare", "aria-hidden": "true" })
119
+ ] });
120
+ const Tag = back ? "button" : "div";
121
+ return /* @__PURE__ */ jsx(
122
+ "div",
123
+ {
124
+ ref: rootRef,
125
+ className: `bl-tcard ${className}`,
126
+ "data-flippable": back ? "" : void 0,
127
+ style: { "--bl-w": `${width}px`, "--bl-radius": `${radius}px`, "--bl-card": cardColor, "--bl-back": backColor, "--bl-ink": textColor, "--bl-muted": mutedColor, "--bl-accent": accentColor, "--bl-frame": frameColor ?? tier.frame, "--bl-foil": tier.foil, "--turn": flipped ? "180deg" : "0deg", "--bl-font": fontFamily, ...style },
128
+ children: /* @__PURE__ */ jsxs(
129
+ Tag,
130
+ {
131
+ className: "bl-tcard__flip",
132
+ type: back ? "button" : void 0,
133
+ "aria-pressed": back ? flipped : void 0,
134
+ "aria-label": back ? `${name}, ${rarity} card. ${flipped ? "Showing the back" : "Showing the front"}; press to flip.` : void 0,
135
+ onClick: back ? flip : void 0,
136
+ children: [
137
+ face,
138
+ back && /* @__PURE__ */ jsx("span", { className: "bl-tcard__face bl-tcard__back", "aria-hidden": !flipped || void 0, children: back })
139
+ ]
140
+ }
141
+ )
142
+ }
143
+ );
144
+ }
145
+ export {
146
+ TradingCard as default
147
+ };
@@ -0,0 +1,34 @@
1
+ import { type CSSProperties } from 'react';
2
+ export interface VanishInputProps {
3
+ /** Suggestions that rotate while the field is empty. */
4
+ placeholders?: string[];
5
+ /** Time per placeholder, in ms. */
6
+ interval?: number;
7
+ /** Called with the trimmed text; the field then clears. */
8
+ onSubmit?: (text: string) => void;
9
+ onChange?: (text: string) => void;
10
+ /** Accessible name of the field. */
11
+ label?: string;
12
+ /** Accessible name of the submit button. */
13
+ submitLabel?: string;
14
+ /** Particle size, in px. */
15
+ particleSize?: number;
16
+ /** Dissolve length, in ms. */
17
+ duration?: number;
18
+ width?: number;
19
+ height?: number;
20
+ radius?: number;
21
+ fontSize?: number;
22
+ /** Left text padding, in px. */
23
+ padding?: number;
24
+ backgroundColor?: string;
25
+ textColor?: string;
26
+ mutedColor?: string;
27
+ lineColor?: string;
28
+ accentColor?: string;
29
+ accentTextColor?: string;
30
+ fontFamily?: string;
31
+ className?: string;
32
+ style?: CSSProperties;
33
+ }
34
+ export default function VanishInput({ placeholders, interval, onSubmit, onChange, label, submitLabel, particleSize, duration, width, height, radius, fontSize, padding, backgroundColor, textColor, mutedColor, lineColor, accentColor, accentTextColor, fontFamily, className, style }: VanishInputProps): import("react").JSX.Element;
@@ -0,0 +1,190 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useInsertionEffect, useRef, useState } from "react";
4
+ const STYLE_ID = "bl-vanish";
5
+ const CSS = `
6
+ .bl-vanish { position: relative; display: flex; align-items: center; width: 100%; max-width: var(--bl-w); height: var(--bl-h); border-radius: var(--bl-radius); background: var(--bl-bg); box-shadow: inset 0 0 0 1px var(--bl-line), 0 10px 30px -18px rgba(0,0,0,0.6); font-family: var(--bl-font); transition: box-shadow 160ms; }
7
+ .bl-vanish:focus-within { box-shadow: inset 0 0 0 1.5px var(--bl-accent), 0 0 0 4px var(--bl-ring); }
8
+ .bl-vanish__field { position: relative; flex: 1; min-width: 0; height: 100%; overflow: hidden; }
9
+ .bl-vanish__input { width: 100%; height: 100%; padding: 0 0 0 var(--bl-pad); border: 0; background: transparent; color: var(--bl-ink); font: inherit; font-size: var(--bl-size); outline: none; }
10
+ .bl-vanish[data-vanishing] .bl-vanish__input { color: transparent; }
11
+ .bl-vanish__placeholder { position: absolute; left: var(--bl-pad); right: 8px; top: 50%; color: var(--bl-muted); font-size: var(--bl-size); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; transform: translateY(-50%); animation: bl-vanish-in 420ms cubic-bezier(.2,.9,.25,1); }
12
+ @keyframes bl-vanish-in { from { opacity: 0; transform: translateY(calc(-50% + 12px)); } }
13
+ .bl-vanish__canvas { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
14
+ .bl-vanish__submit { flex: 0 0 auto; display: grid; place-items: center; width: calc(var(--bl-h) - 12px); height: calc(var(--bl-h) - 12px); margin-right: 6px; border: 0; border-radius: calc(var(--bl-radius) - 4px); background: var(--bl-accent); color: var(--bl-accent-ink); cursor: pointer; transition: opacity 160ms, transform 160ms; }
15
+ .bl-vanish__submit:disabled { opacity: 0.35; cursor: default; }
16
+ .bl-vanish__submit:not(:disabled):hover { transform: scale(1.05); }
17
+ .bl-vanish__submit:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
18
+ @media (prefers-reduced-motion: reduce) { .bl-vanish__placeholder { animation: none; } .bl-vanish, .bl-vanish__submit { transition: none; } }
19
+ `;
20
+ function useStyles() {
21
+ useInsertionEffect(() => {
22
+ if (typeof document === "undefined") return;
23
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
24
+ style.textContent = CSS;
25
+ if (!style.isConnected) document.head.appendChild(style);
26
+ }, []);
27
+ }
28
+ const DEFAULT_PLACEHOLDERS = ["Search for a component\u2026", 'Try "pixel button"', "Find a background", "What are you building today?"];
29
+ function VanishInput({
30
+ placeholders = DEFAULT_PLACEHOLDERS,
31
+ interval = 3e3,
32
+ onSubmit,
33
+ onChange,
34
+ label = "Search",
35
+ submitLabel = "Submit",
36
+ particleSize = 2,
37
+ duration = 900,
38
+ width = 560,
39
+ height = 52,
40
+ radius = 26,
41
+ fontSize = 16,
42
+ padding = 20,
43
+ backgroundColor = "#1a1c2c",
44
+ textColor = "#f4f4f4",
45
+ mutedColor = "#94b0c2",
46
+ lineColor = "rgba(255,255,255,0.12)",
47
+ accentColor = "#73eff7",
48
+ accentTextColor = "#0b0b14",
49
+ fontFamily = "ui-sans-serif, system-ui, sans-serif",
50
+ className = "",
51
+ style
52
+ }) {
53
+ useStyles();
54
+ const [value, setValue] = useState("");
55
+ const [index, setIndex] = useState(0);
56
+ const [vanishing, setVanishing] = useState(false);
57
+ const inputRef = useRef(null);
58
+ const canvasRef = useRef(null);
59
+ const frame = useRef(0);
60
+ useEffect(() => {
61
+ if (placeholders.length < 2 || value) return void 0;
62
+ const timer = setInterval(() => setIndex((i) => (i + 1) % placeholders.length), interval);
63
+ return () => clearInterval(timer);
64
+ }, [placeholders.length, interval, value]);
65
+ useEffect(() => () => cancelAnimationFrame(frame.current), []);
66
+ const vanish = (text) => {
67
+ const input = inputRef.current;
68
+ const canvas = canvasRef.current;
69
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
70
+ if (reduced || !canvas) return;
71
+ const dpr = Math.min(2, window.devicePixelRatio || 1);
72
+ const w = input.clientWidth;
73
+ const h = input.clientHeight;
74
+ canvas.width = Math.round(w * dpr);
75
+ canvas.height = Math.round(h * dpr);
76
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
77
+ const computed = getComputedStyle(input);
78
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
79
+ ctx.font = `${computed.fontWeight} ${computed.fontSize} ${computed.fontFamily}`;
80
+ ctx.fillStyle = textColor;
81
+ ctx.textBaseline = "middle";
82
+ ctx.fillText(text, parseFloat(computed.paddingLeft) - input.scrollLeft, h / 2);
83
+ const step = Math.max(1, Math.round(particleSize * dpr));
84
+ const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height);
85
+ const particles = [];
86
+ let maxX = 0;
87
+ for (let y = 0; y < canvas.height; y += step) {
88
+ for (let x = 0; x < canvas.width; x += step) {
89
+ const i = (y * canvas.width + x) * 4;
90
+ if (data[i + 3] < 100) continue;
91
+ particles.push({ x: x / dpr, y: y / dpr, vx: 0, vy: 0, alpha: 1, color: `rgb(${data[i]},${data[i + 1]},${data[i + 2]})` });
92
+ maxX = Math.max(maxX, x / dpr);
93
+ }
94
+ }
95
+ const size = step / dpr;
96
+ const start = performance.now();
97
+ const sweep = duration * 0.55;
98
+ const tick = (now) => {
99
+ const t = now - start;
100
+ ctx.clearRect(0, 0, w, h);
101
+ let alive = 0;
102
+ for (const p of particles) {
103
+ if (p.alpha <= 0) continue;
104
+ alive++;
105
+ const released = t > (maxX - p.x) / Math.max(1, maxX) * sweep;
106
+ if (released) {
107
+ if (!p.vx) {
108
+ p.vx = 0.6 + Math.random() * 1.6;
109
+ p.vy = (Math.random() - 0.5) * 1.6;
110
+ }
111
+ p.x += p.vx;
112
+ p.y += p.vy;
113
+ p.alpha -= 0.02 + Math.random() * 0.03;
114
+ }
115
+ ctx.globalAlpha = Math.max(0, p.alpha);
116
+ ctx.fillStyle = p.color;
117
+ ctx.fillRect(p.x, p.y, size, size);
118
+ }
119
+ ctx.globalAlpha = 1;
120
+ if (alive && t < duration * 2) frame.current = requestAnimationFrame(tick);
121
+ else {
122
+ ctx.clearRect(0, 0, w, h);
123
+ setVanishing(false);
124
+ }
125
+ };
126
+ setVanishing(true);
127
+ cancelAnimationFrame(frame.current);
128
+ frame.current = requestAnimationFrame(tick);
129
+ };
130
+ const submit = (event) => {
131
+ event.preventDefault();
132
+ const text = value.trim();
133
+ if (!text || vanishing) return;
134
+ vanish(value);
135
+ onSubmit?.(text);
136
+ setValue("");
137
+ onChange?.("");
138
+ };
139
+ return /* @__PURE__ */ jsxs(
140
+ "form",
141
+ {
142
+ className: `bl-vanish ${className}`,
143
+ role: "search",
144
+ "data-vanishing": vanishing || void 0,
145
+ onSubmit: submit,
146
+ style: {
147
+ "--bl-w": `${width}px`,
148
+ "--bl-h": `${height}px`,
149
+ "--bl-radius": `${radius}px`,
150
+ "--bl-size": `${fontSize}px`,
151
+ "--bl-pad": `${padding}px`,
152
+ "--bl-bg": backgroundColor,
153
+ "--bl-ink": textColor,
154
+ "--bl-muted": mutedColor,
155
+ "--bl-line": lineColor,
156
+ "--bl-accent": accentColor,
157
+ "--bl-accent-ink": accentTextColor,
158
+ "--bl-ring": `color-mix(in srgb, ${accentColor} 22%, transparent)`,
159
+ "--bl-font": fontFamily,
160
+ ...style
161
+ },
162
+ children: [
163
+ /* @__PURE__ */ jsxs("div", { className: "bl-vanish__field", children: [
164
+ /* @__PURE__ */ jsx(
165
+ "input",
166
+ {
167
+ ref: inputRef,
168
+ className: "bl-vanish__input",
169
+ type: "text",
170
+ value,
171
+ onChange: (event) => {
172
+ setValue(event.target.value);
173
+ onChange?.(event.target.value);
174
+ },
175
+ "aria-label": label,
176
+ autoComplete: "off",
177
+ enterKeyHint: "send"
178
+ }
179
+ ),
180
+ !value && !vanishing && placeholders.length > 0 && /* @__PURE__ */ jsx("span", { className: "bl-vanish__placeholder", "aria-hidden": "true", children: placeholders[index % placeholders.length] }, index),
181
+ /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "bl-vanish__canvas", "aria-hidden": "true" })
182
+ ] }),
183
+ /* @__PURE__ */ jsx("button", { type: "submit", className: "bl-vanish__submit", disabled: !value.trim() || vanishing, "aria-label": submitLabel, children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M3 8h10M9 4l4 4-4 4" }) }) })
184
+ ]
185
+ }
186
+ );
187
+ }
188
+ export {
189
+ VanishInput as default
190
+ };
@@ -0,0 +1,32 @@
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
+ export interface VideoHeroProps {
3
+ /** A video file to play in the lightbox. */
4
+ src?: string;
5
+ /** An embed player URL (rendered in an iframe) instead of a file. */
6
+ embedUrl?: string;
7
+ /** Thumbnail image (also the video poster). */
8
+ poster?: string;
9
+ /** Custom thumbnail content instead of `poster`. */
10
+ children?: ReactNode;
11
+ title?: ReactNode;
12
+ meta?: ReactNode;
13
+ /** Button name; defaults to "Play video: title". */
14
+ label?: string;
15
+ aspectRatio?: string;
16
+ /** Max thumbnail width, in px. */
17
+ width?: number;
18
+ radius?: number;
19
+ buttonSize?: number;
20
+ /** Play button leans toward the cursor. */
21
+ magnetic?: boolean;
22
+ /** Lightbox grow length, in ms. */
23
+ duration?: number;
24
+ accentColor?: string;
25
+ accentTextColor?: string;
26
+ backdropColor?: string;
27
+ fontFamily?: string;
28
+ onOpenChange?: (open: boolean) => void;
29
+ className?: string;
30
+ style?: CSSProperties;
31
+ }
32
+ export default function VideoHero({ src, embedUrl, poster, children, title, meta, label, aspectRatio, width, radius, buttonSize, magnetic, duration, accentColor, accentTextColor, backdropColor, fontFamily, onOpenChange, className, style }: VideoHeroProps): import("react").JSX.Element;
@@ -0,0 +1,207 @@
1
+ 'use client';
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+ const STYLE_ID = "bl-video-hero";
6
+ const CSS = `
7
+ .bl-vhero { position: relative; display: block; width: 100%; max-width: var(--bl-w); aspect-ratio: var(--bl-ratio); padding: 0; overflow: hidden; border: 0; border-radius: var(--bl-radius); background: #000; color: #fff; font-family: var(--bl-font); cursor: pointer; box-shadow: 0 30px 60px -30px rgba(0,0,0,0.7); }
8
+ .bl-vhero:focus-visible { outline: 3px solid var(--bl-accent); outline-offset: 3px; }
9
+ .bl-vhero__poster { position: absolute; inset: 0; transition: transform 600ms cubic-bezier(.2,.9,.25,1), filter 300ms; }
10
+ .bl-vhero__poster > img, .bl-vhero__poster > video { width: 100%; height: 100%; object-fit: cover; display: block; }
11
+ .bl-vhero:hover .bl-vhero__poster { transform: scale(1.04); filter: brightness(0.8); }
12
+ .bl-vhero__shade { position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,0.55), transparent 55%); }
13
+ .bl-vhero__play { position: absolute; left: 50%; top: 50%; display: grid; place-items: center; width: var(--bl-btn); height: var(--bl-btn); border-radius: 50%; background: var(--bl-accent); color: var(--bl-accent-ink); transform: translate(calc(-50% + var(--bl-px, 0px)), calc(-50% + var(--bl-py, 0px))) scale(var(--bl-ps, 1)); transition: transform 300ms cubic-bezier(.2,.9,.25,1.3); box-shadow: 0 10px 30px -8px rgba(0,0,0,0.6); }
14
+ .bl-vhero:hover .bl-vhero__play { --bl-ps: 1.1; }
15
+ .bl-vhero__play::before, .bl-vhero__play::after { content: ""; position: absolute; inset: 0; border-radius: 50%; border: 2px solid var(--bl-accent); animation: bl-vhero-ring 2.4s cubic-bezier(.2,.6,.3,1) infinite; }
16
+ .bl-vhero__play::after { animation-delay: 1.2s; }
17
+ .bl-vhero__caption { position: absolute; left: 18px; right: 18px; bottom: 16px; text-align: left; }
18
+ .bl-vhero__title { display: block; font-size: 18px; font-weight: 700; }
19
+ .bl-vhero__meta { display: block; margin-top: 3px; color: rgba(255,255,255,0.75); font-size: 13px; }
20
+ .bl-vhero-box__backdrop { position: fixed; inset: 0; z-index: 1400; background: var(--bl-backdrop); }
21
+ .bl-vhero-box { position: fixed; z-index: 1401; overflow: hidden; border-radius: var(--bl-radius); background: #000; box-shadow: 0 40px 100px -30px rgba(0,0,0,0.9); outline: none; }
22
+ .bl-vhero-box > video, .bl-vhero-box > iframe { display: block; width: 100%; height: 100%; border: 0; }
23
+ .bl-vhero-box__close { position: fixed; z-index: 1402; top: 16px; right: 16px; display: grid; place-items: center; width: 42px; height: 42px; border: 0; border-radius: 50%; background: rgba(255,255,255,0.14); color: #fff; cursor: pointer; }
24
+ .bl-vhero-box__close:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
25
+ @keyframes bl-vhero-ring { from { transform: scale(1); opacity: 0.8; } to { transform: scale(1.9); opacity: 0; } }
26
+ @media (prefers-reduced-motion: reduce) { .bl-vhero__poster, .bl-vhero__play { transition: none; } .bl-vhero__play::before, .bl-vhero__play::after { animation: none; opacity: 0; } }
27
+ `;
28
+ function useStyles() {
29
+ useInsertionEffect(() => {
30
+ if (typeof document === "undefined") return;
31
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
32
+ style.textContent = CSS;
33
+ if (!style.isConnected) document.head.appendChild(style);
34
+ }, []);
35
+ }
36
+ const reduced = () => window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
37
+ function VideoHero({
38
+ src,
39
+ embedUrl,
40
+ poster,
41
+ children,
42
+ title,
43
+ meta,
44
+ label,
45
+ aspectRatio = "16 / 9",
46
+ width = 720,
47
+ radius = 16,
48
+ buttonSize = 72,
49
+ magnetic = true,
50
+ duration = 520,
51
+ accentColor = "#f4f4f4",
52
+ accentTextColor = "#1a1c2c",
53
+ backdropColor = "rgba(6,6,12,0.82)",
54
+ fontFamily = "ui-sans-serif, system-ui, sans-serif",
55
+ onOpenChange,
56
+ className = "",
57
+ style
58
+ }) {
59
+ useStyles();
60
+ const triggerRef = useRef(null);
61
+ const boxRef = useRef(null);
62
+ const backdropRef = useRef(null);
63
+ const closeRef = useRef(null);
64
+ const [open, setOpen] = useState(false);
65
+ const [mounted, setMounted] = useState(false);
66
+ const closing = useRef(false);
67
+ const change = (next) => {
68
+ setOpen(next);
69
+ onOpenChange?.(next);
70
+ };
71
+ const target = () => {
72
+ const [w, h] = aspectRatio.split("/").map(Number);
73
+ const ratio = w / h || 16 / 9;
74
+ const maxW = Math.min(window.innerWidth - 32, 1100);
75
+ const maxH = window.innerHeight - 96;
76
+ const width2 = Math.min(maxW, maxH * ratio);
77
+ const height = width2 / ratio;
78
+ return { left: (window.innerWidth - width2) / 2, top: (window.innerHeight - height) / 2, width: width2, height };
79
+ };
80
+ const box = (r) => ({ left: `${r.left}px`, top: `${r.top}px`, width: `${r.width}px`, height: `${r.height}px` });
81
+ useEffect(() => {
82
+ if (open) setMounted(true);
83
+ }, [open]);
84
+ useLayoutEffect(() => {
85
+ if (!mounted || !open) return void 0;
86
+ const node = boxRef.current;
87
+ const from = triggerRef.current.getBoundingClientRect();
88
+ const to = target();
89
+ Object.assign(node.style, box(to));
90
+ if (!reduced()) node.animate([box(from), box(to)], { duration, easing: "cubic-bezier(.2,.9,.25,1)" });
91
+ backdropRef.current.animate({ opacity: [0, 1] }, { duration: duration * 0.6, fill: "both" });
92
+ const video = node.querySelector("video");
93
+ video?.play().catch(() => {
94
+ video.muted = true;
95
+ video.play().catch(() => {
96
+ });
97
+ });
98
+ closeRef.current?.focus({ preventScroll: true });
99
+ const previous = document.body.style.overflow;
100
+ document.body.style.overflow = "hidden";
101
+ const onResize = () => Object.assign(node.style, box(target()));
102
+ window.addEventListener("resize", onResize);
103
+ return () => {
104
+ document.body.style.overflow = previous;
105
+ window.removeEventListener("resize", onResize);
106
+ };
107
+ }, [mounted, open]);
108
+ useEffect(() => {
109
+ if (open || !mounted || closing.current) return;
110
+ closing.current = true;
111
+ const node = boxRef.current;
112
+ const finish = () => {
113
+ closing.current = false;
114
+ setMounted(false);
115
+ triggerRef.current?.focus({ preventScroll: true });
116
+ };
117
+ node.querySelector("video")?.pause();
118
+ backdropRef.current.animate({ opacity: [1, 0] }, { duration: duration * 0.6, fill: "forwards" });
119
+ if (reduced()) return finish();
120
+ const from = node.getBoundingClientRect();
121
+ node.animate([box(from), box(triggerRef.current.getBoundingClientRect())], { duration: duration * 0.85, easing: "cubic-bezier(.4,0,.2,1)", fill: "forwards" }).onfinish = finish;
122
+ }, [open, mounted]);
123
+ const onPointerMove = (event) => {
124
+ if (!magnetic || reduced()) return;
125
+ const rect = event.currentTarget.getBoundingClientRect();
126
+ const x = (event.clientX - rect.left) / rect.width - 0.5;
127
+ const y = (event.clientY - rect.top) / rect.height - 0.5;
128
+ event.currentTarget.style.setProperty("--bl-px", `${x * 40}px`);
129
+ event.currentTarget.style.setProperty("--bl-py", `${y * 30}px`);
130
+ };
131
+ const onPointerLeave = (event) => {
132
+ event.currentTarget.style.removeProperty("--bl-px");
133
+ event.currentTarget.style.removeProperty("--bl-py");
134
+ };
135
+ const vars = {
136
+ "--bl-w": `${width}px`,
137
+ "--bl-ratio": aspectRatio,
138
+ "--bl-radius": `${radius}px`,
139
+ "--bl-btn": `${buttonSize}px`,
140
+ "--bl-accent": accentColor,
141
+ "--bl-accent-ink": accentTextColor,
142
+ "--bl-backdrop": backdropColor,
143
+ "--bl-font": fontFamily
144
+ };
145
+ const name = label ?? (typeof title === "string" ? `Play video: ${title}` : "Play video");
146
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
147
+ /* @__PURE__ */ jsxs(
148
+ "button",
149
+ {
150
+ ref: triggerRef,
151
+ type: "button",
152
+ className: `bl-vhero ${className}`,
153
+ "aria-label": name,
154
+ "aria-haspopup": "dialog",
155
+ onClick: () => change(true),
156
+ onPointerMove,
157
+ onPointerLeave,
158
+ style: { ...vars, ...style },
159
+ children: [
160
+ /* @__PURE__ */ jsx("span", { className: "bl-vhero__poster", "aria-hidden": "true", children: children ?? (poster ? /* @__PURE__ */ jsx("img", { src: poster, alt: "" }) : null) }),
161
+ (title || meta) && /* @__PURE__ */ jsx("span", { className: "bl-vhero__shade", "aria-hidden": "true" }),
162
+ /* @__PURE__ */ jsx("span", { className: "bl-vhero__play", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { width: buttonSize * 0.34, height: buttonSize * 0.34, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M7 4.5v15l13-7.5z" }) }) }),
163
+ (title || meta) && /* @__PURE__ */ jsxs("span", { className: "bl-vhero__caption", "aria-hidden": "true", children: [
164
+ title && /* @__PURE__ */ jsx("span", { className: "bl-vhero__title", children: title }),
165
+ meta && /* @__PURE__ */ jsx("span", { className: "bl-vhero__meta", children: meta })
166
+ ] })
167
+ ]
168
+ }
169
+ ),
170
+ mounted && createPortal(
171
+ /* @__PURE__ */ jsxs("div", { style: vars, children: [
172
+ /* @__PURE__ */ jsx("div", { ref: backdropRef, className: "bl-vhero-box__backdrop", onClick: () => change(false) }),
173
+ /* @__PURE__ */ jsxs(
174
+ "div",
175
+ {
176
+ ref: boxRef,
177
+ className: "bl-vhero-box",
178
+ role: "dialog",
179
+ "aria-modal": "true",
180
+ "aria-label": typeof title === "string" ? title : "Video",
181
+ tabIndex: -1,
182
+ onKeyDown: (event) => {
183
+ if (event.key === "Escape") {
184
+ event.stopPropagation();
185
+ change(false);
186
+ }
187
+ if (event.key === "Tab") {
188
+ const items = [closeRef.current, boxRef.current.querySelector("video, iframe")].filter(Boolean);
189
+ const index = items.indexOf(document.activeElement);
190
+ event.preventDefault();
191
+ items[(index + (event.shiftKey ? -1 : 1) + items.length) % items.length]?.focus();
192
+ }
193
+ },
194
+ children: [
195
+ /* @__PURE__ */ jsx("button", { ref: closeRef, type: "button", className: "bl-vhero-box__close", "aria-label": "Close video", onClick: () => change(false), children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M3 3l10 10M13 3 3 13" }) }) }),
196
+ embedUrl ? /* @__PURE__ */ jsx("iframe", { src: embedUrl, title: typeof title === "string" ? title : "Video", allow: "autoplay; fullscreen; picture-in-picture; encrypted-media", allowFullScreen: true }) : /* @__PURE__ */ jsx("video", { src, poster, controls: true, autoPlay: true, playsInline: true })
197
+ ]
198
+ }
199
+ )
200
+ ] }),
201
+ document.body
202
+ )
203
+ ] });
204
+ }
205
+ export {
206
+ VideoHero as default
207
+ };
@@ -0,0 +1,23 @@
1
+ import { type CSSProperties } from 'react';
2
+ export type OrbState = 'idle' | 'listening' | 'thinking' | 'speaking';
3
+ export interface VoiceOrbProps {
4
+ state?: OrbState;
5
+ /** With "mic", the microphone opens only while listening and is released right after. */
6
+ source?: 'simulated' | 'mic';
7
+ /** Drive the orb yourself (0–1), e.g. from your TTS audio. Overrides the source. */
8
+ level?: number;
9
+ /** Diameter, in px. */
10
+ size?: number;
11
+ /** Gradient stops; they rotate. */
12
+ colors?: string[];
13
+ glowColor?: string;
14
+ /** Level gain. */
15
+ sensitivity?: number;
16
+ /** Accessible name, combined with the state. */
17
+ label?: string;
18
+ /** Called if the microphone is denied or missing (falls back to a simulated voice). */
19
+ onMicError?: (error: unknown) => void;
20
+ className?: string;
21
+ style?: CSSProperties;
22
+ }
23
+ export default function VoiceOrb({ state, source, level, size, colors, glowColor, sensitivity, label, onMicError, className, style }: VoiceOrbProps): import("react").JSX.Element;