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,198 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-chat";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-chat { display: flex; flex-direction: column; gap: 2px; height: var(--bl-h); overflow-y: auto; padding: 16px 14px; border-radius: var(--bl-radius); background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); font-size: 15px; scrollbar-width: thin; overscroll-behavior: contain; }
|
|
7
|
+
.bl-chat__row { position: relative; display: flex; align-items: flex-end; gap: 8px; max-width: 100%; }
|
|
8
|
+
.bl-chat__row[data-from="me"] { flex-direction: row-reverse; }
|
|
9
|
+
.bl-chat__row[data-last] { margin-bottom: 10px; }
|
|
10
|
+
.bl-chat__avatar { flex: 0 0 auto; display: grid; place-items: center; width: 30px; height: 30px; overflow: hidden; border-radius: 50%; background: var(--bl-them); font-size: 13px; font-weight: 700; }
|
|
11
|
+
.bl-chat__avatar[data-hidden] { visibility: hidden; }
|
|
12
|
+
.bl-chat__stack { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; max-width: min(78%, 480px); }
|
|
13
|
+
.bl-chat__row[data-from="me"] .bl-chat__stack { align-items: flex-end; }
|
|
14
|
+
.bl-chat__author { margin: 0 0 3px 12px; color: var(--bl-muted); font-size: 12px; }
|
|
15
|
+
.bl-chat__bubble { position: relative; padding: 9px 13px; border-radius: 18px; background: var(--bl-them); color: var(--bl-them-ink); line-height: 1.45; overflow-wrap: anywhere; outline: none; transform-origin: bottom left; }
|
|
16
|
+
.bl-chat__row[data-from="me"] .bl-chat__bubble { background: var(--bl-me); color: var(--bl-me-ink); transform-origin: bottom right; }
|
|
17
|
+
.bl-chat__row[data-from="them"]:not([data-first]) .bl-chat__bubble { border-top-left-radius: 6px; }
|
|
18
|
+
.bl-chat__row[data-from="them"]:not([data-last]) .bl-chat__bubble { border-bottom-left-radius: 6px; }
|
|
19
|
+
.bl-chat__row[data-from="me"]:not([data-first]) .bl-chat__bubble { border-top-right-radius: 6px; }
|
|
20
|
+
.bl-chat__row[data-from="me"]:not([data-last]) .bl-chat__bubble { border-bottom-right-radius: 6px; }
|
|
21
|
+
.bl-chat__row[data-from="them"][data-last] .bl-chat__bubble { border-bottom-left-radius: 4px; }
|
|
22
|
+
.bl-chat__row[data-from="me"][data-last] .bl-chat__bubble { border-bottom-right-radius: 4px; }
|
|
23
|
+
.bl-chat__bubble:focus-visible { box-shadow: 0 0 0 2px var(--bl-accent); }
|
|
24
|
+
.bl-chat__bubble[data-new] { animation: bl-chat-in 520ms cubic-bezier(.2,1.5,.4,1) both; }
|
|
25
|
+
.bl-chat__time { margin: 3px 6px 0; color: var(--bl-muted); font-size: 11px; }
|
|
26
|
+
.bl-chat__react-btn { position: absolute; top: 50%; display: grid; place-items: center; width: 28px; height: 28px; border: 0; border-radius: 50%; background: var(--bl-chip); color: var(--bl-muted); cursor: pointer; opacity: 0; transform: translateY(-50%); transition: opacity 150ms; }
|
|
27
|
+
.bl-chat__row[data-from="them"] .bl-chat__react-btn { right: -36px; }
|
|
28
|
+
.bl-chat__row[data-from="me"] .bl-chat__react-btn { left: -36px; }
|
|
29
|
+
.bl-chat__bubble-wrap { position: relative; }
|
|
30
|
+
.bl-chat__bubble-wrap:hover .bl-chat__react-btn, .bl-chat__bubble-wrap:focus-within .bl-chat__react-btn { opacity: 1; }
|
|
31
|
+
.bl-chat__react-btn:focus-visible { outline: 2px solid var(--bl-accent); }
|
|
32
|
+
.bl-chat__picker { position: absolute; bottom: calc(100% + 6px); z-index: 3; display: flex; gap: 2px; padding: 4px; border-radius: 999px; background: var(--bl-picker); box-shadow: 0 0 0 1px var(--bl-line), 0 10px 24px -10px rgba(0,0,0,0.7); animation: bl-chat-pop 220ms cubic-bezier(.2,1.5,.4,1); }
|
|
33
|
+
.bl-chat__row[data-from="them"] .bl-chat__picker { left: 0; }
|
|
34
|
+
.bl-chat__row[data-from="me"] .bl-chat__picker { right: 0; }
|
|
35
|
+
.bl-chat__picker button { width: 34px; height: 34px; border: 0; border-radius: 50%; background: transparent; font-size: 19px; cursor: pointer; transition: transform 150ms; }
|
|
36
|
+
.bl-chat__picker button:hover, .bl-chat__picker button:focus-visible { transform: scale(1.25); background: var(--bl-chip); outline: none; }
|
|
37
|
+
.bl-chat__picker button[aria-pressed="true"] { background: var(--bl-chip); }
|
|
38
|
+
.bl-chat__reactions { display: flex; gap: 4px; margin: -6px 8px 0; position: relative; z-index: 1; }
|
|
39
|
+
.bl-chat__reaction { display: inline-flex; align-items: center; gap: 3px; padding: 2px 7px; border-radius: 999px; background: var(--bl-picker); box-shadow: 0 0 0 2px var(--bl-bg); font-size: 13px; animation: bl-chat-pop 300ms cubic-bezier(.2,1.6,.4,1); }
|
|
40
|
+
.bl-chat__typing { display: inline-flex; gap: 4px; padding: 13px 14px; border-radius: 18px 18px 18px 4px; background: var(--bl-them); }
|
|
41
|
+
.bl-chat__typing i { width: 7px; height: 7px; border-radius: 50%; background: var(--bl-muted); animation: bl-chat-dot 1.2s ease-in-out infinite; }
|
|
42
|
+
.bl-chat__typing i:nth-child(2) { animation-delay: 0.15s; }
|
|
43
|
+
.bl-chat__typing i:nth-child(3) { animation-delay: 0.3s; }
|
|
44
|
+
.bl-chat__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
45
|
+
@keyframes bl-chat-in { from { opacity: 0; transform: scale(0.5) translateY(12px); } }
|
|
46
|
+
@keyframes bl-chat-pop { from { opacity: 0; transform: scale(0.4); } }
|
|
47
|
+
@keyframes bl-chat-dot { 0%, 60%, 100% { transform: translateY(0); opacity: 0.5; } 30% { transform: translateY(-5px); opacity: 1; } }
|
|
48
|
+
@media (prefers-reduced-motion: reduce) { .bl-chat__bubble[data-new], .bl-chat__picker, .bl-chat__reaction { animation: none; } .bl-chat__typing i { animation: none; opacity: 0.8; } }
|
|
49
|
+
`;
|
|
50
|
+
function useStyles() {
|
|
51
|
+
useInsertionEffect(() => {
|
|
52
|
+
if (typeof document === "undefined") return;
|
|
53
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
54
|
+
style.textContent = CSS;
|
|
55
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
56
|
+
}, []);
|
|
57
|
+
}
|
|
58
|
+
const DEFAULT_REACTIONS = ["\u2764\uFE0F", "\u{1F602}", "\u{1F44D}", "\u{1F62E}", "\u{1F622}"];
|
|
59
|
+
const REACTION_NAMES = { "\u2764\uFE0F": "heart", "\u{1F602}": "laughing", "\u{1F44D}": "thumbs up", "\u{1F62E}": "surprised", "\u{1F622}": "sad" };
|
|
60
|
+
function ChatBubbles({
|
|
61
|
+
messages = [],
|
|
62
|
+
typing = false,
|
|
63
|
+
typingName,
|
|
64
|
+
reactions = DEFAULT_REACTIONS,
|
|
65
|
+
onReact,
|
|
66
|
+
height = 420,
|
|
67
|
+
radius = 18,
|
|
68
|
+
label = "Conversation",
|
|
69
|
+
backgroundColor = "#11131c",
|
|
70
|
+
meColor = "#3987e5",
|
|
71
|
+
meTextColor = "#ffffff",
|
|
72
|
+
themColor = "#29366f",
|
|
73
|
+
themTextColor = "#f4f4f4",
|
|
74
|
+
textColor = "#f4f4f4",
|
|
75
|
+
mutedColor = "#94b0c2",
|
|
76
|
+
chipColor = "rgba(255,255,255,0.1)",
|
|
77
|
+
pickerColor = "#1f2335",
|
|
78
|
+
lineColor = "rgba(255,255,255,0.1)",
|
|
79
|
+
accentColor = "#73eff7",
|
|
80
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
81
|
+
className = "",
|
|
82
|
+
style
|
|
83
|
+
}) {
|
|
84
|
+
useStyles();
|
|
85
|
+
const ref = useRef(null);
|
|
86
|
+
const seen = useRef(null);
|
|
87
|
+
const [picker, setPicker] = useState(null);
|
|
88
|
+
const [mine, setMine] = useState({});
|
|
89
|
+
const pinned = useRef(true);
|
|
90
|
+
if (seen.current === null) seen.current = new Set(messages.map((m) => m.id));
|
|
91
|
+
const fresh = new Set(messages.filter((m) => !seen.current.has(m.id)).map((m) => m.id));
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
messages.forEach((m) => seen.current.add(m.id));
|
|
94
|
+
});
|
|
95
|
+
useLayoutEffect(() => {
|
|
96
|
+
const node = ref.current;
|
|
97
|
+
if (node && pinned.current) node.scrollTop = node.scrollHeight;
|
|
98
|
+
}, [messages.length, typing]);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (picker === null) return void 0;
|
|
101
|
+
const close = (event) => {
|
|
102
|
+
if (!event.target.closest?.(".bl-chat__picker, .bl-chat__react-btn")) setPicker(null);
|
|
103
|
+
};
|
|
104
|
+
const onKey = (event) => event.key === "Escape" && setPicker(null);
|
|
105
|
+
document.addEventListener("pointerdown", close);
|
|
106
|
+
document.addEventListener("keydown", onKey);
|
|
107
|
+
return () => {
|
|
108
|
+
document.removeEventListener("pointerdown", close);
|
|
109
|
+
document.removeEventListener("keydown", onKey);
|
|
110
|
+
};
|
|
111
|
+
}, [picker]);
|
|
112
|
+
const toggleReaction = (id, emoji) => {
|
|
113
|
+
const current = mine[id] ?? [];
|
|
114
|
+
const on = current.includes(emoji);
|
|
115
|
+
setMine({ ...mine, [id]: on ? current.filter((e) => e !== emoji) : [...current, emoji] });
|
|
116
|
+
onReact?.(id, emoji, !on);
|
|
117
|
+
setPicker(null);
|
|
118
|
+
};
|
|
119
|
+
return /* @__PURE__ */ jsxs(
|
|
120
|
+
"div",
|
|
121
|
+
{
|
|
122
|
+
ref,
|
|
123
|
+
className: `bl-chat ${className}`,
|
|
124
|
+
role: "log",
|
|
125
|
+
"aria-live": "polite",
|
|
126
|
+
"aria-label": label,
|
|
127
|
+
onScroll: (event) => {
|
|
128
|
+
const node = event.currentTarget;
|
|
129
|
+
pinned.current = node.scrollHeight - node.scrollTop - node.clientHeight < 40;
|
|
130
|
+
},
|
|
131
|
+
style: {
|
|
132
|
+
"--bl-h": typeof height === "number" ? `${height}px` : height,
|
|
133
|
+
"--bl-radius": `${radius}px`,
|
|
134
|
+
"--bl-bg": backgroundColor,
|
|
135
|
+
"--bl-me": meColor,
|
|
136
|
+
"--bl-me-ink": meTextColor,
|
|
137
|
+
"--bl-them": themColor,
|
|
138
|
+
"--bl-them-ink": themTextColor,
|
|
139
|
+
"--bl-ink": textColor,
|
|
140
|
+
"--bl-muted": mutedColor,
|
|
141
|
+
"--bl-chip": chipColor,
|
|
142
|
+
"--bl-picker": pickerColor,
|
|
143
|
+
"--bl-line": lineColor,
|
|
144
|
+
"--bl-accent": accentColor,
|
|
145
|
+
"--bl-font": fontFamily,
|
|
146
|
+
...style
|
|
147
|
+
},
|
|
148
|
+
children: [
|
|
149
|
+
messages.map((message, i) => {
|
|
150
|
+
const from = message.from === "me" ? "me" : "them";
|
|
151
|
+
const prev = messages[i - 1];
|
|
152
|
+
const next = messages[i + 1];
|
|
153
|
+
const first = !prev || prev.from === "me" !== (from === "me") || prev.author !== message.author;
|
|
154
|
+
const last = !next || next.from === "me" !== (from === "me") || next.author !== message.author;
|
|
155
|
+
const all = [.../* @__PURE__ */ new Set([...message.reactions ?? [], ...mine[message.id] ?? []])];
|
|
156
|
+
return /* @__PURE__ */ jsxs("div", { className: "bl-chat__row", "data-from": from, "data-first": first || void 0, "data-last": last || void 0, children: [
|
|
157
|
+
from === "them" && /* @__PURE__ */ jsx("span", { className: "bl-chat__avatar", "data-hidden": !last || void 0, "aria-hidden": "true", children: message.avatar ?? (message.author ?? "?").slice(0, 1).toUpperCase() }),
|
|
158
|
+
/* @__PURE__ */ jsxs("div", { className: "bl-chat__stack", children: [
|
|
159
|
+
first && from === "them" && message.author && /* @__PURE__ */ jsx("p", { className: "bl-chat__author", children: message.author }),
|
|
160
|
+
/* @__PURE__ */ jsxs("div", { className: "bl-chat__bubble-wrap", children: [
|
|
161
|
+
/* @__PURE__ */ jsx(
|
|
162
|
+
"div",
|
|
163
|
+
{
|
|
164
|
+
className: "bl-chat__bubble",
|
|
165
|
+
"data-new": fresh.has(message.id) || void 0,
|
|
166
|
+
tabIndex: 0,
|
|
167
|
+
onDoubleClick: () => toggleReaction(message.id, reactions[0]),
|
|
168
|
+
"aria-label": `${from === "me" ? "You" : message.author ?? "Them"}: ${typeof message.text === "string" ? message.text : ""}${message.time ? `, ${message.time}` : ""}`,
|
|
169
|
+
children: message.text
|
|
170
|
+
}
|
|
171
|
+
),
|
|
172
|
+
reactions.length > 0 && /* @__PURE__ */ jsx("button", { type: "button", className: "bl-chat__react-btn", "aria-label": "Add reaction", "aria-expanded": picker === message.id, onClick: () => setPicker(picker === message.id ? null : message.id), children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", "aria-hidden": "true", children: [
|
|
173
|
+
/* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6.2" }),
|
|
174
|
+
/* @__PURE__ */ jsx("path", { d: "M5.5 9.5c.6.9 1.5 1.4 2.5 1.4s1.9-.5 2.5-1.4M6 6.3h.01M10 6.3h.01" })
|
|
175
|
+
] }) }),
|
|
176
|
+
picker === message.id && /* @__PURE__ */ jsx("div", { className: "bl-chat__picker", role: "group", "aria-label": "Reactions", children: reactions.map((emoji) => /* @__PURE__ */ jsx("button", { type: "button", "aria-label": REACTION_NAMES[emoji] ?? emoji, "aria-pressed": (mine[message.id] ?? []).includes(emoji), onClick: () => toggleReaction(message.id, emoji), children: emoji }, emoji)) })
|
|
177
|
+
] }),
|
|
178
|
+
all.length > 0 && /* @__PURE__ */ jsx("div", { className: "bl-chat__reactions", "aria-label": `Reactions: ${all.map((e) => REACTION_NAMES[e] ?? e).join(", ")}`, children: all.map((emoji) => /* @__PURE__ */ jsx("span", { className: "bl-chat__reaction", "aria-hidden": "true", children: emoji }, emoji)) }),
|
|
179
|
+
last && message.time && /* @__PURE__ */ jsx("span", { className: "bl-chat__time", children: message.time })
|
|
180
|
+
] })
|
|
181
|
+
] }, message.id);
|
|
182
|
+
}),
|
|
183
|
+
typing && /* @__PURE__ */ jsxs("div", { className: "bl-chat__row", "data-from": "them", "data-first": true, "data-last": true, children: [
|
|
184
|
+
/* @__PURE__ */ jsx("span", { className: "bl-chat__avatar", "aria-hidden": "true", children: (typingName ?? "?").slice(0, 1).toUpperCase() }),
|
|
185
|
+
/* @__PURE__ */ jsxs("span", { className: "bl-chat__typing", "aria-hidden": "true", children: [
|
|
186
|
+
/* @__PURE__ */ jsx("i", {}),
|
|
187
|
+
/* @__PURE__ */ jsx("i", {}),
|
|
188
|
+
/* @__PURE__ */ jsx("i", {})
|
|
189
|
+
] }),
|
|
190
|
+
/* @__PURE__ */ jsx("span", { className: "bl-chat__sr", children: `${typingName ?? "Someone"} is typing` })
|
|
191
|
+
] })
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
export {
|
|
197
|
+
ChatBubbles as default
|
|
198
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { type ReactNode, type CSSProperties } from 'react';
|
|
2
|
+
export interface ChipRecipe {
|
|
3
|
+
/** Oscillator shape, or "noise" for a pure noise burst. */
|
|
4
|
+
wave?: OscillatorType | 'noise';
|
|
5
|
+
/** Start pitch, in Hz. */
|
|
6
|
+
from?: number;
|
|
7
|
+
/** End pitch, in Hz (slides from `from`). */
|
|
8
|
+
to?: number;
|
|
9
|
+
/** Length of each note, in seconds. */
|
|
10
|
+
duration: number;
|
|
11
|
+
/** Number of notes in an arpeggio. */
|
|
12
|
+
notes?: number;
|
|
13
|
+
/** Semitones between arpeggio notes. */
|
|
14
|
+
interval?: number;
|
|
15
|
+
/** Time between notes, in seconds. */
|
|
16
|
+
gap?: number;
|
|
17
|
+
/** Mix of noise layered under the tone (0–1). */
|
|
18
|
+
noise?: number;
|
|
19
|
+
volume?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const CHIPS: {
|
|
22
|
+
select: {
|
|
23
|
+
wave: "square";
|
|
24
|
+
from: number;
|
|
25
|
+
to: number;
|
|
26
|
+
duration: number;
|
|
27
|
+
volume: number;
|
|
28
|
+
};
|
|
29
|
+
confirm: {
|
|
30
|
+
wave: "square";
|
|
31
|
+
from: number;
|
|
32
|
+
to: number;
|
|
33
|
+
duration: number;
|
|
34
|
+
notes: number;
|
|
35
|
+
interval: number;
|
|
36
|
+
gap: number;
|
|
37
|
+
volume: number;
|
|
38
|
+
};
|
|
39
|
+
cancel: {
|
|
40
|
+
wave: "square";
|
|
41
|
+
from: number;
|
|
42
|
+
to: number;
|
|
43
|
+
duration: number;
|
|
44
|
+
notes: number;
|
|
45
|
+
interval: number;
|
|
46
|
+
gap: number;
|
|
47
|
+
volume: number;
|
|
48
|
+
};
|
|
49
|
+
blip: {
|
|
50
|
+
wave: "square";
|
|
51
|
+
from: number;
|
|
52
|
+
to: number;
|
|
53
|
+
duration: number;
|
|
54
|
+
volume: number;
|
|
55
|
+
};
|
|
56
|
+
coin: {
|
|
57
|
+
wave: "square";
|
|
58
|
+
from: number;
|
|
59
|
+
to: number;
|
|
60
|
+
duration: number;
|
|
61
|
+
notes: number;
|
|
62
|
+
interval: number;
|
|
63
|
+
gap: number;
|
|
64
|
+
volume: number;
|
|
65
|
+
};
|
|
66
|
+
jump: {
|
|
67
|
+
wave: "square";
|
|
68
|
+
from: number;
|
|
69
|
+
to: number;
|
|
70
|
+
duration: number;
|
|
71
|
+
volume: number;
|
|
72
|
+
};
|
|
73
|
+
powerUp: {
|
|
74
|
+
wave: "square";
|
|
75
|
+
from: number;
|
|
76
|
+
to: number;
|
|
77
|
+
duration: number;
|
|
78
|
+
notes: number;
|
|
79
|
+
interval: number;
|
|
80
|
+
gap: number;
|
|
81
|
+
volume: number;
|
|
82
|
+
};
|
|
83
|
+
levelUp: {
|
|
84
|
+
wave: "triangle";
|
|
85
|
+
from: number;
|
|
86
|
+
to: number;
|
|
87
|
+
duration: number;
|
|
88
|
+
notes: number;
|
|
89
|
+
interval: number;
|
|
90
|
+
gap: number;
|
|
91
|
+
volume: number;
|
|
92
|
+
};
|
|
93
|
+
hit: {
|
|
94
|
+
wave: "sawtooth";
|
|
95
|
+
from: number;
|
|
96
|
+
to: number;
|
|
97
|
+
duration: number;
|
|
98
|
+
noise: number;
|
|
99
|
+
volume: number;
|
|
100
|
+
};
|
|
101
|
+
error: {
|
|
102
|
+
wave: "sawtooth";
|
|
103
|
+
from: number;
|
|
104
|
+
to: number;
|
|
105
|
+
duration: number;
|
|
106
|
+
notes: number;
|
|
107
|
+
interval: number;
|
|
108
|
+
gap: number;
|
|
109
|
+
volume: number;
|
|
110
|
+
};
|
|
111
|
+
explosion: {
|
|
112
|
+
wave: "noise";
|
|
113
|
+
duration: number;
|
|
114
|
+
volume: number;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
export type ChipName = keyof typeof CHIPS;
|
|
118
|
+
export interface PlayOptions {
|
|
119
|
+
/** Multiplier on the recipe's own volume (0–1). */
|
|
120
|
+
volume?: number;
|
|
121
|
+
}
|
|
122
|
+
export declare function playChip(sound: ChipName | ChipRecipe, { volume }?: PlayOptions): void;
|
|
123
|
+
export interface ChiptuneControls {
|
|
124
|
+
muted: boolean;
|
|
125
|
+
volume: number;
|
|
126
|
+
/** Play a preset or custom recipe (silent while muted). */
|
|
127
|
+
play: (sound: ChipName | ChipRecipe, options?: PlayOptions) => void;
|
|
128
|
+
setMuted: (muted: boolean) => void;
|
|
129
|
+
toggleMuted: () => void;
|
|
130
|
+
/** 0–1. */
|
|
131
|
+
setVolume: (volume: number) => void;
|
|
132
|
+
}
|
|
133
|
+
export interface ChiptuneProviderProps {
|
|
134
|
+
children?: ReactNode;
|
|
135
|
+
defaultMuted?: boolean;
|
|
136
|
+
defaultVolume?: number;
|
|
137
|
+
/** Remember mute and volume in localStorage. */
|
|
138
|
+
persist?: boolean;
|
|
139
|
+
}
|
|
140
|
+
export declare function ChiptuneProvider({ children, defaultMuted, defaultVolume, persist }: ChiptuneProviderProps): import("react").JSX.Element;
|
|
141
|
+
export declare function useChiptune(): ChiptuneControls;
|
|
142
|
+
export interface MuteToggleProps {
|
|
143
|
+
/** Icon size, in px. */
|
|
144
|
+
size?: number;
|
|
145
|
+
color?: string;
|
|
146
|
+
background?: string;
|
|
147
|
+
className?: string;
|
|
148
|
+
style?: CSSProperties;
|
|
149
|
+
}
|
|
150
|
+
export declare function MuteToggle({ size, color, background, className, style }: MuteToggleProps): import("react").JSX.Element;
|
|
151
|
+
export interface ChiptuneKitProps {
|
|
152
|
+
/** Which presets to show. */
|
|
153
|
+
sounds?: ChipName[];
|
|
154
|
+
volume?: number;
|
|
155
|
+
columns?: number;
|
|
156
|
+
accentColor?: string;
|
|
157
|
+
}
|
|
158
|
+
export default function ChiptuneKit({ sounds, volume, columns, accentColor }: ChiptuneKitProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
|
4
|
+
const CHIPS = {
|
|
5
|
+
select: { wave: "square", from: 1320, to: 1320, duration: 0.05, volume: 0.25 },
|
|
6
|
+
confirm: { wave: "square", from: 660, to: 660, duration: 0.07, notes: 2, interval: 7, gap: 0.06, volume: 0.25 },
|
|
7
|
+
cancel: { wave: "square", from: 520, to: 520, duration: 0.07, notes: 2, interval: -5, gap: 0.06, volume: 0.25 },
|
|
8
|
+
blip: { wave: "square", from: 880, to: 880, duration: 0.025, volume: 0.12 },
|
|
9
|
+
coin: { wave: "square", from: 988, to: 988, duration: 0.1, notes: 2, interval: 5, gap: 0.07, volume: 0.28 },
|
|
10
|
+
jump: { wave: "square", from: 300, to: 900, duration: 0.16, volume: 0.28 },
|
|
11
|
+
powerUp: { wave: "square", from: 330, to: 660, duration: 0.09, notes: 4, interval: 4, gap: 0.06, volume: 0.22 },
|
|
12
|
+
levelUp: { wave: "triangle", from: 523, to: 523, duration: 0.16, notes: 4, interval: 4, gap: 0.1, volume: 0.45 },
|
|
13
|
+
hit: { wave: "sawtooth", from: 400, to: 120, duration: 0.14, noise: 0.3, volume: 0.35 },
|
|
14
|
+
error: { wave: "sawtooth", from: 220, to: 150, duration: 0.16, notes: 2, interval: -1, gap: 0.12, volume: 0.3 },
|
|
15
|
+
explosion: { wave: "noise", duration: 0.55, volume: 0.7 }
|
|
16
|
+
};
|
|
17
|
+
const STORAGE_KEY = "bl-chiptune";
|
|
18
|
+
let context = null;
|
|
19
|
+
let globalMuted = false;
|
|
20
|
+
let globalVolume = 1;
|
|
21
|
+
function audio() {
|
|
22
|
+
if (typeof window === "undefined") return null;
|
|
23
|
+
if (!context) {
|
|
24
|
+
const Ctx = window.AudioContext || window.webkitAudioContext;
|
|
25
|
+
if (!Ctx) return null;
|
|
26
|
+
context = new Ctx();
|
|
27
|
+
}
|
|
28
|
+
if (context.state === "suspended") context.resume();
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
31
|
+
function noiseBuffer(ctx, seconds) {
|
|
32
|
+
const buffer = ctx.createBuffer(1, Math.ceil(ctx.sampleRate * seconds), ctx.sampleRate);
|
|
33
|
+
const data = buffer.getChannelData(0);
|
|
34
|
+
for (let i = 0; i < data.length; i++) data[i] = Math.random() * 2 - 1;
|
|
35
|
+
return buffer;
|
|
36
|
+
}
|
|
37
|
+
function playChip(sound, { volume = 1 } = {}) {
|
|
38
|
+
if (globalMuted) return;
|
|
39
|
+
const recipe = typeof sound === "string" ? CHIPS[sound] : sound;
|
|
40
|
+
const ctx = recipe && audio();
|
|
41
|
+
if (!ctx) return;
|
|
42
|
+
const master = ctx.createGain();
|
|
43
|
+
master.gain.value = (recipe.volume ?? 0.3) * volume * globalVolume;
|
|
44
|
+
master.connect(ctx.destination);
|
|
45
|
+
const notes = recipe.notes ?? 1;
|
|
46
|
+
for (let i = 0; i < notes; i++) {
|
|
47
|
+
const start = ctx.currentTime + i * (recipe.gap ?? 0.08);
|
|
48
|
+
const end = start + recipe.duration;
|
|
49
|
+
const ratio = 2 ** (i * (recipe.interval ?? 0) / 12);
|
|
50
|
+
const envelope = ctx.createGain();
|
|
51
|
+
envelope.gain.setValueAtTime(1e-4, start);
|
|
52
|
+
envelope.gain.exponentialRampToValueAtTime(1, start + 4e-3);
|
|
53
|
+
envelope.gain.exponentialRampToValueAtTime(1e-4, end);
|
|
54
|
+
envelope.connect(master);
|
|
55
|
+
if (recipe.wave === "noise" || recipe.noise) {
|
|
56
|
+
const source = ctx.createBufferSource();
|
|
57
|
+
source.buffer = noiseBuffer(ctx, recipe.duration + 0.05);
|
|
58
|
+
const level = ctx.createGain();
|
|
59
|
+
level.gain.value = recipe.wave === "noise" ? 1 : recipe.noise;
|
|
60
|
+
source.connect(level).connect(envelope);
|
|
61
|
+
source.start(start);
|
|
62
|
+
source.stop(end + 0.02);
|
|
63
|
+
}
|
|
64
|
+
if (recipe.wave !== "noise") {
|
|
65
|
+
const osc = ctx.createOscillator();
|
|
66
|
+
osc.type = recipe.wave ?? "square";
|
|
67
|
+
osc.frequency.setValueAtTime(recipe.from * ratio, start);
|
|
68
|
+
osc.frequency.exponentialRampToValueAtTime(Math.max(20, (recipe.to ?? recipe.from) * ratio), end);
|
|
69
|
+
osc.connect(envelope);
|
|
70
|
+
osc.start(start);
|
|
71
|
+
osc.stop(end + 0.02);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const ChiptuneContext = createContext(null);
|
|
76
|
+
function readStored() {
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(localStorage.getItem(STORAGE_KEY)) ?? {};
|
|
79
|
+
} catch {
|
|
80
|
+
return {};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function ChiptuneProvider({ children, defaultMuted = false, defaultVolume = 1, persist = true }) {
|
|
84
|
+
const [state, setState] = useState(() => ({ muted: defaultMuted, volume: defaultVolume, ...persist ? readStored() : null }));
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
globalMuted = state.muted;
|
|
87
|
+
globalVolume = state.volume;
|
|
88
|
+
if (!persist) return;
|
|
89
|
+
try {
|
|
90
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
}, [state, persist]);
|
|
94
|
+
const value = useMemo(
|
|
95
|
+
() => ({
|
|
96
|
+
...state,
|
|
97
|
+
play: (sound, options) => playChip(sound, options),
|
|
98
|
+
setMuted: (muted) => setState((prev) => ({ ...prev, muted })),
|
|
99
|
+
toggleMuted: () => setState((prev) => ({ ...prev, muted: !prev.muted })),
|
|
100
|
+
setVolume: (volume) => setState((prev) => ({ ...prev, volume: Math.min(1, Math.max(0, volume)) }))
|
|
101
|
+
}),
|
|
102
|
+
[state]
|
|
103
|
+
);
|
|
104
|
+
return /* @__PURE__ */ jsx(ChiptuneContext.Provider, { value, children });
|
|
105
|
+
}
|
|
106
|
+
function useChiptune() {
|
|
107
|
+
const shared = useContext(ChiptuneContext);
|
|
108
|
+
const [localMuted, setLocalMuted] = useState(false);
|
|
109
|
+
const play = useCallback((sound, options) => !localMuted && playChip(sound, options), [localMuted]);
|
|
110
|
+
return shared ?? {
|
|
111
|
+
muted: localMuted,
|
|
112
|
+
volume: 1,
|
|
113
|
+
play,
|
|
114
|
+
setMuted: setLocalMuted,
|
|
115
|
+
toggleMuted: () => setLocalMuted((muted) => !muted),
|
|
116
|
+
setVolume: () => {
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function MuteToggle({ size = 32, color = "#f4f4f4", background = "#333c57", className = "", style }) {
|
|
121
|
+
const { muted, toggleMuted } = useChiptune();
|
|
122
|
+
const px = size / 16;
|
|
123
|
+
return /* @__PURE__ */ jsx(
|
|
124
|
+
"button",
|
|
125
|
+
{
|
|
126
|
+
type: "button",
|
|
127
|
+
className,
|
|
128
|
+
onClick: toggleMuted,
|
|
129
|
+
"aria-pressed": muted,
|
|
130
|
+
"aria-label": muted ? "Unmute sound effects" : "Mute sound effects",
|
|
131
|
+
title: muted ? "Sound off" : "Sound on",
|
|
132
|
+
style: {
|
|
133
|
+
display: "grid",
|
|
134
|
+
placeItems: "center",
|
|
135
|
+
width: size + 12,
|
|
136
|
+
height: size + 12,
|
|
137
|
+
border: 0,
|
|
138
|
+
background,
|
|
139
|
+
color,
|
|
140
|
+
cursor: "pointer",
|
|
141
|
+
boxShadow: `0 -${px * 2}px 0 0 #0b0b14, 0 ${px * 2}px 0 0 #0b0b14, -${px * 2}px 0 0 0 #0b0b14, ${px * 2}px 0 0 0 #0b0b14`,
|
|
142
|
+
...style
|
|
143
|
+
},
|
|
144
|
+
children: /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 16 16", shapeRendering: "crispEdges", "aria-hidden": "true", fill: "currentColor", children: [
|
|
145
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "6", width: "3", height: "4" }),
|
|
146
|
+
/* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "1", height: "6" }),
|
|
147
|
+
/* @__PURE__ */ jsx("rect", { x: "6", y: "4", width: "1", height: "8" }),
|
|
148
|
+
/* @__PURE__ */ jsx("rect", { x: "7", y: "3", width: "1", height: "10" }),
|
|
149
|
+
muted ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
150
|
+
/* @__PURE__ */ jsx("rect", { x: "10", y: "5", width: "1", height: "1" }),
|
|
151
|
+
/* @__PURE__ */ jsx("rect", { x: "11", y: "6", width: "1", height: "1" }),
|
|
152
|
+
/* @__PURE__ */ jsx("rect", { x: "12", y: "7", width: "1", height: "2" }),
|
|
153
|
+
/* @__PURE__ */ jsx("rect", { x: "11", y: "9", width: "1", height: "1" }),
|
|
154
|
+
/* @__PURE__ */ jsx("rect", { x: "10", y: "10", width: "1", height: "1" }),
|
|
155
|
+
/* @__PURE__ */ jsx("rect", { x: "13", y: "6", width: "1", height: "1" }),
|
|
156
|
+
/* @__PURE__ */ jsx("rect", { x: "14", y: "5", width: "1", height: "1" }),
|
|
157
|
+
/* @__PURE__ */ jsx("rect", { x: "13", y: "9", width: "1", height: "1" }),
|
|
158
|
+
/* @__PURE__ */ jsx("rect", { x: "14", y: "10", width: "1", height: "1" })
|
|
159
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
160
|
+
/* @__PURE__ */ jsx("rect", { x: "10", y: "6", width: "1", height: "4" }),
|
|
161
|
+
/* @__PURE__ */ jsx("rect", { x: "12", y: "4", width: "1", height: "8" }),
|
|
162
|
+
/* @__PURE__ */ jsx("rect", { x: "14", y: "2", width: "1", height: "12" })
|
|
163
|
+
] })
|
|
164
|
+
] })
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
function ChiptuneKit({ sounds = Object.keys(CHIPS), volume = 1, columns = 4, accentColor = "#ffcd75" }) {
|
|
169
|
+
const { play } = useChiptune();
|
|
170
|
+
return /* @__PURE__ */ jsx(
|
|
171
|
+
"div",
|
|
172
|
+
{
|
|
173
|
+
role: "group",
|
|
174
|
+
"aria-label": "Chiptune sounds",
|
|
175
|
+
style: { display: "grid", gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`, gap: 12, width: "min(560px, 100%)" },
|
|
176
|
+
children: sounds.map((name) => /* @__PURE__ */ jsxs(
|
|
177
|
+
"button",
|
|
178
|
+
{
|
|
179
|
+
type: "button",
|
|
180
|
+
onClick: () => play(name, { volume }),
|
|
181
|
+
style: {
|
|
182
|
+
padding: "14px 8px",
|
|
183
|
+
border: "3px solid #0b0b14",
|
|
184
|
+
background: "#1a1c2c",
|
|
185
|
+
color: accentColor,
|
|
186
|
+
font: '9px/1.2 "Press Start 2P", ui-monospace, monospace',
|
|
187
|
+
textTransform: "uppercase",
|
|
188
|
+
cursor: "pointer"
|
|
189
|
+
},
|
|
190
|
+
children: [
|
|
191
|
+
"\u266A ",
|
|
192
|
+
name
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
name
|
|
196
|
+
))
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
export {
|
|
201
|
+
CHIPS,
|
|
202
|
+
ChiptuneProvider,
|
|
203
|
+
MuteToggle,
|
|
204
|
+
ChiptuneKit as default,
|
|
205
|
+
playChip,
|
|
206
|
+
useChiptune
|
|
207
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface CipherCardProps {
|
|
3
|
+
/** Content on the center badge. */
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
charset?: string;
|
|
6
|
+
/** How many characters fill the card. */
|
|
7
|
+
density?: number;
|
|
8
|
+
/** Reveal radius around the pointer, in px. */
|
|
9
|
+
spotlight?: number;
|
|
10
|
+
width?: number;
|
|
11
|
+
aspectRatio?: string;
|
|
12
|
+
radius?: number;
|
|
13
|
+
gradient?: string;
|
|
14
|
+
backgroundColor?: string;
|
|
15
|
+
textColor?: string;
|
|
16
|
+
charColor?: string;
|
|
17
|
+
lineColor?: string;
|
|
18
|
+
fontFamily?: string;
|
|
19
|
+
className?: string;
|
|
20
|
+
style?: CSSProperties;
|
|
21
|
+
}
|
|
22
|
+
export default function CipherCard({ children, charset, density, spotlight, width, aspectRatio, radius, gradient, backgroundColor, textColor, charColor, lineColor, fontFamily, className, style }: CipherCardProps): import("react").JSX.Element;
|