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.
- package/LICENSE +21 -0
- package/README.md +51 -2
- package/dist/Accordion.d.ts +52 -0
- package/dist/Accordion.js +192 -0
- package/dist/AchievementToast.d.ts +36 -0
- package/dist/AchievementToast.js +162 -0
- package/dist/AgentCard.d.ts +41 -0
- package/dist/AgentCard.js +168 -0
- package/dist/ArcadeButtons.d.ts +23 -0
- package/dist/ArcadeButtons.js +169 -0
- package/dist/AuroraBands.d.ts +19 -0
- package/dist/AuroraBands.js +126 -0
- package/dist/AvatarTooltips.d.ts +32 -0
- package/dist/AvatarTooltips.js +121 -0
- package/dist/BarRace.d.ts +39 -0
- package/dist/BarRace.js +227 -0
- package/dist/BentoGrid.d.ts +50 -0
- package/dist/BentoGrid.js +145 -0
- package/dist/BootSequence.d.ts +48 -0
- package/dist/BootSequence.js +234 -0
- package/dist/BreadcrumbTrail.d.ts +31 -0
- package/dist/BreadcrumbTrail.js +181 -0
- package/dist/CRTPower.d.ts +30 -0
- package/dist/CRTPower.js +142 -0
- package/dist/CardDeck.d.ts +29 -0
- package/dist/CardDeck.js +154 -0
- package/dist/ChangelogTimeline.d.ts +35 -0
- package/dist/ChangelogTimeline.js +137 -0
- package/dist/ChapterProgress.d.ts +23 -0
- package/dist/ChapterProgress.js +211 -0
- package/dist/ChatBubbles.d.ts +42 -0
- package/dist/ChatBubbles.js +198 -0
- package/dist/ChiptuneKit.d.ts +158 -0
- package/dist/ChiptuneKit.js +207 -0
- package/dist/CipherCard.d.ts +22 -0
- package/dist/CipherCard.js +91 -0
- package/dist/CloudSky.d.ts +28 -0
- package/dist/CloudSky.js +222 -0
- package/dist/CodeRain.d.ts +24 -0
- package/dist/CodeRain.js +158 -0
- package/dist/CodeShowcase.d.ts +50 -0
- package/dist/CodeShowcase.js +307 -0
- package/dist/ColorDial.d.ts +42 -0
- package/dist/ColorDial.js +288 -0
- package/dist/CommandPalette.d.ts +59 -0
- package/dist/CommandPalette.js +349 -0
- package/dist/CompareSlider.d.ts +33 -0
- package/dist/CompareSlider.js +152 -0
- package/dist/Confetti.d.ts +61 -0
- package/dist/Confetti.js +176 -0
- package/dist/ConnectBeams.d.ts +39 -0
- package/dist/ConnectBeams.js +132 -0
- package/dist/ContextOrb.d.ts +34 -0
- package/dist/ContextOrb.js +269 -0
- package/dist/ContourLines.d.ts +20 -0
- package/dist/ContourLines.js +159 -0
- package/dist/Coverflow.d.ts +27 -0
- package/dist/Coverflow.js +167 -0
- package/dist/CursorAttractor.d.ts +21 -0
- package/dist/CursorAttractor.js +136 -0
- package/dist/CutReveal.d.ts +21 -0
- package/dist/CutReveal.js +116 -0
- package/dist/DateRange.d.ts +45 -0
- package/dist/DateRange.js +327 -0
- package/dist/DeviceFrame.d.ts +30 -0
- package/dist/DeviceFrame.js +145 -0
- package/dist/DiffReveal.d.ts +36 -0
- package/dist/DiffReveal.js +247 -0
- package/dist/DirectionHover.d.ts +15 -0
- package/dist/DirectionHover.js +90 -0
- package/dist/Discard.d.ts +18 -0
- package/dist/Discard.js +61 -0
- package/dist/DitherField.d.ts +15 -0
- package/dist/DitherField.js +157 -0
- package/dist/DotGlobe.d.ts +48 -0
- package/dist/DotGlobe.js +312 -0
- package/dist/DotMap.d.ts +47 -0
- package/dist/DotMap.js +172 -0
- package/dist/DropZone.d.ts +57 -0
- package/dist/DropZone.js +304 -0
- package/dist/DynamicIsland.d.ts +32 -0
- package/dist/DynamicIsland.js +92 -0
- package/dist/EdgeFX.d.ts +27 -0
- package/dist/EdgeFX.js +98 -0
- package/dist/ElasticString.d.ts +22 -0
- package/dist/ElasticString.js +127 -0
- package/dist/ExpandCard.d.ts +44 -0
- package/dist/ExpandCard.js +182 -0
- package/dist/FeatureTabs.d.ts +34 -0
- package/dist/FeatureTabs.js +169 -0
- package/dist/FeedList.d.ts +37 -0
- package/dist/FeedList.js +163 -0
- package/dist/FlickerGrid.d.ts +29 -0
- package/dist/FlickerGrid.js +156 -0
- package/dist/FocusGrid.d.ts +20 -0
- package/dist/FocusGrid.js +48 -0
- package/dist/FocusPull.d.ts +26 -0
- package/dist/FocusPull.js +106 -0
- package/dist/Footprints.d.ts +24 -0
- package/dist/Footprints.js +126 -0
- package/dist/FunnelFlow.d.ts +34 -0
- package/dist/FunnelFlow.js +267 -0
- package/dist/GameOver.d.ts +46 -0
- package/dist/GameOver.js +191 -0
- package/dist/GaugeMeter.d.ts +38 -0
- package/dist/GaugeMeter.js +195 -0
- package/dist/GlassPanel.d.ts +27 -0
- package/dist/GlassPanel.js +80 -0
- package/dist/GrainFX.d.ts +34 -0
- package/dist/GrainFX.js +122 -0
- package/dist/GravityBox.d.ts +30 -0
- package/dist/GravityBox.js +220 -0
- package/dist/HealthBar.d.ts +33 -0
- package/dist/HealthBar.js +140 -0
- package/dist/Heatmap.d.ts +42 -0
- package/dist/Heatmap.js +232 -0
- package/dist/HexField.d.ts +29 -0
- package/dist/HexField.js +188 -0
- package/dist/HoldToConfirm.d.ts +29 -0
- package/dist/HoldToConfirm.js +163 -0
- package/dist/HorizontalTrack.d.ts +28 -0
- package/dist/HorizontalTrack.js +195 -0
- package/dist/HotbarDock.d.ts +40 -0
- package/dist/HotbarDock.js +148 -0
- package/dist/InkText.d.ts +27 -0
- package/dist/InkText.js +65 -0
- package/dist/InlineEdit.d.ts +30 -0
- package/dist/InlineEdit.js +198 -0
- package/dist/InteractiveGrid.d.ts +25 -0
- package/dist/InteractiveGrid.js +194 -0
- package/dist/InventoryGrid.d.ts +45 -0
- package/dist/InventoryGrid.js +272 -0
- package/dist/JellyBlob.d.ts +30 -0
- package/dist/JellyBlob.js +209 -0
- package/dist/KineticHeadline.d.ts +30 -0
- package/dist/KineticHeadline.js +133 -0
- package/dist/LavaLamp.d.ts +21 -0
- package/dist/LavaLamp.js +133 -0
- package/dist/LetterSwap.d.ts +20 -0
- package/dist/LetterSwap.js +78 -0
- package/dist/LevelMap.d.ts +35 -0
- package/dist/LevelMap.js +189 -0
- package/dist/LightShafts.d.ts +22 -0
- package/dist/LightShafts.js +170 -0
- package/dist/LightboxGallery.d.ts +26 -0
- package/dist/LightboxGallery.js +327 -0
- package/dist/LinkPeek.d.ts +36 -0
- package/dist/LinkPeek.js +194 -0
- package/dist/LogoOrbit.d.ts +37 -0
- package/dist/LogoOrbit.js +117 -0
- package/dist/Marquee.d.ts +33 -0
- package/dist/Marquee.js +148 -0
- package/dist/MarqueeLine.d.ts +33 -0
- package/dist/MarqueeLine.js +167 -0
- package/dist/MasonryWall.d.ts +13 -0
- package/dist/MasonryWall.js +90 -0
- package/dist/MegaMenu.d.ts +32 -0
- package/dist/MegaMenu.js +280 -0
- package/dist/MeteorShower.d.ts +24 -0
- package/dist/MeteorShower.js +155 -0
- package/dist/Minimap.d.ts +21 -0
- package/dist/Minimap.js +236 -0
- package/dist/MobileTabBar.d.ts +38 -0
- package/dist/MobileTabBar.js +148 -0
- package/dist/MorphDialog.d.ts +34 -0
- package/dist/MorphDialog.js +199 -0
- package/dist/MorphPopover.d.ts +33 -0
- package/dist/MorphPopover.js +168 -0
- package/dist/MorphWords.d.ts +18 -0
- package/dist/MorphWords.js +136 -0
- package/dist/OTPInput.d.ts +43 -0
- package/dist/OTPInput.js +174 -0
- package/dist/OverlayKit.d.ts +97 -0
- package/dist/OverlayKit.js +466 -0
- package/dist/OverlayMenu.d.ts +32 -0
- package/dist/OverlayMenu.js +154 -0
- package/dist/ParallaxCollage.d.ts +41 -0
- package/dist/ParallaxCollage.js +143 -0
- package/dist/PasswordMeter.d.ts +44 -0
- package/dist/PasswordMeter.js +165 -0
- package/dist/PegBoard.d.ts +26 -0
- package/dist/PegBoard.js +184 -0
- package/dist/PinCard.d.ts +21 -0
- package/dist/PinCard.js +84 -0
- package/dist/PixelAvatar.d.ts +33 -0
- package/dist/PixelAvatar.js +144 -0
- package/dist/PixelButton.d.ts +20 -0
- package/dist/PixelButton.js +128 -0
- package/dist/PixelExplode.d.ts +31 -0
- package/dist/PixelExplode.js +164 -0
- package/dist/PixelLoader.d.ts +32 -0
- package/dist/PixelLoader.js +142 -0
- package/dist/PixelParallax.d.ts +41 -0
- package/dist/PixelParallax.js +151 -0
- package/dist/PixelSpinners.d.ts +21 -0
- package/dist/PixelSpinners.js +110 -0
- package/dist/PixelType.d.ts +26 -0
- package/dist/PixelType.js +220 -0
- package/dist/PointerKit.d.ts +23 -0
- package/dist/PointerKit.js +222 -0
- package/dist/Polaroids.d.ts +41 -0
- package/dist/Polaroids.js +293 -0
- package/dist/PressStart.d.ts +28 -0
- package/dist/PressStart.js +99 -0
- package/dist/PricingFlip.d.ts +52 -0
- package/dist/PricingFlip.js +190 -0
- package/dist/ProximityType.d.ts +26 -0
- package/dist/ProximityType.js +135 -0
- package/dist/QuestDialog.d.ts +50 -0
- package/dist/QuestDialog.js +204 -0
- package/dist/QuestLog.d.ts +39 -0
- package/dist/QuestLog.js +214 -0
- package/dist/RangeDual.d.ts +34 -0
- package/dist/RangeDual.js +238 -0
- package/dist/RatingPips.d.ts +25 -0
- package/dist/RatingPips.js +129 -0
- package/dist/RetroHorizon.d.ts +27 -0
- package/dist/RetroHorizon.js +169 -0
- package/dist/Reveal.d.ts +30 -0
- package/dist/Reveal.js +92 -0
- package/dist/RingProgress.d.ts +45 -0
- package/dist/RingProgress.js +180 -0
- package/dist/RollingNumber.d.ts +28 -0
- package/dist/RollingNumber.js +108 -0
- package/dist/ScanScope.d.ts +24 -0
- package/dist/ScanScope.js +164 -0
- package/dist/ScoreTicker.d.ts +32 -0
- package/dist/ScoreTicker.js +176 -0
- package/dist/ScreenScroller.d.ts +24 -0
- package/dist/ScreenScroller.js +138 -0
- package/dist/Screensaver.d.ts +31 -0
- package/dist/Screensaver.js +221 -0
- package/dist/ScrollTilt.d.ts +27 -0
- package/dist/ScrollTilt.js +100 -0
- package/dist/ScrollTypography.d.ts +26 -0
- package/dist/ScrollTypography.js +124 -0
- package/dist/ScrollZoom.d.ts +31 -0
- package/dist/ScrollZoom.js +123 -0
- package/dist/ScrubNumber.d.ts +29 -0
- package/dist/ScrubNumber.js +152 -0
- package/dist/ShadowStack.d.ts +28 -0
- package/dist/ShadowStack.js +116 -0
- package/dist/SideRail.d.ts +43 -0
- package/dist/SideRail.js +253 -0
- package/dist/SignalText.d.ts +25 -0
- package/dist/SignalText.js +178 -0
- package/dist/SkeletonShimmer.d.ts +18 -0
- package/dist/SkeletonShimmer.js +111 -0
- package/dist/SketchHighlight.d.ts +27 -0
- package/dist/SketchHighlight.js +180 -0
- package/dist/SlideTabs.d.ts +40 -0
- package/dist/SlideTabs.js +190 -0
- package/dist/SmartNavbar.d.ts +48 -0
- package/dist/SmartNavbar.js +188 -0
- package/dist/SparkChart.d.ts +41 -0
- package/dist/SparkChart.js +188 -0
- package/dist/Spoiler.d.ts +22 -0
- package/dist/Spoiler.js +97 -0
- package/dist/SpotlightTour.d.ts +38 -0
- package/dist/SpotlightTour.js +231 -0
- package/dist/SpringDrawer.d.ts +39 -0
- package/dist/SpringDrawer.js +360 -0
- package/dist/SpriteAnimator.d.ts +41 -0
- package/dist/SpriteAnimator.js +124 -0
- package/dist/StatCard.d.ts +43 -0
- package/dist/StatCard.js +168 -0
- package/dist/StepLoader.d.ts +41 -0
- package/dist/StepLoader.js +191 -0
- package/dist/StepperForm.d.ts +41 -0
- package/dist/StepperForm.js +206 -0
- package/dist/StickerBoard.d.ts +39 -0
- package/dist/StickerBoard.js +209 -0
- package/dist/StickyReveal.d.ts +42 -0
- package/dist/StickyReveal.js +181 -0
- package/dist/StoryViewer.d.ts +36 -0
- package/dist/StoryViewer.js +175 -0
- package/dist/StreamingText.d.ts +29 -0
- package/dist/StreamingText.js +271 -0
- package/dist/SuggestionChips.d.ts +32 -0
- package/dist/SuggestionChips.js +96 -0
- package/dist/SwipeActions.d.ts +30 -0
- package/dist/SwipeActions.js +141 -0
- package/dist/TagInput.d.ts +30 -0
- package/dist/TagInput.js +284 -0
- package/dist/TallyUp.d.ts +38 -0
- package/dist/TallyUp.js +157 -0
- package/dist/TerminalDemo.d.ts +63 -0
- package/dist/TerminalDemo.js +214 -0
- package/dist/TestimonialWall.d.ts +35 -0
- package/dist/TestimonialWall.js +131 -0
- package/dist/TextOnPath.d.ts +36 -0
- package/dist/TextOnPath.js +130 -0
- package/dist/ThinkingShimmer.d.ts +36 -0
- package/dist/ThinkingShimmer.js +132 -0
- package/dist/ToastStack.d.ts +62 -0
- package/dist/ToastStack.js +289 -0
- package/dist/ToggleGroup.d.ts +39 -0
- package/dist/ToggleGroup.js +190 -0
- package/dist/ToggleKit.d.ts +70 -0
- package/dist/ToggleKit.js +229 -0
- package/dist/TracingLine.d.ts +24 -0
- package/dist/TracingLine.js +139 -0
- package/dist/TradingCard.d.ts +30 -0
- package/dist/TradingCard.js +147 -0
- package/dist/VanishInput.d.ts +34 -0
- package/dist/VanishInput.js +190 -0
- package/dist/VideoHero.d.ts +32 -0
- package/dist/VideoHero.js +207 -0
- package/dist/VoiceOrb.d.ts +23 -0
- package/dist/VoiceOrb.js +182 -0
- package/dist/VoxelTiles.d.ts +38 -0
- package/dist/VoxelTiles.js +201 -0
- package/dist/WarpField.d.ts +18 -0
- package/dist/WarpField.js +160 -0
- package/dist/WebcamPixels.d.ts +38 -0
- package/dist/WebcamPixels.js +292 -0
- package/dist/WheelPicker.d.ts +29 -0
- package/dist/WheelPicker.js +199 -0
- package/dist/WobbleCard.d.ts +23 -0
- package/dist/WobbleCard.js +112 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +159 -0
- package/package.json +34 -4
package/dist/PinCard.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useInsertionEffect } from "react";
|
|
4
|
+
const STYLE_ID = "bl-pin-card";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-pin { position: relative; display: grid; justify-items: center; width: var(--bl-w); max-width: 100%; padding-top: 56px; perspective: 1000px; color: var(--bl-ink); font-family: var(--bl-font); text-decoration: none; outline: none; }
|
|
7
|
+
.bl-pin__label { position: absolute; top: 0; left: 50%; z-index: 3; padding: 5px 12px; border-radius: 999px; background: var(--bl-label-bg); box-shadow: 0 0 0 1px var(--bl-line); color: var(--bl-ink); font-size: 13px; font-weight: 600; white-space: nowrap; opacity: 0; transform: translate(-50%, 10px); transition: opacity 200ms, transform 300ms cubic-bezier(.3,1.4,.5,1); }
|
|
8
|
+
.bl-pin__card { position: relative; z-index: 2; width: 100%; border-radius: var(--bl-radius); background: var(--bl-bg); box-shadow: 0 0 0 1px var(--bl-line), 0 18px 40px -20px rgba(0,0,0,0.7); transform-origin: 50% 100%; transition: transform 600ms cubic-bezier(.2,.9,.25,1), box-shadow 400ms; overflow: hidden; }
|
|
9
|
+
.bl-pin__ground { position: relative; width: 100%; height: 96px; margin-top: -40px; transform: rotateX(70deg); transform-style: preserve-3d; pointer-events: none; }
|
|
10
|
+
.bl-pin__ring { position: absolute; left: 50%; top: 50%; width: 140px; height: 140px; margin: -70px 0 0 -70px; border-radius: 50%; background: radial-gradient(color-mix(in srgb, var(--bl-accent) 40%, transparent), transparent 65%); opacity: 0; transition: opacity 300ms; }
|
|
11
|
+
.bl-pin__ring::before, .bl-pin__ring::after { content: ""; position: absolute; inset: 0; border-radius: 50%; box-shadow: 0 0 0 2px var(--bl-accent); opacity: 0; }
|
|
12
|
+
.bl-pin__beam { position: absolute; left: 50%; bottom: 46px; z-index: 1; width: 2px; height: 0; margin-left: -1px; background: linear-gradient(transparent, var(--bl-accent)); transition: height 500ms cubic-bezier(.2,.9,.25,1); pointer-events: none; }
|
|
13
|
+
.bl-pin__beam::after { content: ""; position: absolute; left: 50%; bottom: -4px; width: 8px; height: 8px; margin-left: -4px; border-radius: 50%; background: var(--bl-accent); box-shadow: 0 0 12px 3px var(--bl-accent); }
|
|
14
|
+
.bl-pin:is(:hover, :focus-visible, :focus-within) .bl-pin__card { transform: translateY(-14px) rotateX(38deg) scale(0.86); box-shadow: 0 0 0 1px var(--bl-accent), 0 40px 60px -30px rgba(0,0,0,0.9); }
|
|
15
|
+
.bl-pin:is(:hover, :focus-visible, :focus-within) .bl-pin__label { opacity: 1; transform: translate(-50%, 0); }
|
|
16
|
+
.bl-pin:is(:hover, :focus-visible, :focus-within) .bl-pin__beam { height: 90px; }
|
|
17
|
+
.bl-pin:is(:hover, :focus-visible, :focus-within) .bl-pin__ring { opacity: 1; }
|
|
18
|
+
.bl-pin:is(:hover, :focus-visible, :focus-within) .bl-pin__ring::before { animation: bl-pin-pulse 2s ease-out infinite; }
|
|
19
|
+
.bl-pin:is(:hover, :focus-visible, :focus-within) .bl-pin__ring::after { animation: bl-pin-pulse 2s ease-out 1s infinite; }
|
|
20
|
+
.bl-pin:focus-visible .bl-pin__label { box-shadow: 0 0 0 2px var(--bl-accent); }
|
|
21
|
+
@keyframes bl-pin-pulse { from { transform: scale(0.2); opacity: 1; } to { transform: scale(1); opacity: 0; } }
|
|
22
|
+
@media (prefers-reduced-motion: reduce) {
|
|
23
|
+
.bl-pin__card, .bl-pin__beam, .bl-pin__label { transition: opacity 150ms; }
|
|
24
|
+
.bl-pin:is(:hover, :focus-visible, :focus-within) .bl-pin__card { transform: none; }
|
|
25
|
+
.bl-pin__ring::before, .bl-pin__ring::after { animation: none !important; }
|
|
26
|
+
}
|
|
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
|
+
function PinCard({
|
|
37
|
+
children,
|
|
38
|
+
title,
|
|
39
|
+
href,
|
|
40
|
+
target,
|
|
41
|
+
width = 300,
|
|
42
|
+
radius = 16,
|
|
43
|
+
backgroundColor = "#1a1c2c",
|
|
44
|
+
labelColor = "#0b0b14",
|
|
45
|
+
textColor = "#f4f4f4",
|
|
46
|
+
lineColor = "#333c57",
|
|
47
|
+
accentColor = "#73eff7",
|
|
48
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
49
|
+
className = "",
|
|
50
|
+
style
|
|
51
|
+
}) {
|
|
52
|
+
useStyles();
|
|
53
|
+
const Tag = href ? "a" : "div";
|
|
54
|
+
return /* @__PURE__ */ jsxs(
|
|
55
|
+
Tag,
|
|
56
|
+
{
|
|
57
|
+
className: `bl-pin ${className}`,
|
|
58
|
+
href,
|
|
59
|
+
target,
|
|
60
|
+
rel: target === "_blank" ? "noopener noreferrer" : void 0,
|
|
61
|
+
tabIndex: href ? void 0 : 0,
|
|
62
|
+
style: {
|
|
63
|
+
"--bl-w": `${width}px`,
|
|
64
|
+
"--bl-radius": `${radius}px`,
|
|
65
|
+
"--bl-bg": backgroundColor,
|
|
66
|
+
"--bl-label-bg": labelColor,
|
|
67
|
+
"--bl-ink": textColor,
|
|
68
|
+
"--bl-line": lineColor,
|
|
69
|
+
"--bl-accent": accentColor,
|
|
70
|
+
"--bl-font": fontFamily,
|
|
71
|
+
...style
|
|
72
|
+
},
|
|
73
|
+
children: [
|
|
74
|
+
title && /* @__PURE__ */ jsx("span", { className: "bl-pin__label", children: title }),
|
|
75
|
+
/* @__PURE__ */ jsx("div", { className: "bl-pin__card", children }),
|
|
76
|
+
/* @__PURE__ */ jsx("span", { className: "bl-pin__beam", "aria-hidden": "true" }),
|
|
77
|
+
/* @__PURE__ */ jsx("span", { className: "bl-pin__ground", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "bl-pin__ring" }) })
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
PinCard as default
|
|
84
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
export type AvatarVariant = 'creature' | 'identicon';
|
|
3
|
+
export interface AvatarGrid {
|
|
4
|
+
/** 10×10 colors, row by row; null cells are empty. */
|
|
5
|
+
grid: (string | null)[][];
|
|
6
|
+
/** [x, y] cells that form the eyes (empty for identicons). */
|
|
7
|
+
eyes: [number, number][];
|
|
8
|
+
body: string;
|
|
9
|
+
shade: string;
|
|
10
|
+
backdrop: string;
|
|
11
|
+
}
|
|
12
|
+
export interface PixelAvatarProps {
|
|
13
|
+
/** Any string; the same seed always draws the same avatar. */
|
|
14
|
+
seed?: string | number;
|
|
15
|
+
/** Rendered size in px. */
|
|
16
|
+
size?: number;
|
|
17
|
+
variant?: AvatarVariant;
|
|
18
|
+
shape?: 'rounded' | 'square' | 'circle';
|
|
19
|
+
/** true for a seeded backdrop, a color of your own, or false for none. */
|
|
20
|
+
background?: boolean | string;
|
|
21
|
+
/** Idle blink, offset per seed. */
|
|
22
|
+
blink?: boolean;
|
|
23
|
+
/** Accessible name; defaults to "Avatar for {seed}". */
|
|
24
|
+
title?: string;
|
|
25
|
+
/** Hide from assistive tech (when a name sits next to it). */
|
|
26
|
+
decorative?: boolean;
|
|
27
|
+
className?: string;
|
|
28
|
+
style?: CSSProperties;
|
|
29
|
+
}
|
|
30
|
+
export declare function avatarGrid(seed: string | number, variant?: AvatarVariant): AvatarGrid;
|
|
31
|
+
declare function PixelAvatar({ seed, size, variant, shape, background, blink, title, decorative, className, style }: PixelAvatarProps): import("react").JSX.Element;
|
|
32
|
+
declare const _default: import("react").MemoExoticComponent<typeof PixelAvatar>;
|
|
33
|
+
export default _default;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { memo, useId, useInsertionEffect, useMemo } from "react";
|
|
4
|
+
const STYLE_ID = "bl-pixel-avatar";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-ava { display: inline-block; flex: 0 0 auto; vertical-align: middle; }
|
|
7
|
+
.bl-ava__eyes { transform-box: fill-box; transform-origin: center; animation: bl-ava-blink 5s steps(1) infinite; animation-delay: var(--bl-blink-delay, 0s); }
|
|
8
|
+
@keyframes bl-ava-blink { 0%, 95%, 100% { transform: none; } 96%, 98% { transform: scaleY(0.2); } }
|
|
9
|
+
@media (prefers-reduced-motion: reduce) { .bl-ava__eyes { animation: none; } }
|
|
10
|
+
`;
|
|
11
|
+
function useStyles() {
|
|
12
|
+
useInsertionEffect(() => {
|
|
13
|
+
if (typeof document === "undefined") return;
|
|
14
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
15
|
+
style.textContent = CSS;
|
|
16
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
17
|
+
}, []);
|
|
18
|
+
}
|
|
19
|
+
function hash(text) {
|
|
20
|
+
let h1 = 3735928559;
|
|
21
|
+
let h2 = 1103547991;
|
|
22
|
+
for (let i = 0; i < text.length; i++) {
|
|
23
|
+
const c = text.charCodeAt(i);
|
|
24
|
+
h1 = Math.imul(h1 ^ c, 2654435761);
|
|
25
|
+
h2 = Math.imul(h2 ^ c, 1597334677);
|
|
26
|
+
}
|
|
27
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
28
|
+
return h1 >>> 0;
|
|
29
|
+
}
|
|
30
|
+
function random(seed) {
|
|
31
|
+
let a = seed;
|
|
32
|
+
return () => {
|
|
33
|
+
a = a + 1831565813 | 0;
|
|
34
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
35
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
36
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const PALETTES = [
|
|
40
|
+
["#b13e53", "#5d275d"],
|
|
41
|
+
["#ef7d57", "#b13e53"],
|
|
42
|
+
["#ffcd75", "#ef7d57"],
|
|
43
|
+
["#a7f070", "#38b764"],
|
|
44
|
+
["#38b764", "#257179"],
|
|
45
|
+
["#73eff7", "#41a6f6"],
|
|
46
|
+
["#41a6f6", "#3b5dc9"],
|
|
47
|
+
["#94b0c2", "#566c86"]
|
|
48
|
+
];
|
|
49
|
+
const BACKDROPS = ["#333c57", "#29366f", "#5d275d", "#257179", "#1a1c2c"];
|
|
50
|
+
const INK = "#0b0b14";
|
|
51
|
+
const WHITE = "#f4f4f4";
|
|
52
|
+
const G = 10;
|
|
53
|
+
function avatarGrid(seed, variant = "creature") {
|
|
54
|
+
const rand = random(hash(String(seed)));
|
|
55
|
+
const [body, shade] = PALETTES[Math.floor(rand() * PALETTES.length)];
|
|
56
|
+
const backdrop = BACKDROPS[Math.floor(rand() * BACKDROPS.length)];
|
|
57
|
+
const cells = Array.from({ length: G }, () => Array(G).fill(0));
|
|
58
|
+
const eyes = [];
|
|
59
|
+
if (variant === "identicon") {
|
|
60
|
+
for (let y = 1; y < G - 1; y++) for (let x = 1; x < G / 2; x++) cells[y][x] = rand() < 0.5 ? 1 : 0;
|
|
61
|
+
} else {
|
|
62
|
+
for (let y = 1; y < G - 1; y++) {
|
|
63
|
+
for (let x = 1; x < G / 2; x++) {
|
|
64
|
+
const towardCenter = (x - 1) / (G / 2 - 2);
|
|
65
|
+
const towardMiddle = 1 - Math.abs((y - 4.5) / 4.5);
|
|
66
|
+
cells[y][x] = rand() < 0.12 + 0.8 * Math.min(1, towardCenter * 0.7 + towardMiddle * 0.6) ? 1 : 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (let y = 3; y <= 6; y++) cells[y][3] = cells[y][4] = 1;
|
|
70
|
+
}
|
|
71
|
+
for (let y = 0; y < G; y++) for (let x = 0; x < G / 2; x++) cells[y][G - 1 - x] = cells[y][x];
|
|
72
|
+
const grid = cells.map((row, y) => row.map((on) => on ? variant !== "identicon" && y >= 7 ? shade : body : null));
|
|
73
|
+
if (variant !== "identicon") {
|
|
74
|
+
const outline = (x, y) => [[1, 0], [-1, 0], [0, 1], [0, -1]].some(([dx, dy]) => cells[y + dy]?.[x + dx]);
|
|
75
|
+
for (let y = 0; y < G; y++) for (let x = 0; x < G; x++) if (!cells[y][x] && outline(x, y)) grid[y][x] = INK;
|
|
76
|
+
const ey = 3 + Math.floor(rand() * 2);
|
|
77
|
+
const ex = rand() < 0.5 ? 3 : 2;
|
|
78
|
+
for (const x of [ex, G - 1 - ex]) {
|
|
79
|
+
grid[ey][x] = WHITE;
|
|
80
|
+
grid[ey + 1][x] = INK;
|
|
81
|
+
eyes.push([x, ey], [x, ey + 1]);
|
|
82
|
+
}
|
|
83
|
+
if (rand() < 0.6) grid[ey + 3][4] = grid[ey + 3][5] = INK;
|
|
84
|
+
}
|
|
85
|
+
return { grid, eyes, body, shade, backdrop };
|
|
86
|
+
}
|
|
87
|
+
function PixelAvatar({ seed = "bits", size = 64, variant = "creature", shape = "rounded", background = true, blink = false, title, decorative = false, className = "", style }) {
|
|
88
|
+
useStyles();
|
|
89
|
+
const clip = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
90
|
+
const { grid, eyes, backdrop } = useMemo(() => avatarGrid(seed, variant), [seed, variant]);
|
|
91
|
+
const eyeSet = new Set(eyes.map(([x, y]) => `${x},${y}`));
|
|
92
|
+
const body = [];
|
|
93
|
+
const eyeRects = [];
|
|
94
|
+
grid.forEach((row, y) => {
|
|
95
|
+
let x = 0;
|
|
96
|
+
while (x < G) {
|
|
97
|
+
const color = row[x];
|
|
98
|
+
if (!color) {
|
|
99
|
+
x++;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (eyeSet.has(`${x},${y}`)) {
|
|
103
|
+
eyeRects.push(/* @__PURE__ */ jsx("rect", { x, y, width: 1, height: 1, fill: color }, `e${x}-${y}`));
|
|
104
|
+
x++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
let run = 1;
|
|
108
|
+
while (x + run < G && row[x + run] === color && !eyeSet.has(`${x + run},${y}`)) run++;
|
|
109
|
+
body.push(/* @__PURE__ */ jsx("rect", { x, y, width: run, height: 1, fill: color }, `${x}-${y}`));
|
|
110
|
+
x += run;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const fill = background === true ? backdrop : background || "none";
|
|
114
|
+
const radius = shape === "circle" ? 6 : shape === "rounded" ? 2 : 0;
|
|
115
|
+
const delay = `${-(hash(String(seed)) % 5e3) / 1e3}s`;
|
|
116
|
+
const label = title ?? `Avatar for ${seed}`;
|
|
117
|
+
return /* @__PURE__ */ jsxs(
|
|
118
|
+
"svg",
|
|
119
|
+
{
|
|
120
|
+
className: `bl-ava ${className}`,
|
|
121
|
+
viewBox: "-1 -1 12 12",
|
|
122
|
+
width: size,
|
|
123
|
+
height: size,
|
|
124
|
+
shapeRendering: "crispEdges",
|
|
125
|
+
role: decorative ? void 0 : "img",
|
|
126
|
+
"aria-label": decorative ? void 0 : label,
|
|
127
|
+
"aria-hidden": decorative || void 0,
|
|
128
|
+
style: { "--bl-blink-delay": delay, ...style },
|
|
129
|
+
children: [
|
|
130
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: `${clip}-shape`, children: /* @__PURE__ */ jsx("rect", { x: -1, y: -1, width: 12, height: 12, rx: radius }) }) }),
|
|
131
|
+
/* @__PURE__ */ jsxs("g", { clipPath: `url(#${clip}-shape)`, children: [
|
|
132
|
+
/* @__PURE__ */ jsx("rect", { x: -1, y: -1, width: 12, height: 12, fill }),
|
|
133
|
+
body,
|
|
134
|
+
/* @__PURE__ */ jsx("g", { className: blink && eyeRects.length ? "bl-ava__eyes" : void 0, children: eyeRects })
|
|
135
|
+
] })
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
var stdin_default = memo(PixelAvatar);
|
|
141
|
+
export {
|
|
142
|
+
avatarGrid,
|
|
143
|
+
stdin_default as default
|
|
144
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type MouseEvent, type ReactNode, type Ref } from 'react';
|
|
2
|
+
export type PixelButtonVariant = 'primary' | 'secondary' | 'success' | 'warning' | 'info';
|
|
3
|
+
export interface PixelButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'color'> {
|
|
4
|
+
/** Preset face and text colors. */
|
|
5
|
+
variant?: PixelButtonVariant;
|
|
6
|
+
size?: 'sm' | 'md' | 'lg';
|
|
7
|
+
/** Custom face color; overrides the variant. */
|
|
8
|
+
color?: string;
|
|
9
|
+
/** Custom label color; overrides the variant. */
|
|
10
|
+
textColor?: string;
|
|
11
|
+
/** Shown before the label. */
|
|
12
|
+
icon?: ReactNode;
|
|
13
|
+
/** Shows loading pips, sets aria-busy and ignores clicks. */
|
|
14
|
+
loading?: boolean;
|
|
15
|
+
fontFamily?: string;
|
|
16
|
+
/** Like onClick, but skipped while loading. */
|
|
17
|
+
onPress?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
18
|
+
ref?: Ref<HTMLButtonElement>;
|
|
19
|
+
}
|
|
20
|
+
export default function PixelButton({ children, variant, size, color, textColor, icon, loading, disabled, fontFamily, type, className, style, onPress, onClick, ref, ...rest }: PixelButtonProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useInsertionEffect } from "react";
|
|
4
|
+
const STYLE_ID = "bl-pixel-button";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-pxbtn {
|
|
7
|
+
--bl-face: #b13e53; --bl-text: #f4f4f4; --bl-ink: #0b0b14; --bl-px: 4px;
|
|
8
|
+
position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 10px;
|
|
9
|
+
margin: var(--bl-px); border: 0; cursor: pointer; user-select: none;
|
|
10
|
+
background: var(--bl-face); color: var(--bl-text);
|
|
11
|
+
font-family: var(--bl-font, "Press Start 2P", ui-monospace, monospace); text-transform: uppercase; line-height: 1;
|
|
12
|
+
box-shadow:
|
|
13
|
+
0 calc(var(--bl-px) * -1) 0 0 var(--bl-ink), 0 var(--bl-px) 0 0 var(--bl-ink),
|
|
14
|
+
calc(var(--bl-px) * -1) 0 0 0 var(--bl-ink), var(--bl-px) 0 0 0 var(--bl-ink),
|
|
15
|
+
inset calc(var(--bl-px) * -1) calc(var(--bl-px) * -1) 0 0 rgba(11, 11, 20, 0.28),
|
|
16
|
+
inset var(--bl-px) var(--bl-px) 0 0 rgba(255, 255, 255, 0.32),
|
|
17
|
+
var(--bl-px) calc(var(--bl-px) * 2) 0 0 rgba(11, 11, 20, 0.5);
|
|
18
|
+
transition: transform 60ms steps(1), filter 60ms steps(1);
|
|
19
|
+
}
|
|
20
|
+
.bl-pxbtn:hover:not(:disabled) { filter: brightness(1.12); }
|
|
21
|
+
.bl-pxbtn:active:not(:disabled), .bl-pxbtn[data-pressed] {
|
|
22
|
+
transform: translateY(var(--bl-px));
|
|
23
|
+
box-shadow:
|
|
24
|
+
0 calc(var(--bl-px) * -1) 0 0 var(--bl-ink), 0 var(--bl-px) 0 0 var(--bl-ink),
|
|
25
|
+
calc(var(--bl-px) * -1) 0 0 0 var(--bl-ink), var(--bl-px) 0 0 0 var(--bl-ink),
|
|
26
|
+
inset var(--bl-px) var(--bl-px) 0 0 rgba(11, 11, 20, 0.3);
|
|
27
|
+
}
|
|
28
|
+
.bl-pxbtn:focus-visible { outline: var(--bl-px) solid #41a6f6; outline-offset: calc(var(--bl-px) * 2); }
|
|
29
|
+
.bl-pxbtn[data-size="sm"] { padding: 8px 12px; font-size: 8px; --bl-px: 3px; }
|
|
30
|
+
.bl-pxbtn[data-size="md"] { padding: 12px 18px; font-size: 11px; }
|
|
31
|
+
.bl-pxbtn[data-size="lg"] { padding: 16px 26px; font-size: 14px; --bl-px: 5px; }
|
|
32
|
+
.bl-pxbtn:disabled { cursor: not-allowed; filter: grayscale(0.85) brightness(0.8); }
|
|
33
|
+
.bl-pxbtn:disabled::after {
|
|
34
|
+
content: ""; position: absolute; inset: 0; pointer-events: none;
|
|
35
|
+
background: repeating-linear-gradient(135deg, transparent 0 6px, rgba(11, 11, 20, 0.35) 6px 8px);
|
|
36
|
+
}
|
|
37
|
+
.bl-pxbtn__label { position: relative; }
|
|
38
|
+
.bl-pxbtn[data-loading] .bl-pxbtn__label { visibility: hidden; }
|
|
39
|
+
.bl-pxbtn__pips { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 6px; }
|
|
40
|
+
.bl-pxbtn__pips i { width: 6px; height: 6px; background: currentColor; animation: bl-pxbtn-pip 0.9s steps(1) infinite; }
|
|
41
|
+
.bl-pxbtn__pips i:nth-child(2) { animation-delay: 0.3s; }
|
|
42
|
+
.bl-pxbtn__pips i:nth-child(3) { animation-delay: 0.6s; }
|
|
43
|
+
@keyframes bl-pxbtn-pip { 0%, 100% { opacity: 0.25; } 33% { opacity: 1; } }
|
|
44
|
+
@media (prefers-reduced-motion: reduce) {
|
|
45
|
+
.bl-pxbtn { transition: none; }
|
|
46
|
+
.bl-pxbtn__pips i { animation: none; opacity: 0.8; }
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
function useStyles() {
|
|
50
|
+
useInsertionEffect(() => {
|
|
51
|
+
if (typeof document === "undefined") return;
|
|
52
|
+
const existing = document.getElementById(STYLE_ID);
|
|
53
|
+
if (existing) {
|
|
54
|
+
existing.textContent = CSS;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const style = document.createElement("style");
|
|
58
|
+
style.id = STYLE_ID;
|
|
59
|
+
style.textContent = CSS;
|
|
60
|
+
document.head.appendChild(style);
|
|
61
|
+
}, []);
|
|
62
|
+
}
|
|
63
|
+
const VARIANTS = {
|
|
64
|
+
primary: { face: "#b13e53", text: "#f4f4f4" },
|
|
65
|
+
secondary: { face: "#94b0c2", text: "#0b0b14" },
|
|
66
|
+
success: { face: "#38b764", text: "#0b0b14" },
|
|
67
|
+
warning: { face: "#ffcd75", text: "#0b0b14" },
|
|
68
|
+
info: { face: "#41a6f6", text: "#0b0b14" }
|
|
69
|
+
};
|
|
70
|
+
function PixelButton({
|
|
71
|
+
children,
|
|
72
|
+
variant = "primary",
|
|
73
|
+
size = "md",
|
|
74
|
+
color,
|
|
75
|
+
textColor,
|
|
76
|
+
icon,
|
|
77
|
+
loading = false,
|
|
78
|
+
disabled = false,
|
|
79
|
+
fontFamily,
|
|
80
|
+
type = "button",
|
|
81
|
+
className = "",
|
|
82
|
+
style,
|
|
83
|
+
onPress,
|
|
84
|
+
onClick,
|
|
85
|
+
ref,
|
|
86
|
+
...rest
|
|
87
|
+
}) {
|
|
88
|
+
useStyles();
|
|
89
|
+
const palette = VARIANTS[variant] ?? VARIANTS.primary;
|
|
90
|
+
return /* @__PURE__ */ jsxs(
|
|
91
|
+
"button",
|
|
92
|
+
{
|
|
93
|
+
ref,
|
|
94
|
+
type,
|
|
95
|
+
className: `bl-pxbtn ${className}`,
|
|
96
|
+
"data-size": size,
|
|
97
|
+
"data-loading": loading || void 0,
|
|
98
|
+
disabled,
|
|
99
|
+
"aria-busy": loading || void 0,
|
|
100
|
+
onClick: (event) => {
|
|
101
|
+
if (loading) return;
|
|
102
|
+
onClick?.(event);
|
|
103
|
+
onPress?.(event);
|
|
104
|
+
},
|
|
105
|
+
style: {
|
|
106
|
+
"--bl-face": color ?? palette.face,
|
|
107
|
+
"--bl-text": textColor ?? palette.text,
|
|
108
|
+
...fontFamily ? { "--bl-font": fontFamily } : null,
|
|
109
|
+
...style
|
|
110
|
+
},
|
|
111
|
+
...rest,
|
|
112
|
+
children: [
|
|
113
|
+
/* @__PURE__ */ jsxs("span", { className: "bl-pxbtn__label", style: { display: "inline-flex", alignItems: "center", gap: 10 }, children: [
|
|
114
|
+
icon,
|
|
115
|
+
children
|
|
116
|
+
] }),
|
|
117
|
+
loading && /* @__PURE__ */ jsxs("span", { className: "bl-pxbtn__pips", "aria-hidden": "true", children: [
|
|
118
|
+
/* @__PURE__ */ jsx("i", {}),
|
|
119
|
+
/* @__PURE__ */ jsx("i", {}),
|
|
120
|
+
/* @__PURE__ */ jsx("i", {})
|
|
121
|
+
] })
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
export {
|
|
127
|
+
PixelButton as default
|
|
128
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode, type Ref } from 'react';
|
|
2
|
+
export type ExplodeDirection = 'right' | 'left' | 'up' | 'down' | 'center';
|
|
3
|
+
export interface PixelExplodeHandle {
|
|
4
|
+
explode: () => void;
|
|
5
|
+
reassemble: () => void;
|
|
6
|
+
}
|
|
7
|
+
export interface PixelExplodeProps {
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
/** Controlled: true explodes, false reassembles. */
|
|
10
|
+
exploded?: boolean;
|
|
11
|
+
/** Tile size in px. */
|
|
12
|
+
pieceSize?: number;
|
|
13
|
+
/** Large elements use bigger tiles to stay under this count. */
|
|
14
|
+
maxPieces?: number;
|
|
15
|
+
/** Flight time in ms. */
|
|
16
|
+
duration?: number;
|
|
17
|
+
spread?: number;
|
|
18
|
+
gravity?: number;
|
|
19
|
+
/** The direction the disintegration sweeps. */
|
|
20
|
+
direction?: ExplodeDirection;
|
|
21
|
+
/** Choppy 8-bit motion instead of smooth. */
|
|
22
|
+
stepped?: boolean;
|
|
23
|
+
onExploded?: () => void;
|
|
24
|
+
onReassembled?: () => void;
|
|
25
|
+
/** CSS display of the wrapper. */
|
|
26
|
+
display?: CSSProperties['display'];
|
|
27
|
+
ref?: Ref<PixelExplodeHandle>;
|
|
28
|
+
className?: string;
|
|
29
|
+
style?: CSSProperties;
|
|
30
|
+
}
|
|
31
|
+
export default function PixelExplode({ children, exploded, pieceSize, maxPieces, duration, spread, gravity, direction, stepped, onExploded, onReassembled, display, ref, className, style }: PixelExplodeProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useImperativeHandle, useRef } from "react";
|
|
4
|
+
function prefersReduced() {
|
|
5
|
+
return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
6
|
+
}
|
|
7
|
+
function PixelExplode({
|
|
8
|
+
children,
|
|
9
|
+
exploded,
|
|
10
|
+
pieceSize = 8,
|
|
11
|
+
maxPieces = 500,
|
|
12
|
+
duration = 900,
|
|
13
|
+
spread = 1,
|
|
14
|
+
gravity = 1,
|
|
15
|
+
direction = "right",
|
|
16
|
+
stepped = true,
|
|
17
|
+
onExploded,
|
|
18
|
+
onReassembled,
|
|
19
|
+
display = "inline-block",
|
|
20
|
+
ref,
|
|
21
|
+
className = "",
|
|
22
|
+
style
|
|
23
|
+
}) {
|
|
24
|
+
const contentRef = useRef(null);
|
|
25
|
+
const layerRef = useRef(null);
|
|
26
|
+
const animations = useRef([]);
|
|
27
|
+
const state = useRef(exploded ? "gone" : "whole");
|
|
28
|
+
const callbacks = useRef({ onExploded, onReassembled });
|
|
29
|
+
callbacks.current = { onExploded, onReassembled };
|
|
30
|
+
const options = useRef(null);
|
|
31
|
+
options.current = { pieceSize, maxPieces, duration, spread, gravity, direction, stepped };
|
|
32
|
+
const clear = () => {
|
|
33
|
+
animations.current.forEach((animation) => animation.cancel());
|
|
34
|
+
animations.current = [];
|
|
35
|
+
layerRef.current?.replaceChildren();
|
|
36
|
+
};
|
|
37
|
+
const setHidden = (hidden) => {
|
|
38
|
+
const content = contentRef.current;
|
|
39
|
+
if (!content) return;
|
|
40
|
+
content.style.visibility = hidden ? "hidden" : "";
|
|
41
|
+
content.inert = hidden;
|
|
42
|
+
};
|
|
43
|
+
const build = (reverse) => {
|
|
44
|
+
const content = contentRef.current;
|
|
45
|
+
const layer = layerRef.current;
|
|
46
|
+
if (!content || !layer) return Promise.resolve();
|
|
47
|
+
clear();
|
|
48
|
+
const o = options.current;
|
|
49
|
+
const width = content.offsetWidth;
|
|
50
|
+
const height = content.offsetHeight;
|
|
51
|
+
if (!width || !height) return Promise.resolve();
|
|
52
|
+
const size = Math.max(o.pieceSize, Math.ceil(Math.sqrt(width * height / o.maxPieces)));
|
|
53
|
+
const source = content.cloneNode(true);
|
|
54
|
+
source.removeAttribute("id");
|
|
55
|
+
source.querySelectorAll("[id]").forEach((node) => node.removeAttribute("id"));
|
|
56
|
+
source.style.visibility = "visible";
|
|
57
|
+
source.style.width = `${width}px`;
|
|
58
|
+
source.style.height = `${height}px`;
|
|
59
|
+
source.style.margin = "0";
|
|
60
|
+
const cols = Math.ceil(width / size);
|
|
61
|
+
const rows = Math.ceil(height / size);
|
|
62
|
+
const fragment = document.createDocumentFragment();
|
|
63
|
+
const pending = [];
|
|
64
|
+
for (let r = 0; r < rows; r++) {
|
|
65
|
+
for (let c = 0; c < cols; c++) {
|
|
66
|
+
const tile = document.createElement("div");
|
|
67
|
+
tile.style.cssText = `position:absolute;left:${c * size}px;top:${r * size}px;width:${size}px;height:${size}px;overflow:hidden;will-change:transform,opacity;`;
|
|
68
|
+
const copy = source.cloneNode(true);
|
|
69
|
+
copy.style.position = "absolute";
|
|
70
|
+
copy.style.left = `${-c * size}px`;
|
|
71
|
+
copy.style.top = `${-r * size}px`;
|
|
72
|
+
tile.appendChild(copy);
|
|
73
|
+
fragment.appendChild(tile);
|
|
74
|
+
const along = o.direction === "left" ? 1 - c / cols : o.direction === "up" ? 1 - r / rows : o.direction === "down" ? r / rows : o.direction === "center" ? Math.hypot(c / cols - 0.5, r / rows - 0.5) * 1.4 : c / cols;
|
|
75
|
+
const delay = along * o.duration * 0.6 + Math.random() * o.duration * 0.15;
|
|
76
|
+
const angle = (Math.random() - 0.5) * Math.PI * 0.9 - Math.PI / 2;
|
|
77
|
+
const distance = (40 + Math.random() * 90) * o.spread;
|
|
78
|
+
const dx = Math.cos(angle) * distance * 0.7 + (o.direction === "left" ? -1 : o.direction === "right" ? 1 : 0) * distance * 0.6;
|
|
79
|
+
const dy = Math.sin(angle) * distance;
|
|
80
|
+
const fall = 60 * o.gravity;
|
|
81
|
+
const spin = (Math.random() - 0.5) * 180;
|
|
82
|
+
const keyframes = [
|
|
83
|
+
{ transform: "translate(0, 0) rotate(0deg) scale(1)", opacity: 1 },
|
|
84
|
+
{ transform: `translate(${dx * 0.55}px, ${dy * 0.55}px) rotate(${spin * 0.5}deg) scale(0.9)`, opacity: 1, offset: 0.45 },
|
|
85
|
+
{ transform: `translate(${dx}px, ${dy + fall}px) rotate(${spin}deg) scale(0.4)`, opacity: 0 }
|
|
86
|
+
];
|
|
87
|
+
const animation = tile.animate(keyframes, {
|
|
88
|
+
duration: o.duration * (0.7 + Math.random() * 0.5),
|
|
89
|
+
delay: reverse ? 0 : delay,
|
|
90
|
+
endDelay: reverse ? delay : 0,
|
|
91
|
+
easing: o.stepped ? "steps(10)" : "cubic-bezier(.3,.6,.5,1)",
|
|
92
|
+
fill: "both"
|
|
93
|
+
});
|
|
94
|
+
if (reverse) animation.reverse();
|
|
95
|
+
animations.current.push(animation);
|
|
96
|
+
pending.push(animation.finished.catch(() => null));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
layer.appendChild(fragment);
|
|
100
|
+
return Promise.all(pending);
|
|
101
|
+
};
|
|
102
|
+
const explode = () => {
|
|
103
|
+
if (state.current === "gone" || state.current === "leaving") return;
|
|
104
|
+
state.current = "leaving";
|
|
105
|
+
if (prefersReduced()) {
|
|
106
|
+
const fade = contentRef.current?.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 200 });
|
|
107
|
+
(fade?.finished ?? Promise.resolve()).catch(() => null).then(() => {
|
|
108
|
+
if (state.current !== "leaving") return;
|
|
109
|
+
setHidden(true);
|
|
110
|
+
state.current = "gone";
|
|
111
|
+
callbacks.current.onExploded?.();
|
|
112
|
+
});
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const run = build(false);
|
|
116
|
+
setHidden(true);
|
|
117
|
+
run.then(() => {
|
|
118
|
+
if (state.current !== "leaving") return;
|
|
119
|
+
clear();
|
|
120
|
+
state.current = "gone";
|
|
121
|
+
callbacks.current.onExploded?.();
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
const reassemble = () => {
|
|
125
|
+
if (state.current === "whole" || state.current === "returning") return;
|
|
126
|
+
state.current = "returning";
|
|
127
|
+
if (prefersReduced()) {
|
|
128
|
+
clear();
|
|
129
|
+
setHidden(false);
|
|
130
|
+
contentRef.current?.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 200 });
|
|
131
|
+
state.current = "whole";
|
|
132
|
+
callbacks.current.onReassembled?.();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const run = build(true);
|
|
136
|
+
run.then(() => {
|
|
137
|
+
if (state.current !== "returning") return;
|
|
138
|
+
clear();
|
|
139
|
+
setHidden(false);
|
|
140
|
+
state.current = "whole";
|
|
141
|
+
callbacks.current.onReassembled?.();
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
useImperativeHandle(ref, () => ({ explode, reassemble }));
|
|
145
|
+
const first = useRef(true);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (first.current) {
|
|
148
|
+
first.current = false;
|
|
149
|
+
if (exploded) setHidden(true);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (exploded === void 0) return;
|
|
153
|
+
if (exploded) explode();
|
|
154
|
+
else reassemble();
|
|
155
|
+
}, [exploded]);
|
|
156
|
+
useEffect(() => () => animations.current.forEach((animation) => animation.cancel()), []);
|
|
157
|
+
return /* @__PURE__ */ jsxs("div", { className, style: { position: "relative", display, ...style }, children: [
|
|
158
|
+
/* @__PURE__ */ jsx("div", { ref: contentRef, children }),
|
|
159
|
+
/* @__PURE__ */ jsx("div", { ref: layerRef, "aria-hidden": "true", style: { position: "absolute", inset: 0, pointerEvents: "none", overflow: "visible" } })
|
|
160
|
+
] });
|
|
161
|
+
}
|
|
162
|
+
export {
|
|
163
|
+
PixelExplode as default
|
|
164
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
export interface PixelLoaderProps {
|
|
3
|
+
/** 0–100. Leave undefined to simulate a load. */
|
|
4
|
+
progress?: number;
|
|
5
|
+
label?: string;
|
|
6
|
+
/** Title shown when progress reaches 100. */
|
|
7
|
+
doneLabel?: string;
|
|
8
|
+
/** Rotating tips under the bar; empty to hide. */
|
|
9
|
+
tips?: string[];
|
|
10
|
+
/** Time per tip, in ms. */
|
|
11
|
+
tipInterval?: number;
|
|
12
|
+
/** Number of chunks in the bar. */
|
|
13
|
+
chunks?: number;
|
|
14
|
+
/** Max width, in px. */
|
|
15
|
+
width?: number;
|
|
16
|
+
accentColor?: string;
|
|
17
|
+
frameColor?: string;
|
|
18
|
+
textColor?: string;
|
|
19
|
+
mutedColor?: string;
|
|
20
|
+
/** Rough length of a simulated load, in ms. */
|
|
21
|
+
simulateDuration?: number;
|
|
22
|
+
/** Restart a simulated load after COMPLETE. */
|
|
23
|
+
loop?: boolean;
|
|
24
|
+
/** Show the percent and chunk count. */
|
|
25
|
+
showPercent?: boolean;
|
|
26
|
+
fontFamily?: string;
|
|
27
|
+
/** Called when progress reaches 100. */
|
|
28
|
+
onComplete?: () => void;
|
|
29
|
+
className?: string;
|
|
30
|
+
style?: CSSProperties;
|
|
31
|
+
}
|
|
32
|
+
export default function PixelLoader({ progress, label, doneLabel, tips, tipInterval, chunks, width, accentColor, frameColor, textColor, mutedColor, simulateDuration, loop, showPercent, fontFamily, onComplete, className, style }: PixelLoaderProps): import("react").JSX.Element;
|