diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 00000000..37719678 --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,52 @@ +# Development + +This repo uses Bun workspaces. The docs site and registry live in `apps/www`. + +## Setup + +```bash +bun install +cd apps/www +bun run dev +``` + +The local site runs on the Next.js dev server. + +## Commands + +Run these from the repo root unless the command says otherwise. + +```bash +bun run lint +bun run format:check +bun run test:local +``` + +Registry and site checks: + +```bash +cd apps/www +bun run registry:build +bun run validate:registries +bun run build +``` + +## Project Layout + +| Path | Purpose | +| --- | --- | +| `apps/www/content/docs` | Fumadocs documentation | +| `apps/www/registry/default/ui` | Copyable UI primitives | +| `apps/www/registry/default/hooks` | Feature hooks and utilities | +| `apps/www/registry/default/blocks` | Complete player blocks | +| `apps/www/registry/collection` | shadcn registry metadata | +| `apps/www/public/r` | Generated registry JSON | +| `prompts` | Project rules for local assistants and review tooling | + +## Contribution Notes + +- Public examples should import from `@/components/limeplay/*` and `@/hooks/limeplay/*`, not `@/registry/*`. +- New or changed components, hooks, and utilities need registry metadata. +- Registry dependencies should be explicit and major versions with breaking changes should be pinned. +- Blocks should keep public loading props consistent: `source`, `loading`, `sourceKey`, `autoLoad`, `initialIndex`, and `mediaProps`. +- Use feature selectors and `useMediaEvents()` instead of deleted convenience hooks or `on*` callback fields. diff --git a/README.md b/README.md index 5335cd93..9b464f57 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,52 @@ -# Limeplay - Modern UI Library for Video Players +# Limeplay -Limeplay Banner +Limeplay preview -🔰 [@shadcn](https://ui.shadcn.com/docs/cli) CLI based UI library for building video player in React. Uses [@shaka-player](https://github.com/shaka-project/shaka-player) as the playback engine. Accessible and customizable components & hooks that you can copy and paste into your apps. +Copy-paste media player components for React. Limeplay follows the shadcn/ui model: install a block, get the files in your app, and customize them like code you wrote yourself. -🏗️ Checkout [Blocks](https://limeplay.winoffrg.dev/blocks) for cooked examples. +Built on Shaka Player for HLS, DASH, adaptive bitrate, and DRM-capable playback. -## Documentation +[Documentation](https://limeplay.winoffrg.dev/docs/quick-start) · [Blocks](https://limeplay.winoffrg.dev/blocks/video-player) · [Discord](https://discord.gg/ZjXFzqmqjn) · [Development](./DEVELOPMENT.md) -Visit https://limeplay.winoffrg.dev/docs/quick-start to view the documentation. +## Install -Discussion and ideas on our [Discord Server](https://discord.gg/ZjXFzqmqjn) +Start with a React project that already has shadcn/ui initialized. + +```bash +npx shadcn@latest init +``` + +Add the video player block: + +```bash +npx shadcn add @limeplay/video-player +``` + +Use it anywhere in your app: + +```tsx +import { VideoPlayer } from "@/components/limeplay/video-player/components/media-player" + +export function Player() { + return ( + + ) +} +``` + +> [!TIP] +> You can find more blocks in [Limeplay Blocks](https://limeplay.winoffrg.dev/blocks/video-player) showcase. + +Player teaser + +## Goals + +1. Ship production-grade media players without rebuilding the hard parts. Limeplay gives you Netflix, YouTube, and Spotify-style interaction patterns with real playback behavior, not static UI. +2. Cover the details users notice: resilient error states, accessible controls, keyboard navigation, responsive layouts, browser support, and modern interaction states. +3. Keep ownership of the UI. Limeplay handles player logic, state, events, playlists, and media controls while the components stay fully editable in your app. +4. Build on a serious playback engine. Shaka Player brings HLS, DASH, live streaming, DRM-capable playback, adaptive bitrate, and more. + +For custom layouts, feature hooks, and API references, start with the [quick start docs](https://limeplay.winoffrg.dev/docs/quick-start). ## License diff --git a/apps/www/components/block-viewer.tsx b/apps/www/components/block-viewer.tsx index 308d2fb2..db84c32d 100644 --- a/apps/www/components/block-viewer.tsx +++ b/apps/www/components/block-viewer.tsx @@ -342,14 +342,16 @@ function BlockViewerToolbar() { gap-1 *:data-[slot=toggle-group-item]:size-6! *:data-[slot=toggle-group-item]:rounded-sm! `} - defaultValue="100" + defaultValue={["100"]} onValueChange={(value) => { + const nextValue = value[0] + if (!nextValue) return + setView("preview") if (resizablePanelRef?.current) { - resizablePanelRef.current.resize(parseInt(value)) + resizablePanelRef.current.resize(parseInt(nextValue)) } }} - type="single" > diff --git a/apps/www/components/blocks/block-toolbar.tsx b/apps/www/components/blocks/block-toolbar.tsx index 916b7299..bf42dec8 100644 --- a/apps/www/components/blocks/block-toolbar.tsx +++ b/apps/www/components/blocks/block-toolbar.tsx @@ -3,6 +3,7 @@ import { CodeXmlIcon, CogIcon, + type LucideIcon, Maximize2Icon, Minimize2Icon, MoonIcon, @@ -228,7 +229,7 @@ export function BlockToolbar({ type ToolbarItem = { active: boolean - icon: React.ElementType + icon: LucideIcon iconStyle?: string id: string label: string diff --git a/apps/www/components/blocks/preview-pane.tsx b/apps/www/components/blocks/preview-pane.tsx index dbf4dd57..df31b818 100644 --- a/apps/www/components/blocks/preview-pane.tsx +++ b/apps/www/components/blocks/preview-pane.tsx @@ -51,8 +51,8 @@ export function BlockPreviewWithToolbar({ ) const handleExpandToggle = useCallback(() => { - setExpanded((currentExpanded) => { - const nextExpanded = !currentExpanded + setExpanded((previousExpanded) => { + const nextExpanded = !previousExpanded updateExpandedQuery(nextExpanded) return nextExpanded }) diff --git a/apps/www/components/mdx-components.tsx b/apps/www/components/mdx-components.tsx index a8a10415..aa400e79 100644 --- a/apps/www/components/mdx-components.tsx +++ b/apps/www/components/mdx-components.tsx @@ -4,22 +4,28 @@ import type { ReactNode } from "react" import { createGenerator } from "fumadocs-typescript" import { AutoTypeTable } from "fumadocs-typescript/ui" import { CodeBlock, Pre } from "fumadocs-ui/components/codeblock" -import * as TabsComponents from "fumadocs-ui/components/tabs" +import { + Tab, + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "fumadocs-ui/components/tabs" import defaultComponents from "fumadocs-ui/mdx" import { ComponentPreview } from "@/components/component-preview" import { Mermaid } from "@/components/mdx/mermaid" const generator = createGenerator() +const fumadocsComponents = defaultComponents as MDXComponents export function getMDXComponents(components?: MDXComponents): MDXComponents { return { - ...defaultComponents, - AutoTypeTable: (props) => ( + ...fumadocsComponents, + Attribution, + AutoTypeTable: (props: React.ComponentProps) => ( ), - ...TabsComponents, - Attribution, ComponentPreview, License, Mermaid, @@ -28,8 +34,13 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents {
{props.children}
), + Tab, + Tabs, + TabsContent, + TabsList, + TabsTrigger, ...components, - } + } as unknown as MDXComponents } function Attribution({ diff --git a/apps/www/components/players/audio-player/hover-player.tsx b/apps/www/components/players/audio-player/hover-player.tsx index d0d0c71d..5751214a 100644 --- a/apps/www/components/players/audio-player/hover-player.tsx +++ b/apps/www/components/players/audio-player/hover-player.tsx @@ -1,9 +1,9 @@ "use client" -import { IconVoiceHigh } from "@central-icons-react/round-filled-radius-0-stroke-1/IconVoiceHigh" import { ListMusicIcon, SquareArrowOutUpRightIcon } from "lucide-react" import { motion } from "motion/react" import Link from "next/link" +import { useCallback, useEffect, useState } from "react" import { StreamPanel, @@ -12,7 +12,9 @@ import { } from "@/components/stream-panel" import { useStreamPanelSync } from "@/components/stream-panel/use-stream-panel-sync" import { Button } from "@/components/ui/button" +import { type AgentState, Orb } from "@/components/ui/orb" import { PopoverTrigger } from "@/components/ui/popover" +import { usePlaybackStore } from "@/registry/default/hooks/use-playback" import { AudioPlayerDemo } from "./demo-player" @@ -24,6 +26,30 @@ export function AudioPlayerHover() { ) } +function AudioHoverOrbSync({ + onAgentStateChange, +}: { + onAgentStateChange: (agentState: AgentState) => void +}) { + const status = usePlaybackStore((state) => state.status) + + useEffect(() => { + if (status === "playing") { + onAgentStateChange("talking") + return + } + + if (status === "buffering" || status === "loading") { + onAgentStateChange("thinking") + return + } + + onAgentStateChange(null) + }, [onAgentStateChange, status]) + + return null +} + function AudioHoverStreamPanel() { const { handleLoadStream, handlePlaylistPresetChange, handlePresetChange } = useStreamPanelSync({ playerType: "audio" }) @@ -56,6 +82,13 @@ function AudioHoverStreamTrigger() { function AudioPlayerHoverContent() { const { open } = useStreamPanel() + const [orbAgentState, setOrbAgentState] = useState(null) + const isAudioActive = + orbAgentState === "talking" || orbAgentState === "thinking" + + const handleAgentStateChange = useCallback((agentState: AgentState) => { + setOrbAgentState(agentState) + }, []) return (
- + Audio @@ -99,6 +142,7 @@ function AudioPlayerHoverContent() {
+ diff --git a/apps/www/components/stream-panel/content-catalog.ts b/apps/www/components/stream-panel/content-catalog.ts index 654a8536..4ebdf97b 100644 --- a/apps/www/components/stream-panel/content-catalog.ts +++ b/apps/www/components/stream-panel/content-catalog.ts @@ -26,6 +26,11 @@ export interface AppleMusicChartAsset extends Asset { url?: string } +export interface AppleMusicChartPage { + assets: AppleMusicChartAsset[] + nextPage?: number +} + export interface BlenderOpenFilmAsset extends Asset { duration?: number images?: BlenderOpenFilmImages @@ -211,6 +216,31 @@ export async function addBlenderCaptions( }) } +export async function fetchAppleMusicChartAssetsPage( + page: number, + signal?: AbortSignal +): Promise { + const { locale, storefront } = getAppleMusicLocaleHeaders() + const combinedSignal = createTimeoutSignal( + signal, + APPLE_MUSIC_CHARTS_TIMEOUT_MS + ) + + const chart = await fetchAppleMusicChartPage({ + locale, + page, + signal: combinedSignal, + storefront, + }) + + return { + assets: chart.items + .filter((item) => Boolean(item.previewUrl)) + .map(toAppleMusicChartAsset), + nextPage: chart.nextPage, + } +} + export async function fetchBlenderStream( assetId: string, signal?: AbortSignal @@ -232,7 +262,7 @@ export async function fetchPlaylistPresetAssets( signal?: AbortSignal ): Promise { if (playlistId === APPLE_MUSIC_CHARTS_PLAYLIST_ID) { - return fetchAppleMusicChartAssets(signal) + return (await fetchAppleMusicChartAssetsPage(1, signal)).assets } if (playlistId !== BLENDER_OPEN_FILMS_PLAYLIST_ID) { @@ -273,40 +303,46 @@ function createTimeoutSignal( return AbortSignal.any([signal, timeoutSignal]) } -async function fetchAppleMusicChartAssets( +async function fetchAppleMusicChartPage({ + locale, + page, + signal, + storefront, +}: { + locale: string + page: number signal?: AbortSignal -): Promise { - const { locale, storefront } = getAppleMusicLocaleHeaders() - const combinedSignal = createTimeoutSignal( - signal, - APPLE_MUSIC_CHARTS_TIMEOUT_MS + storefront: string +}) { + const response = await fetch( + `${APPLE_MUSIC_API_BASE_URL}/charts?page=${page}`, + { + headers: { + "x-locale": locale, + "x-storefront": storefront, + }, + signal, + } ) - const response = await fetch(`${APPLE_MUSIC_API_BASE_URL}/charts?page=1`, { - headers: { - "x-locale": locale, - "x-storefront": storefront, - }, - signal: combinedSignal, - }) - throwIfAborted(combinedSignal) + throwIfAborted(signal) if (!response.ok) { throw new Error( - `Failed to fetch Apple Music charts: ${response.statusText}` + `Failed to fetch Apple Music charts page ${page}: ${response.statusText}` ) } const responseJson: unknown = await response.json() - throwIfAborted(combinedSignal) + throwIfAborted(signal) const chart = AppleMusicChartsResponseSchema.safeParse(responseJson) if (!chart.success) { - throw new Error(`Invalid Apple Music charts response: ${chart.error}`) + throw new Error( + `Invalid Apple Music charts page ${page} response: ${chart.error}` + ) } - return chart.data.items - .filter((item) => Boolean(item.previewUrl)) - .map(toAppleMusicChartAsset) + return chart.data } function getAppleMusicLocaleHeaders(): { locale: string; storefront: string } { diff --git a/apps/www/components/stream-panel/panel-popover.tsx b/apps/www/components/stream-panel/panel-popover.tsx index ecbef359..9113a7eb 100644 --- a/apps/www/components/stream-panel/panel-popover.tsx +++ b/apps/www/components/stream-panel/panel-popover.tsx @@ -412,10 +412,12 @@ function PanelToggleRow({ {children} { - if (v) onValueChange(v === "on") + const nextValue = v[0] + if (!nextValue) return + + onValueChange(nextValue === "on") }} - type="single" - value={value ? "on" : "off"} + value={value ? ["on"] : ["off"]} > Off diff --git a/apps/www/components/stream-panel/use-stream-panel-sync.ts b/apps/www/components/stream-panel/use-stream-panel-sync.ts index d13c4794..eab7e86e 100644 --- a/apps/www/components/stream-panel/use-stream-panel-sync.ts +++ b/apps/www/components/stream-panel/use-stream-panel-sync.ts @@ -13,6 +13,7 @@ import { addBlenderCaptions, APPLE_MUSIC_CHARTS_PLAYLIST_ID, type BlenderStreamResponse, + fetchAppleMusicChartAssetsPage, fetchBlenderStream, fetchPlaylistPresetAssets, isBlenderOpenFilmAsset, @@ -33,6 +34,10 @@ import { import { useMediaStore } from "@/registry/default/hooks/use-media" import { PLAYBACK_FEATURE_KEY } from "@/registry/default/hooks/use-playback" import { usePlayerStore } from "@/registry/default/hooks/use-player" +import { + PLAYLIST_FEATURE_KEY, + type PlaylistStore, +} from "@/registry/default/hooks/use-playlist" import { useVolumeStore } from "@/registry/default/hooks/use-volume" import { useMediaEvents, @@ -40,6 +45,12 @@ import { } from "@/registry/default/ui/media-provider" const DEFAULT_VIDEO_PRESET_ID = "mux-big-buck-bunny" +const APPLE_MUSIC_LOAD_MORE_THRESHOLD = 5 + +interface AppleMusicPaginationState { + loadingPage?: number + nextPage?: number +} export function useStreamPanelSync({ playerType = "video", @@ -47,6 +58,7 @@ export function useStreamPanelSync({ playerType?: StreamPanelPlayerType } = {}) { const playbackApi = useMediaFeatureApi(PLAYBACK_FEATURE_KEY) + const playlistApi = useMediaFeatureApi(PLAYLIST_FEATURE_KEY) const events = useMediaEvents() const mediaElement = useMediaStore((state) => state.mediaElement) const player = usePlayerStore((state) => state.instance) @@ -62,6 +74,7 @@ export function useStreamPanelSync({ const setVolume = useVolumeStore((s) => s.setVolume) const setMuted = useVolumeStore((s) => s.setMuted) + const appleMusicPaginationRef = useRef({}) const playlistAbortRef = useRef(null) const restoredSelectionRef = useRef(false) const blenderStreamCacheRef = useRef() + const appendNextAppleMusicChartPage = useCallback( + (currentIndex: number) => { + const pagination = appleMusicPaginationRef.current + if (!pagination.nextPage || pagination.loadingPage) return + + const playlist = playlistApi.getState().playlist + const remainingItems = playlist.queue.length - currentIndex - 1 + if (remainingItems >= APPLE_MUSIC_LOAD_MORE_THRESHOLD) return + + const page = pagination.nextPage + const abortController = playlistAbortRef.current + appleMusicPaginationRef.current = { + ...pagination, + loadingPage: page, + } + + void fetchAppleMusicChartAssetsPage(page, abortController?.signal) + .then(({ assets, nextPage }) => { + if (abortController?.signal.aborted) return + + const selection = + useStreamPanelStore.getState().contentSelections[playerType] + if ( + !selection || + selection.kind !== "playlist" || + selection.id !== APPLE_MUSIC_CHARTS_PLAYLIST_ID + ) { + return + } + + const existingIds = new Set( + playlistApi.getState().playlist.queue.map((item) => item.id) + ) + const newItems = assets + .filter((asset) => asset.id && !existingIds.has(asset.id)) + .map((asset) => ({ + id: asset.id!, + properties: asset, + })) + + if (newItems.length > 0) { + playlistApi.getState().playlist.append(newItems) + } + + appleMusicPaginationRef.current = { + nextPage, + } + }) + .catch((error: unknown) => { + if (error instanceof DOMException && error.name === "AbortError") + return + + playbackApi.getState().playback.setError(error) + appleMusicPaginationRef.current = { + nextPage: page, + } + }) + }, + [playbackApi, playerType, playlistApi] + ) + useEffect(() => { return events.on("assetchange", (event) => { const selection = useStreamPanelStore.getState().contentSelections[playerType] if (!selection || selection.kind !== "playlist") return - if (selection.index === event.currentIndex) return - setContentSelection(playerType, { - ...selection, - index: event.currentIndex, - }) + if (selection.index !== event.currentIndex) { + setContentSelection(playerType, { + ...selection, + index: event.currentIndex, + }) + } + + if ( + playerType === "audio" && + selection.id === APPLE_MUSIC_CHARTS_PLAYLIST_ID + ) { + appendNextAppleMusicChartPage(event.currentIndex) + } }) - }, [events, playerType, setContentSelection]) + }, [appendNextAppleMusicChartPage, events, playerType, setContentSelection]) useEffect(() => { if (!mediaElement) return @@ -168,6 +250,7 @@ export function useStreamPanelSync({ }, [autoplay, mediaElement]) const abortPlaylistRequest = useCallback(() => { + appleMusicPaginationRef.current = {} playlistAbortRef.current?.abort() }, []) @@ -207,15 +290,36 @@ export function useStreamPanelSync({ const loadPlaylistPreset = useCallback( (playlistId: string, startIndex = 0) => { + appleMusicPaginationRef.current = {} playlistAbortRef.current?.abort() const abortController = new AbortController() playlistAbortRef.current = abortController - void fetchPlaylistPresetAssets(playlistId, abortController.signal) + const playlistRequest = + playlistId === APPLE_MUSIC_CHARTS_PLAYLIST_ID + ? fetchAppleMusicChartAssetsPage(1, abortController.signal).then( + ({ assets, nextPage }) => { + appleMusicPaginationRef.current = { + nextPage, + } + return assets + } + ) + : fetchPlaylistPresetAssets(playlistId, abortController.signal).then( + (assets) => { + appleMusicPaginationRef.current = {} + return assets + } + ) + + void playlistRequest .then((assets) => { if (abortController.signal.aborted) return - const index = normalizePlaylistIndex(startIndex, assets.length) + const index = normalizePlaylistIndex( + playlistId === APPLE_MUSIC_CHARTS_PLAYLIST_ID ? 0 : startIndex, + assets.length + ) setContentSelection(playerType, { id: playlistId, index, @@ -225,6 +329,10 @@ export function useStreamPanelSync({ initialIndex: index, loading: assetOptions, }) + + if (playlistId === APPLE_MUSIC_CHARTS_PLAYLIST_ID) { + appendNextAppleMusicChartPage(index) + } }) .catch((error: unknown) => { if (error instanceof DOMException && error.name === "AbortError") @@ -233,7 +341,14 @@ export function useStreamPanelSync({ playbackApi.getState().playback.setError(error) }) }, - [assetOptions, loadSource, playbackApi, playerType, setContentSelection] + [ + appendNextAppleMusicChartPage, + assetOptions, + loadSource, + playbackApi, + playerType, + setContentSelection, + ] ) const restoreContentSelection = useCallback( diff --git a/apps/www/components/ui/dropdown-menu.tsx b/apps/www/components/ui/dropdown-menu.tsx index beec2665..b2a10b02 100644 --- a/apps/www/components/ui/dropdown-menu.tsx +++ b/apps/www/components/ui/dropdown-menu.tsx @@ -55,13 +55,22 @@ function DropdownMenuContent({ align = "start", alignOffset = 0, className, + collisionAvoidance, + collisionBoundary, + collisionPadding, side = "bottom", sideOffset = 4, ...props }: MenuPrimitive.Popup.Props & Pick< MenuPrimitive.Positioner.Props, - "align" | "alignOffset" | "side" | "sideOffset" + | "align" + | "alignOffset" + | "collisionAvoidance" + | "collisionBoundary" + | "collisionPadding" + | "side" + | "sideOffset" >) { return ( @@ -69,6 +78,9 @@ function DropdownMenuContent({ align={align} alignOffset={alignOffset} className="isolate z-50 outline-none" + collisionAvoidance={collisionAvoidance} + collisionBoundary={collisionBoundary} + collisionPadding={collisionPadding} side={side} sideOffset={sideOffset} > diff --git a/apps/www/components/ui/orb.tsx b/apps/www/components/ui/orb.tsx new file mode 100644 index 00000000..00fc7ac5 --- /dev/null +++ b/apps/www/components/ui/orb.tsx @@ -0,0 +1,507 @@ +"use client" + +import { useTexture } from "@react-three/drei" +import { Canvas, useFrame, useThree } from "@react-three/fiber" +import { useEffect, useMemo, useRef } from "react" +import * as THREE from "three" + +export type AgentState = "listening" | "talking" | "thinking" | null + +type OrbProps = { + agentState?: AgentState + className?: string + colors?: [string, string] + colorsRef?: React.RefObject<[string, string]> + getInputVolume?: () => number + getOutputVolume?: () => number + inputVolumeRef?: React.RefObject + manualInput?: number + manualOutput?: number + outputVolumeRef?: React.RefObject + resizeDebounce?: number + seed?: number + volumeMode?: "auto" | "manual" +} + +export function Orb({ + agentState = null, + className, + colors = ["#CADCFC", "#A0B9D1"], + colorsRef, + getInputVolume, + getOutputVolume, + inputVolumeRef, + manualInput, + manualOutput, + outputVolumeRef, + resizeDebounce = 100, + seed, + volumeMode = "auto", +}: OrbProps) { + return ( +
+ + + +
+ ) +} + +function clamp01(n: number) { + if (!Number.isFinite(n)) return 0 + return Math.min(1, Math.max(0, n)) +} + +function normalizeSmoothingFactor(factor: number, delta: number) { + return 1 - Math.pow(1 - factor, delta * 60) +} + +function Scene({ + agentState, + colors, + colorsRef, + getInputVolume, + getOutputVolume, + inputVolumeRef, + manualInput, + manualOutput, + outputVolumeRef, + seed, + volumeMode, +}: { + agentState: AgentState + colors: [string, string] + colorsRef?: React.RefObject<[string, string]> + getInputVolume?: () => number + getOutputVolume?: () => number + inputVolumeRef?: React.RefObject + manualInput?: number + manualOutput?: number + outputVolumeRef?: React.RefObject + seed?: number + volumeMode: "auto" | "manual" +}) { + const { gl } = useThree() + const circleRef = + useRef>(null) + const initialColorsRef = useRef<[string, string]>(colors) + const targetColor1Ref = useRef(new THREE.Color(colors[0])) + const targetColor2Ref = useRef(new THREE.Color(colors[1])) + const animSpeedRef = useRef(0.1) + const perlinNoiseTexture = useTexture( + "https://storage.googleapis.com/eleven-public-cdn/images/perlin-noise.png" + ) + + const agentRef = useRef(agentState) + const modeRef = useRef<"auto" | "manual">(volumeMode) + const manualInRef = useRef(manualInput ?? 0) + const manualOutRef = useRef(manualOutput ?? 0) + const curInRef = useRef(0) + const curOutRef = useRef(0) + + useEffect(() => { + agentRef.current = agentState + }, [agentState]) + + useEffect(() => { + modeRef.current = volumeMode + }, [volumeMode]) + + useEffect(() => { + manualInRef.current = clamp01( + manualInput ?? inputVolumeRef?.current ?? getInputVolume?.() ?? 0 + ) + }, [manualInput, inputVolumeRef, getInputVolume]) + + useEffect(() => { + manualOutRef.current = clamp01( + manualOutput ?? outputVolumeRef?.current ?? getOutputVolume?.() ?? 0 + ) + }, [manualOutput, outputVolumeRef, getOutputVolume]) + + const random = useMemo( + () => splitmix32(seed ?? Math.floor(Math.random() * 2 ** 32)), + [seed] + ) + const offsets = useMemo( + () => + new Float32Array(Array.from({ length: 7 }, () => random() * Math.PI * 2)), + [random] + ) + + useEffect(() => { + targetColor1Ref.current = new THREE.Color(colors[0]) + targetColor2Ref.current = new THREE.Color(colors[1]) + }, [colors]) + + useEffect(() => { + const apply = () => { + if (!circleRef.current) return + const isDark = document.documentElement.classList.contains("dark") + circleRef.current.material.uniforms.uInverted.value = isDark ? 1 : 0 + } + + apply() + + const observer = new MutationObserver(apply) + observer.observe(document.documentElement, { + attributeFilter: ["class"], + attributes: true, + }) + return () => observer.disconnect() + }, []) + + useFrame((_, delta: number) => { + const mat = circleRef.current?.material + if (!mat) return + const live = colorsRef?.current + if (live) { + if (live[0]) targetColor1Ref.current.set(live[0]) + if (live[1]) targetColor2Ref.current.set(live[1]) + } + const u = mat.uniforms + u.uTime.value += delta * 0.5 + + if (u.uOpacity.value < 1) { + u.uOpacity.value = Math.min(1, u.uOpacity.value + delta * 2) + } + + let targetIn = 0 + let targetOut = 0.3 + if (modeRef.current === "manual") { + targetIn = clamp01( + manualInput ?? inputVolumeRef?.current ?? getInputVolume?.() ?? 0 + ) + targetOut = clamp01( + manualOutput ?? outputVolumeRef?.current ?? getOutputVolume?.() ?? 0 + ) + } else { + const t = u.uTime.value * 2 + if (agentRef.current === null) { + targetIn = 0 + targetOut = 0.3 + } else if (agentRef.current === "listening") { + targetIn = clamp01(0.55 + Math.sin(t * 3.2) * 0.35) + targetOut = 0.45 + } else if (agentRef.current === "talking") { + targetIn = clamp01(0.65 + Math.sin(t * 4.8) * 0.22) + targetOut = clamp01(0.75 + Math.sin(t * 3.6) * 0.22) + } else { + const base = 0.38 + 0.07 * Math.sin(t * 0.7) + const wander = 0.05 * Math.sin(t * 2.1) * Math.sin(t * 0.37 + 1.2) + targetIn = clamp01(base + wander) + targetOut = clamp01(0.48 + 0.12 * Math.sin(t * 1.05 + 0.6)) + } + } + + const volumeSmoothing = normalizeSmoothingFactor(0.2, delta) + const speedSmoothing = normalizeSmoothingFactor(0.12, delta) + const colorSmoothing = normalizeSmoothingFactor(0.08, delta) + + curInRef.current += (targetIn - curInRef.current) * volumeSmoothing + curOutRef.current += (targetOut - curOutRef.current) * volumeSmoothing + + const targetSpeed = 0.1 + (1 - Math.pow(curOutRef.current - 1, 2)) * 0.9 + animSpeedRef.current += + (targetSpeed - animSpeedRef.current) * speedSmoothing + + u.uAnimation.value += delta * animSpeedRef.current + u.uInputVolume.value = curInRef.current + u.uOutputVolume.value = curOutRef.current + u.uColor1.value.lerp(targetColor1Ref.current, colorSmoothing) + u.uColor2.value.lerp(targetColor2Ref.current, colorSmoothing) + }) + + useEffect(() => { + const canvas = gl.domElement + const onContextLost = (event: Event) => { + event.preventDefault() + setTimeout(() => { + gl.forceContextRestore() + }, 1) + } + canvas.addEventListener("webglcontextlost", onContextLost, false) + return () => + canvas.removeEventListener("webglcontextlost", onContextLost, false) + }, [gl]) + + const uniforms = useMemo(() => { + perlinNoiseTexture.wrapS = THREE.RepeatWrapping + perlinNoiseTexture.wrapT = THREE.RepeatWrapping + const isDark = + typeof document !== "undefined" && + document.documentElement.classList.contains("dark") + return { + uAnimation: new THREE.Uniform(0.1), + uColor1: new THREE.Uniform(new THREE.Color(initialColorsRef.current[0])), + uColor2: new THREE.Uniform(new THREE.Color(initialColorsRef.current[1])), + uInputVolume: new THREE.Uniform(0), + uInverted: new THREE.Uniform(isDark ? 1 : 0), + uOffsets: { value: offsets }, + uOpacity: new THREE.Uniform(0), + uOutputVolume: new THREE.Uniform(0), + uPerlinTexture: new THREE.Uniform(perlinNoiseTexture), + uTime: new THREE.Uniform(0), + } + }, [perlinNoiseTexture, offsets]) + + return ( + + + + + ) +} + +function splitmix32(a: number) { + return function () { + a |= 0 + a = (a + 0x9e3779b9) | 0 + let t = a ^ (a >>> 16) + t = Math.imul(t, 0x21f0aaad) + t = t ^ (t >>> 15) + t = Math.imul(t, 0x735a2d97) + return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296 + } +} +const vertexShader = /* glsl */ ` +uniform float uTime; +uniform sampler2D uPerlinTexture; +varying vec2 vUv; + +void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); +} +` + +const fragmentShader = /* glsl */ ` +uniform float uTime; +uniform float uAnimation; +uniform float uInverted; +uniform float uOffsets[7]; +uniform vec3 uColor1; +uniform vec3 uColor2; +uniform float uInputVolume; +uniform float uOutputVolume; +uniform float uOpacity; +uniform sampler2D uPerlinTexture; +varying vec2 vUv; + +const float PI = 3.14159265358979323846; + +// Draw a single oval with soft edges and calculate its gradient color +bool drawOval(vec2 polarUv, vec2 polarCenter, float a, float b, bool reverseGradient, float softness, out vec4 color) { + vec2 p = polarUv - polarCenter; + float oval = (p.x * p.x) / (a * a) + (p.y * p.y) / (b * b); + + float edge = smoothstep(1.0, 1.0 - softness, oval); + + if (edge > 0.0) { + float gradient = reverseGradient ? (1.0 - (p.x / a + 1.0) / 2.0) : ((p.x / a + 1.0) / 2.0); + // Flatten gradient toward middle value for more uniform appearance + gradient = mix(0.5, gradient, 0.1); + color = vec4(vec3(gradient), 0.85 * edge); + return true; + } + return false; +} + +// Map grayscale value to a 4-color ramp (color1, color2, color3, color4) +vec3 colorRamp(float grayscale, vec3 color1, vec3 color2, vec3 color3, vec3 color4) { + if (grayscale < 0.33) { + return mix(color1, color2, grayscale * 3.0); + } else if (grayscale < 0.66) { + return mix(color2, color3, (grayscale - 0.33) * 3.0); + } else { + return mix(color3, color4, (grayscale - 0.66) * 3.0); + } +} + +vec2 hash2(vec2 p) { + return fract(sin(vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)))) * 43758.5453); +} + +// 2D noise for the ring +float noise2D(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + + vec2 u = f * f * (3.0 - 2.0 * f); + float n = mix( + mix(dot(hash2(i + vec2(0.0, 0.0)), f - vec2(0.0, 0.0)), + dot(hash2(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0)), u.x), + mix(dot(hash2(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)), + dot(hash2(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0)), u.x), + u.y + ); + + return 0.5 + 0.5 * n; +} + +float sharpRing(vec3 decomposed, float time) { + float ringStart = 1.0; + float ringWidth = 0.3; + float noiseScale = 5.0; + + float noise = mix( + noise2D(vec2(decomposed.x, time) * noiseScale), + noise2D(vec2(decomposed.y, time) * noiseScale), + decomposed.z + ); + + noise = (noise - 0.5) * 2.5; + + return ringStart + noise * ringWidth * 1.5; +} + +float smoothRing(vec3 decomposed, float time) { + float ringStart = 0.9; + float ringWidth = 0.2; + float noiseScale = 6.0; + + float noise = mix( + noise2D(vec2(decomposed.x, time) * noiseScale), + noise2D(vec2(decomposed.y, time) * noiseScale), + decomposed.z + ); + + noise = (noise - 0.5) * 5.0; + + return ringStart + noise * ringWidth; +} + +float flow(vec3 decomposed, float time) { + return mix( + texture(uPerlinTexture, vec2(time, decomposed.x / 2.0)).r, + texture(uPerlinTexture, vec2(time, decomposed.y / 2.0)).r, + decomposed.z + ); +} + +void main() { + // Normalize vUv to be centered around (0.0, 0.0) + vec2 uv = vUv * 2.0 - 1.0; + + // Convert uv to polar coordinates + float radius = length(uv); + float theta = atan(uv.y, uv.x); + if (theta < 0.0) theta += 2.0 * PI; // Normalize theta to [0, 2*PI] + + // Decomposed angle is used for sampling noise textures without seams: + // float noise = mix(sample(decomposed.x), sample(decomposed.y), decomposed.z); + vec3 decomposed = vec3( + // angle in the range [0, 1] + theta / (2.0 * PI), + // angle offset by 180 degrees in the range [1, 2] + mod(theta / (2.0 * PI) + 0.5, 1.0) + 1.0, + // mixing factor between two noises + abs(theta / PI - 1.0) + ); + + // Add noise to the angle for a flow-like distortion (reduced for flatter look) + float noise = flow(decomposed, radius * 0.03 - uAnimation * 0.2) - 0.5; + theta += noise * mix(0.08, 0.25, uOutputVolume); + + // Initialize the base color to white + vec4 color = vec4(1.0, 1.0, 1.0, 1.0); + + // Original parameters for the ovals in polar coordinates + float originalCenters[7] = float[7](0.0, 0.5 * PI, 1.0 * PI, 1.5 * PI, 2.0 * PI, 2.5 * PI, 3.0 * PI); + + // Parameters for the animated centers in polar coordinates + float centers[7]; + for (int i = 0; i < 7; i++) { + centers[i] = originalCenters[i] + 0.5 * sin(uTime / 20.0 + uOffsets[i]); + } + + float a, b; + vec4 ovalColor; + + // Check if the pixel is inside any of the ovals + for (int i = 0; i < 7; i++) { + float noise = texture(uPerlinTexture, vec2(mod(centers[i] + uTime * 0.05, 1.0), 0.5)).r; + a = 0.5 + noise * 0.3; // Increased for more coverage + b = noise * mix(3.5, 2.5, uInputVolume); // Increased height for fuller appearance + bool reverseGradient = (i % 2 == 1); // Reverse gradient for every second oval + + // Calculate the distance in polar coordinates + float distTheta = min( + abs(theta - centers[i]), + min( + abs(theta + 2.0 * PI - centers[i]), + abs(theta - 2.0 * PI - centers[i]) + ) + ); + float distRadius = radius; + + float softness = 0.6; // Increased softness for flatter, less pronounced edges + + // Check if the pixel is inside the oval in polar coordinates + if (drawOval(vec2(distTheta, distRadius), vec2(0.0, 0.0), a, b, reverseGradient, softness, ovalColor)) { + // Blend the oval color with the existing color + color.rgb = mix(color.rgb, ovalColor.rgb, ovalColor.a); + color.a = max(color.a, ovalColor.a); // Max alpha + } + } + + // Calculate both noisy rings + float ringRadius1 = sharpRing(decomposed, uTime * 0.1); + float ringRadius2 = smoothRing(decomposed, uTime * 0.1); + + // Adjust rings based on input volume (reduced for flatter appearance) + float inputRadius1 = radius + uInputVolume * 0.2; + float inputRadius2 = radius + uInputVolume * 0.15; + float opacity1 = mix(0.2, 0.6, uInputVolume); + float opacity2 = mix(0.15, 0.45, uInputVolume); + + // Blend both rings + float ringAlpha1 = (inputRadius2 >= ringRadius1) ? opacity1 : 0.0; + float ringAlpha2 = smoothstep(ringRadius2 - 0.05, ringRadius2 + 0.05, inputRadius1) * opacity2; + + float totalRingAlpha = max(ringAlpha1, ringAlpha2); + + // Apply screen blend mode for combined rings + vec3 ringColor = vec3(1.0); // White ring color + color.rgb = 1.0 - (1.0 - color.rgb) * (1.0 - ringColor * totalRingAlpha); + + // Define colours to ramp against greyscale (could increase the amount of colours in the ramp) + vec3 color1 = vec3(0.0, 0.0, 0.0); // Black + vec3 color2 = uColor1; // Darker Color + vec3 color3 = uColor2; // Lighter Color + vec3 color4 = vec3(1.0, 1.0, 1.0); // White + + // Convert grayscale color to the color ramp + float luminance = mix(color.r, 1.0 - color.r, uInverted); + color.rgb = colorRamp(luminance, color1, color2, color3, color4); // Apply the color ramp + + // Apply fade-in opacity + color.a *= uOpacity; + + gl_FragColor = color; +} +` diff --git a/apps/www/components/ui/toggle-group.tsx b/apps/www/components/ui/toggle-group.tsx index ca645bbb..9e076afd 100644 --- a/apps/www/components/ui/toggle-group.tsx +++ b/apps/www/components/ui/toggle-group.tsx @@ -1,7 +1,8 @@ "use client" +import { Toggle as TogglePrimitive } from "@base-ui/react/toggle" +import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group" import { type VariantProps } from "class-variance-authority" -import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui" import * as React from "react" import { toggleVariants } from "@/components/ui/toggle" @@ -9,34 +10,40 @@ import { cn } from "@/lib/utils" const ToggleGroupContext = React.createContext< VariantProps & { + orientation?: "horizontal" | "vertical" spacing?: number } >({ + orientation: "horizontal", size: "default", - spacing: 0, + spacing: 2, variant: "default", }) function ToggleGroup({ children, className, + orientation = "horizontal", size, - spacing = 0, + spacing = 2, variant, ...props -}: React.ComponentProps & +}: ToggleGroupPrimitive.Props & VariantProps & { + orientation?: "horizontal" | "vertical" spacing?: number }) { return ( - - + {children} - + ) } function ToggleGroupItem({ children, className, - size, - variant, + size = "default", + variant = "default", ...props -}: React.ComponentProps & - VariantProps) { +}: TogglePrimitive.Props & VariantProps) { const context = React.useContext(ToggleGroupContext) return ( - {children} - + ) } diff --git a/apps/www/components/ui/toggle.tsx b/apps/www/components/ui/toggle.tsx index a8944aa0..f2bd64ba 100644 --- a/apps/www/components/ui/toggle.tsx +++ b/apps/www/components/ui/toggle.tsx @@ -1,19 +1,19 @@ "use client" -import * as TogglePrimitive from "@radix-ui/react-toggle" +import { Toggle as TogglePrimitive } from "@base-ui/react/toggle" import { cva, type VariantProps } from "class-variance-authority" -import * as React from "react" import { cn } from "@/lib/utils" const toggleVariants = cva( ` - inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none - hover:bg-muted hover:text-muted-foreground + group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none + hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 - data-[state=on]:bg-accent data-[state=on]:text-accent-foreground + aria-pressed:bg-muted + data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 @@ -25,15 +25,28 @@ const toggleVariants = cva( }, variants: { size: { - default: "h-9 min-w-9 px-2", - lg: "h-10 min-w-10 px-2.5", - sm: "h-8 min-w-8 px-1.5", + default: ` + h-8 min-w-8 px-2.5 + has-data-[icon=inline-end]:pr-2 + has-data-[icon=inline-start]:pl-2 + `, + lg: ` + h-9 min-w-9 px-2.5 + has-data-[icon=inline-end]:pr-2 + has-data-[icon=inline-start]:pl-2 + `, + sm: ` + h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] + has-data-[icon=inline-end]:pr-1.5 + has-data-[icon=inline-start]:pl-1.5 + [&_svg:not([class*='size-'])]:size-3.5 + `, }, variant: { default: "bg-transparent", outline: ` - border border-input bg-transparent shadow-xs - hover:bg-accent hover:text-accent-foreground + border border-input bg-transparent + hover:bg-muted `, }, }, @@ -42,13 +55,12 @@ const toggleVariants = cva( function Toggle({ className, - size, - variant, + size = "default", + variant = "default", ...props -}: React.ComponentProps & - VariantProps) { +}: TogglePrimitive.Props & VariantProps) { return ( - /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/www/package.json b/apps/www/package.json index 6f40d822..3a878fae 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -34,6 +34,9 @@ "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.6.1", + "@types/three": "^0.184.1", "@userjot/next": "^1.0.0-beta.1", "@vercel/analytics": "^1.6.1", "@vercel/speed-insights": "^1.3.1", @@ -69,6 +72,7 @@ "remark-rehype": "^11.1.2", "shaka-player": "^4.16.34", "tailwind-merge": "^3.6.0", + "three": "^0.184.0", "ts-morph": "27.0.2", "tw-animate-css": "^1.4.0", "zod": "^4.4.3", diff --git a/apps/www/public/github_preview.png b/apps/www/public/github_preview.png new file mode 100644 index 00000000..9023e9b7 Binary files /dev/null and b/apps/www/public/github_preview.png differ diff --git a/apps/www/registry/collection/registry-blocks.ts b/apps/www/registry/collection/registry-blocks.ts index eb8076f0..c2d9650c 100644 --- a/apps/www/registry/collection/registry-blocks.ts +++ b/apps/www/registry/collection/registry-blocks.ts @@ -202,6 +202,7 @@ export const blocks: Registry["items"] = [ registryDependencies: [ "media-provider", "media", + "root-container", "timeline-control", "timeline-labels", "mute-control", diff --git a/apps/www/registry/collection/registry-ui.ts b/apps/www/registry/collection/registry-ui.ts index ca9aef92..ba69b5ed 100644 --- a/apps/www/registry/collection/registry-ui.ts +++ b/apps/www/registry/collection/registry-ui.ts @@ -276,6 +276,7 @@ export const ui: Registry["items"] = [ type: "registry:ui", }, { + dependencies: ["@radix-ui/react-compose-refs", "@radix-ui/react-slot"], files: [ { path: "ui/root-container.tsx", @@ -284,7 +285,12 @@ export const ui: Registry["items"] = [ }, ], name: "root-container", - registryDependencies: ["media-provider", "use-media", "use-playback"], + registryDependencies: [ + "media-provider", + "use-media", + "use-playback", + "use-player", + ], type: "registry:ui", }, { diff --git a/apps/www/registry/default/blocks/audio-player/components/action-controls.tsx b/apps/www/registry/default/blocks/audio-player/components/action-controls.tsx index beb94274..e3319097 100644 --- a/apps/www/registry/default/blocks/audio-player/components/action-controls.tsx +++ b/apps/www/registry/default/blocks/audio-player/components/action-controls.tsx @@ -15,7 +15,6 @@ import { export function ActionControls() { const [value, setValue] = React.useState("") - const toggleGroupProps = { onValueChange: (next: string | string[]) => { setValue(Array.isArray(next) ? (next[0] ?? "") : next) diff --git a/apps/www/registry/default/blocks/audio-player/components/media-player.tsx b/apps/www/registry/default/blocks/audio-player/components/media-player.tsx index 90a493e0..c176155f 100644 --- a/apps/www/registry/default/blocks/audio-player/components/media-player.tsx +++ b/apps/www/registry/default/blocks/audio-player/components/media-player.tsx @@ -6,6 +6,8 @@ import type { ReactNode, } from "react" +import React from "react" + import type { AudioPlayerAsset, AudioSourceProviderProps, @@ -18,6 +20,7 @@ import { PlayerControls } from "@/registry/default/blocks/audio-player/component import { TimelineControl } from "@/registry/default/blocks/audio-player/components/fixed-timeline-control" import { MediaProvider } from "@/registry/default/blocks/audio-player/lib/media-kit" import { Media } from "@/registry/default/ui/media" +import { RootContainer } from "@/registry/default/ui/root-container" import styles from "../audio-player.module.css" @@ -38,45 +41,55 @@ export interface AudioPlayerProps { sourceKey?: string } -export function AudioPlayer({ - autoLoad, - children, - className, - debug, - initialIndex, - loading, - mediaProps, - source, - sourceKey, -}: AudioPlayerProps = {}) { - const { className: mediaClassName, ...safeMediaProps } = mediaProps ?? {} +export const AudioPlayer = React.forwardRef( + ( + { + autoLoad, + children, + className, + debug, + initialIndex, + loading, + mediaProps, + source, + sourceKey, + }, + ref + ) => { + const { className: mediaClassName, ...safeMediaProps } = mediaProps ?? {} - return ( - - -
+ - )} - as="audio" - className={mediaClassName} - /> - - -
- {children} -
-
- ) -} + + )} + as="audio" + className={mediaClassName} + /> + + + + {children} + + + ) + } +) + +AudioPlayer.displayName = "AudioPlayer" diff --git a/apps/www/registry/default/blocks/audio-player/components/playback-controls.tsx b/apps/www/registry/default/blocks/audio-player/components/playback-controls.tsx index 52846041..ebbe6f01 100644 --- a/apps/www/registry/default/blocks/audio-player/components/playback-controls.tsx +++ b/apps/www/registry/default/blocks/audio-player/components/playback-controls.tsx @@ -82,12 +82,10 @@ function AnimatedIcon({ return ( {children} diff --git a/apps/www/registry/default/blocks/audio-player/components/playlist.tsx b/apps/www/registry/default/blocks/audio-player/components/playlist.tsx index af6f8ce1..4780584a 100644 --- a/apps/www/registry/default/blocks/audio-player/components/playlist.tsx +++ b/apps/www/registry/default/blocks/audio-player/components/playlist.tsx @@ -1,8 +1,8 @@ "use client" import { CardsThreeIcon, PlayIcon } from "@phosphor-icons/react" -import { Volume2Icon } from "lucide-react" -import { useCallback, useMemo, useRef } from "react" +import { XIcon } from "lucide-react" +import { type ReactNode, useCallback, useMemo, useState } from "react" import { DropdownMenu, @@ -19,7 +19,10 @@ import { usePlayerStore } from "@/registry/default/hooks/use-player" import { usePlaylistStore } from "@/registry/default/hooks/use-playlist" import { LimeplayLogo } from "@/registry/default/ui/limeplay-logo" +const PLAYLIST_SIDE_OFFSET = 24 + export function Playlist() { + const [open, setOpen] = useState(false) const currentItem = usePlaylistStore( (state) => state.currentItem as null | { id: string; properties: AudioPlayerAsset } @@ -28,30 +31,77 @@ export function Playlist() { const queue = usePlaylistStore( (state) => state.queue as { id: string; properties: AudioPlayerAsset }[] ) + const repeatMode = usePlaylistStore((state) => state.repeatMode) const shuffle = usePlaylistStore((state) => state.shuffle) const shuffleOrder = usePlaylistStore((state) => state.shuffleOrder) const skipToId = usePlaylistStore((state) => state.skipToId) - const scrollRef = useRef(null) const orderedItems = useMemo(() => { - if (!shuffle || shuffleOrder.length === 0) return queue + if (!shuffle || shuffleOrder.length === 0) { + return queue.map((item, queueIndex) => ({ item, queueIndex })) + } return shuffleOrder - .map((index) => queue[index]) - .filter((item): item is { id: string; properties: AudioPlayerAsset } => - Boolean(item) + .map((queueIndex) => ({ + item: queue[queueIndex], + queueIndex, + })) + .filter( + ( + entry + ): entry is { + item: { id: string; properties: AudioPlayerAsset } + queueIndex: number + } => Boolean(entry.item) ) }, [queue, shuffle, shuffleOrder]) const displayAssets = useMemo( () => - orderedItems.map((item) => ({ + orderedItems.map(({ item, queueIndex }) => ({ asset: item.properties, id: item.id, + queueIndex, })), [orderedItems] ) + const currentDisplayAsset = useMemo(() => { + if (!currentItem) return null + + return ( + displayAssets.find(({ id }) => id === currentItem.id) ?? { + asset: currentItem.properties, + id: currentItem.id, + queueIndex: queue.findIndex((item) => item.id === currentItem.id), + } + ) + }, [currentItem, displayAssets, queue]) + + const currentDisplayIndex = useMemo(() => { + if (!currentItem) return -1 + return displayAssets.findIndex(({ id }) => id === currentItem.id) + }, [currentItem, displayAssets]) + + const nextDisplayAssets = useMemo(() => { + if (!currentItem) return displayAssets + if (currentDisplayIndex === -1) return displayAssets + + if (repeatMode === "all" && displayAssets.length > 1) { + return [ + ...displayAssets.slice(currentDisplayIndex + 1), + ...displayAssets.slice(0, currentDisplayIndex), + ] + } + + return displayAssets.slice(currentDisplayIndex + 1) + }, [currentDisplayIndex, currentItem, displayAssets, repeatMode]) + + const playlistName = useMemo( + () => getPlaylistName(currentItem?.properties, queue), + [currentItem, queue] + ) + const handleAssetSelect = useCallback( (assetId: string) => { skipToId(assetId) @@ -59,17 +109,8 @@ export function Playlist() { [skipToId] ) - const scrollToActive = useCallback(() => { - const container = scrollRef.current - if (!container || !currentItem) return - const activeEl = container.querySelector("[data-active='true']") - if (activeEl) { - activeEl.scrollIntoView({ behavior: "instant", block: "center" }) - } - }, [currentItem]) - return ( - +
{displayAssets.length === 0 && ( -
+
Queue is empty
)} - {displayAssets.map(({ asset, id }, index) => ( - handleAssetSelect(id)} - preloaded={preloadManagers.has(id)} - setSize={displayAssets.length} - /> - ))} + {displayAssets.length > 0 && ( +
+ {currentDisplayAsset && ( +
+ + Now playing + + handleAssetSelect(currentDisplayAsset.id)} + preloaded={preloadManagers.has(currentDisplayAsset.id)} + setSize={1} + showArtworkOverlay={false} + /> +
+ )} + +
+ + Next From: {playlistName} + + {nextDisplayAssets.length === 0 ? ( +
+ Nothing queued next +
+ ) : ( +
+ {nextDisplayAssets.map(({ asset, id }, index) => ( + handleAssetSelect(id)} + preloaded={preloadManagers.has(id)} + setSize={nextDisplayAssets.length} + /> + ))} +
+ )} +
+
+ )}
) } +function firstNonEmpty( + ...values: (null | number | string | undefined)[] +): string | undefined { + for (const value of values) { + if (typeof value === "number" && Number.isFinite(value)) { + return String(value) + } + + if (typeof value !== "string") continue + + const trimmed = value.trim() + if (trimmed) return trimmed + } + + return undefined +} + function formatDuration(ms?: number) { if (typeof ms !== "number" || !Number.isFinite(ms)) return "--:--" @@ -140,6 +229,49 @@ function formatDuration(ms?: number) { return `${m}:${String(s).padStart(2, "0")}` } +function getAssetPlaylistName( + asset: AudioPlayerAsset | null | undefined +): string | undefined { + if (!asset) return undefined + + const assetWithPlaylist = asset as AudioPlayerAsset & { + collectionName?: string + playlistName?: string + playlistTitle?: string + sourceName?: string + } + + return firstNonEmpty( + assetWithPlaylist.playlistName, + assetWithPlaylist.playlistTitle, + assetWithPlaylist.collectionName, + assetWithPlaylist.sourceName, + asset.group + ) +} + +function getPlaylistName( + currentAsset: AudioPlayerAsset | null | undefined, + queue: { id: string; properties: AudioPlayerAsset }[] +) { + return ( + getAssetPlaylistName(currentAsset) ?? + queue.map((item) => getAssetPlaylistName(item.properties)).find(Boolean) ?? + "Queue" + ) +} + +function SectionHeading({ children, id }: { children: ReactNode; id: string }) { + return ( +

+ {children} +

+ ) +} + function TrackRow({ asset, index, @@ -147,6 +279,7 @@ function TrackRow({ onSelect, preloaded, setSize, + showArtworkOverlay = true, }: { asset: AudioPlayerAsset index: number @@ -154,6 +287,7 @@ function TrackRow({ onSelect: () => void preloaded: boolean setSize: number + showArtworkOverlay?: boolean }) { const metadata = getAudioAssetMetadata(asset, "Untitled track") @@ -163,15 +297,22 @@ function TrackRow({ aria-setsize={setSize} className={cn( ` - group relative flex h-auto w-full cursor-pointer items-center gap-4 rounded-lg px-3 py-2.5 text-left transition-[color,background-color] - duration-150 ease-out outline-none + group/track h-auto w-full cursor-pointer justify-start gap-3 rounded-lg bg-transparent px-3 py-2.5 text-left transition-[background-color] + duration-150 ease-out + hover:bg-muted + focus-visible:outline-offset-0 `, - isActive ? "bg-white/[0.07]" : "hover:bg-white/4" + isActive + ? ` + bg-accent/70 + hover:bg-accent/70 + ` + : "" )} - data-active={isActive} + data-active={isActive ? "true" : undefined} onClick={onSelect} > -
+
{metadata.poster && (
)} -
- {isActive ? ( - - ) : ( + {showArtworkOverlay && ( +
- )} -
+
+ )}
- - {index + 1} -

{metadata.title}

{preloaded && !isActive && ( - + )}
{metadata.subtitle && ( -

+

{metadata.subtitle}

)}
- + {formatDuration(asset.duration)} diff --git a/apps/www/registry/default/ui/root-container.tsx b/apps/www/registry/default/ui/root-container.tsx index 6a6f7ad6..b77b4648 100644 --- a/apps/www/registry/default/ui/root-container.tsx +++ b/apps/www/registry/default/ui/root-container.tsx @@ -1,6 +1,7 @@ "use client" import { composeRefs } from "@radix-ui/react-compose-refs" +import { Slot } from "@radix-ui/react-slot" import React from "react" import { cn } from "@/lib/utils" @@ -9,6 +10,12 @@ import { usePlaybackStore } from "@/registry/default/hooks/use-playback" import { usePlayerStore } from "@/registry/default/hooks/use-player" export interface RootContainerProps extends React.ComponentPropsWithoutRef<"div"> { + asChild?: boolean + /** + * Aspect ratio for the player root. Pass false for players that should size + * from their content, such as compact audio controls. + */ + aspectRatio?: false | number | string /** * Height in pixels for aspect ratio calculation. * Used only if aspectRatio prop is not provided. @@ -23,14 +30,29 @@ export interface RootContainerProps extends React.ComponentPropsWithoutRef<"div" export type RootContainerPropsDocs = Pick< RootContainerProps, - "height" | "width" + "asChild" | "aspectRatio" | "height" | "width" > export const RootContainer = React.forwardRef< HTMLDivElement, RootContainerProps >((props, forwardedRef) => { - const { children, className, height = 1080, width = 1920, ...etc } = props + const { + asChild = false, + aspectRatio: aspectRatioProp, + children, + className, + height = 1080, + onBlur, + onFocus, + onPointerEnter, + onPointerLeave, + onPointerMove, + onPointerUp, + style, + width = 1920, + ...etc + } = props const idle = useMediaStore((state) => state.idle) const forceIdle = useMediaStore((state) => state.forceIdle) const setIdle = useMediaStore((state) => state.setIdle) @@ -39,59 +61,59 @@ export const RootContainer = React.forwardRef< const setPlayerContainerRef = usePlayerStore((state) => state.setContainerRef) const aspectRatio = React.useMemo( - () => calculateAspectRatio(width, height), - [height, width] + () => resolveAspectRatio(aspectRatioProp, width, height), + [aspectRatioProp, height, width] ) - ?.split(":") - .join("/") + const Component = asChild ? Slot : "div" return ( -
{ + onBlur={composeEventHandlers(onBlur, () => { if (!forceIdle) { setIdle(true) } - }} - onFocus={() => { + })} + onFocus={composeEventHandlers(onFocus, () => { if (!forceIdle) { setIdle(false) } - }} - onPointerEnter={() => { + })} + onPointerEnter={composeEventHandlers(onPointerEnter, () => { if (!forceIdle) { setIdle(false) } - }} - onPointerLeave={() => { + })} + onPointerLeave={composeEventHandlers(onPointerLeave, () => { if (!forceIdle) { setIdle(true) } - }} - onPointerMove={() => { + })} + onPointerMove={composeEventHandlers(onPointerMove, () => { if (!forceIdle) { setIdle(false) } - }} - onPointerUp={() => { + })} + onPointerUp={composeEventHandlers(onPointerUp, () => { if (!forceIdle) { setIdle(false) } - }} + })} ref={composeRefs(forwardedRef, setPlayerContainerRef)} role="region" style={{ + ...style, ["--aspect-ratio" as string]: aspectRatio, ["--height" as string]: height, ["--width" as string]: width, @@ -99,7 +121,7 @@ export const RootContainer = React.forwardRef< {...etc} > {children} -
+ ) }) @@ -116,3 +138,27 @@ function calculateAspectRatio(width?: number, height?: number) { return `${aspectWidth}:${aspectHeight}` } } + +function composeEventHandlers( + consumerHandler: ((event: E) => void) | undefined, + internalHandler: (event: E) => void +) { + return (event: E) => { + internalHandler(event) + consumerHandler?.(event) + } +} + +function resolveAspectRatio( + aspectRatio: false | number | string | undefined, + width?: number, + height?: number +) { + if (aspectRatio === false) return undefined + if (typeof aspectRatio === "number") return String(aspectRatio) + if (typeof aspectRatio === "string") { + return aspectRatio.split(":").join("/") + } + + return calculateAspectRatio(width, height)?.split(":").join("/") +} diff --git a/apps/www/vercel.json b/apps/www/vercel.json index cd20dcdb..fbdeed1f 100644 --- a/apps/www/vercel.json +++ b/apps/www/vercel.json @@ -6,6 +6,11 @@ "destination": "/docs/quick-start", "permanent": true }, + { + "source": "/blocks", + "destination": "/blocks/video-player", + "permanent": false + }, { "source": "/blocks/linear-player", "destination": "/blocks/video-player", diff --git a/bun.lock b/bun.lock index 54dabb63..c8049217 100644 --- a/bun.lock +++ b/bun.lock @@ -33,6 +33,9 @@ "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.6.1", + "@types/three": "^0.184.1", "@userjot/next": "^1.0.0-beta.1", "@vercel/analytics": "^1.6.1", "@vercel/speed-insights": "^1.3.1", @@ -68,6 +71,7 @@ "remark-rehype": "^11.1.2", "shaka-player": "^4.16.34", "tailwind-merge": "^3.6.0", + "three": "^0.184.0", "ts-morph": "27.0.2", "tw-animate-css": "^1.4.0", "zod": "^4.4.3", @@ -179,6 +183,8 @@ "@central-icons-react/round-filled-radius-0-stroke-1": ["@central-icons-react/round-filled-radius-0-stroke-1@1.1.257", "", { "peerDependencies": { "react": ">=14.0.0 <= 19" } }, "sha512-2ardg45gigXlHiMfWg258Q1xYqRy0AoAqf8BRD1r7+JLGIhHy+UrL7kmWp8Ds2azeCCrDxX+CFDhMEgkCZ0kcA=="], + "@dimforge/rapier3d-compat": ["@dimforge/rapier3d-compat@0.12.0", "", {}, "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow=="], + "@dotenvx/dotenvx": ["@dotenvx/dotenvx@1.51.1", "", { "dependencies": { "commander": "^11.1.0", "dotenv": "^17.2.1", "eciesjs": "^0.4.10", "execa": "^5.1.1", "fdir": "^6.2.0", "ignore": "^5.3.0", "object-treeify": "1.1.33", "picomatch": "^4.0.2", "which": "^4.0.0" }, "bin": { "dotenvx": "src/cli/dotenvx.js" } }, "sha512-fqcQxcxC4LOaUlW8IkyWw8x0yirlLUkbxohz9OnWvVWjf73J5yyw7jxWnkOJaUKXZotcGEScDox9MU6rSkcDgg=="], "@ecies/ciphers": ["@ecies/ciphers@0.2.5", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-GalEZH4JgOMHYYcYmVqnFirFsjZHeoGMDt9IxEnM9F7GRUUyUksJ7Ou53L83WHJq3RWKD3AcBpo0iQh0oMpf8A=="], @@ -361,8 +367,12 @@ "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], + "@mediapipe/tasks-vision": ["@mediapipe/tasks-vision@0.10.17", "", {}, "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.27.1", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA=="], + "@monogrid/gainmap-js": ["@monogrid/gainmap-js@3.4.0", "", { "dependencies": { "promise-worker-transferable": "^1.0.4" }, "peerDependencies": { "three": ">= 0.159.0" } }, "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg=="], + "@mswjs/interceptors": ["@mswjs/interceptors@0.40.0", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-EFd6cVbHsgLa6wa4RljGj6Wk75qoHxUSyc5asLyyPSyuhIcdS2Q3Phw6ImS1q+CkALthJRShiYfKANcQMuMqsQ=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], @@ -535,6 +545,10 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + "@react-three/drei": ["@react-three/drei@10.7.7", "", { "dependencies": { "@babel/runtime": "^7.26.0", "@mediapipe/tasks-vision": "0.10.17", "@monogrid/gainmap-js": "^3.0.6", "@use-gesture/react": "^10.3.1", "camera-controls": "^3.1.0", "cross-env": "^7.0.3", "detect-gpu": "^5.0.56", "glsl-noise": "^0.0.0", "hls.js": "^1.5.17", "maath": "^0.10.8", "meshline": "^3.3.1", "stats-gl": "^2.2.8", "stats.js": "^0.17.0", "suspend-react": "^0.1.3", "three-mesh-bvh": "^0.8.3", "three-stdlib": "^2.35.6", "troika-three-text": "^0.52.4", "tunnel-rat": "^0.1.2", "use-sync-external-store": "^1.4.0", "utility-types": "^3.11.0", "zustand": "^5.0.1" }, "peerDependencies": { "@react-three/fiber": "^9.0.0", "react": "^19", "react-dom": "^19", "three": ">=0.159" }, "optionalPeers": ["react-dom"] }, "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ=="], + + "@react-three/fiber": ["@react-three/fiber@9.6.1", "", { "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", "base64-js": "^1.5.1", "buffer": "^6.0.3", "its-fine": "^2.0.0", "react-use-measure": "^2.1.7", "scheduler": "^0.27.0", "suspend-react": "^0.1.3", "use-sync-external-store": "^1.4.0", "zustand": "^5.0.3" }, "peerDependencies": { "expo": ">=43.0", "expo-asset": ">=8.4", "expo-file-system": ">=11.0", "expo-gl": ">=11.0", "react": ">=19 <19.3", "react-dom": ">=19 <19.3", "react-native": ">=0.78", "three": ">=0.156" }, "optionalPeers": ["expo", "expo-asset", "expo-file-system", "expo-gl", "react-dom", "react-native"] }, "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg=="], + "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], @@ -605,12 +619,16 @@ "@turbo/windows-arm64": ["@turbo/windows-arm64@2.9.14", "", { "os": "win32", "cpu": "arm64" }, "sha512-fVdCsnmYoKICsycbWuuGp6Jvi51/3G/UluFWuAUCvR8PIW5IJkAk5BM9UF8PSm0Q2IphWHFZjYEgjHsh3B9y/g=="], + "@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@types/async-retry": ["@types/async-retry@1.4.9", "", { "dependencies": { "@types/retry": "*" } }, "sha512-s1ciZQJzRh3708X/m3vPExr5KJlzlZJvXsKpbtE2luqNcbROr64qU+3KpJsYHqWMeaxI839OvXf9PrUSw1Xtyg=="], "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/draco3d": ["@types/draco3d@1.4.10", "", {}, "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -639,18 +657,28 @@ "@types/node": ["@types/node@24.1.0", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w=="], + "@types/offscreencanvas": ["@types/offscreencanvas@2019.7.3", "", {}, "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A=="], + "@types/react": ["@types/react@19.2.15", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q=="], "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="], + "@types/retry": ["@types/retry@0.12.5", "", {}, "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw=="], + "@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="], + "@types/statuses": ["@types/statuses@2.0.6", "", {}, "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA=="], + "@types/three": ["@types/three@0.184.1", "", { "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": ">=0.5.17", "fflate": "~0.8.2", "meshoptimizer": "~1.1.1" } }, "sha512-6q4VdiqVsrTRqmk62/BnlcAvIrnDM0zf2ZDVKI5kZiniWrSaOHaQzmbp+BNzoggc/8tgW412pL//wZIxu2PPTA=="], + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@types/validate-npm-package-name": ["@types/validate-npm-package-name@4.0.2", "", {}, "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw=="], + "@types/webxr": ["@types/webxr@0.5.24", "", {}, "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.4", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.4", "@typescript-eslint/type-utils": "8.59.4", "@typescript-eslint/utils": "8.59.4", "@typescript-eslint/visitor-keys": "8.59.4", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.4", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.4", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.4", "@typescript-eslint/types": "8.59.4", "@typescript-eslint/typescript-estree": "8.59.4", "@typescript-eslint/visitor-keys": "8.59.4", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ=="], @@ -711,6 +739,10 @@ "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], + "@use-gesture/core": ["@use-gesture/core@10.3.1", "", {}, "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw=="], + + "@use-gesture/react": ["@use-gesture/react@10.3.1", "", { "dependencies": { "@use-gesture/core": "10.3.1" }, "peerDependencies": { "react": ">= 16.8.0" } }, "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g=="], + "@userjot/next": ["@userjot/next@1.0.0-beta.1", "", { "peerDependencies": { "next": "^11.1.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-rr9qjwweNLfDfphpf2OXNfxtc7FnmJmbv6yk9DpQJ/CLgGv0mrGRLs5usJfHzgdl7SQYcu5l88y0/Ct5qw5IAA=="], "@valibot/to-json-schema": ["@valibot/to-json-schema@1.7.0", "", { "peerDependencies": { "valibot": "^1.4.0" } }, "sha512-Y3pPVibbIOHzohrlxSINvO7w/bvXkoYS3BQHoImV9ynE+bXKf171bdMucPurV2zp7gdmt0L1HCcNAsbo7cFRQw=="], @@ -783,10 +815,14 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.23", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g=="], "beautiful-mermaid": ["beautiful-mermaid@1.1.3", "", { "dependencies": { "elkjs": "^0.11.0", "entities": "^7.0.1" } }, "sha512-TItrtrAyHp1vwFfFVYauWGrquouk/6SS21Aq3RsxindSYZODcN4xYrPZD6BiZRU+o5mKJzDPz9MUSMvELdylyg=="], + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], + "body-parser": ["body-parser@2.2.1", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="], "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], @@ -795,6 +831,8 @@ "browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="], + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], @@ -807,6 +845,8 @@ "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + "camera-controls": ["camera-controls@3.1.2", "", { "peerDependencies": { "three": ">=0.126.1" } }, "sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA=="], + "caniuse-lite": ["caniuse-lite@1.0.30001757", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -869,6 +909,8 @@ "cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], + "cross-env": ["cross-env@7.0.3", "", { "dependencies": { "cross-spawn": "^7.0.1" }, "bin": { "cross-env": "src/bin/cross-env.js", "cross-env-shell": "src/bin/cross-env-shell.js" } }, "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "css-in-js-utils": ["css-in-js-utils@3.1.0", "", { "dependencies": { "hyphenate-style-name": "^1.0.3" } }, "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A=="], @@ -915,6 +957,8 @@ "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + "detect-gpu": ["detect-gpu@5.0.70", "", { "dependencies": { "webgl-constants": "^1.1.1" } }, "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], @@ -927,6 +971,8 @@ "dotenv": ["dotenv@16.0.3", "", {}, "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ=="], + "draco3d": ["draco3d@1.5.7", "", {}, "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ=="], + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "eciesjs": ["eciesjs@0.4.16", "", { "dependencies": { "@ecies/ciphers": "^0.2.4", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.7", "@noble/hashes": "^1.8.0" } }, "sha512-dS5cbA9rA2VR4Ybuvhg6jvdmp46ubLn3E+px8cG/35aEDNclrqoCjg6mt0HYZ/M+OoESS3jSkCrqk1kWAEhWAw=="], @@ -1069,6 +1115,8 @@ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="], + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], @@ -1143,6 +1191,8 @@ "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + "glsl-noise": ["glsl-noise@0.0.0", "", {}, "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w=="], + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -1191,6 +1241,8 @@ "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + "hls.js": ["hls.js@1.6.16", "", {}, "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA=="], + "hono": ["hono@4.12.4", "", {}, "sha512-ooiZW1Xy8rQ4oELQ++otI2T9DsKpV0M6c6cO6JGx4RTfav9poFFLlet9UMXHZnoM1yG0HWGlQLswBGX3RZmHtg=="], "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], @@ -1205,8 +1257,12 @@ "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="], + "immer": ["immer@11.1.8", "", {}, "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -1287,7 +1343,7 @@ "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], - "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="], "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], @@ -1321,6 +1377,8 @@ "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], + "its-fine": ["its-fine@2.0.0", "", { "dependencies": { "@types/react-reconciler": "^0.28.9" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], @@ -1385,6 +1443,8 @@ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], @@ -1433,6 +1493,8 @@ "lucide-react": ["lucide-react@1.17.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w=="], + "maath": ["maath@0.10.8", "", { "peerDependencies": { "@types/three": ">=0.134.0", "three": ">=0.134.0" } }, "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], @@ -1483,6 +1545,10 @@ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "meshline": ["meshline@3.3.1", "", { "peerDependencies": { "three": ">=0.137" } }, "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ=="], + + "meshoptimizer": ["meshoptimizer@1.1.1", "", {}, "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g=="], + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], @@ -1685,6 +1751,8 @@ "postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="], + "potpack": ["potpack@1.0.2", "", {}, "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ=="], + "powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], @@ -1693,6 +1761,8 @@ "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + "promise-worker-transferable": ["promise-worker-transferable@1.0.4", "", { "dependencies": { "is-promise": "^2.1.0", "lie": "^3.0.2" } }, "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw=="], + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], @@ -1731,6 +1801,8 @@ "react-use": ["react-use@17.6.0", "", { "dependencies": { "@types/js-cookie": "^2.2.6", "@xobotyi/scrollbar-width": "^1.9.5", "copy-to-clipboard": "^3.3.1", "fast-deep-equal": "^3.1.3", "fast-shallow-equal": "^1.0.0", "js-cookie": "^2.2.1", "nano-css": "^5.6.2", "react-universal-interface": "^0.6.2", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.1.0", "set-harmonic-interval": "^1.0.1", "throttle-debounce": "^3.0.1", "ts-easing": "^0.2.0", "tslib": "^2.1.0" }, "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-OmedEScUMKFfzn1Ir8dBxiLLSOzhKe/dPZwVxcujweSj45aNM7BEGPb9BEVIgVEqEXx6f3/TsXzwIktNgUR02g=="], + "react-use-measure": ["react-use-measure@2.1.7", "", { "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" }, "optionalPeers": ["react-dom"] }, "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg=="], + "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], @@ -1877,6 +1949,10 @@ "stacktrace-js": ["stacktrace-js@2.0.2", "", { "dependencies": { "error-stack-parser": "^2.0.6", "stack-generator": "^2.0.5", "stacktrace-gps": "^3.0.4" } }, "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg=="], + "stats-gl": ["stats-gl@2.4.2", "", { "dependencies": { "@types/three": "*", "three": "^0.170.0" } }, "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ=="], + + "stats.js": ["stats.js@0.17.0", "", {}, "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw=="], + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], @@ -1925,6 +2001,8 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], + "suspend-react": ["suspend-react@0.1.3", "", { "peerDependencies": { "react": ">=17.0" } }, "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ=="], + "swr": ["swr@2.3.6", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw=="], "synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="], @@ -1939,6 +2017,12 @@ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + "three": ["three@0.184.0", "", {}, "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg=="], + + "three-mesh-bvh": ["three-mesh-bvh@0.8.3", "", { "peerDependencies": { "three": ">= 0.159.0" } }, "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg=="], + + "three-stdlib": ["three-stdlib@2.36.1", "", { "dependencies": { "@types/draco3d": "^1.4.0", "@types/offscreencanvas": "^2019.6.4", "@types/webxr": "^0.5.2", "draco3d": "^1.4.1", "fflate": "^0.6.9", "potpack": "^1.0.1" }, "peerDependencies": { "three": ">=0.128.0" } }, "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg=="], + "throttle-debounce": ["throttle-debounce@3.0.1", "", {}, "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg=="], "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], @@ -1967,6 +2051,12 @@ "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + "troika-three-text": ["troika-three-text@0.52.4", "", { "dependencies": { "bidi-js": "^1.0.2", "troika-three-utils": "^0.52.4", "troika-worker-utils": "^0.52.0", "webgl-sdf-generator": "1.1.1" }, "peerDependencies": { "three": ">=0.125.0" } }, "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg=="], + + "troika-three-utils": ["troika-three-utils@0.52.4", "", { "peerDependencies": { "three": ">=0.125.0" } }, "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A=="], + + "troika-worker-utils": ["troika-worker-utils@0.52.0", "", {}, "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw=="], + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], @@ -1981,6 +2071,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tunnel-rat": ["tunnel-rat@0.1.2", "", { "dependencies": { "zustand": "^4.3.2" } }, "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ=="], + "turbo": ["turbo@2.9.14", "", { "optionalDependencies": { "@turbo/darwin-64": "2.9.14", "@turbo/darwin-arm64": "2.9.14", "@turbo/linux-64": "2.9.14", "@turbo/linux-arm64": "2.9.14", "@turbo/windows-64": "2.9.14", "@turbo/windows-arm64": "2.9.14" }, "bin": { "turbo": "bin/turbo" } }, "sha512-BQqXRr4UoWI3UPFrtznCLykYHxwxWh53iCB57x092jPMjIlW1wnm3N895g5irpiXmnxUhREBB0n6+y8BHhs4nw=="], "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], @@ -2045,6 +2137,8 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "utility-types": ["utility-types@3.11.0", "", {}, "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw=="], + "valibot": ["valibot@1.4.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg=="], "validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="], @@ -2061,6 +2155,10 @@ "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "webgl-constants": ["webgl-constants@1.1.1", "", {}, "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="], + + "webgl-sdf-generator": ["webgl-sdf-generator@1.1.1", "", {}, "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], @@ -2293,6 +2391,8 @@ "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + "router/is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], "rtl-css-js/@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], @@ -2307,14 +2407,20 @@ "stacktrace-gps/source-map": ["source-map@0.5.6", "", {}, "sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA=="], + "stats-gl/three": ["three@0.170.0", "", {}, "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="], + "string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "three-stdlib/fflate": ["fflate@0.6.10", "", {}, "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg=="], + "to-vfile/vfile": ["vfile@4.2.1", "", { "dependencies": { "@types/unist": "^2.0.0", "is-buffer": "^2.0.0", "unist-util-stringify-position": "^2.0.0", "vfile-message": "^2.0.0" } }, "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA=="], "tsconfig-paths-webpack-plugin/enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "tsconfig-paths-webpack-plugin/tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], + "tunnel-rat/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], + "typescript-eslint/@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.4", "@typescript-eslint/types": "8.59.4", "@typescript-eslint/typescript-estree": "8.59.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw=="], "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],