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,214 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-terminal-demo";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-term { position: relative; width: 100%; max-width: var(--bl-w); overflow: hidden; border-radius: var(--bl-radius); background: var(--bl-bg); color: var(--bl-ink); box-shadow: inset 0 0 0 1px var(--bl-line), 0 30px 60px -30px rgba(0,0,0,0.6); font: 400 var(--bl-size)/1.6 var(--bl-font); }
|
|
7
|
+
.bl-term__bar { position: relative; display: flex; align-items: center; height: 38px; padding: 0 14px; border-bottom: 1px solid var(--bl-line); background: var(--bl-bar); }
|
|
8
|
+
.bl-term__lights { display: flex; gap: 7px; }
|
|
9
|
+
.bl-term__lights i { width: 11px; height: 11px; border-radius: 50%; }
|
|
10
|
+
.bl-term__title { position: absolute; left: 50%; transform: translateX(-50%); color: var(--bl-muted); font-size: 12px; white-space: nowrap; }
|
|
11
|
+
.bl-term__replay { margin-left: auto; padding: 3px 8px; border: 1px solid var(--bl-line); border-radius: 6px; background: transparent; color: var(--bl-muted); font: inherit; font-size: 11px; cursor: pointer; }
|
|
12
|
+
.bl-term__replay:hover { color: var(--bl-ink); }
|
|
13
|
+
.bl-term__replay:focus-visible { outline: 2px solid var(--bl-prompt); outline-offset: 2px; }
|
|
14
|
+
.bl-term__body { height: var(--bl-h); overflow-y: auto; padding: 14px 16px; scrollbar-width: thin; }
|
|
15
|
+
.bl-term__line { min-height: 1.6em; white-space: pre-wrap; word-break: break-word; }
|
|
16
|
+
.bl-term__prompt { color: var(--bl-prompt); margin-right: 0.6em; user-select: none; }
|
|
17
|
+
.bl-term__line[data-kind="output"] { color: var(--bl-muted); }
|
|
18
|
+
.bl-term__line[data-kind="success"] { color: var(--bl-success); }
|
|
19
|
+
.bl-term__line[data-kind="error"] { color: var(--bl-error); }
|
|
20
|
+
.bl-term__line[data-kind="info"] { color: var(--bl-info); }
|
|
21
|
+
.bl-term__line[data-kind="spinner"] { color: var(--bl-ink); }
|
|
22
|
+
.bl-term__mark { display: inline-block; width: 1.4em; }
|
|
23
|
+
.bl-term__cursor { display: inline-block; width: 0.6em; height: 1.15em; margin-left: 1px; vertical-align: text-bottom; background: var(--bl-ink); animation: bl-term-blink 1s steps(1) infinite; }
|
|
24
|
+
@keyframes bl-term-blink { 50% { opacity: 0; } }
|
|
25
|
+
.bl-term__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
26
|
+
@media (prefers-reduced-motion: reduce) { .bl-term__cursor { animation: none; } }
|
|
27
|
+
`;
|
|
28
|
+
function useStyles() {
|
|
29
|
+
useInsertionEffect(() => {
|
|
30
|
+
if (typeof document === "undefined") return;
|
|
31
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
32
|
+
style.textContent = CSS;
|
|
33
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
34
|
+
}, []);
|
|
35
|
+
}
|
|
36
|
+
const SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
37
|
+
const MARKS = { success: "\u2714", error: "\u2716", info: "\u2139" };
|
|
38
|
+
const DEFAULT_SCRIPT = [
|
|
39
|
+
{ type: "command", text: "npx bits-lab add dot-globe" },
|
|
40
|
+
{ type: "output", text: "Resolving registry\u2026" },
|
|
41
|
+
{ type: "spinner", text: "Fetching component", duration: 1200, done: "Fetched dot-globe.jsx (12 kB)" },
|
|
42
|
+
{ type: "spinner", text: "Writing files", duration: 800, done: "Wrote src/components/DotGlobe.jsx" },
|
|
43
|
+
{ type: "info", text: "No dependencies to install." },
|
|
44
|
+
{ type: "pause", duration: 400 },
|
|
45
|
+
{ type: "command", text: "npm run dev" },
|
|
46
|
+
{ type: "output", text: " VITE ready in 312 ms\n \u279C Local: http://localhost:5173/" },
|
|
47
|
+
{ type: "success", text: "Done. Happy shipping!" }
|
|
48
|
+
];
|
|
49
|
+
const finalLine = (step, prompt) => {
|
|
50
|
+
if (step.type === "command") return { kind: "command", text: step.text, prompt: step.prompt ?? prompt };
|
|
51
|
+
if (step.type === "spinner") return { kind: step.failed ? "error" : "success", text: step.done ?? step.text };
|
|
52
|
+
return { kind: step.type, text: step.text };
|
|
53
|
+
};
|
|
54
|
+
function TerminalDemo({
|
|
55
|
+
script = DEFAULT_SCRIPT,
|
|
56
|
+
title = "zsh \u2014 bits-lab",
|
|
57
|
+
prompt = "$",
|
|
58
|
+
typingSpeed = 45,
|
|
59
|
+
lineDelay = 260,
|
|
60
|
+
loop = false,
|
|
61
|
+
loopDelay = 2600,
|
|
62
|
+
startOnView = true,
|
|
63
|
+
showControls = true,
|
|
64
|
+
width = 640,
|
|
65
|
+
height = 300,
|
|
66
|
+
radius = 12,
|
|
67
|
+
fontSize = 13.5,
|
|
68
|
+
fontFamily = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
69
|
+
background = "#11131c",
|
|
70
|
+
barColor = "#181b27",
|
|
71
|
+
lineColor = "rgba(255,255,255,0.08)",
|
|
72
|
+
textColor = "#e8eaf0",
|
|
73
|
+
mutedColor = "#94b0c2",
|
|
74
|
+
promptColor = "#73eff7",
|
|
75
|
+
successColor = "#a7f070",
|
|
76
|
+
errorColor = "#ef7d57",
|
|
77
|
+
infoColor = "#ffcd75",
|
|
78
|
+
className = "",
|
|
79
|
+
style
|
|
80
|
+
}) {
|
|
81
|
+
useStyles();
|
|
82
|
+
const rootRef = useRef(null);
|
|
83
|
+
const bodyRef = useRef(null);
|
|
84
|
+
const [lines, setLines] = useState([]);
|
|
85
|
+
const [running, setRunning] = useState(false);
|
|
86
|
+
const [finished, setFinished] = useState(false);
|
|
87
|
+
const [run, setRun] = useState(0);
|
|
88
|
+
const [inView, setInView] = useState(!startOnView);
|
|
89
|
+
const scriptKey = JSON.stringify(script);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!startOnView || inView) return;
|
|
92
|
+
const io = new IntersectionObserver(([entry]) => entry.isIntersecting && setInView(true), { threshold: 0.35 });
|
|
93
|
+
io.observe(rootRef.current);
|
|
94
|
+
return () => io.disconnect();
|
|
95
|
+
}, [startOnView, inView]);
|
|
96
|
+
useEffect(() => {
|
|
97
|
+
if (!inView) return;
|
|
98
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
99
|
+
if (reduced) {
|
|
100
|
+
setLines(script.filter((step) => step.type !== "pause").map((step) => finalLine(step, prompt)));
|
|
101
|
+
setFinished(true);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
let cancelled = false;
|
|
105
|
+
const timers = /* @__PURE__ */ new Set();
|
|
106
|
+
const wait = (ms) => new Promise((resolve) => {
|
|
107
|
+
const id = setTimeout(() => {
|
|
108
|
+
timers.delete(id);
|
|
109
|
+
resolve();
|
|
110
|
+
}, ms);
|
|
111
|
+
timers.add(id);
|
|
112
|
+
});
|
|
113
|
+
const push = (line) => setLines((prev) => [...prev, line]);
|
|
114
|
+
const patchLast = (patch) => setLines((prev) => [...prev.slice(0, -1), { ...prev[prev.length - 1], ...patch }]);
|
|
115
|
+
(async () => {
|
|
116
|
+
do {
|
|
117
|
+
setLines([]);
|
|
118
|
+
setFinished(false);
|
|
119
|
+
setRunning(true);
|
|
120
|
+
for (const step of script) {
|
|
121
|
+
if (cancelled) return;
|
|
122
|
+
if (step.type === "pause") {
|
|
123
|
+
await wait(step.duration ?? 600);
|
|
124
|
+
} else if (step.type === "command") {
|
|
125
|
+
push({ kind: "command", text: "", prompt: step.prompt ?? prompt, typing: true });
|
|
126
|
+
await wait(step.delay ?? 380);
|
|
127
|
+
for (let i = 1; i <= step.text.length && !cancelled; i++) {
|
|
128
|
+
patchLast({ text: step.text.slice(0, i) });
|
|
129
|
+
await wait(typingSpeed * (0.55 + Math.random() * 0.9));
|
|
130
|
+
}
|
|
131
|
+
await wait(320);
|
|
132
|
+
patchLast({ typing: false });
|
|
133
|
+
} else if (step.type === "spinner") {
|
|
134
|
+
push({ kind: "spinner", text: step.text, frame: 0 });
|
|
135
|
+
const end = performance.now() + (step.duration ?? 1e3);
|
|
136
|
+
for (let frame = 1; performance.now() < end && !cancelled; frame++) {
|
|
137
|
+
await wait(80);
|
|
138
|
+
patchLast({ frame });
|
|
139
|
+
}
|
|
140
|
+
patchLast(finalLine(step, prompt));
|
|
141
|
+
} else {
|
|
142
|
+
push(finalLine(step, prompt));
|
|
143
|
+
}
|
|
144
|
+
await wait(step.after ?? lineDelay);
|
|
145
|
+
}
|
|
146
|
+
if (cancelled) return;
|
|
147
|
+
setRunning(false);
|
|
148
|
+
setFinished(true);
|
|
149
|
+
if (loop) await wait(loopDelay);
|
|
150
|
+
} while (loop && !cancelled);
|
|
151
|
+
})();
|
|
152
|
+
return () => {
|
|
153
|
+
cancelled = true;
|
|
154
|
+
timers.forEach(clearTimeout);
|
|
155
|
+
};
|
|
156
|
+
}, [inView, run, scriptKey, prompt, typingSpeed, lineDelay, loop, loopDelay]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const body = bodyRef.current;
|
|
159
|
+
if (body) body.scrollTop = body.scrollHeight;
|
|
160
|
+
}, [lines]);
|
|
161
|
+
const transcript = script.filter((step) => step.type !== "pause").map((step) => finalLine(step, prompt)).map((line) => line.kind === "command" ? `${line.prompt} ${line.text}` : line.text).join("\n");
|
|
162
|
+
return /* @__PURE__ */ jsxs(
|
|
163
|
+
"div",
|
|
164
|
+
{
|
|
165
|
+
ref: rootRef,
|
|
166
|
+
className: `bl-term ${className}`,
|
|
167
|
+
style: {
|
|
168
|
+
"--bl-w": `${width}px`,
|
|
169
|
+
"--bl-h": `${height}px`,
|
|
170
|
+
"--bl-radius": `${radius}px`,
|
|
171
|
+
"--bl-size": `${fontSize}px`,
|
|
172
|
+
"--bl-font": fontFamily,
|
|
173
|
+
"--bl-bg": background,
|
|
174
|
+
"--bl-bar": barColor,
|
|
175
|
+
"--bl-line": lineColor,
|
|
176
|
+
"--bl-ink": textColor,
|
|
177
|
+
"--bl-muted": mutedColor,
|
|
178
|
+
"--bl-prompt": promptColor,
|
|
179
|
+
"--bl-success": successColor,
|
|
180
|
+
"--bl-error": errorColor,
|
|
181
|
+
"--bl-info": infoColor,
|
|
182
|
+
...style
|
|
183
|
+
},
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ jsxs("div", { className: "bl-term__bar", children: [
|
|
186
|
+
/* @__PURE__ */ jsxs("span", { className: "bl-term__lights", "aria-hidden": "true", children: [
|
|
187
|
+
/* @__PURE__ */ jsx("i", { style: { background: "#ff5f57" } }),
|
|
188
|
+
/* @__PURE__ */ jsx("i", { style: { background: "#febc2e" } }),
|
|
189
|
+
/* @__PURE__ */ jsx("i", { style: { background: "#28c840" } })
|
|
190
|
+
] }),
|
|
191
|
+
/* @__PURE__ */ jsx("span", { className: "bl-term__title", children: title }),
|
|
192
|
+
showControls && !loop && finished && /* @__PURE__ */ jsx("button", { className: "bl-term__replay", type: "button", onClick: () => setRun((r) => r + 1), children: "Replay" })
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsxs("div", { ref: bodyRef, className: "bl-term__body", "aria-hidden": "true", children: [
|
|
195
|
+
lines.map((line, i) => /* @__PURE__ */ jsxs("div", { className: "bl-term__line", "data-kind": line.kind, children: [
|
|
196
|
+
line.kind === "command" && /* @__PURE__ */ jsx("span", { className: "bl-term__prompt", children: line.prompt }),
|
|
197
|
+
line.kind === "spinner" && /* @__PURE__ */ jsx("span", { className: "bl-term__mark", children: SPINNER[line.frame % SPINNER.length] }),
|
|
198
|
+
MARKS[line.kind] && /* @__PURE__ */ jsx("span", { className: "bl-term__mark", children: MARKS[line.kind] }),
|
|
199
|
+
line.text,
|
|
200
|
+
line.typing && /* @__PURE__ */ jsx("span", { className: "bl-term__cursor" })
|
|
201
|
+
] }, i)),
|
|
202
|
+
(finished || !running) && /* @__PURE__ */ jsxs("div", { className: "bl-term__line", children: [
|
|
203
|
+
/* @__PURE__ */ jsx("span", { className: "bl-term__prompt", children: prompt }),
|
|
204
|
+
/* @__PURE__ */ jsx("span", { className: "bl-term__cursor" })
|
|
205
|
+
] })
|
|
206
|
+
] }),
|
|
207
|
+
/* @__PURE__ */ jsx("pre", { className: "bl-term__sr", "aria-label": `${title} terminal transcript`, children: transcript })
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
export {
|
|
213
|
+
TerminalDemo as default
|
|
214
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface Testimonial {
|
|
3
|
+
quote: ReactNode;
|
|
4
|
+
name: ReactNode;
|
|
5
|
+
role?: ReactNode;
|
|
6
|
+
/** An image URL or any node. */
|
|
7
|
+
avatar?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export interface TestimonialWallProps {
|
|
10
|
+
testimonials?: Testimonial[];
|
|
11
|
+
rows?: number;
|
|
12
|
+
/** Pixels per second. */
|
|
13
|
+
speed?: number;
|
|
14
|
+
cardWidth?: number;
|
|
15
|
+
/** Lines shown until a card is hovered or focused. */
|
|
16
|
+
lines?: number;
|
|
17
|
+
gap?: number;
|
|
18
|
+
/** Width of the edge fade in px. */
|
|
19
|
+
fade?: number;
|
|
20
|
+
showPause?: boolean;
|
|
21
|
+
pauseLabel?: string;
|
|
22
|
+
playLabel?: string;
|
|
23
|
+
label?: string;
|
|
24
|
+
backgroundColor?: string;
|
|
25
|
+
cardColor?: string;
|
|
26
|
+
textColor?: string;
|
|
27
|
+
mutedColor?: string;
|
|
28
|
+
lineColor?: string;
|
|
29
|
+
accentColor?: string;
|
|
30
|
+
radius?: number;
|
|
31
|
+
fontFamily?: string;
|
|
32
|
+
className?: string;
|
|
33
|
+
style?: CSSProperties;
|
|
34
|
+
}
|
|
35
|
+
export default function TestimonialWall({ testimonials, rows, speed, cardWidth, lines, gap, fade, showPause, pauseLabel, playLabel, label, backgroundColor, cardColor, textColor, mutedColor, lineColor, accentColor, radius, fontFamily, className, style }: TestimonialWallProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-testimonial-wall";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-wall { position: relative; display: grid; gap: var(--bl-gap); width: 100%; color: var(--bl-ink); font-family: var(--bl-font); }
|
|
7
|
+
.bl-wall__row { position: relative; overflow: hidden; padding: 14px 0; -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-wall__track { display: flex; width: max-content; animation: bl-wall-scroll var(--bl-dur, 60s) linear infinite; animation-direction: var(--bl-dir, normal); }
|
|
9
|
+
.bl-wall__set { display: flex; flex: 0 0 auto; align-items: flex-start; gap: var(--bl-gap); margin: 0; padding: 0 var(--bl-gap) 0 0; list-style: none; }
|
|
10
|
+
.bl-wall__row:hover .bl-wall__track, .bl-wall__row:focus-within .bl-wall__track, .bl-wall[data-paused] .bl-wall__track { animation-play-state: paused; }
|
|
11
|
+
.bl-wall__card { position: relative; display: grid; gap: 12px; width: var(--bl-card-w); margin: 0; padding: 18px; box-sizing: border-box; border-radius: var(--bl-radius); background: var(--bl-card); box-shadow: 0 0 0 1px var(--bl-line); outline: none; transition: transform 220ms cubic-bezier(.2,.9,.25,1.2), box-shadow 220ms; }
|
|
12
|
+
.bl-wall__card:hover, .bl-wall__card:focus-visible { z-index: 1; transform: translateY(-4px) scale(1.03); box-shadow: 0 0 0 2px var(--bl-accent), 0 18px 40px -18px rgba(0,0,0,0.7); }
|
|
13
|
+
.bl-wall__quote { margin: 0; color: var(--bl-ink); font-size: 15px; line-height: 1.55; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: var(--bl-lines); overflow: hidden; }
|
|
14
|
+
.bl-wall__card:hover .bl-wall__quote, .bl-wall__card:focus-visible .bl-wall__quote { -webkit-line-clamp: unset; }
|
|
15
|
+
.bl-wall__who { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
16
|
+
.bl-wall__avatar { flex: 0 0 auto; display: grid; place-items: center; width: 36px; height: 36px; overflow: hidden; border-radius: 50%; background: var(--bl-line); }
|
|
17
|
+
.bl-wall__avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
18
|
+
.bl-wall__name { display: block; font-size: 14px; font-weight: 700; font-style: normal; }
|
|
19
|
+
.bl-wall__role { display: block; color: var(--bl-muted); font-size: 13px; }
|
|
20
|
+
.bl-wall__pause { justify-self: end; display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border: 0; border-radius: 999px; background: var(--bl-card); color: var(--bl-muted); font: inherit; font-size: 13px; cursor: pointer; box-shadow: 0 0 0 1px var(--bl-line); }
|
|
21
|
+
.bl-wall__pause:hover { color: var(--bl-ink); }
|
|
22
|
+
.bl-wall__pause:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
|
|
23
|
+
@keyframes bl-wall-scroll { to { transform: translateX(-50%); } }
|
|
24
|
+
@media (prefers-reduced-motion: reduce) {
|
|
25
|
+
.bl-wall__row { overflow-x: auto; -webkit-mask-image: none; mask-image: none; }
|
|
26
|
+
.bl-wall__track { animation: none; }
|
|
27
|
+
.bl-wall__set[aria-hidden] { display: none; }
|
|
28
|
+
.bl-wall__pause { display: none; }
|
|
29
|
+
}
|
|
30
|
+
`;
|
|
31
|
+
function useStyles() {
|
|
32
|
+
useInsertionEffect(() => {
|
|
33
|
+
if (typeof document === "undefined") return;
|
|
34
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
35
|
+
style.textContent = CSS;
|
|
36
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
37
|
+
}, []);
|
|
38
|
+
}
|
|
39
|
+
function Card({ item, hidden }) {
|
|
40
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("figure", { className: "bl-wall__card", tabIndex: hidden ? -1 : 0, children: [
|
|
41
|
+
/* @__PURE__ */ jsx("blockquote", { className: "bl-wall__quote", children: item.quote }),
|
|
42
|
+
/* @__PURE__ */ jsxs("figcaption", { className: "bl-wall__who", children: [
|
|
43
|
+
item.avatar && /* @__PURE__ */ jsx("span", { className: "bl-wall__avatar", children: typeof item.avatar === "string" ? /* @__PURE__ */ jsx("img", { src: item.avatar, alt: "" }) : item.avatar }),
|
|
44
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
45
|
+
/* @__PURE__ */ jsx("cite", { className: "bl-wall__name", children: item.name }),
|
|
46
|
+
item.role && /* @__PURE__ */ jsx("span", { className: "bl-wall__role", children: item.role })
|
|
47
|
+
] })
|
|
48
|
+
] })
|
|
49
|
+
] }) });
|
|
50
|
+
}
|
|
51
|
+
function Row({ items, reverse, speed }) {
|
|
52
|
+
const setRef = useRef(null);
|
|
53
|
+
const [duration, setDuration] = useState(60);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const node = setRef.current;
|
|
56
|
+
const measure = () => setDuration(Math.max(8, node.offsetWidth / speed));
|
|
57
|
+
const resize = new ResizeObserver(measure);
|
|
58
|
+
resize.observe(node);
|
|
59
|
+
measure();
|
|
60
|
+
return () => resize.disconnect();
|
|
61
|
+
}, [speed]);
|
|
62
|
+
return /* @__PURE__ */ jsx("div", { className: "bl-wall__row", children: /* @__PURE__ */ jsxs("div", { className: "bl-wall__track", style: { "--bl-dur": `${duration}s`, "--bl-dir": reverse ? "reverse" : "normal" }, children: [
|
|
63
|
+
/* @__PURE__ */ jsx("ul", { ref: setRef, className: "bl-wall__set", children: items.map((item, i) => /* @__PURE__ */ jsx(Card, { item }, i)) }),
|
|
64
|
+
/* @__PURE__ */ jsx("ul", { className: "bl-wall__set", "aria-hidden": "true", children: items.map((item, i) => /* @__PURE__ */ jsx(Card, { item, hidden: true }, i)) })
|
|
65
|
+
] }) });
|
|
66
|
+
}
|
|
67
|
+
function TestimonialWall({
|
|
68
|
+
testimonials = [],
|
|
69
|
+
rows = 3,
|
|
70
|
+
speed = 30,
|
|
71
|
+
cardWidth = 300,
|
|
72
|
+
lines = 4,
|
|
73
|
+
gap = 16,
|
|
74
|
+
fade = 80,
|
|
75
|
+
showPause = true,
|
|
76
|
+
pauseLabel = "Pause",
|
|
77
|
+
playLabel = "Play",
|
|
78
|
+
label = "What people say",
|
|
79
|
+
backgroundColor = "#1a1c2c",
|
|
80
|
+
cardColor = "#262b44",
|
|
81
|
+
textColor = "#f4f4f4",
|
|
82
|
+
mutedColor = "#94b0c2",
|
|
83
|
+
lineColor = "#333c57",
|
|
84
|
+
accentColor = "#73eff7",
|
|
85
|
+
radius = 14,
|
|
86
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
87
|
+
className = "",
|
|
88
|
+
style
|
|
89
|
+
}) {
|
|
90
|
+
useStyles();
|
|
91
|
+
const [paused, setPaused] = useState(false);
|
|
92
|
+
const rowCount = Math.max(1, Math.min(rows, testimonials.length || 1));
|
|
93
|
+
const perRow = Math.max(3, Math.ceil(testimonials.length / rowCount));
|
|
94
|
+
const dealt = Array.from(
|
|
95
|
+
{ length: rowCount },
|
|
96
|
+
(_, r) => testimonials.length ? Array.from({ length: perRow }, (_2, i) => testimonials[(r * perRow + i) % testimonials.length]) : []
|
|
97
|
+
);
|
|
98
|
+
return /* @__PURE__ */ jsxs(
|
|
99
|
+
"section",
|
|
100
|
+
{
|
|
101
|
+
className: `bl-wall ${className}`,
|
|
102
|
+
"aria-label": label,
|
|
103
|
+
"data-paused": paused || void 0,
|
|
104
|
+
style: {
|
|
105
|
+
"--bl-gap": `${gap}px`,
|
|
106
|
+
"--bl-fade": `${fade}px`,
|
|
107
|
+
"--bl-card-w": `min(${cardWidth}px, 80vw)`,
|
|
108
|
+
"--bl-lines": lines,
|
|
109
|
+
"--bl-bg": backgroundColor,
|
|
110
|
+
"--bl-card": cardColor,
|
|
111
|
+
"--bl-ink": textColor,
|
|
112
|
+
"--bl-muted": mutedColor,
|
|
113
|
+
"--bl-line": lineColor,
|
|
114
|
+
"--bl-accent": accentColor,
|
|
115
|
+
"--bl-radius": `${radius}px`,
|
|
116
|
+
"--bl-font": fontFamily,
|
|
117
|
+
...style
|
|
118
|
+
},
|
|
119
|
+
children: [
|
|
120
|
+
dealt.map((items, r) => /* @__PURE__ */ jsx(Row, { items, reverse: r % 2 === 1, speed: speed * (1 + r % 3 * 0.18) }, r)),
|
|
121
|
+
showPause && /* @__PURE__ */ jsxs("button", { type: "button", className: "bl-wall__pause", onClick: () => setPaused((p) => !p), children: [
|
|
122
|
+
/* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "currentColor", "aria-hidden": "true", children: paused ? /* @__PURE__ */ jsx("path", { d: "M3 1.5v9l7.5-4.5z" }) : /* @__PURE__ */ jsx("path", { d: "M2.5 1.5h2.5v9H2.5zM7 1.5h2.5v9H7z" }) }),
|
|
123
|
+
paused ? playLabel : pauseLabel
|
|
124
|
+
] })
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
TestimonialWall as default
|
|
131
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
export type PathPreset = 'wave' | 'arc' | 'smile' | 'circle' | 'loop';
|
|
3
|
+
export declare const PATHS: Record<PathPreset, {
|
|
4
|
+
viewBox: string;
|
|
5
|
+
d: string;
|
|
6
|
+
}>;
|
|
7
|
+
export interface TextOnPathProps {
|
|
8
|
+
/** End it with a separator so the loop reads cleanly. */
|
|
9
|
+
text?: string;
|
|
10
|
+
/** A preset name or any SVG path data (pair custom paths with viewBox). */
|
|
11
|
+
path?: PathPreset | (string & {});
|
|
12
|
+
viewBox?: string;
|
|
13
|
+
/** Minimum copies of the text; more are added if the path needs them. */
|
|
14
|
+
repeat?: number;
|
|
15
|
+
/** Scroll speed in viewBox units per second; 0 for still text. */
|
|
16
|
+
speed?: number;
|
|
17
|
+
direction?: 'forward' | 'reverse';
|
|
18
|
+
/** Starting position along the path, in percent. */
|
|
19
|
+
offset?: number;
|
|
20
|
+
fontSize?: number;
|
|
21
|
+
fontWeight?: number;
|
|
22
|
+
letterSpacing?: number;
|
|
23
|
+
/** Draw the guide path. */
|
|
24
|
+
showPath?: boolean;
|
|
25
|
+
pauseOnHover?: boolean;
|
|
26
|
+
/** Max width, in px. */
|
|
27
|
+
width?: number;
|
|
28
|
+
textColor?: string;
|
|
29
|
+
guideColor?: string;
|
|
30
|
+
fontFamily?: string;
|
|
31
|
+
/** Accessible name; defaults to the text. */
|
|
32
|
+
label?: string;
|
|
33
|
+
className?: string;
|
|
34
|
+
style?: CSSProperties;
|
|
35
|
+
}
|
|
36
|
+
export default function TextOnPath({ text, path, viewBox, repeat, speed, direction, offset, fontSize, fontWeight, letterSpacing, showPath, pauseOnHover, width, textColor, guideColor, fontFamily, label, className, style }: TextOnPathProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-text-on-path";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-tpath { display: block; width: 100%; max-width: var(--bl-w); overflow: visible; color: var(--bl-ink); font-family: var(--bl-font); }
|
|
7
|
+
.bl-tpath text { fill: currentColor; font-size: var(--bl-size); font-weight: var(--bl-weight); letter-spacing: var(--bl-spacing); }
|
|
8
|
+
.bl-tpath__guide { fill: none; stroke: var(--bl-guide); stroke-width: 1; stroke-dasharray: 3 5; }
|
|
9
|
+
`;
|
|
10
|
+
function useStyles() {
|
|
11
|
+
useInsertionEffect(() => {
|
|
12
|
+
if (typeof document === "undefined") return;
|
|
13
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
14
|
+
style.textContent = CSS;
|
|
15
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
16
|
+
}, []);
|
|
17
|
+
}
|
|
18
|
+
const PATHS = {
|
|
19
|
+
wave: { viewBox: "0 0 1000 240", d: "M0,140 C125,40 250,40 375,140 S625,240 750,140 S875,40 1000,120" },
|
|
20
|
+
arc: { viewBox: "0 0 1000 320", d: "M40,300 Q500,-120 960,300" },
|
|
21
|
+
smile: { viewBox: "0 0 1000 300", d: "M40,40 Q500,460 960,40" },
|
|
22
|
+
circle: { viewBox: "0 0 1000 1000", d: "M500,500 m-380,0 a380,380 0 1,1 760,0 a380,380 0 1,1 -760,0" },
|
|
23
|
+
loop: { viewBox: "0 0 1000 400", d: "M40,300 C200,300 260,60 420,60 C600,60 560,340 420,340 C280,340 360,120 600,120 C780,120 820,300 960,300" }
|
|
24
|
+
};
|
|
25
|
+
function TextOnPath({
|
|
26
|
+
text = "Text along a path \xB7 ",
|
|
27
|
+
path = "wave",
|
|
28
|
+
viewBox,
|
|
29
|
+
repeat = 3,
|
|
30
|
+
speed = 40,
|
|
31
|
+
direction = "forward",
|
|
32
|
+
offset = 0,
|
|
33
|
+
fontSize = 42,
|
|
34
|
+
fontWeight = 700,
|
|
35
|
+
letterSpacing = 1,
|
|
36
|
+
showPath = false,
|
|
37
|
+
pauseOnHover = true,
|
|
38
|
+
width = 900,
|
|
39
|
+
textColor = "#f4f4f4",
|
|
40
|
+
guideColor = "rgba(255,255,255,0.18)",
|
|
41
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
42
|
+
label,
|
|
43
|
+
className = "",
|
|
44
|
+
style
|
|
45
|
+
}) {
|
|
46
|
+
useStyles();
|
|
47
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
48
|
+
const preset = typeof path === "string" && PATHS[path] ? PATHS[path] : null;
|
|
49
|
+
const d = preset ? preset.d : path;
|
|
50
|
+
const box = viewBox ?? preset?.viewBox ?? "0 0 1000 300";
|
|
51
|
+
const svgRef = useRef(null);
|
|
52
|
+
const textPathRef = useRef(null);
|
|
53
|
+
const hovering = useRef(false);
|
|
54
|
+
const [grown, setGrown] = useState(1);
|
|
55
|
+
const copies = Math.max(repeat, grown);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const svg = svgRef.current;
|
|
58
|
+
const textPath = textPathRef.current;
|
|
59
|
+
if (!svg || !textPath) return void 0;
|
|
60
|
+
textPath.setAttribute("startOffset", `${offset}%`);
|
|
61
|
+
if (!speed || window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return void 0;
|
|
62
|
+
const pathEl = svg.querySelector("path[id]");
|
|
63
|
+
const length = pathEl.getTotalLength();
|
|
64
|
+
const copy = textPath.getComputedTextLength() / copies || length;
|
|
65
|
+
const needed = Math.ceil(length / copy) + 1;
|
|
66
|
+
if (needed > copies) {
|
|
67
|
+
setGrown(needed);
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
let shift = 0;
|
|
71
|
+
let last = performance.now();
|
|
72
|
+
let frame = 0;
|
|
73
|
+
let visible = true;
|
|
74
|
+
const sign = direction === "reverse" ? 1 : -1;
|
|
75
|
+
const tick = (now) => {
|
|
76
|
+
const dt = Math.min(0.1, (now - last) / 1e3);
|
|
77
|
+
last = now;
|
|
78
|
+
if (!(pauseOnHover && hovering.current)) shift = (shift + speed * dt) % copy;
|
|
79
|
+
textPath.setAttribute("startOffset", `${offset / 100 * length + sign * shift + (sign < 0 ? 0 : -copy)}`);
|
|
80
|
+
if (visible) frame = requestAnimationFrame(tick);
|
|
81
|
+
};
|
|
82
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
83
|
+
visible = entry.isIntersecting;
|
|
84
|
+
if (visible && !frame) {
|
|
85
|
+
last = performance.now();
|
|
86
|
+
frame = requestAnimationFrame(tick);
|
|
87
|
+
} else if (!visible) {
|
|
88
|
+
cancelAnimationFrame(frame);
|
|
89
|
+
frame = 0;
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
observer.observe(svg);
|
|
93
|
+
frame = requestAnimationFrame(tick);
|
|
94
|
+
return () => {
|
|
95
|
+
cancelAnimationFrame(frame);
|
|
96
|
+
observer.disconnect();
|
|
97
|
+
};
|
|
98
|
+
}, [d, speed, direction, copies, offset, text, fontSize, letterSpacing, pauseOnHover]);
|
|
99
|
+
const content = speed ? text.repeat(copies) : text;
|
|
100
|
+
return /* @__PURE__ */ jsxs(
|
|
101
|
+
"svg",
|
|
102
|
+
{
|
|
103
|
+
ref: svgRef,
|
|
104
|
+
className: `bl-tpath ${className}`,
|
|
105
|
+
viewBox: box,
|
|
106
|
+
role: "img",
|
|
107
|
+
"aria-label": label ?? text.trim(),
|
|
108
|
+
onPointerEnter: () => hovering.current = true,
|
|
109
|
+
onPointerLeave: () => hovering.current = false,
|
|
110
|
+
style: {
|
|
111
|
+
"--bl-w": `${width}px`,
|
|
112
|
+
"--bl-size": `${fontSize}px`,
|
|
113
|
+
"--bl-weight": fontWeight,
|
|
114
|
+
"--bl-spacing": `${letterSpacing}px`,
|
|
115
|
+
"--bl-ink": textColor,
|
|
116
|
+
"--bl-guide": guideColor,
|
|
117
|
+
"--bl-font": fontFamily,
|
|
118
|
+
...style
|
|
119
|
+
},
|
|
120
|
+
children: [
|
|
121
|
+
/* @__PURE__ */ jsx("path", { id: `${uid}-path`, d, fill: "none", className: showPath ? "bl-tpath__guide" : void 0 }),
|
|
122
|
+
/* @__PURE__ */ jsx("text", { "aria-hidden": "true", dominantBaseline: "middle", children: /* @__PURE__ */ jsx("textPath", { ref: textPathRef, href: `#${uid}-path`, children: content }) })
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
PATHS,
|
|
129
|
+
TextOnPath as default
|
|
130
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface ThinkingStep {
|
|
3
|
+
id?: string | number;
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
status?: 'pending' | 'active' | 'done';
|
|
6
|
+
detail?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export interface ThinkingShimmerProps {
|
|
9
|
+
/** While true the label shimmers and the timer ticks. */
|
|
10
|
+
thinking?: boolean;
|
|
11
|
+
steps?: ThinkingStep[];
|
|
12
|
+
label?: string;
|
|
13
|
+
/** Header text once done, given the elapsed seconds. */
|
|
14
|
+
doneLabel?: (seconds: number) => string;
|
|
15
|
+
/** Epoch ms when the work began, if it started before this mounted. */
|
|
16
|
+
startedAt?: number;
|
|
17
|
+
showTimer?: boolean;
|
|
18
|
+
defaultOpen?: boolean;
|
|
19
|
+
open?: boolean;
|
|
20
|
+
onOpenChange?: (open: boolean) => void;
|
|
21
|
+
/** One shimmer pass, in ms. */
|
|
22
|
+
shimmerSpeed?: number;
|
|
23
|
+
/** Max width, in px. */
|
|
24
|
+
width?: number;
|
|
25
|
+
textColor?: string;
|
|
26
|
+
mutedColor?: string;
|
|
27
|
+
shineColor?: string;
|
|
28
|
+
lineColor?: string;
|
|
29
|
+
hoverColor?: string;
|
|
30
|
+
accentColor?: string;
|
|
31
|
+
successColor?: string;
|
|
32
|
+
fontFamily?: string;
|
|
33
|
+
className?: string;
|
|
34
|
+
style?: CSSProperties;
|
|
35
|
+
}
|
|
36
|
+
export default function ThinkingShimmer({ thinking, steps, label, doneLabel, startedAt, showTimer, defaultOpen, open: controlledOpen, onOpenChange, shimmerSpeed, width, textColor, mutedColor, shineColor, lineColor, hoverColor, accentColor, successColor, fontFamily, className, style }: ThinkingShimmerProps): import("react").JSX.Element;
|