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.
- package/LICENSE +21 -0
- package/README.md +57 -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 +58 -4
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-logo-orbit";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-orbit { position: relative; width: min(var(--bl-size), 100%); aspect-ratio: 1; margin: 0 auto; container-type: inline-size; color: var(--bl-ink); font-family: var(--bl-font); }
|
|
7
|
+
.bl-orbit__ring { position: absolute; inset: 0; animation: bl-orbit-spin var(--bl-dur) linear infinite; animation-direction: var(--bl-dir); pointer-events: none; }
|
|
8
|
+
.bl-orbit__track { position: absolute; left: 50%; top: 50%; width: calc(var(--bl-r) * 2cqi); height: calc(var(--bl-r) * 2cqi); border-radius: 50%; border: 1px dashed var(--bl-line); transform: translate(-50%, -50%); pointer-events: none; }
|
|
9
|
+
.bl-orbit__item { position: absolute; left: 50%; top: 50%; width: 0; height: 0; transform: rotate(var(--bl-a)) translateX(calc(var(--bl-r) * 1cqi)) rotate(calc(var(--bl-a) * -1)); }
|
|
10
|
+
.bl-orbit__logo { position: absolute; left: 0; top: 0; display: grid; place-items: center; width: var(--bl-item); height: var(--bl-item); margin: calc(var(--bl-item) / -2) 0 0 calc(var(--bl-item) / -2); border-radius: var(--bl-item-radius); background: var(--bl-item-bg); box-shadow: 0 0 0 1px var(--bl-line), 0 8px 20px -10px rgba(0,0,0,0.6); pointer-events: auto; outline: none; animation: bl-orbit-spin var(--bl-dur) linear infinite; animation-direction: var(--bl-cdir); transition: box-shadow 160ms, scale 160ms; }
|
|
11
|
+
.bl-orbit__logo > * { max-width: 62%; max-height: 62%; }
|
|
12
|
+
.bl-orbit__logo:hover, .bl-orbit__logo:focus-visible { scale: 1.15; box-shadow: 0 0 0 2px var(--bl-accent), 0 8px 24px -8px rgba(0,0,0,0.6); }
|
|
13
|
+
.bl-orbit__tip { position: absolute; left: 50%; bottom: calc(100% + 6px); padding: 3px 8px; border-radius: 6px; background: var(--bl-ink); color: var(--bl-bg); font-size: 12px; white-space: nowrap; transform: translateX(-50%); opacity: 0; pointer-events: none; transition: opacity 120ms; }
|
|
14
|
+
.bl-orbit__logo:hover .bl-orbit__tip, .bl-orbit__logo:focus-visible .bl-orbit__tip { opacity: 1; }
|
|
15
|
+
.bl-orbit__center { position: absolute; left: 50%; top: 50%; display: grid; place-items: center; width: var(--bl-center); height: var(--bl-center); border-radius: 50%; transform: translate(-50%, -50%); background: var(--bl-center-bg); box-shadow: 0 0 0 1px var(--bl-line), 0 0 60px -10px var(--bl-accent); }
|
|
16
|
+
.bl-orbit[data-pausable]:hover :is(.bl-orbit__ring, .bl-orbit__logo), .bl-orbit[data-pausable]:focus-within :is(.bl-orbit__ring, .bl-orbit__logo), .bl-orbit[data-paused] :is(.bl-orbit__ring, .bl-orbit__logo) { animation-play-state: paused; }
|
|
17
|
+
.bl-orbit__list { margin: 0; padding: 0; list-style: none; }
|
|
18
|
+
@keyframes bl-orbit-spin { to { rotate: 360deg; } }
|
|
19
|
+
@media (prefers-reduced-motion: reduce) { .bl-orbit__ring, .bl-orbit__logo { animation: none; } }
|
|
20
|
+
`;
|
|
21
|
+
function useStyles() {
|
|
22
|
+
useInsertionEffect(() => {
|
|
23
|
+
if (typeof document === "undefined") return;
|
|
24
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
25
|
+
style.textContent = CSS;
|
|
26
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
27
|
+
}, []);
|
|
28
|
+
}
|
|
29
|
+
function distribute(n, radii) {
|
|
30
|
+
const total = radii.reduce((a, b) => a + b, 0);
|
|
31
|
+
const counts = radii.map((r) => Math.floor(n * r / total));
|
|
32
|
+
let left = n - counts.reduce((a, b) => a + b, 0);
|
|
33
|
+
for (let i = radii.length - 1; left > 0; i = (i - 1 + radii.length) % radii.length, left--) counts[i]++;
|
|
34
|
+
return counts;
|
|
35
|
+
}
|
|
36
|
+
function LogoOrbit({
|
|
37
|
+
logos = [],
|
|
38
|
+
center,
|
|
39
|
+
rings = 2,
|
|
40
|
+
size = 480,
|
|
41
|
+
innerRadius = 24,
|
|
42
|
+
ringGap = 16,
|
|
43
|
+
itemSize = 52,
|
|
44
|
+
centerSize = 96,
|
|
45
|
+
duration = 40,
|
|
46
|
+
pauseOnHover = true,
|
|
47
|
+
showTracks = true,
|
|
48
|
+
label = "Integrations",
|
|
49
|
+
backgroundColor = "#1a1c2c",
|
|
50
|
+
itemColor = "#262b44",
|
|
51
|
+
centerColor = "#333c57",
|
|
52
|
+
lineColor = "rgba(148,176,194,0.25)",
|
|
53
|
+
accentColor = "#73eff7",
|
|
54
|
+
textColor = "#f4f4f4",
|
|
55
|
+
itemRadius = 14,
|
|
56
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
57
|
+
className = "",
|
|
58
|
+
style
|
|
59
|
+
}) {
|
|
60
|
+
useStyles();
|
|
61
|
+
const rootRef = useRef(null);
|
|
62
|
+
const [offscreen, setOffscreen] = useState(false);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const io = new IntersectionObserver(([entry]) => setOffscreen(!entry.isIntersecting));
|
|
65
|
+
io.observe(rootRef.current);
|
|
66
|
+
return () => io.disconnect();
|
|
67
|
+
}, []);
|
|
68
|
+
const radii = Array.from({ length: Math.max(1, rings) }, (_, k) => innerRadius + k * ringGap);
|
|
69
|
+
const counts = distribute(logos.length, radii);
|
|
70
|
+
let next = 0;
|
|
71
|
+
const groups = counts.map((count) => {
|
|
72
|
+
const group = logos.slice(next, next + count);
|
|
73
|
+
next += count;
|
|
74
|
+
return group;
|
|
75
|
+
});
|
|
76
|
+
return /* @__PURE__ */ jsxs(
|
|
77
|
+
"div",
|
|
78
|
+
{
|
|
79
|
+
ref: rootRef,
|
|
80
|
+
className: `bl-orbit ${className}`,
|
|
81
|
+
role: "group",
|
|
82
|
+
"aria-label": label,
|
|
83
|
+
"data-pausable": pauseOnHover || void 0,
|
|
84
|
+
"data-paused": offscreen || void 0,
|
|
85
|
+
style: {
|
|
86
|
+
"--bl-size": `${size}px`,
|
|
87
|
+
// Tiles shrink with the box on small screens (cqi resolves against the orbit).
|
|
88
|
+
"--bl-item": `min(${itemSize}px, 12cqi)`,
|
|
89
|
+
"--bl-center": `min(${centerSize}px, 22cqi)`,
|
|
90
|
+
"--bl-item-radius": `${itemRadius}px`,
|
|
91
|
+
"--bl-bg": backgroundColor,
|
|
92
|
+
"--bl-item-bg": itemColor,
|
|
93
|
+
"--bl-center-bg": centerColor,
|
|
94
|
+
"--bl-line": lineColor,
|
|
95
|
+
"--bl-accent": accentColor,
|
|
96
|
+
"--bl-ink": textColor,
|
|
97
|
+
"--bl-font": fontFamily,
|
|
98
|
+
...style
|
|
99
|
+
},
|
|
100
|
+
children: [
|
|
101
|
+
showTracks && radii.map((r, k) => /* @__PURE__ */ jsx("div", { className: "bl-orbit__track", style: { "--bl-r": r }, "aria-hidden": "true" }, `t${k}`)),
|
|
102
|
+
/* @__PURE__ */ jsx("div", { className: "bl-orbit__center", children: center }),
|
|
103
|
+
/* @__PURE__ */ jsx("ul", { className: "bl-orbit__list", children: groups.map((group, k) => {
|
|
104
|
+
const ringStyle = { "--bl-dur": `${duration * (1 + k * 0.5)}s`, "--bl-dir": k % 2 ? "reverse" : "normal", "--bl-cdir": k % 2 ? "normal" : "reverse", "--bl-r": radii[k] };
|
|
105
|
+
if (!group.length) return null;
|
|
106
|
+
return /* @__PURE__ */ jsx("li", { className: "bl-orbit__ring", style: ringStyle, children: /* @__PURE__ */ jsx("ul", { className: "bl-orbit__list", children: group.map((logo, i) => /* @__PURE__ */ jsx("li", { className: "bl-orbit__item", style: { "--bl-a": `${360 / group.length * i + k * 17}deg` }, children: /* @__PURE__ */ jsxs("span", { className: "bl-orbit__logo", tabIndex: 0, "aria-label": logo.label, role: "img", children: [
|
|
107
|
+
logo.icon,
|
|
108
|
+
logo.label && /* @__PURE__ */ jsx("span", { className: "bl-orbit__tip", "aria-hidden": "true", children: logo.label })
|
|
109
|
+
] }) }, logo.id ?? logo.label ?? i)) }) }, k);
|
|
110
|
+
}) })
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
LogoOrbit as default
|
|
117
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface MarqueeProps {
|
|
3
|
+
/** Logos or cards, dealt round-robin into lanes. */
|
|
4
|
+
items?: ReactNode[];
|
|
5
|
+
lanes?: number;
|
|
6
|
+
/** Lanes run up and down instead of across. */
|
|
7
|
+
vertical?: boolean;
|
|
8
|
+
/** px per second. */
|
|
9
|
+
speed?: number;
|
|
10
|
+
/** How much lane speeds differ (0–1). */
|
|
11
|
+
variance?: number;
|
|
12
|
+
/** Neighbouring lanes run opposite ways. */
|
|
13
|
+
alternate?: boolean;
|
|
14
|
+
/** Speed of a hovered lane, 0–1 (0 stops it). */
|
|
15
|
+
slow?: number;
|
|
16
|
+
/** Opacity of the other items in a hovered lane. */
|
|
17
|
+
dim?: number;
|
|
18
|
+
gap?: number;
|
|
19
|
+
/** Edge fade in px. */
|
|
20
|
+
fade?: number;
|
|
21
|
+
/** Height of a vertical wall in px. */
|
|
22
|
+
height?: number;
|
|
23
|
+
showPause?: boolean;
|
|
24
|
+
/** Name of the items list for screen readers, and of the pause button. */
|
|
25
|
+
label?: string;
|
|
26
|
+
textColor?: string;
|
|
27
|
+
accentColor?: string;
|
|
28
|
+
chipColor?: string;
|
|
29
|
+
fontFamily?: string;
|
|
30
|
+
className?: string;
|
|
31
|
+
style?: CSSProperties;
|
|
32
|
+
}
|
|
33
|
+
export default function Marquee({ items, lanes, vertical, speed, variance, alternate, slow, dim, gap, fade, height, showPause, label, textColor, accentColor, chipColor, fontFamily, className, style }: MarqueeProps): import("react").JSX.Element;
|
package/dist/Marquee.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-marquee-wall";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-mwall { position: relative; display: grid; gap: 10px; color: var(--bl-ink); font-family: var(--bl-font); }
|
|
7
|
+
.bl-mwall__lanes { display: flex; flex-direction: column; gap: var(--bl-gap); }
|
|
8
|
+
.bl-mwall[data-vertical] .bl-mwall__lanes { flex-direction: row; height: var(--bl-h); }
|
|
9
|
+
.bl-mwall__lane { overflow: hidden; -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--bl-fade), #000 calc(100% - var(--bl-fade)), transparent); mask-image: linear-gradient(90deg, transparent, #000 var(--bl-fade), #000 calc(100% - var(--bl-fade)), transparent); }
|
|
10
|
+
.bl-mwall[data-vertical] .bl-mwall__lane { flex: 1; min-width: 0; -webkit-mask-image: linear-gradient(180deg, transparent, #000 var(--bl-fade), #000 calc(100% - var(--bl-fade)), transparent); mask-image: linear-gradient(180deg, transparent, #000 var(--bl-fade), #000 calc(100% - var(--bl-fade)), transparent); }
|
|
11
|
+
.bl-mwall__track { display: flex; width: max-content; will-change: transform; }
|
|
12
|
+
.bl-mwall[data-vertical] .bl-mwall__track { flex-direction: column; width: auto; }
|
|
13
|
+
.bl-mwall__set { display: flex; flex: none; gap: var(--bl-gap); padding-right: var(--bl-gap); }
|
|
14
|
+
.bl-mwall[data-vertical] .bl-mwall__set { flex-direction: column; padding: 0 0 var(--bl-gap); }
|
|
15
|
+
.bl-mwall__item { flex: none; transition: transform 220ms steps(3), opacity 220ms steps(3); }
|
|
16
|
+
.bl-mwall__lane[data-focus] .bl-mwall__item { opacity: var(--bl-dim); }
|
|
17
|
+
.bl-mwall__lane[data-focus] .bl-mwall__item:hover { opacity: 1; transform: scale(1.06); }
|
|
18
|
+
.bl-mwall__pause { justify-self: end; display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 0; border-radius: 8px; background: var(--bl-chip); color: var(--bl-ink); cursor: pointer; }
|
|
19
|
+
.bl-mwall__pause:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
|
|
20
|
+
.bl-mwall__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
21
|
+
@media (prefers-reduced-motion: reduce) { .bl-mwall__item { transition: none; } }
|
|
22
|
+
`;
|
|
23
|
+
function useStyles() {
|
|
24
|
+
useInsertionEffect(() => {
|
|
25
|
+
if (typeof document === "undefined") return;
|
|
26
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
27
|
+
style.textContent = CSS;
|
|
28
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
29
|
+
}, []);
|
|
30
|
+
}
|
|
31
|
+
function Lane({ items, index, vertical, speed, variance, alternate, slow, paused }) {
|
|
32
|
+
const laneRef = useRef(null);
|
|
33
|
+
const trackRef = useRef(null);
|
|
34
|
+
const setRef = useRef(null);
|
|
35
|
+
const pausedRef = useRef(paused);
|
|
36
|
+
pausedRef.current = paused;
|
|
37
|
+
const [copies, setCopies] = useState(2);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const lane = laneRef.current;
|
|
40
|
+
const set = setRef.current;
|
|
41
|
+
const fit = () => {
|
|
42
|
+
const view = vertical ? lane.clientHeight : lane.clientWidth;
|
|
43
|
+
const one = (vertical ? set.offsetHeight : set.offsetWidth) || 1;
|
|
44
|
+
setCopies(Math.max(2, Math.ceil(view / one) + 1));
|
|
45
|
+
};
|
|
46
|
+
const resize = new ResizeObserver(fit);
|
|
47
|
+
resize.observe(lane);
|
|
48
|
+
resize.observe(set);
|
|
49
|
+
return () => resize.disconnect();
|
|
50
|
+
}, [vertical]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const lane = laneRef.current;
|
|
53
|
+
const track = trackRef.current;
|
|
54
|
+
const set = setRef.current;
|
|
55
|
+
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return void 0;
|
|
56
|
+
const dir = alternate && index % 2 ? -1 : 1;
|
|
57
|
+
const own = speed * (1 + (index * 37 % 7 / 6 - 0.5) * variance);
|
|
58
|
+
let offset = 0;
|
|
59
|
+
let rate = 1;
|
|
60
|
+
let hovered = false;
|
|
61
|
+
let visible = true;
|
|
62
|
+
let raf = 0;
|
|
63
|
+
let last = 0;
|
|
64
|
+
const tick = (now) => {
|
|
65
|
+
const dt = Math.max(0, Math.min(0.05, (now - last) / 1e3));
|
|
66
|
+
last = now;
|
|
67
|
+
const goal = pausedRef.current ? 0 : hovered ? slow : 1;
|
|
68
|
+
rate += (goal - rate) * Math.min(1, dt * 6);
|
|
69
|
+
const size = (vertical ? set.offsetHeight : set.offsetWidth) || 1;
|
|
70
|
+
offset = ((offset + dir * own * rate * dt) % size + size) % size;
|
|
71
|
+
track.style.transform = vertical ? `translateY(${-offset.toFixed(2)}px)` : `translateX(${-offset.toFixed(2)}px)`;
|
|
72
|
+
raf = visible && !document.hidden ? requestAnimationFrame(tick) : 0;
|
|
73
|
+
};
|
|
74
|
+
const start = () => {
|
|
75
|
+
if (raf || !visible || document.hidden) return;
|
|
76
|
+
last = performance.now();
|
|
77
|
+
raf = requestAnimationFrame(tick);
|
|
78
|
+
};
|
|
79
|
+
const onEnter = () => {
|
|
80
|
+
hovered = true;
|
|
81
|
+
lane.dataset.focus = "";
|
|
82
|
+
};
|
|
83
|
+
const onLeave = () => {
|
|
84
|
+
hovered = false;
|
|
85
|
+
delete lane.dataset.focus;
|
|
86
|
+
};
|
|
87
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
88
|
+
visible = entry.isIntersecting;
|
|
89
|
+
start();
|
|
90
|
+
});
|
|
91
|
+
observer.observe(lane);
|
|
92
|
+
document.addEventListener("visibilitychange", start);
|
|
93
|
+
lane.addEventListener("pointerenter", onEnter);
|
|
94
|
+
lane.addEventListener("pointerleave", onLeave);
|
|
95
|
+
start();
|
|
96
|
+
return () => {
|
|
97
|
+
cancelAnimationFrame(raf);
|
|
98
|
+
observer.disconnect();
|
|
99
|
+
document.removeEventListener("visibilitychange", start);
|
|
100
|
+
lane.removeEventListener("pointerenter", onEnter);
|
|
101
|
+
lane.removeEventListener("pointerleave", onLeave);
|
|
102
|
+
track.style.transform = "";
|
|
103
|
+
};
|
|
104
|
+
}, [index, vertical, speed, variance, alternate, slow]);
|
|
105
|
+
return /* @__PURE__ */ jsx("div", { ref: laneRef, className: "bl-mwall__lane", children: /* @__PURE__ */ jsx("div", { ref: trackRef, className: "bl-mwall__track", children: Array.from({ length: copies }, (_, c) => /* @__PURE__ */ jsx("div", { ref: c === 0 ? setRef : void 0, className: "bl-mwall__set", children: items.map((item, i) => /* @__PURE__ */ jsx("div", { className: "bl-mwall__item", children: item }, i)) }, c)) }) });
|
|
106
|
+
}
|
|
107
|
+
function Marquee({
|
|
108
|
+
items = [],
|
|
109
|
+
lanes = 2,
|
|
110
|
+
vertical = false,
|
|
111
|
+
speed = 40,
|
|
112
|
+
variance = 0.3,
|
|
113
|
+
alternate = true,
|
|
114
|
+
slow = 0.15,
|
|
115
|
+
dim = 0.45,
|
|
116
|
+
gap = 16,
|
|
117
|
+
fade = 56,
|
|
118
|
+
height = 420,
|
|
119
|
+
showPause = true,
|
|
120
|
+
label = "Featured",
|
|
121
|
+
textColor = "#f4f4f4",
|
|
122
|
+
accentColor = "#73eff7",
|
|
123
|
+
chipColor = "rgba(255,255,255,0.08)",
|
|
124
|
+
fontFamily = "inherit",
|
|
125
|
+
className = "",
|
|
126
|
+
style
|
|
127
|
+
}) {
|
|
128
|
+
useStyles();
|
|
129
|
+
const [paused, setPaused] = useState(false);
|
|
130
|
+
const count = Math.max(1, Math.min(lanes, items.length || 1));
|
|
131
|
+
const dealt = Array.from({ length: count }, (_, l) => items.filter((_2, i) => i % count === l));
|
|
132
|
+
return /* @__PURE__ */ jsxs(
|
|
133
|
+
"div",
|
|
134
|
+
{
|
|
135
|
+
className: `bl-mwall ${className}`,
|
|
136
|
+
"data-vertical": vertical || void 0,
|
|
137
|
+
style: { "--bl-gap": `${gap}px`, "--bl-fade": `${fade}px`, "--bl-h": `${height}px`, "--bl-dim": dim, "--bl-ink": textColor, "--bl-accent": accentColor, "--bl-chip": chipColor, "--bl-font": fontFamily, ...style },
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ jsx("ul", { className: "bl-mwall__sr", "aria-label": label, children: items.map((item, i) => /* @__PURE__ */ jsx("li", { children: item }, i)) }),
|
|
140
|
+
/* @__PURE__ */ jsx("div", { className: "bl-mwall__lanes", "aria-hidden": "true", children: dealt.map((laneItems, l) => /* @__PURE__ */ jsx(Lane, { items: laneItems, index: l, vertical, speed, variance, alternate, slow, paused }, l)) }),
|
|
141
|
+
showPause && /* @__PURE__ */ jsx("button", { type: "button", className: "bl-mwall__pause", "aria-pressed": paused, "aria-label": paused ? `Play ${label}` : `Pause ${label}`, onClick: () => setPaused((p) => !p), children: paused ? /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M3 1.5v9l7-4.5z" }) }) : /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2.5 1.5h2.5v9H2.5zM7 1.5h2.5v9H7z" }) }) })
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
export {
|
|
147
|
+
Marquee as default
|
|
148
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface MarqueeLineProps {
|
|
3
|
+
/** What scrolls past. */
|
|
4
|
+
items?: ReactNode[];
|
|
5
|
+
/** Cruising speed in px per second. */
|
|
6
|
+
speed?: number;
|
|
7
|
+
direction?: 'left' | 'right';
|
|
8
|
+
/** Scroll surge: up to this many times cruising speed (scrolling up can reverse it). */
|
|
9
|
+
boost?: number;
|
|
10
|
+
/** Lean in degrees at full surge. */
|
|
11
|
+
skew?: number;
|
|
12
|
+
separator?: ReactNode;
|
|
13
|
+
pauseOnHover?: boolean;
|
|
14
|
+
/** Drag to scrub. */
|
|
15
|
+
draggable?: boolean;
|
|
16
|
+
/** A pause button; motion that runs for more than 5 seconds needs one. */
|
|
17
|
+
showPause?: boolean;
|
|
18
|
+
/** Name of the items list for screen readers, and of the pause button. */
|
|
19
|
+
label?: string;
|
|
20
|
+
gap?: number;
|
|
21
|
+
/** Width of the faded edges in px. */
|
|
22
|
+
fade?: number;
|
|
23
|
+
padding?: number;
|
|
24
|
+
fontSize?: number;
|
|
25
|
+
backgroundColor?: string;
|
|
26
|
+
textColor?: string;
|
|
27
|
+
accentColor?: string;
|
|
28
|
+
chipColor?: string;
|
|
29
|
+
fontFamily?: string;
|
|
30
|
+
className?: string;
|
|
31
|
+
style?: CSSProperties;
|
|
32
|
+
}
|
|
33
|
+
export default function MarqueeLine({ items, speed, direction, boost, skew, separator, pauseOnHover, draggable, showPause, label, gap, fade, padding, fontSize, backgroundColor, textColor, accentColor, chipColor, fontFamily, className, style }: MarqueeLineProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment, useEffect, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-marquee-line";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-marquee { position: relative; display: flex; align-items: center; gap: 8px; overflow: hidden; padding: var(--bl-pad) 0; background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); font-size: var(--bl-size); }
|
|
7
|
+
.bl-marquee__viewport { flex: 1; min-width: 0; overflow: hidden; cursor: grab; touch-action: pan-y; -webkit-mask-image: linear-gradient(90deg, transparent, #000 var(--bl-fade), #000 calc(100% - var(--bl-fade)), transparent); mask-image: linear-gradient(90deg, transparent, #000 var(--bl-fade), #000 calc(100% - var(--bl-fade)), transparent); }
|
|
8
|
+
.bl-marquee__viewport[data-dragging] { cursor: grabbing; }
|
|
9
|
+
.bl-marquee__track { display: flex; width: max-content; will-change: transform; }
|
|
10
|
+
.bl-marquee__set { display: flex; flex: none; align-items: center; gap: var(--bl-gap); padding-right: var(--bl-gap); white-space: nowrap; }
|
|
11
|
+
.bl-marquee__item { display: inline-flex; align-items: center; gap: 0.4em; }
|
|
12
|
+
.bl-marquee__sep { color: var(--bl-accent); }
|
|
13
|
+
.bl-marquee__pause { flex: none; display: grid; place-items: center; width: 30px; height: 30px; margin-right: 8px; padding: 0; border: 0; border-radius: 8px; background: var(--bl-chip); color: var(--bl-ink); cursor: pointer; }
|
|
14
|
+
.bl-marquee__pause:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
|
|
15
|
+
.bl-marquee__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
16
|
+
`;
|
|
17
|
+
function useStyles() {
|
|
18
|
+
useInsertionEffect(() => {
|
|
19
|
+
if (typeof document === "undefined") return;
|
|
20
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
21
|
+
style.textContent = CSS;
|
|
22
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
23
|
+
}, []);
|
|
24
|
+
}
|
|
25
|
+
function MarqueeLine({
|
|
26
|
+
items = [],
|
|
27
|
+
speed = 60,
|
|
28
|
+
direction = "left",
|
|
29
|
+
boost = 6,
|
|
30
|
+
skew = 8,
|
|
31
|
+
separator = "\u2726",
|
|
32
|
+
pauseOnHover = true,
|
|
33
|
+
draggable = true,
|
|
34
|
+
showPause = true,
|
|
35
|
+
label = "Announcements",
|
|
36
|
+
gap = 28,
|
|
37
|
+
fade = 48,
|
|
38
|
+
padding = 14,
|
|
39
|
+
fontSize = 22,
|
|
40
|
+
backgroundColor = "#1a1c2c",
|
|
41
|
+
textColor = "#f4f4f4",
|
|
42
|
+
accentColor = "#ffcd75",
|
|
43
|
+
chipColor = "rgba(255,255,255,0.08)",
|
|
44
|
+
fontFamily = "inherit",
|
|
45
|
+
className = "",
|
|
46
|
+
style
|
|
47
|
+
}) {
|
|
48
|
+
useStyles();
|
|
49
|
+
const viewportRef = useRef(null);
|
|
50
|
+
const trackRef = useRef(null);
|
|
51
|
+
const setRef = useRef(null);
|
|
52
|
+
const [copies, setCopies] = useState(2);
|
|
53
|
+
const [paused, setPaused] = useState(false);
|
|
54
|
+
const pausedRef = useRef(paused);
|
|
55
|
+
pausedRef.current = paused;
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const viewport = viewportRef.current;
|
|
58
|
+
const set = setRef.current;
|
|
59
|
+
const fit = () => setCopies(Math.max(2, Math.ceil(viewport.clientWidth * 2 / Math.max(1, set.offsetWidth)) + 1));
|
|
60
|
+
const resize = new ResizeObserver(fit);
|
|
61
|
+
resize.observe(viewport);
|
|
62
|
+
resize.observe(set);
|
|
63
|
+
return () => resize.disconnect();
|
|
64
|
+
}, []);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const viewport = viewportRef.current;
|
|
67
|
+
const track = trackRef.current;
|
|
68
|
+
const set = setRef.current;
|
|
69
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
70
|
+
if (reduced) return void 0;
|
|
71
|
+
const sign = direction === "right" ? -1 : 1;
|
|
72
|
+
let offset = 0;
|
|
73
|
+
let surge = 0;
|
|
74
|
+
let lastY = window.scrollY;
|
|
75
|
+
let hovered = false;
|
|
76
|
+
let drag = null;
|
|
77
|
+
let visible = true;
|
|
78
|
+
let raf = 0;
|
|
79
|
+
let last = 0;
|
|
80
|
+
const tick = (now) => {
|
|
81
|
+
const dt = Math.max(0, Math.min(0.05, (now - last) / 1e3));
|
|
82
|
+
last = now;
|
|
83
|
+
const y = window.scrollY;
|
|
84
|
+
const scrolled = (y - lastY) / Math.max(dt, 1e-3);
|
|
85
|
+
lastY = y;
|
|
86
|
+
const kick = Math.max(-boost, Math.min(boost, scrolled / 400));
|
|
87
|
+
if (Math.abs(kick) > Math.abs(surge)) surge = kick;
|
|
88
|
+
surge *= Math.exp(-dt * 2.5);
|
|
89
|
+
const holding = pausedRef.current || pauseOnHover && hovered;
|
|
90
|
+
if (!drag && !holding) offset += sign * speed * (1 + surge) * dt;
|
|
91
|
+
const width = set.offsetWidth || 1;
|
|
92
|
+
offset = (offset % width + width) % width;
|
|
93
|
+
const lean = holding ? 0 : Math.max(-1, Math.min(1, surge / boost)) * skew * sign;
|
|
94
|
+
track.style.transform = `translateX(${-offset.toFixed(2)}px) skewX(${(-lean).toFixed(2)}deg)`;
|
|
95
|
+
raf = visible && !document.hidden ? requestAnimationFrame(tick) : 0;
|
|
96
|
+
};
|
|
97
|
+
const start = () => {
|
|
98
|
+
if (raf || !visible || document.hidden) return;
|
|
99
|
+
last = performance.now();
|
|
100
|
+
lastY = window.scrollY;
|
|
101
|
+
raf = requestAnimationFrame(tick);
|
|
102
|
+
};
|
|
103
|
+
const onEnter = () => hovered = true;
|
|
104
|
+
const onLeave = () => hovered = false;
|
|
105
|
+
const onDown = (event) => {
|
|
106
|
+
if (!draggable || event.button > 0) return;
|
|
107
|
+
drag = { x: event.clientX, offset };
|
|
108
|
+
viewport.dataset.dragging = "";
|
|
109
|
+
try {
|
|
110
|
+
viewport.setPointerCapture(event.pointerId);
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const onMove = (event) => {
|
|
115
|
+
if (drag) offset = drag.offset - (event.clientX - drag.x);
|
|
116
|
+
};
|
|
117
|
+
const onUp = () => {
|
|
118
|
+
drag = null;
|
|
119
|
+
delete viewport.dataset.dragging;
|
|
120
|
+
};
|
|
121
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
122
|
+
visible = entry.isIntersecting;
|
|
123
|
+
if (visible) start();
|
|
124
|
+
});
|
|
125
|
+
observer.observe(viewport);
|
|
126
|
+
const onVisibility = () => start();
|
|
127
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
128
|
+
viewport.addEventListener("pointerenter", onEnter);
|
|
129
|
+
viewport.addEventListener("pointerleave", onLeave);
|
|
130
|
+
viewport.addEventListener("pointerdown", onDown);
|
|
131
|
+
viewport.addEventListener("pointermove", onMove);
|
|
132
|
+
viewport.addEventListener("pointerup", onUp);
|
|
133
|
+
viewport.addEventListener("pointercancel", onUp);
|
|
134
|
+
start();
|
|
135
|
+
return () => {
|
|
136
|
+
cancelAnimationFrame(raf);
|
|
137
|
+
observer.disconnect();
|
|
138
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
139
|
+
viewport.removeEventListener("pointerenter", onEnter);
|
|
140
|
+
viewport.removeEventListener("pointerleave", onLeave);
|
|
141
|
+
viewport.removeEventListener("pointerdown", onDown);
|
|
142
|
+
viewport.removeEventListener("pointermove", onMove);
|
|
143
|
+
viewport.removeEventListener("pointerup", onUp);
|
|
144
|
+
viewport.removeEventListener("pointercancel", onUp);
|
|
145
|
+
track.style.transform = "";
|
|
146
|
+
};
|
|
147
|
+
}, [speed, direction, boost, skew, pauseOnHover, draggable]);
|
|
148
|
+
const renderSet = (key, ref) => /* @__PURE__ */ jsx("div", { ref, className: "bl-marquee__set", children: items.map((item, i) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
149
|
+
/* @__PURE__ */ jsx("span", { className: "bl-marquee__item", children: item }),
|
|
150
|
+
separator && /* @__PURE__ */ jsx("span", { className: "bl-marquee__sep", "aria-hidden": "true", children: separator })
|
|
151
|
+
] }, i)) }, key);
|
|
152
|
+
return /* @__PURE__ */ jsxs(
|
|
153
|
+
"div",
|
|
154
|
+
{
|
|
155
|
+
className: `bl-marquee ${className}`,
|
|
156
|
+
style: { "--bl-gap": `${gap}px`, "--bl-fade": `${fade}px`, "--bl-pad": `${padding}px`, "--bl-size": `${fontSize}px`, "--bl-bg": backgroundColor, "--bl-ink": textColor, "--bl-accent": accentColor, "--bl-chip": chipColor, "--bl-font": fontFamily, ...style },
|
|
157
|
+
children: [
|
|
158
|
+
/* @__PURE__ */ jsx("ul", { className: "bl-marquee__sr", "aria-label": label, children: items.map((item, i) => /* @__PURE__ */ jsx("li", { children: item }, i)) }),
|
|
159
|
+
/* @__PURE__ */ jsx("div", { ref: viewportRef, className: "bl-marquee__viewport", "aria-hidden": "true", children: /* @__PURE__ */ jsx("div", { ref: trackRef, className: "bl-marquee__track", children: Array.from({ length: copies }, (_, i) => renderSet(i, i === 0 ? setRef : void 0)) }) }),
|
|
160
|
+
showPause && /* @__PURE__ */ jsx("button", { type: "button", className: "bl-marquee__pause", "aria-pressed": paused, "aria-label": paused ? `Play ${label}` : `Pause ${label}`, onClick: () => setPaused((p) => !p), children: paused ? /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M3 1.5v9l7-4.5z" }) }) : /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2.5 1.5h2.5v9H2.5zM7 1.5h2.5v9H7z" }) }) })
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
MarqueeLine as default
|
|
167
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface MasonryWallProps {
|
|
3
|
+
/** Items of any height; give each a key so it can glide when the list changes. */
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
minColumnWidth?: number;
|
|
6
|
+
maxColumns?: number;
|
|
7
|
+
gap?: number;
|
|
8
|
+
/** Glide time in ms. */
|
|
9
|
+
duration?: number;
|
|
10
|
+
className?: string;
|
|
11
|
+
style?: CSSProperties;
|
|
12
|
+
}
|
|
13
|
+
export default function MasonryWall({ children, minColumnWidth, maxColumns, gap, duration, className, style }: MasonryWallProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Children, useEffect, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-masonry-wall";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-masonry { position: relative; width: 100%; }
|
|
7
|
+
.bl-masonry__item { position: absolute; top: 0; left: 0; width: var(--bl-col-w); transform: translate(var(--x), var(--y)); transition: transform var(--bl-dur) cubic-bezier(.2,.8,.2,1); }
|
|
8
|
+
.bl-masonry__item[data-new] { animation: bl-masonry-pop 360ms steps(4) both; animation-delay: calc(var(--i) * 40ms); }
|
|
9
|
+
.bl-masonry[data-ready="false"] .bl-masonry__item { transition: none; visibility: hidden; }
|
|
10
|
+
@keyframes bl-masonry-pop { from { opacity: 0; scale: 0.85; } }
|
|
11
|
+
@media (prefers-reduced-motion: reduce) { .bl-masonry__item { transition: none; } .bl-masonry__item[data-new] { animation: none; } }
|
|
12
|
+
`;
|
|
13
|
+
function useStyles() {
|
|
14
|
+
useInsertionEffect(() => {
|
|
15
|
+
if (typeof document === "undefined") return;
|
|
16
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
17
|
+
style.textContent = CSS;
|
|
18
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
19
|
+
}, []);
|
|
20
|
+
}
|
|
21
|
+
function MasonryWall({ children, minColumnWidth = 220, maxColumns = 6, gap = 16, duration = 450, className = "", style }) {
|
|
22
|
+
useStyles();
|
|
23
|
+
const items = Children.toArray(children);
|
|
24
|
+
const keys = items.map((item, i) => String(item.key ?? i));
|
|
25
|
+
const rootRef = useRef(null);
|
|
26
|
+
const nodes = useRef(/* @__PURE__ */ new Map());
|
|
27
|
+
const seen = useRef(null);
|
|
28
|
+
const [layout, setLayout] = useState({ ready: false, colWidth: 0, height: 0, pos: {} });
|
|
29
|
+
const [fresh, setFresh] = useState(() => /* @__PURE__ */ new Set());
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (seen.current) {
|
|
32
|
+
const added = keys.filter((k) => !seen.current.has(k));
|
|
33
|
+
if (added.length) setFresh(new Set(added));
|
|
34
|
+
}
|
|
35
|
+
seen.current = new Set(keys);
|
|
36
|
+
}, [keys.join("|")]);
|
|
37
|
+
useLayoutEffect(() => {
|
|
38
|
+
const root = rootRef.current;
|
|
39
|
+
const place = () => {
|
|
40
|
+
const width = root.clientWidth;
|
|
41
|
+
const cols = Math.max(1, Math.min(maxColumns, Math.floor((width + gap) / (minColumnWidth + gap))));
|
|
42
|
+
const colWidth = (width - gap * (cols - 1)) / cols;
|
|
43
|
+
const heights = new Array(cols).fill(0);
|
|
44
|
+
const pos = {};
|
|
45
|
+
keys.forEach((k) => {
|
|
46
|
+
const node = nodes.current.get(k);
|
|
47
|
+
if (!node) return;
|
|
48
|
+
const c = heights.indexOf(Math.min(...heights));
|
|
49
|
+
pos[k] = { x: c * (colWidth + gap), y: heights[c] };
|
|
50
|
+
heights[c] += node.offsetHeight + gap;
|
|
51
|
+
});
|
|
52
|
+
setLayout({ ready: true, colWidth, height: Math.max(0, Math.max(...heights) - gap), pos });
|
|
53
|
+
};
|
|
54
|
+
place();
|
|
55
|
+
const resize = new ResizeObserver(place);
|
|
56
|
+
resize.observe(root);
|
|
57
|
+
nodes.current.forEach((node) => resize.observe(node));
|
|
58
|
+
return () => resize.disconnect();
|
|
59
|
+
}, [keys.join("|"), minColumnWidth, maxColumns, gap]);
|
|
60
|
+
return /* @__PURE__ */ jsx(
|
|
61
|
+
"div",
|
|
62
|
+
{
|
|
63
|
+
ref: rootRef,
|
|
64
|
+
className: `bl-masonry ${className}`,
|
|
65
|
+
"data-ready": layout.ready,
|
|
66
|
+
style: { "--bl-col-w": `${layout.colWidth}px`, "--bl-dur": `${duration}ms`, height: layout.height, ...style },
|
|
67
|
+
children: items.map((item, i) => {
|
|
68
|
+
const k = keys[i];
|
|
69
|
+
const p = layout.pos[k] ?? { x: 0, y: 0 };
|
|
70
|
+
return /* @__PURE__ */ jsx(
|
|
71
|
+
"div",
|
|
72
|
+
{
|
|
73
|
+
ref: (node) => {
|
|
74
|
+
if (node) nodes.current.set(k, node);
|
|
75
|
+
else nodes.current.delete(k);
|
|
76
|
+
},
|
|
77
|
+
className: "bl-masonry__item",
|
|
78
|
+
"data-new": fresh.has(k) || void 0,
|
|
79
|
+
style: { "--x": `${p.x}px`, "--y": `${p.y}px`, "--i": i },
|
|
80
|
+
children: item
|
|
81
|
+
},
|
|
82
|
+
k
|
|
83
|
+
);
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
MasonryWall as default
|
|
90
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface MegaMenuItem {
|
|
3
|
+
label: string;
|
|
4
|
+
/** Opens the shared panel with this content. */
|
|
5
|
+
content?: ReactNode;
|
|
6
|
+
/** A plain link when there is no content. */
|
|
7
|
+
href?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface MegaMenuProps {
|
|
10
|
+
items?: MegaMenuItem[];
|
|
11
|
+
/** Hover intent before opening, in ms. */
|
|
12
|
+
openDelay?: number;
|
|
13
|
+
/** Grace period before closing after the pointer leaves, in ms. */
|
|
14
|
+
closeDelay?: number;
|
|
15
|
+
/** Space between the bar and the panel. */
|
|
16
|
+
gap?: number;
|
|
17
|
+
maxPanelWidth?: number;
|
|
18
|
+
/** Slide and resize time between triggers, in ms. */
|
|
19
|
+
duration?: number;
|
|
20
|
+
label?: string;
|
|
21
|
+
backgroundColor?: string;
|
|
22
|
+
textColor?: string;
|
|
23
|
+
mutedColor?: string;
|
|
24
|
+
hoverColor?: string;
|
|
25
|
+
lineColor?: string;
|
|
26
|
+
accentColor?: string;
|
|
27
|
+
radius?: number;
|
|
28
|
+
fontFamily?: string;
|
|
29
|
+
className?: string;
|
|
30
|
+
style?: CSSProperties;
|
|
31
|
+
}
|
|
32
|
+
export default function MegaMenu({ items, openDelay, closeDelay, gap, maxPanelWidth, duration, label, backgroundColor, textColor, mutedColor, hoverColor, lineColor, accentColor, radius, fontFamily, className, style }: MegaMenuProps): import("react").JSX.Element;
|