diff --git a/apps/editor/app/editor/[projectId]/layout.tsx b/apps/editor/app/editor/[projectId]/layout.tsx index 82f03def..61badebf 100644 --- a/apps/editor/app/editor/[projectId]/layout.tsx +++ b/apps/editor/app/editor/[projectId]/layout.tsx @@ -14,5 +14,9 @@ export default function EditorProjectLayout({ }: Readonly<{ children: React.ReactNode }>) { - return children + return ( +
+ {children} +
+ ) } diff --git a/apps/editor/app/editor/[projectId]/page.tsx b/apps/editor/app/editor/[projectId]/page.tsx index 2a013627..8fa64c75 100644 --- a/apps/editor/app/editor/[projectId]/page.tsx +++ b/apps/editor/app/editor/[projectId]/page.tsx @@ -2,9 +2,10 @@ import Editor from '@/components/editor' import { useParams, useRouter } from 'next/navigation' -import { useLayoutEffect } from 'react' +import { useEffect, useState } from 'react' import { useProjectStore } from '@/features/community/lib/projects/store' import { useAuth } from '@/features/community/lib/auth/hooks' +import { SceneLoader } from '@/components/ui/scene-loader' export default function EditorPage() { const params = useParams() @@ -12,9 +13,14 @@ export default function EditorPage() { const { isAuthenticated, isLoading } = useAuth() const setActiveProject = useProjectStore((state) => state.setActiveProject) const router = useRouter() + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) // Use layoutEffect to set active project BEFORE the editor renders and hooks run - useLayoutEffect(() => { + useEffect(() => { if (isLoading) return if (!isAuthenticated) { router.replace('/') @@ -25,7 +31,11 @@ export default function EditorPage() { } }, [projectId, isAuthenticated, isLoading, setActiveProject, router]) - if (isLoading || !isAuthenticated) { + if (!mounted || isLoading) { + return + } + + if (!isAuthenticated) { return null } diff --git a/apps/editor/app/globals.css b/apps/editor/app/globals.css index 1ff450ab..c4c8a77b 100644 --- a/apps/editor/app/globals.css +++ b/apps/editor/app/globals.css @@ -85,7 +85,7 @@ } .dark { - --background: oklch(0.145 0 0); + --background: oklch(0.205 0 0); /* ~171717 */ --foreground: oklch(0.985 0 0); --card: oklch(0.205 0 0); --card-foreground: oklch(0.985 0 0); @@ -97,7 +97,7 @@ --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.269 0 0); --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); + --accent: oklch(0.235 0 0); /* slightly lighter than background (0.205) but darker than previous (0.269) */ --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); @@ -112,7 +112,7 @@ --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent: oklch(0.235 0 0); /* matching accent */ --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.556 0 0); @@ -161,3 +161,95 @@ scroll-behavior: auto !important; } } + +/* Loaders */ +.pascal-loader-1 { + width: 45px; + aspect-ratio: 1; + --c:no-repeat linear-gradient(currentColor 0 0); + background: var(--c), var(--c), var(--c); + animation: + pascal-l1-1 1s infinite, + pascal-l1-2 1s infinite; +} +@keyframes pascal-l1-1 { + 0%,100% {background-size:20% 100%} + 33%,66% {background-size:20% 20%} +} +@keyframes pascal-l1-2 { + 0%,33% {background-position: 0 0,50% 50%,100% 100%} + 66%,100% {background-position: 100% 0,50% 50%,0 100%} +} + +.pascal-loader-2 { + width: 45px; + aspect-ratio: .75; + --c: no-repeat linear-gradient(currentColor 0 0); + background: + var(--c) 0% 50%, + var(--c) 50% 50%, + var(--c) 100% 50%; + background-size: 20% 50%; + animation: pascal-l2 1s infinite linear; +} +@keyframes pascal-l2 { + 20% {background-position: 0% 0% ,50% 50% ,100% 50% } + 40% {background-position: 0% 100%,50% 0% ,100% 50% } + 60% {background-position: 0% 50% ,50% 100%,100% 0% } + 80% {background-position: 0% 50% ,50% 50% ,100% 100%} +} + +.pascal-loader-3 { + width: 45px; + aspect-ratio: .75; + --c:no-repeat linear-gradient(currentColor 0 0); + background: + var(--c) 0% 100%, + var(--c) 50% 100%, + var(--c) 100% 100%; + background-size: 20% 65%; + animation: pascal-l3 1s infinite linear; +} +@keyframes pascal-l3 { + 16.67% {background-position: 0% 0% ,50% 100%,100% 100%} + 33.33% {background-position: 0% 0% ,50% 0% ,100% 100%} + 50% {background-position: 0% 0% ,50% 0% ,100% 0% } + 66.67% {background-position: 0% 100%,50% 0% ,100% 0% } + 83.33% {background-position: 0% 100%,50% 100%,100% 0% } +} + +.pascal-loader-4 { + width: 45px; + aspect-ratio: 1; + --c:no-repeat linear-gradient(currentColor 0 0); + background: var(--c), var(--c), var(--c); + animation: + pascal-l4-1 1s infinite, + pascal-l4-2 1s infinite; +} +@keyframes pascal-l4-1 { + 0%,100% {background-size:20% 100%} + 33%,66% {background-size:20% 40%} +} +@keyframes pascal-l4-2 { + 0%,33% {background-position: 0 0,50% 100%,100% 100%} + 66%,100% {background-position: 100% 0,0 100%,50% 100%} +} + +.pascal-loader-5 { + width: 45px; + aspect-ratio: 1; + --c:no-repeat linear-gradient(currentColor 0 0); + background: var(--c), var(--c), var(--c); + animation: + pascal-l5-1 1s infinite, + pascal-l5-2 1s infinite; +} +@keyframes pascal-l5-1 { + 0%,100% {background-size:20% 100%} + 33%,66% {background-size:20% 40%} +} +@keyframes pascal-l5-2 { + 0%,33% {background-position: 0 0 ,50% 100%,100% 0} + 66%,100% {background-position: 0 100%,50% 0 ,100% 100%} +} diff --git a/apps/editor/app/viewer/[id]/page.tsx b/apps/editor/app/viewer/[id]/page.tsx index 74ac18bb..a21f97d3 100644 --- a/apps/editor/app/viewer/[id]/page.tsx +++ b/apps/editor/app/viewer/[id]/page.tsx @@ -14,6 +14,8 @@ import { ViewerGuestCTA } from './viewer-guest-cta' import { ViewerOverlay } from './viewer-overlay' import { ViewerZoneSystem } from './viewer-zone-system' +import { SceneLoader } from '@/components/ui/scene-loader' + export default function ViewerPage() { const params = useParams() const id = params.id as string @@ -90,14 +92,6 @@ export default function ViewerPage() { loadContent() }, [id, setScene]) - if (loading) { - return ( -
-

Loading...

-
- ) - } - if (error) { return (
@@ -108,13 +102,20 @@ export default function ViewerPage() { return (
- - + {loading && } + + {!loading && ( + <> + + + + )} + diff --git a/apps/editor/components/editor/floating-action-menu.tsx b/apps/editor/components/editor/floating-action-menu.tsx new file mode 100644 index 00000000..26f0853d --- /dev/null +++ b/apps/editor/components/editor/floating-action-menu.tsx @@ -0,0 +1,141 @@ +'use client' + +import { type AnyNode, type AnyNodeId, ItemNode, WindowNode, DoorNode, sceneRegistry, useScene } from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { Html } from '@react-three/drei' +import { useFrame } from '@react-three/fiber' +import { Copy, Move, Trash2 } from 'lucide-react' +import { useCallback, useRef } from 'react' +import * as THREE from 'three' +import { sfxEmitter } from '@/lib/sfx-bus' +import useEditor from '@/store/use-editor' + +const ALLOWED_TYPES = ['item', 'door', 'window'] + +export function FloatingActionMenu() { + const selectedIds = useViewer((s) => s.selection.selectedIds) + const nodes = useScene((s) => s.nodes) + const deleteNode = useScene((s) => s.deleteNode) + const setMovingNode = useEditor((s) => s.setMovingNode) + const setSelection = useViewer((s) => s.setSelection) + const isEditor = useViewer((state) => state.isEditor) + + const groupRef = useRef(null) + + // Only show for single selection of specific types + const selectedId = selectedIds.length === 1 ? selectedIds[0] : null + const node = selectedId ? nodes[selectedId as AnyNodeId] : null + const isValidType = node ? ALLOWED_TYPES.includes(node.type) : false + + useFrame(() => { + if (!selectedId || !isValidType || !groupRef.current) return + + const obj = sceneRegistry.nodes.get(selectedId) + if (obj) { + // Calculate bounding box in world space + const box = new THREE.Box3().setFromObject(obj) + if (!box.isEmpty()) { + const center = box.getCenter(new THREE.Vector3()) + // Position slightly above the object + groupRef.current.position.set(center.x, box.max.y + 0.3, center.z) + } + } + }) + + const handleMove = useCallback((e: React.MouseEvent) => { + e.stopPropagation() + if (!node) return + sfxEmitter.emit('sfx:item-pick') + if (node.type === 'item' || node.type === 'window' || node.type === 'door') { + setMovingNode(node as any) + } + setSelection({ selectedIds: [] }) + }, [node, setMovingNode, setSelection]) + + const handleDuplicate = useCallback((e: React.MouseEvent) => { + e.stopPropagation() + if (!node || !node.parentId) return + sfxEmitter.emit('sfx:item-pick') + useScene.temporal.getState().pause() + + let duplicateInfo = structuredClone(node) as any + delete duplicateInfo.id + duplicateInfo.metadata = { ...duplicateInfo.metadata, isNew: true } + + let duplicate: AnyNode | null = null + try { + if (node.type === 'door') { + duplicate = DoorNode.parse(duplicateInfo) + } else if (node.type === 'window') { + duplicate = WindowNode.parse(duplicateInfo) + } else if (node.type === 'item') { + duplicate = ItemNode.parse(duplicateInfo) + } + } catch (error) { + console.error('Failed to parse duplicate', error) + return + } + + if (duplicate) { + if (duplicate.type === 'door' || duplicate.type === 'window') { + useScene.getState().createNode(duplicate, duplicate.parentId as AnyNodeId) + } + if (duplicate.type === 'item' || duplicate.type === 'window' || duplicate.type === 'door') { + setMovingNode(duplicate as any) + } + setSelection({ selectedIds: [] }) + } + }, [node, setMovingNode, setSelection]) + + const handleDelete = useCallback((e: React.MouseEvent) => { + e.stopPropagation() + if (!selectedId || !node) return + sfxEmitter.emit('sfx:item-delete') + deleteNode(selectedId as AnyNodeId) + if (node.parentId) useScene.getState().dirtyNodes.add(node.parentId as AnyNodeId) + setSelection({ selectedIds: [] }) + }, [selectedId, node, deleteNode, setSelection]) + + if (!isEditor || !selectedId || !node || !isValidType) return null + + return ( + + +
e.stopPropagation()} + onPointerUp={(e) => e.stopPropagation()} + > + + + +
+ +
+ ) +} diff --git a/apps/editor/components/editor/index.tsx b/apps/editor/components/editor/index.tsx index 8f3d4e4f..e63890da 100644 --- a/apps/editor/components/editor/index.tsx +++ b/apps/editor/components/editor/index.tsx @@ -1,7 +1,8 @@ 'use client' +import { useEffect } from 'react' import { initSpaceDetectionSync, initSpatialGridSync, useScene } from '@pascal-app/core' -import { Viewer } from '@pascal-app/viewer' +import { Viewer, useViewer } from '@pascal-app/viewer' import { useProjectScene } from '@/features/community/lib/models/hooks' import { useKeyboard } from '@/hooks/use-keyboard' import { initSFXBus } from '@/lib/sfx-bus' @@ -18,15 +19,43 @@ import { SidebarProvider } from '../ui/primitives/sidebar' import { AppSidebar } from '../ui/sidebar/app-sidebar' import { CustomCameraControls } from './custom-camera-controls' import { ExportManager } from './export-manager' +import { FloatingActionMenu } from './floating-action-menu' import { Grid } from './grid' import { SelectionManager } from './selection-manager' import { ThumbnailGenerator } from './thumbnail-generator' +import { useProjectStore } from '@/features/community/lib/projects/store' +import { SceneLoader } from '../ui/scene-loader' // Load default scene initially (will be replaced when project loads) useScene.getState().loadScene() initSpatialGridSync() initSpaceDetectionSync(useScene, useEditor) +// Auto-select the first building and level for the default scene +const sceneNodes = useScene.getState().nodes as Record +const sceneRootIds = useScene.getState().rootNodeIds +const siteNode = sceneRootIds[0] ? sceneNodes[sceneRootIds[0]] : null +const resolve = (child: any) => typeof child === 'string' ? sceneNodes[child] : child +const firstBuilding = siteNode?.children?.map(resolve).find((n: any) => n?.type === 'building') +const firstLevel = firstBuilding?.children?.map(resolve).find((n: any) => n?.type === 'level') + +if (firstBuilding && firstLevel) { + useViewer.getState().setSelection({ + buildingId: firstBuilding.id, + levelId: firstLevel.id, + selectedIds: [], + zoneId: null, + }) + useEditor.getState().setPhase('structure') + useEditor.getState().setStructureLayer('elements') + + // Auto-select the wall tool if the level is empty + if (!firstLevel.children || firstLevel.children.length === 0) { + useEditor.getState().setMode('build') + useEditor.getState().setTool('wall') + } +} + // Initialize SFX bus to connect events to sound effects initSFXBus() @@ -38,14 +67,26 @@ export default function Editor({ projectId }: EditorProps) { useKeyboard() useProjectScene() + const isProjectLoading = useProjectStore((state) => state.isLoading) + const isSceneLoading = useProjectStore((state) => state.isSceneLoading) + const isLoading = isProjectLoading || isSceneLoading + + useEffect(() => { + document.body.classList.add('dark') + return () => { + document.body.classList.remove('dark') + } + }, []) + return ( -
+
+ {isLoading && } {/* Top-right controls */} -
+
@@ -59,6 +100,7 @@ export default function Editor({ projectId }: EditorProps) { + {/* Editor only system to toggle zone visibility */} diff --git a/apps/editor/components/editor/selection-manager.tsx b/apps/editor/components/editor/selection-manager.tsx index 57c12cc6..d1f31627 100644 --- a/apps/editor/components/editor/selection-manager.tsx +++ b/apps/editor/components/editor/selection-manager.tsx @@ -10,7 +10,7 @@ import { } from "@pascal-app/core"; import { useViewer } from "@pascal-app/viewer"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import useEditor from "@/store/use-editor"; const isNodeInCurrentLevel = (node: AnyNode): boolean => { @@ -22,13 +22,58 @@ const isNodeInCurrentLevel = (node: AnyNode): boolean => { type SelectableNodeType = "wall" | "item" | "building" | "zone" | 'slab' | 'ceiling' | 'roof' | 'window' | 'door'; +type ModifierKeys = { + meta: boolean; + ctrl: boolean; +}; + interface SelectionStrategy { types: SelectableNodeType[]; - handleSelect: (node: AnyNode, isShift: boolean) => void; + handleSelect: (node: AnyNode, nativeEvent?: any, modifierKeys?: ModifierKeys) => void; handleDeselect: () => void; isValid: (node: AnyNode) => boolean; } +export const resolveBuildingId = (levelId: string, nodes: Record): string | null => { + const level = nodes[levelId]; + if (!level) return null; + if (level.parentId && nodes[level.parentId]?.type === "building") { + return level.parentId; + } + return null; +}; + +const computeNextIds = ( + node: AnyNode, + selectedIds: string[], + event?: any, + modifierKeys?: ModifierKeys +): string[] => { + const isMeta = event?.metaKey || event?.nativeEvent?.metaKey || modifierKeys?.meta || false; + const isCtrl = event?.ctrlKey || event?.nativeEvent?.ctrlKey || modifierKeys?.ctrl || false; + + console.log("computeNextIds:", { + nodeId: node.id, + selectedIds, + isMeta, + isCtrl, + eventMeta: event?.metaKey, + nativeMeta: event?.nativeEvent?.metaKey, + modMeta: modifierKeys?.meta + }); + + if (isMeta || isCtrl) { + if (selectedIds.includes(node.id)) { + return selectedIds.filter((id) => id !== node.id); + } else { + return [...selectedIds, node.id]; + } + } + + // Not holding modifiers: select only this node + return [node.id]; +}; + const SELECTION_STRATEGIES: Record = { site: { types: ["building"], @@ -45,17 +90,28 @@ const SELECTION_STRATEGIES: Record = { structure: { types: ["wall", "item", "zone", "slab", "ceiling", "roof", "window", "door"], - handleSelect: (node, isShift) => { + handleSelect: (node, nativeEvent, modifierKeys) => { const { selection, setSelection } = useViewer.getState(); + const nodes = useScene.getState().nodes; + const nodeLevelId = resolveLevelId(node, nodes); + const buildingId = resolveBuildingId(nodeLevelId, nodes); + + const updates: any = {}; + if (nodeLevelId !== "default" && nodeLevelId !== selection.levelId) { + updates.levelId = nodeLevelId; + } + if (buildingId && buildingId !== selection.buildingId) { + updates.buildingId = buildingId; + } + if (node.type === 'zone') { - setSelection({ zoneId: node.id }); + updates.zoneId = node.id; + // Don't reset selectedIds in structure phase for zone, but if we changed level, it might reset them via hierarchy guard. + // Wait, the hierarchy guard resets zoneId if levelId changes. That's fine since we provide zoneId. + setSelection(updates); } else { - const nextIds = isShift - ? selection.selectedIds.includes(node.id) - ? selection.selectedIds.filter((id) => id !== node.id) - : [...selection.selectedIds, node.id] - : [node.id]; - setSelection({ selectedIds: nextIds }); + updates.selectedIds = computeNextIds(node, selection.selectedIds, nativeEvent, modifierKeys); + setSelection(updates); } }, handleDeselect: () => { @@ -89,14 +145,22 @@ const SELECTION_STRATEGIES: Record = { furnish: { types: ["item"], - handleSelect: (node, isShift) => { + handleSelect: (node, nativeEvent, modifierKeys) => { const { selection, setSelection } = useViewer.getState(); - const nextIds = isShift - ? selection.selectedIds.includes(node.id) - ? selection.selectedIds.filter((id) => id !== node.id) - : [...selection.selectedIds, node.id] - : [node.id]; - setSelection({ selectedIds: nextIds }); + const nodes = useScene.getState().nodes; + const nodeLevelId = resolveLevelId(node, nodes); + const buildingId = resolveBuildingId(nodeLevelId, nodes); + + const updates: any = {}; + if (nodeLevelId !== "default" && nodeLevelId !== selection.levelId) { + updates.levelId = nodeLevelId; + } + if (buildingId && buildingId !== selection.buildingId) { + updates.buildingId = buildingId; + } + + updates.selectedIds = computeNextIds(node, selection.selectedIds, nativeEvent, modifierKeys); + setSelection(updates); }, handleDeselect: () => { useViewer.getState().setSelection({ selectedIds: [] }); @@ -113,39 +177,116 @@ const SELECTION_STRATEGIES: Record = { export const SelectionManager = () => { const phase = useEditor((s) => s.phase); const mode = useEditor((s) => s.mode); + const modifierKeysRef = useRef({ + meta: false, + ctrl: false, + }); + const clickHandledRef = useRef(false); const movingNode = useEditor((s) => s.movingNode); + useEffect(() => { + const onKeyDown = (event: KeyboardEvent) => { + if (event.key === "Meta") modifierKeysRef.current.meta = true; + if (event.key === "Control") modifierKeysRef.current.ctrl = true; + }; + + const onKeyUp = (event: KeyboardEvent) => { + if (event.key === "Meta") modifierKeysRef.current.meta = false; + if (event.key === "Control") modifierKeysRef.current.ctrl = false; + }; + + const clearModifiers = () => { + modifierKeysRef.current.meta = false; + modifierKeysRef.current.ctrl = false; + }; + + window.addEventListener("keydown", onKeyDown); + window.addEventListener("keyup", onKeyUp); + window.addEventListener("blur", clearModifiers); + + return () => { + window.removeEventListener("keydown", onKeyDown); + window.removeEventListener("keyup", onKeyUp); + window.removeEventListener("blur", clearModifiers); + }; + }, []); + useEffect(() => { if (mode !== "select") return; if (movingNode) return; - const strategy = SELECTION_STRATEGIES[phase]; - if (!strategy) return; - const onClick = (event: NodeEvent) => { - if (!strategy.isValid(event.node)) return; + const node = event.node; + let currentPhase = useEditor.getState().phase; + let targetPhase = currentPhase; - event.stopPropagation(); - const isShift = event.nativeEvent?.shiftKey; - strategy.handleSelect(event.node, isShift ?? false); + // Auto-switch between structure and furnish phases when clicking elements on the same level + if (currentPhase === "structure" || currentPhase === "furnish") { + if (isNodeInCurrentLevel(node)) { + if ( + node.type === "wall" || + node.type === "slab" || + node.type === "ceiling" || + node.type === "roof" || + node.type === "window" || + node.type === "door" + ) { + targetPhase = "structure"; + } else if (node.type === "item") { + const item = node as ItemNode; + if (item.asset.category === "door" || item.asset.category === "window") { + targetPhase = "structure"; + } else { + targetPhase = "furnish"; + } + } + + if (targetPhase !== currentPhase) { + useEditor.getState().setPhase(targetPhase); + if (targetPhase === "structure" && useEditor.getState().structureLayer === "zones") { + useEditor.getState().setStructureLayer("elements"); + } + currentPhase = targetPhase; + } + } + } + + const activeStrategy = SELECTION_STRATEGIES[currentPhase]; + if (activeStrategy?.isValid(node)) { + event.stopPropagation(); + clickHandledRef.current = true; + + console.log("[SelectionManager] Valid click on:", node.type, node.id, "Shift:", event.nativeEvent.shiftKey); + activeStrategy.handleSelect(node, event.nativeEvent, modifierKeysRef.current); + + // Reset the handled flag after a short delay to allow grid:click to be ignored + setTimeout(() => { + clickHandledRef.current = false; + }, 50); + } }; - // Bind listeners for all potential types this strategy might care about - strategy.types.forEach((type) => { - emitter.on(`${type}:click`, onClick); + const allTypes = ["wall", "item", "building", "zone", "slab", "ceiling", "roof", "window", "door"]; + allTypes.forEach((type) => { + emitter.on(`${type}:click` as any, onClick as any); }); - const onGridClick = () => strategy.handleDeselect(); + const onGridClick = () => { + if (clickHandledRef.current) return; + console.log("onGridClick triggered! Deselecting."); + const activeStrategy = SELECTION_STRATEGIES[useEditor.getState().phase]; + if (activeStrategy) activeStrategy.handleDeselect(); + }; emitter.on("grid:click", onGridClick); return () => { - strategy.types.forEach((type) => { - emitter.off(`${type}:click`, onClick); + allTypes.forEach((type) => { + emitter.off(`${type}:click` as any, onClick as any); }); emitter.off("grid:click", onGridClick); }; - }, [phase, mode, movingNode]); + }, [mode, movingNode]); // Global double-click handler for auto-switching phases and cross-phase hover useEffect(() => { @@ -231,8 +372,7 @@ export const SelectionManager = () => { const strategy = SELECTION_STRATEGIES[targetPhase]; if (strategy) { - const isShift = event.nativeEvent?.shiftKey; - strategy.handleSelect(node, isShift ?? false); + strategy.handleSelect(node, event.nativeEvent, modifierKeysRef.current); } } }; diff --git a/apps/editor/components/pascal-radio.tsx b/apps/editor/components/pascal-radio.tsx index d882c6ab..e812b5f6 100644 --- a/apps/editor/components/pascal-radio.tsx +++ b/apps/editor/components/pascal-radio.tsx @@ -3,10 +3,10 @@ import { Howl } from 'howler' import { Disc3, Settings2, SkipBack, SkipForward, Volume2, VolumeX } from 'lucide-react' import { useCallback, useEffect, useRef, useState } from 'react' -import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/primitives/popover' import { Slider } from '@/components/ui/slider' import { cn } from '@/lib/utils' import useAudio from '@/store/use-audio' +import { motion, AnimatePresence } from 'framer-motion' const PLAYLIST = [ { @@ -66,6 +66,8 @@ export function PascalRadio() { const [currentTrackIndex, setCurrentTrackIndex] = useState(0) const { masterVolume, radioVolume, muted, isRadioPlaying, setRadioPlaying } = useAudio() const soundRef = useRef(null) + const [isOpen, setIsOpen] = useState(false) + const containerRef = useRef(null) const currentTrack = shuffledPlaylist[currentTrackIndex]! @@ -144,74 +146,126 @@ export function PascalRadio() { useAudio.setState({ radioVolume: value[0] }) } + // Handle click outside to close + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsOpen(false) + } + } + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside) + } + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [isOpen]) + return ( -
- - Radio Pascal -
{ - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault() - handlePlayPause() - } - }} - > - {isRadioPlaying ? : } -
- - + { + if (!isOpen) setIsOpen(true) + }} + transition={{ type: 'spring', bounce: 0.2, duration: 0.6 }} + className={cn( + "flex flex-col rounded-lg border border-border bg-background/95 shadow-lg backdrop-blur-md overflow-hidden", + !isOpen && "cursor-pointer hover:bg-accent/30 transition-colors" + )} + > +
+
+ + Radio Pascal +
+
+
{ + e.stopPropagation() + handlePlayPause() + }} + className="rounded-sm p-1 transition-all cursor-pointer bg-accent/30 hover:bg-accent hover:text-accent-foreground hover:shadow-sm" + role="button" + tabIndex={0} + aria-label={isRadioPlaying ? 'Pause' : 'Play'} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + e.stopPropagation() + handlePlayPause() + } + }} + > + {isRadioPlaying ? : } +
- - -
- {/* Current song info with prev/next */} -
-

Now Playing

-
- -

{currentTrack.title}

- +
+
+ + + {isOpen && ( + +
+
+ {/* Current song info with prev/next */} +
+

Now Playing

+
+ +

+ {currentTrack.title} +

+ +
+
+ + {/* Volume control */} +
+ + + {radioVolume}%
- - {/* Volume control */} -
- - - {radioVolume}% -
-
- - -
+ + )} + + ) } diff --git a/apps/editor/components/systems/zone/zone-system.tsx b/apps/editor/components/systems/zone/zone-system.tsx index 1c5e2cf6..883123de 100644 --- a/apps/editor/components/systems/zone/zone-system.tsx +++ b/apps/editor/components/systems/zone/zone-system.tsx @@ -1,14 +1,14 @@ import { sceneRegistry, useScene, type ZoneNode } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' -import { useEffect } from 'react' +import { useFrame } from '@react-three/fiber' import useEditor from '@/store/use-editor' export const ZoneSystem = () => { - const structureLayer = useEditor((state) => state.structureLayer) - const levelMode = useViewer((state) => state.levelMode) - const selectedLevelId = useViewer((state) => state.selection.levelId) + useFrame(() => { + const structureLayer = useEditor.getState().structureLayer + const levelMode = useViewer.getState().levelMode + const selectedLevelId = useViewer.getState().selection.levelId - useEffect(() => { const visible = structureLayer === 'zones' const zones = sceneRegistry.byType.zone || new Set() const nodes = useScene.getState().nodes @@ -23,21 +23,27 @@ export const ZoneSystem = () => { const isOnSelectedLevel = zone?.parentId === selectedLevelId const hideInSoloMode = levelMode === 'solo' && selectedLevelId && !isOnSelectedLevel - obj.visible = visible + if (obj.visible !== visible) { + obj.visible = visible + } const label = obj.getObjectByName('label') if (label) { // Hide label if zone layer is off OR if in solo mode on a different level const showLabel = visible && !hideInSoloMode; const labelPosition = obj.userData.labelPosition as [number, number, number] | undefined - if (showLabel && labelPosition) { - label.position.set(...labelPosition) - } else { - label.position.set(-9999, -9999, -9999) + + const targetX = showLabel && labelPosition ? labelPosition[0] : -9999 + if (label.position.x !== targetX) { + if (showLabel && labelPosition) { + label.position.set(...labelPosition) + } else { + label.position.set(-9999, -9999, -9999) + } } } }) - }, [structureLayer, levelMode, selectedLevelId]) + }) return null } diff --git a/apps/editor/components/ui/action-menu/camera-actions.tsx b/apps/editor/components/ui/action-menu/camera-actions.tsx index 929b557f..63309368 100644 --- a/apps/editor/components/ui/action-menu/camera-actions.tsx +++ b/apps/editor/components/ui/action-menu/camera-actions.tsx @@ -1,7 +1,7 @@ 'use client' import { emitter } from '@pascal-app/core' -import { RotateCcw, RotateCw, Rotate3D } from 'lucide-react' +import Image from 'next/image' import { Button } from '@/components/ui/primitives/button' import { Tooltip, @@ -28,12 +28,18 @@ export function CameraActions() { @@ -45,12 +51,18 @@ export function CameraActions() { @@ -62,12 +74,18 @@ export function CameraActions() { diff --git a/apps/editor/components/ui/action-menu/control-modes.tsx b/apps/editor/components/ui/action-menu/control-modes.tsx index c4d09097..15b97c62 100644 --- a/apps/editor/components/ui/action-menu/control-modes.tsx +++ b/apps/editor/components/ui/action-menu/control-modes.tsx @@ -1,19 +1,21 @@ "use client"; +import Image from "next/image"; import { Button } from "@/components/ui/primitives/button"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/primitives/tooltip"; -import { Hammer, MousePointer2, Pencil, Trash2 } from "lucide-react"; +import { Pencil, Trash2, type LucideIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import useEditor, { Mode, Phase } from "@/store/use-editor"; type ModeConfig = { id: Mode; - icon: typeof MousePointer2; + icon?: LucideIcon; + imageSrc?: string; label: string; shortcut: string; color: string; @@ -24,7 +26,7 @@ type ModeConfig = { const allModes: ModeConfig[] = [ { id: "select", - icon: MousePointer2, + imageSrc: "/icons/select.png", label: "Select", shortcut: "V", color: "hover:bg-blue-500/20 hover:text-blue-400", @@ -40,7 +42,7 @@ const allModes: ModeConfig[] = [ }, { id: "build", - icon: Hammer, + imageSrc: "/icons/build.png", label: "Build", shortcut: "B", color: "hover:bg-green-500/20 hover:text-green-400", @@ -98,22 +100,39 @@ export function ControlModes() { {availableModes.map((m) => { const Icon = m.icon; const isActive = mode === m.id; + const isImageMode = Boolean(m.imageSrc); return ( diff --git a/apps/editor/components/ui/action-menu/index.tsx b/apps/editor/components/ui/action-menu/index.tsx index 461ab7d5..c5a14aff 100644 --- a/apps/editor/components/ui/action-menu/index.tsx +++ b/apps/editor/components/ui/action-menu/index.tsx @@ -30,7 +30,7 @@ export function ActionMenu({ className }: { className?: string }) { transition={transition} className={cn( "-translate-x-1/2 fixed bottom-6 left-1/2 z-50", - "rounded-2xl border border-zinc-800 bg-zinc-950/90 shadow-2xl backdrop-blur-md", + "rounded-2xl border border-border bg-background/90 shadow-2xl backdrop-blur-md", "transition-colors duration-200 ease-out", className, )} @@ -40,7 +40,7 @@ export function ActionMenu({ className }: { className?: string }) { {mode === "build" && tool === "item" && catalogCategory && ( -
+
-
+
diff --git a/apps/editor/components/ui/action-menu/view-toggles.tsx b/apps/editor/components/ui/action-menu/view-toggles.tsx index aa40e294..330385bd 100644 --- a/apps/editor/components/ui/action-menu/view-toggles.tsx +++ b/apps/editor/components/ui/action-menu/view-toggles.tsx @@ -85,7 +85,7 @@ export function ViewToggles() { @@ -107,7 +107,7 @@ export function ViewToggles() { @@ -131,7 +131,7 @@ export function ViewToggles() { @@ -156,7 +156,7 @@ export function ViewToggles() { @@ -178,7 +178,7 @@ export function ViewToggles() { diff --git a/apps/editor/components/ui/primitives/sidebar.tsx b/apps/editor/components/ui/primitives/sidebar.tsx index d6c8b0d7..fda56db9 100644 --- a/apps/editor/components/ui/primitives/sidebar.tsx +++ b/apps/editor/components/ui/primitives/sidebar.tsx @@ -4,6 +4,8 @@ import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { PanelLeftIcon } from "lucide-react"; import * as React from "react"; +import { create } from "zustand"; +import { persist } from "zustand/middleware"; import { Button } from "@/components/ui/primitives/button"; import { Input } from "@/components/ui/primitives/input"; import { Separator } from "@/components/ui/primitives/separator"; @@ -31,6 +33,28 @@ const SIDEBAR_WIDTH_MOBILE = "18rem"; const SIDEBAR_WIDTH_ICON = "3rem"; const SIDEBAR_KEYBOARD_SHORTCUT = "b"; +type SidebarStore = { + width: number; + setWidth: (width: number) => void; + isDragging: boolean; + setIsDragging: (isDragging: boolean) => void; +}; + +export const useSidebarStore = create()( + persist( + (set) => ({ + width: 288, // 18rem = 288px + setWidth: (width) => set({ width: Math.max(288, Math.min(width, 800)) }), + isDragging: false, + setIsDragging: (isDragging) => set({ isDragging }), + }), + { + name: "sidebar-preferences", + partialize: (state) => ({ width: state.width }), // Only persist width + } + ) +); + type SidebarContextProps = { state: "expanded" | "collapsed"; open: boolean; @@ -67,6 +91,8 @@ function SidebarProvider({ }) { const isMobile = useIsMobile(); const [openMobile, setOpenMobile] = React.useState(false); + const sidebarWidth = useSidebarStore((state) => state.width); + const isDragging = useSidebarStore((state) => state.isDragging); // This is the internal state of the sidebar. // We use openProp and setOpenProp for control from outside the component. @@ -136,9 +162,10 @@ function SidebarProvider({ className, )} data-slot="sidebar-wrapper" + data-dragging={isDragging} style={ { - "--sidebar-width": SIDEBAR_WIDTH, + "--sidebar-width": `${sidebarWidth}px`, "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, ...style, } as React.CSSProperties @@ -152,6 +179,52 @@ function SidebarProvider({ ); } +function SidebarResizer({ side }: { side: "left" | "right" }) { + const setWidth = useSidebarStore((state) => state.setWidth); + const setIsDragging = useSidebarStore((state) => state.setIsDragging); + const isResizing = React.useRef(false); + + const handlePointerDown = (e: React.PointerEvent) => { + isResizing.current = true; + setIsDragging(true); + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + }; + + React.useEffect(() => { + const handlePointerMove = (e: PointerEvent) => { + if (!isResizing.current) return; + const newWidth = side === "left" ? e.clientX : window.innerWidth - e.clientX; + setWidth(Math.max(288, Math.min(newWidth, 800))); + }; + + const handlePointerUp = () => { + isResizing.current = false; + setIsDragging(false); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }; + + window.addEventListener("pointermove", handlePointerMove); + window.addEventListener("pointerup", handlePointerUp); + + return () => { + window.removeEventListener("pointermove", handlePointerMove); + window.removeEventListener("pointerup", handlePointerUp); + }; + }, [setWidth, side]); + + return ( +
+ ); +} + function Sidebar({ side = "left", variant = "sidebar", @@ -219,6 +292,7 @@ function Sidebar({
diff --git a/apps/editor/components/ui/scene-loader.tsx b/apps/editor/components/ui/scene-loader.tsx new file mode 100644 index 00000000..14530033 --- /dev/null +++ b/apps/editor/components/ui/scene-loader.tsx @@ -0,0 +1,40 @@ +'use client' + +import { useEffect, useState } from 'react' +import { cn } from '@/lib/utils' + +const LOADERS = [ + 'pascal-loader-1', + 'pascal-loader-2', + 'pascal-loader-3', + 'pascal-loader-4', + 'pascal-loader-5', +] + +interface SceneLoaderProps { + className?: string + fullScreen?: boolean +} + +export function SceneLoader({ className, fullScreen = false }: SceneLoaderProps) { + const [loaderClass, setLoaderClass] = useState(null) + + useEffect(() => { + // Pick a random loader on mount + setLoaderClass(LOADERS[Math.floor(Math.random() * LOADERS.length)] ?? LOADERS[0]!) + }, []) + + if (!loaderClass) return null + + return ( +
+
+
+ ) +} diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/building-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/building-tree-node.tsx index 18ec0101..1e390cf7 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/building-tree-node.tsx @@ -13,9 +13,10 @@ import { interface BuildingTreeNodeProps { node: BuildingNode; depth: number; + isLast?: boolean; } -export function BuildingTreeNode({ node, depth }: BuildingTreeNodeProps) { +export function BuildingTreeNode({ node, depth, isLast }: BuildingTreeNodeProps) { const [expanded, setExpanded] = useState(true); const createNode = useScene((state) => state.createNode); const isSelected = useViewer((state) => state.selection.buildingId === node.id); @@ -47,6 +48,7 @@ export function BuildingTreeNode({ node, depth }: BuildingTreeNodeProps) { onClick={handleClick} isSelected={isSelected} isHovered={isHovered} + isLast={isLast} actions={
@@ -64,8 +66,8 @@ export function BuildingTreeNode({ node, depth }: BuildingTreeNodeProps) {
} > - {node.children.map((childId) => ( - + {node.children.map((childId, index) => ( + ))} ); diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx index e002cbcb..70942b6c 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx @@ -2,16 +2,18 @@ import { type AnyNodeId, CeilingNode, useScene } from "@pascal-app/core"; import { useViewer } from "@pascal-app/viewer"; import Image from "next/image"; import { useState, useEffect } from "react"; +import useEditor from "@/store/use-editor"; import { InlineRenameInput } from "./inline-rename-input"; -import { TreeNode, TreeNodeWrapper } from "./tree-node"; +import { TreeNode, TreeNodeWrapper, handleTreeSelection } from "./tree-node"; import { TreeNodeActions } from "./tree-node-actions"; interface CeilingTreeNodeProps { node: CeilingNode; depth: number; + isLast?: boolean; } -export function CeilingTreeNode({ node, depth }: CeilingTreeNodeProps) { +export function CeilingTreeNode({ node, depth, isLast }: CeilingTreeNodeProps) { const [expanded, setExpanded] = useState(false); const [isEditing, setIsEditing] = useState(false); const selectedIds = useViewer((state) => state.selection.selectedIds); @@ -40,8 +42,12 @@ export function CeilingTreeNode({ node, depth }: CeilingTreeNodeProps) { } }, [selectedIds, node.id]); - const handleClick = () => { - setSelection({ selectedIds: [node.id] }); + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const handled = handleTreeSelection(e, node.id, selectedIds, setSelection); + if (!handled && useEditor.getState().phase === "furnish") { + useEditor.getState().setPhase("structure"); + } }; const handleDoubleClick = () => { @@ -62,6 +68,7 @@ export function CeilingTreeNode({ node, depth }: CeilingTreeNodeProps) { return ( } label={ } > - {node.children.map((childId) => ( - + {node.children.map((childId, index) => ( + ))} ); diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/door-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/door-tree-node.tsx index ec2fb556..503f1146 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/door-tree-node.tsx @@ -4,18 +4,21 @@ import { DoorNode } from "@pascal-app/core" import { useViewer } from "@pascal-app/viewer" import Image from "next/image" import { useState } from "react" +import useEditor from "@/store/use-editor" import { InlineRenameInput } from "./inline-rename-input" -import { TreeNodeWrapper } from "./tree-node" +import { TreeNodeWrapper, handleTreeSelection } from "./tree-node" import { TreeNodeActions } from "./tree-node-actions" interface DoorTreeNodeProps { node: DoorNode depth: number + isLast?: boolean } -export function DoorTreeNode({ node, depth }: DoorTreeNodeProps) { +export function DoorTreeNode({ node, depth, isLast }: DoorTreeNodeProps) { const [isEditing, setIsEditing] = useState(false) - const isSelected = useViewer((state) => state.selection.selectedIds.includes(node.id)) + const selectedIds = useViewer((state) => state.selection.selectedIds) + const isSelected = selectedIds.includes(node.id) const isHovered = useViewer((state) => state.hoveredId === node.id) const setSelection = useViewer((state) => state.setSelection) const setHoveredId = useViewer((state) => state.setHoveredId) @@ -24,6 +27,7 @@ export function DoorTreeNode({ node, depth }: DoorTreeNodeProps) { return ( } label={ {}} - onClick={() => setSelection({ selectedIds: [node.id] })} + onClick={(e: React.MouseEvent) => { + e.stopPropagation() + const handled = handleTreeSelection(e, node.id, selectedIds, setSelection) + if (!handled && useEditor.getState().phase === "furnish") { + useEditor.getState().setPhase("structure") + } + }} onDoubleClick={() => setIsEditing(true)} onMouseEnter={() => setHoveredId(node.id)} onMouseLeave={() => setHoveredId(null)} isSelected={isSelected} isHovered={isHovered} isVisible={node.visible !== false} + isLast={isLast} actions={} /> ) diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/index.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/index.tsx index 4740f0e3..c59c8e5f 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/index.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/index.tsx @@ -7,30 +7,40 @@ import { type SiteNode, useScene, type ZoneNode, + type ScanNode, + type GuideNode, + ScanNode as ScanNodeSchema, + GuideNode as GuideNodeSchema, } from "@pascal-app/core"; import { useViewer } from "@pascal-app/viewer"; import { + Box, Building2, Camera, ChevronDown, + Image as ImageIcon, Layers, + Loader2, Pentagon, MoreHorizontal, Pencil, Plus, Trash2, + X, } from "lucide-react"; import { useState, useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; import useEditor from "@/store/use-editor"; import { TreeNode } from "./tree-node"; -import { ReferencesDialog } from "./references-dialog"; import { InlineRenameInput } from "./inline-rename-input"; +import { useProjectStore } from '@/features/community/lib/projects/store'; +import { deleteProjectAssetByUrl, uploadProjectAsset } from '@/features/community/lib/assets/actions'; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/primitives/popover"; +import { motion, AnimatePresence, LayoutGroup } from "motion/react"; // Preset colors for zones const PRESET_COLORS = [ @@ -315,11 +325,188 @@ function CameraPopover({ } +function ReferenceItem({ refNode, isLastRow, setSelectedReferenceId, handleDelete }: { + refNode: ScanNode | GuideNode; + isLastRow: boolean; + setSelectedReferenceId: (id: string) => void; + handleDelete: (id: string, e: React.MouseEvent) => void; +}) { + const [isEditing, setIsEditing] = useState(false); + + return ( +
+
+
+ +
setSelectedReferenceId(refNode.id)} + onDoubleClick={() => setIsEditing(true)} + > + {refNode.type === 'scan' ? Scan : Guide} + setIsEditing(false)} + onStartEditing={() => setIsEditing(true)} + defaultName={refNode.type === 'scan' ? '3D Scan' : 'Guide Image'} + /> +
+ + +
+ ); +} + +const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB + +function LevelReferences({ levelId, isLastLevel }: { levelId: string, isLastLevel?: boolean }) { + const nodes = useScene((s) => s.nodes); + const createNode = useScene((s) => s.createNode); + const deleteNode = useScene((s) => s.deleteNode); + const setSelectedReferenceId = useEditor((s) => s.setSelectedReferenceId); + const activeProject = useProjectStore((s) => s.activeProject); + const [uploadError, setUploadError] = useState(null); + const [uploading, setUploading] = useState(false); + const [uploadingType, setUploadingType] = useState<'scan'|'guide'|null>(null); + + const scanInputRef = useRef(null); + + const references = Object.values(nodes).filter( + (node): node is ScanNode | GuideNode => + (node.type === 'scan' || node.type === 'guide') && node.parentId === levelId, + ); + + const handleAddAsset = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + e.target.value = ''; + + const projectId = activeProject?.id; + if (!projectId) { + setUploadError('No active project. Please open a project first.'); + return; + } + + if (file.size > MAX_FILE_SIZE) { + setUploadError(`File is too large (${(file.size / 1024 / 1024).toFixed(0)} MB). Maximum size is 100 MB.`); + return; + } + + // Auto-detect type based on file extension/mime type + const isScan = file.name.toLowerCase().endsWith('.glb') || file.name.toLowerCase().endsWith('.gltf'); + const isImage = file.type.startsWith('image/'); + + if (!isScan && !isImage) { + setUploadError('Invalid file type. Please upload a .glb/.gltf scan or an image.'); + return; + } + + const type = isScan ? 'scan' : 'guide'; + + setUploadError(null); + setUploading(true); + setUploadingType(type); + const result = await uploadProjectAsset(projectId, file, type); + setUploading(false); + setUploadingType(null); + + if (!result.success) { + setUploadError(result.error); + return; + } + + const Schema = type === 'scan' ? ScanNodeSchema : GuideNodeSchema; + const node = Schema.parse({ + url: result.url, + name: file.name, + parentId: levelId, + }); + createNode(node, levelId as AnyNodeId); + setSelectedReferenceId(node.id); + }; + + const handleDelete = async (nodeId: string, e: React.MouseEvent) => { + e.stopPropagation(); + const refNode = nodes[nodeId as AnyNodeId] as ScanNode | GuideNode | undefined; + const projectId = activeProject?.id; + + if ( + projectId && + refNode?.url && + (refNode.url.startsWith('http://') || refNode.url.startsWith('https://')) + ) { + deleteProjectAssetByUrl(projectId, refNode.url); + } + deleteNode(nodeId as AnyNodeId); + }; + + const rows = [ + { type: 'upload' as const }, + ...references.map(ref => ({ type: 'ref' as const, data: ref })) + ]; + + return ( +
+ {!isLastLevel && ( +
+ )} + + {rows.map((row, i) => { + const isLastRow = i === rows.length - 1; + + if (row.type === 'upload') { + return ( +
+
+
+ + + + +
+ ); + } + + const ref = row.data as ScanNode | GuideNode; + return ( + + ); + })} + + {uploadError && ( +
+
+ {uploadError} +
+ )} +
+ ); +} + function LevelItem({ level, selectedLevelId, setSelection, - setReferencesLevelId, deleteNode, updateNode, isLast, @@ -327,7 +514,6 @@ function LevelItem({ level: LevelNode; selectedLevelId: string | null; setSelection: (selection: any) => void; - setReferencesLevelId: (id: string | null) => void; deleteNode: (id: AnyNodeId) => void; updateNode: (id: AnyNodeId, updates: Partial) => void; isLast?: boolean; @@ -336,6 +522,11 @@ function LevelItem({ const [isEditing, setIsEditing] = useState(false); const itemRef = useRef(null); const isSelected = selectedLevelId === level.id; + const [isExpanded, setIsExpanded] = useState(isSelected); + + useEffect(() => { + setIsExpanded(isSelected); + }, [isSelected]); useEffect(() => { if (isSelected && itemRef.current) { @@ -344,37 +535,66 @@ function LevelItem({ }, [isSelected]); return ( -
- {/* Vertical tree line */} -
- {/* Horizontal branch line */} -
- +
setSelection({ levelId: level.id })} - onDoubleClick={() => setIsEditing(true)} + ref={itemRef} + className={cn( + "flex items-center group/level border-b border-border/50 pr-2 transition-all duration-200 relative h-8 select-none", + isSelected + ? "bg-accent/50 text-foreground" + : "text-muted-foreground hover:bg-accent/30 hover:text-foreground" + )} > - + {/* Horizontal branch line */} +
+
- setIsEditing(false)} - onStartEditing={() => setIsEditing(true)} - defaultName={`Level ${level.level}`} - /> -
+ {/* Line down to children */} + {isExpanded && ( +
+ )} + +
+ +
+ +
setSelection({ levelId: level.id })} + onDoubleClick={() => setIsEditing(true)} + > + Level + setIsEditing(false)} + onStartEditing={() => setIsEditing(true)} + defaultName={`Level ${level.level}`} + /> +
{/* Camera snapshot button */} @@ -456,12 +676,6 @@ function LevelItem({ - {level.level !== 0 && (
+ + {isExpanded && ( + + + + )} + +
); } @@ -486,8 +714,6 @@ function LevelsSection() { const selectedLevelId = useViewer((state) => state.selection.levelId); const setSelection = useViewer((state) => state.setSelection); - const [referencesLevelId, setReferencesLevelId] = useState(null); - const building = selectedBuildingId ? (nodes[selectedBuildingId] as BuildingNode) : null; @@ -511,24 +737,27 @@ function LevelsSection() { return (
{/* Level buttons */} -
+
{levels.length === 0 && ( -
+
{/* Vertical tree line */}
{/* Horizontal branch line */} -
+
No levels yet
)} @@ -538,24 +767,12 @@ function LevelsSection() { level={level} selectedLevelId={selectedLevelId} setSelection={setSelection} - setReferencesLevelId={setReferencesLevelId} deleteNode={deleteNode} updateNode={updateNode} isLast={index === levels.length - 1} /> ))}
- - {/* References dialog */} - {referencesLevelId && ( - { - if (!open) setReferencesLevelId(null); - }} - /> - )}
); } @@ -566,13 +783,18 @@ function LayerToggle() { const phase = useEditor((state) => state.phase); const setPhase = useEditor((state) => state.setPhase); + const activeTab = + phase === "structure" && structureLayer === "elements" ? "structure" : + phase === "furnish" ? "furnish" : + phase === "structure" && structureLayer === "zones" ? "zones" : "none"; + return ( -
+
+ +
); } -function ZoneItem({ zone }: { zone: ZoneNode }) { +function ZoneItem({ zone, isLast }: { zone: ZoneNode, isLast?: boolean }) { const [isEditing, setIsEditing] = useState(false); const [cameraPopoverOpen, setCameraPopoverOpen] = useState(false); const deleteNode = useScene((state) => state.deleteNode); @@ -680,7 +931,7 @@ function ZoneItem({ zone }: { zone: ZoneNode }) {
setHoveredId(zone.id)} onMouseLeave={() => setHoveredId(null)} > + {/* Vertical tree line */} +
+ {/* Horizontal branch line */} +
+ +
+
+ ); +} + function ContentSection() { const nodes = useScene((state) => state.nodes); const selectedLevelId = useViewer((state) => state.selection.levelId); @@ -853,8 +1131,8 @@ function ContentSection() { return (
- {levelZones.map((zone) => ( - + {levelZones.map((zone, index) => ( + ))}
); @@ -865,28 +1143,9 @@ function ContentSection() { const childNode = nodes[childId]; if (!childNode || childNode.type === "zone") return false; - // In structure mode, show structural elements (walls, slabs, etc.) and doors/windows - if (phase === "structure") { - if (childNode.type === "item") { - const category = childNode.asset?.category?.toLowerCase() || ""; - // Only show doors and windows in structure mode - return category === "door" || category === "window"; - } - // Show all other structural elements (walls, slabs, ceiling, roof) - return true; - } - - // In furnish mode, only show items that are NOT doors or windows - if (phase === "furnish") { - if (childNode.type === "item") { - const category = childNode.asset?.category?.toLowerCase() || ""; - // Hide doors and windows in furnish mode - return category !== "door" && category !== "window"; - } - // Hide structural elements in furnish mode - return false; - } - + // We no longer filter out structural nodes in furnish mode or furnish nodes in structure mode + // This allows nested items (like lights in a ceiling or cabinetry on a wall) to remain visible + // and selectable in both modes, ensuring seamless transition in the tree view. return true; }); @@ -900,8 +1159,8 @@ function ContentSection() { return (
- {elementChildren.map((childId) => ( - + {elementChildren.map((childId, index) => ( + ))}
); @@ -931,8 +1190,13 @@ function BuildingItem({ }, [isBuildingActive]); return ( -
-
+ -
+ {/* Tools and content for the active building */} - {isBuildingActive && ( -
-
- - -
-
- -
-
- )} -
+ + {isBuildingActive && ( + +
+
+ + +
+
+ + +
+
+
+ )} +
+ ); } @@ -1064,61 +1339,77 @@ export function SitePanel() { .filter((node): node is BuildingNode => node?.type === "building"); return ( -
- {/* Site Header */} - {siteNode && ( -
setPhase("site")} - > -
- Site +
+ {/* Site Header */} + {siteNode && ( + setPhase("site")} + > +
+ Site + {siteNode.name || "Site"} +
+ - {siteNode.name || "Site"} -
- -
- )} - -
- {/* When phase is site, show property line immediately under site header */} - {phase === "site" &&
} - - {/* Buildings List */} - {buildings.length === 0 ? ( -
- No buildings yet -
- ) : ( -
- {buildings.map((building) => { - const isBuildingActive = (phase === "structure" || phase === "furnish") && selectedBuildingId === building.id; - - return ( - - ); - })} -
+ )} + + + {/* When phase is site, show property line immediately under site header */} + + {phase === "site" && ( + + + + )} + + + {/* Buildings List */} + {buildings.length === 0 ? ( + + No buildings yet + + ) : ( + + {buildings.map((building) => { + const isBuildingActive = (phase === "structure" || phase === "furnish") && selectedBuildingId === building.id; + + return ( + + ); + })} + + )} +
-
+ ); } diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/item-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/item-tree-node.tsx index 27704b7e..3204e3a3 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/item-tree-node.tsx @@ -2,8 +2,9 @@ import { type AnyNodeId, ItemNode, useScene } from "@pascal-app/core"; import { useViewer } from "@pascal-app/viewer"; import Image from "next/image"; import { useState, useEffect } from "react"; +import useEditor from "@/store/use-editor"; import { InlineRenameInput } from "./inline-rename-input"; -import { TreeNode, TreeNodeWrapper } from "./tree-node"; +import { TreeNode, TreeNodeWrapper, handleTreeSelection } from "./tree-node"; import { TreeNodeActions } from "./tree-node-actions"; const CATEGORY_ICONS: Record = { @@ -19,9 +20,10 @@ const CATEGORY_ICONS: Record = { interface ItemTreeNodeProps { node: ItemNode; depth: number; + isLast?: boolean; } -export function ItemTreeNode({ node, depth }: ItemTreeNodeProps) { +export function ItemTreeNode({ node, depth, isLast }: ItemTreeNodeProps) { const [isEditing, setIsEditing] = useState(false); const [expanded, setExpanded] = useState(true); const iconSrc = CATEGORY_ICONS[node.asset.category] || "/icons/couch.png"; @@ -51,8 +53,12 @@ export function ItemTreeNode({ node, depth }: ItemTreeNodeProps) { } }, [selectedIds, node.id]); - const handleClick = () => { - setSelection({ selectedIds: [node.id] }); + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const handled = handleTreeSelection(e, node.id, selectedIds, setSelection); + if (!handled && useEditor.getState().phase === "structure") { + useEditor.getState().setPhase("furnish"); + } }; const handleDoubleClick = () => { @@ -72,6 +78,7 @@ export function ItemTreeNode({ node, depth }: ItemTreeNodeProps) { return ( } label={ } > - {hasChildren && node.children.map((childId) => ( - + {hasChildren && node.children.map((childId, index) => ( + ))} ); diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/level-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/level-tree-node.tsx index 1bab93de..159ef72e 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/level-tree-node.tsx @@ -9,9 +9,10 @@ import { TreeNodeActions } from "./tree-node-actions"; interface LevelTreeNodeProps { node: LevelNode; depth: number; + isLast?: boolean; } -export function LevelTreeNode({ node, depth }: LevelTreeNodeProps) { +export function LevelTreeNode({ node, depth, isLast }: LevelTreeNodeProps) { const [expanded, setExpanded] = useState(true); const [isEditing, setIsEditing] = useState(false); const isSelected = useViewer((state) => state.selection.levelId === node.id); @@ -48,10 +49,11 @@ export function LevelTreeNode({ node, depth }: LevelTreeNodeProps) { onDoubleClick={handleDoubleClick} isSelected={isSelected} isHovered={isHovered} + isLast={isLast} actions={} > - {node.children.map((childId) => ( - + {node.children.map((childId, index) => ( + ))} ); diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/references-dialog.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/references-dialog.tsx deleted file mode 100644 index 949c084f..00000000 --- a/apps/editor/components/ui/sidebar/panels/site-panel/references-dialog.tsx +++ /dev/null @@ -1,265 +0,0 @@ -'use client' - -import { - type AnyNodeId, - type GuideNode, - GuideNode as GuideNodeSchema, - type LevelNode, - type ScanNode, - ScanNode as ScanNodeSchema, - useScene, -} from '@pascal-app/core' -import { Box, Image, Pencil, Plus, Trash2 } from 'lucide-react' -import { useCallback, useRef, useState } from 'react' -import useEditor from '@/store/use-editor' -import { deleteProjectAssetByUrl, uploadProjectAsset } from '@/features/community/lib/assets/actions' -import { useProjectStore } from '@/features/community/lib/projects/store' -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from '@/components/ui/primitives/dialog' -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@/components/ui/primitives/popover' - -const MAX_FILE_SIZE = 100 * 1024 * 1024 // 100MB — matches server action bodySizeLimit - -interface ReferencesDialogProps { - levelId: string - open: boolean - onOpenChange: (open: boolean) => void -} - -export function ReferencesDialog({ levelId, open, onOpenChange }: ReferencesDialogProps) { - const nodes = useScene((s) => s.nodes) - const createNode = useScene((s) => s.createNode) - const deleteNode = useScene((s) => s.deleteNode) - const setSelectedReferenceId = useEditor((s) => s.setSelectedReferenceId) - const activeProject = useProjectStore((s) => s.activeProject) - const [uploadError, setUploadError] = useState(null) - const [uploading, setUploading] = useState(false) - - const scanInputRef = useRef(null) - const guideInputRef = useRef(null) - - const handleAddScan = useCallback( - async (e: React.ChangeEvent) => { - const file = e.target.files?.[0] - if (!file) return - e.target.value = '' - - const projectId = activeProject?.id - if (!projectId) { - setUploadError('No active project. Please open a project first.') - return - } - - if (file.size > MAX_FILE_SIZE) { - setUploadError(`File is too large (${(file.size / 1024 / 1024).toFixed(0)} MB). Maximum size is 100 MB.`) - return - } - - setUploadError(null) - setUploading(true) - const result = await uploadProjectAsset(projectId, file, 'scan') - setUploading(false) - - if (!result.success) { - setUploadError(result.error) - return - } - - const node = ScanNodeSchema.parse({ - url: result.url, - name: file.name, - parentId: levelId, - }) - createNode(node, levelId as AnyNodeId) - // Auto-select and close dialog - setSelectedReferenceId(node.id) - onOpenChange(false) - }, - [levelId, createNode, setSelectedReferenceId, onOpenChange, activeProject], - ) - - const handleAddGuide = useCallback( - async (e: React.ChangeEvent) => { - const file = e.target.files?.[0] - if (!file) return - e.target.value = '' - - const projectId = activeProject?.id - if (!projectId) { - setUploadError('No active project. Please open a project first.') - return - } - - if (file.size > MAX_FILE_SIZE) { - setUploadError(`File is too large (${(file.size / 1024 / 1024).toFixed(0)} MB). Maximum size is 100 MB.`) - return - } - - setUploadError(null) - setUploading(true) - const result = await uploadProjectAsset(projectId, file, 'guide') - setUploading(false) - - if (!result.success) { - setUploadError(result.error) - return - } - - const node = GuideNodeSchema.parse({ - url: result.url, - name: file.name, - parentId: levelId, - }) - createNode(node, levelId as AnyNodeId) - // Auto-select and close dialog - setSelectedReferenceId(node.id) - onOpenChange(false) - }, - [levelId, createNode, setSelectedReferenceId, onOpenChange, activeProject], - ) - - const handleEdit = useCallback( - (nodeId: string) => { - setSelectedReferenceId(nodeId) - onOpenChange(false) - }, - [setSelectedReferenceId, onOpenChange], - ) - - const handleDelete = useCallback( - async (nodeId: string) => { - const refNode = nodes[nodeId as AnyNodeId] as ScanNode | GuideNode | undefined - const projectId = activeProject?.id - - // Delete storage asset first (before removing from scene) - if ( - projectId && - refNode?.url && - (refNode.url.startsWith('http://') || refNode.url.startsWith('https://')) - ) { - const result = await deleteProjectAssetByUrl(projectId, refNode.url) - if (!result.success) { - setUploadError(`Failed to delete asset: ${result.error}`) - return - } - } - - deleteNode(nodeId as AnyNodeId) - }, - [deleteNode, nodes, activeProject], - ) - - const level = nodes[levelId as AnyNodeId] as LevelNode | undefined - if (!level) return null - - // Find all scan and guide children of this level - const references = Object.values(nodes).filter( - (node): node is ScanNode | GuideNode => - (node.type === 'scan' || node.type === 'guide') && node.parentId === levelId, - ) - - return ( - - - - References — {level.name || `Level ${level.level}`} - - -
- {references.length === 0 && ( -

- No references yet. Add a 3D scan or guide image. -

- )} - - {references.map((ref) => ( -
- {ref.type === 'scan' ? ( - - ) : ( - - )} - - {ref.name || (ref.type === 'scan' ? '3D Scan' : 'Guide Image')} - - - -
- ))} -
- - {uploadError && ( -

{uploadError}

- )} - -
- - - - - - - - - - - - -
-
-
- ) -} diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx index 371fe068..9beb9eff 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx @@ -2,24 +2,31 @@ import { RoofNode } from "@pascal-app/core"; import { useViewer } from "@pascal-app/viewer"; import Image from "next/image"; import { useState } from "react"; +import useEditor from "@/store/use-editor"; import { InlineRenameInput } from "./inline-rename-input"; -import { TreeNodeWrapper } from "./tree-node"; +import { TreeNodeWrapper, handleTreeSelection } from "./tree-node"; import { TreeNodeActions } from "./tree-node-actions"; interface RoofTreeNodeProps { node: RoofNode; depth: number; + isLast?: boolean; } -export function RoofTreeNode({ node, depth }: RoofTreeNodeProps) { +export function RoofTreeNode({ node, depth, isLast }: RoofTreeNodeProps) { const [isEditing, setIsEditing] = useState(false); - const isSelected = useViewer((state) => state.selection.selectedIds.includes(node.id)); + const selectedIds = useViewer((state) => state.selection.selectedIds); + const isSelected = selectedIds.includes(node.id); const isHovered = useViewer((state) => state.hoveredId === node.id); const setSelection = useViewer((state) => state.setSelection); const setHoveredId = useViewer((state) => state.setHoveredId); - const handleClick = () => { - setSelection({ selectedIds: [node.id] }); + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const handled = handleTreeSelection(e, node.id, selectedIds, setSelection); + if (!handled && useEditor.getState().phase === "furnish") { + useEditor.getState().setPhase("structure"); + } }; const handleDoubleClick = () => { @@ -41,6 +48,7 @@ export function RoofTreeNode({ node, depth }: RoofTreeNodeProps) { return ( } label={ } /> ); diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx index d720ac86..07d2e4e8 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx @@ -2,24 +2,31 @@ import { SlabNode } from "@pascal-app/core"; import { useViewer } from "@pascal-app/viewer"; import Image from "next/image"; import { useState } from "react"; +import useEditor from "@/store/use-editor"; import { InlineRenameInput } from "./inline-rename-input"; -import { TreeNodeWrapper } from "./tree-node"; +import { TreeNodeWrapper, handleTreeSelection } from "./tree-node"; import { TreeNodeActions } from "./tree-node-actions"; interface SlabTreeNodeProps { node: SlabNode; depth: number; + isLast?: boolean; } -export function SlabTreeNode({ node, depth }: SlabTreeNodeProps) { +export function SlabTreeNode({ node, depth, isLast }: SlabTreeNodeProps) { const [isEditing, setIsEditing] = useState(false); - const isSelected = useViewer((state) => state.selection.selectedIds.includes(node.id)); + const selectedIds = useViewer((state) => state.selection.selectedIds); + const isSelected = selectedIds.includes(node.id); const isHovered = useViewer((state) => state.hoveredId === node.id); const setSelection = useViewer((state) => state.setSelection); const setHoveredId = useViewer((state) => state.setHoveredId); - const handleClick = () => { - setSelection({ selectedIds: [node.id] }); + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const handled = handleTreeSelection(e, node.id, selectedIds, setSelection); + if (!handled && useEditor.getState().phase === "furnish") { + useEditor.getState().setPhase("structure"); + } }; const handleDoubleClick = () => { @@ -40,6 +47,7 @@ export function SlabTreeNode({ node, depth }: SlabTreeNodeProps) { return ( } label={ } /> ); diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/tree-node.tsx index 6e9044e4..51dde090 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/tree-node.tsx @@ -2,6 +2,53 @@ import { AnyNodeId, useScene } from "@pascal-app/core"; import { ChevronDown, ChevronRight } from "lucide-react"; import { forwardRef, useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; +import { motion, AnimatePresence } from "motion/react"; + +export function handleTreeSelection( + e: React.MouseEvent, + nodeId: string, + selectedIds: string[], + setSelection: (s: any) => void +) { + if (e.metaKey || e.ctrlKey) { + if (selectedIds.includes(nodeId)) { + setSelection({ selectedIds: selectedIds.filter((id) => id !== nodeId) }); + } else { + setSelection({ selectedIds: [...selectedIds, nodeId] }); + } + return true; + } + + if (e.shiftKey && selectedIds.length > 0) { + const lastSelectedId = selectedIds[selectedIds.length - 1]; + if (lastSelectedId) { + const nodes = Array.from(document.querySelectorAll('[data-treenode-id]')); + const nodeIds = nodes.map(n => n.getAttribute('data-treenode-id') as string); + + const startIndex = nodeIds.indexOf(lastSelectedId); + const endIndex = nodeIds.indexOf(nodeId); + + if (startIndex !== -1 && endIndex !== -1) { + const start = Math.min(startIndex, endIndex); + const end = Math.max(startIndex, endIndex); + const range = nodeIds.slice(start, end + 1); + + // We can keep the previous selections that were outside the range if we want, + // but standard file system shift-click replaces the selection with the range. + setSelection({ selectedIds: range }); + return true; + } + } + // Fallback: if range selection fails (e.g. node not visible in tree), just add to selection + if (!selectedIds.includes(nodeId)) { + setSelection({ selectedIds: [...selectedIds, nodeId] }); + return true; + } + } + + setSelection({ selectedIds: [nodeId] }); + return false; +} import { BuildingTreeNode } from "./building-tree-node"; import { CeilingTreeNode } from "./ceiling-tree-node"; import { DoorTreeNode } from "./door-tree-node"; @@ -16,47 +63,49 @@ import { ZoneTreeNode } from "./zone-tree-node"; interface TreeNodeProps { nodeId: AnyNodeId; depth?: number; + isLast?: boolean; } -export function TreeNode({ nodeId, depth = 0 }: TreeNodeProps) { +export function TreeNode({ nodeId, depth = 0, isLast }: TreeNodeProps) { const node = useScene((state) => state.nodes[nodeId]); if (!node) return null; switch (node.type) { case "building": - return ; + return ; case "ceiling": - return ; + return ; case "level": - return ; + return ; case "slab": - return ; + return ; case "wall": - return ; + return ; case "roof": - return ; + return ; case "item": - return ; + return ; case "door": - return ; + return ; case "window": - return ; + return ; case "zone": - return ; + return ; default: return null; } } interface TreeNodeWrapperProps { + nodeId?: string; icon: React.ReactNode; label: React.ReactNode; depth: number; hasChildren: boolean; expanded: boolean; onToggle: () => void; - onClick: () => void; + onClick: (e: React.MouseEvent) => void; onDoubleClick?: () => void; onMouseEnter?: () => void; onMouseLeave?: () => void; @@ -65,56 +114,80 @@ interface TreeNodeWrapperProps { isSelected?: boolean; isHovered?: boolean; isVisible?: boolean; + isLast?: boolean; } export const TreeNodeWrapper = forwardRef( - function TreeNodeWrapper( - { - icon, - label, - depth, - hasChildren, - expanded, - onToggle, - onClick, - onDoubleClick, - onMouseEnter, - onMouseLeave, - actions, - children, - isSelected, - isHovered, - isVisible = true, - }, - ref - ) { - const rowRef = useRef(null); + function TreeNodeWrapper( + { + nodeId, + icon, + label, + depth, + hasChildren, + expanded, + onToggle, + onClick, + onDoubleClick, + onMouseEnter, + onMouseLeave, + actions, + children, + isSelected, + isHovered, + isVisible = true, + isLast, + }, + ref + ) { + const rowRef = useRef(null); - useEffect(() => { - if (isSelected && rowRef.current) { - rowRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - } - }, [isSelected]); + useEffect(() => { + if (isSelected && rowRef.current) { + rowRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } + }, [isSelected]); - return ( -
-
-
)}
- {expanded && children} + + {expanded && children && ( + + {children} + + )} +
); } diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx index a6935926..c386ae99 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx @@ -2,16 +2,18 @@ import { type AnyNodeId, WallNode, useScene } from "@pascal-app/core"; import { useViewer } from "@pascal-app/viewer"; import Image from "next/image"; import { useState, useEffect } from "react"; +import useEditor from "@/store/use-editor"; import { InlineRenameInput } from "./inline-rename-input"; -import { TreeNode, TreeNodeWrapper } from "./tree-node"; +import { TreeNode, TreeNodeWrapper, handleTreeSelection } from "./tree-node"; import { TreeNodeActions } from "./tree-node-actions"; interface WallTreeNodeProps { node: WallNode; depth: number; + isLast?: boolean; } -export function WallTreeNode({ node, depth }: WallTreeNodeProps) { +export function WallTreeNode({ node, depth, isLast }: WallTreeNodeProps) { const [expanded, setExpanded] = useState(false); const [isEditing, setIsEditing] = useState(false); const selectedIds = useViewer((state) => state.selection.selectedIds); @@ -40,8 +42,12 @@ export function WallTreeNode({ node, depth }: WallTreeNodeProps) { } }, [selectedIds, node.id]); - const handleClick = () => { - setSelection({ selectedIds: [node.id] }); + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + const handled = handleTreeSelection(e, node.id, selectedIds, setSelection); + if (!handled && useEditor.getState().phase === "furnish") { + useEditor.getState().setPhase("structure"); + } }; const handleDoubleClick = () => { @@ -60,6 +66,7 @@ export function WallTreeNode({ node, depth }: WallTreeNodeProps) { return ( } label={ } > - {node.children.map((childId) => ( - + {node.children.map((childId, index) => ( + ))} ); diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/window-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/window-tree-node.tsx index 285f6223..27983eda 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/window-tree-node.tsx @@ -4,18 +4,21 @@ import { WindowNode } from "@pascal-app/core" import { useViewer } from "@pascal-app/viewer" import Image from "next/image" import { useState } from "react" +import useEditor from "@/store/use-editor" import { InlineRenameInput } from "./inline-rename-input" -import { TreeNodeWrapper } from "./tree-node" +import { TreeNodeWrapper, handleTreeSelection } from "./tree-node" import { TreeNodeActions } from "./tree-node-actions" interface WindowTreeNodeProps { node: WindowNode depth: number + isLast?: boolean } -export function WindowTreeNode({ node, depth }: WindowTreeNodeProps) { +export function WindowTreeNode({ node, depth, isLast }: WindowTreeNodeProps) { const [isEditing, setIsEditing] = useState(false) - const isSelected = useViewer((state) => state.selection.selectedIds.includes(node.id)) + const selectedIds = useViewer((state) => state.selection.selectedIds) + const isSelected = selectedIds.includes(node.id) const isHovered = useViewer((state) => state.hoveredId === node.id) const setSelection = useViewer((state) => state.setSelection) const setHoveredId = useViewer((state) => state.setHoveredId) @@ -24,6 +27,7 @@ export function WindowTreeNode({ node, depth }: WindowTreeNodeProps) { return ( } label={ {}} - onClick={() => setSelection({ selectedIds: [node.id] })} + onClick={(e: React.MouseEvent) => { + e.stopPropagation() + const handled = handleTreeSelection(e, node.id, selectedIds, setSelection) + if (!handled && useEditor.getState().phase === "furnish") { + useEditor.getState().setPhase("structure") + } + }} onDoubleClick={() => setIsEditing(true)} onMouseEnter={() => setHoveredId(node.id)} onMouseLeave={() => setHoveredId(null)} isSelected={isSelected} isHovered={isHovered} isVisible={node.visible !== false} + isLast={isLast} actions={} /> ) diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx index 2879717b..f4186290 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx @@ -8,9 +8,10 @@ import { TreeNodeActions } from "./tree-node-actions"; interface ZoneTreeNodeProps { node: ZoneNode; depth: number; + isLast?: boolean; } -export function ZoneTreeNode({ node, depth }: ZoneTreeNodeProps) { +export function ZoneTreeNode({ node, depth, isLast }: ZoneTreeNodeProps) { const [isEditing, setIsEditing] = useState(false); const isSelected = useViewer((state) => state.selection.zoneId === node.id); const isHovered = useViewer((state) => state.hoveredId === node.id); @@ -64,6 +65,7 @@ export function ZoneTreeNode({ node, depth }: ZoneTreeNodeProps) { onMouseLeave={handleMouseLeave} isSelected={isSelected} isHovered={isHovered} + isLast={isLast} actions={} /> ); diff --git a/apps/editor/features/community/components/community-hub.tsx b/apps/editor/features/community/components/community-hub.tsx index 4c2e235d..00eb1289 100644 --- a/apps/editor/features/community/components/community-hub.tsx +++ b/apps/editor/features/community/components/community-hub.tsx @@ -46,11 +46,7 @@ export default function CommunityHub() { } }, [isAuthenticated, authLoading]) - const handleProjectCreated = async (projectId: string) => { - const result = await getUserProjects() - if (result.success) { - setUserProjects(result.data || []) - } + const handleProjectCreated = (projectId: string) => { router.push(`/editor/${projectId}`) } diff --git a/apps/editor/features/community/components/new-project-dialog.tsx b/apps/editor/features/community/components/new-project-dialog.tsx index 5b5fcc63..65d3e75b 100644 --- a/apps/editor/features/community/components/new-project-dialog.tsx +++ b/apps/editor/features/community/components/new-project-dialog.tsx @@ -4,6 +4,7 @@ import { X } from 'lucide-react' import { useState } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/primitives/dialog' import { Switch } from '@/components/ui/primitives/switch' +import { useScene } from '@pascal-app/core' import { createProject } from '../lib/projects/actions' interface NewProjectDialogProps { @@ -30,7 +31,12 @@ export function NewProjectDialog({ open, onOpenChange, onSuccess }: NewProjectDi setIsCreating(true) try { - const result = await createProject({ name, isPrivate }) + // Get the default scene graph + useScene.getState().clearScene() + const { nodes, rootNodeIds } = useScene.getState() + const sceneGraph = { nodes, rootNodeIds } + + const result = await createProject({ name, isPrivate, sceneGraph }) if (result.success && result.data) { onOpenChange(false) diff --git a/apps/editor/features/community/lib/models/hooks.ts b/apps/editor/features/community/lib/models/hooks.ts index 7b7025a3..1c51abad 100644 --- a/apps/editor/features/community/lib/models/hooks.ts +++ b/apps/editor/features/community/lib/models/hooks.ts @@ -61,44 +61,48 @@ export function useProjectScene() { async function loadScene() { // Suppress auto-save for the store update caused by setScene/clearScene isLoadingSceneRef.current = true + + useProjectStore.getState().setIsSceneLoading(true) try { + useScene.getState().clearScene() + const result = await getProjectModel(projectId || '') if (result.success && result.data?.scene_graph) { // Load the scene graph into the store const { nodes, rootNodeIds } = result.data.scene_graph useScene.getState().setScene(nodes, rootNodeIds) - - // Auto-select the first building + level after store is updated - const sceneNodes = useScene.getState().nodes as Record - const sceneRootIds = useScene.getState().rootNodeIds - const siteNode = sceneRootIds[0] ? sceneNodes[sceneRootIds[0]] : null - const resolve = (child: any) => - typeof child === 'string' ? sceneNodes[child] : child - const firstBuilding = siteNode?.children?.map(resolve).find((n: any) => n?.type === 'building') - const firstLevel = firstBuilding?.children?.map(resolve).find((n: any) => n?.type === 'level') - - if (firstBuilding && firstLevel) { - useViewer.getState().setSelection({ - buildingId: firstBuilding.id, - levelId: firstLevel.id, - selectedIds: [], - zoneId: null, - }) - useEditor.getState().setPhase('structure') - } else { - useEditor.getState().setPhase('site') - useViewer.getState().setSelection({ - buildingId: null, - levelId: null, - selectedIds: [], - zoneId: null, - }) - } } else { // No scene found - clear the scene useScene.getState().clearScene() + } + + // Auto-select the first building + level after store is updated + const sceneNodes = useScene.getState().nodes as Record + const sceneRootIds = useScene.getState().rootNodeIds + const siteNode = sceneRootIds[0] ? sceneNodes[sceneRootIds[0]] : null + const resolve = (child: any) => + typeof child === 'string' ? sceneNodes[child] : child + const firstBuilding = siteNode?.children?.map(resolve).find((n: any) => n?.type === 'building') + const firstLevel = firstBuilding?.children?.map(resolve).find((n: any) => n?.type === 'level') + + if (firstBuilding && firstLevel) { + useViewer.getState().setSelection({ + buildingId: firstBuilding.id, + levelId: firstLevel.id, + selectedIds: [], + zoneId: null, + }) + useEditor.getState().setPhase('structure') + useEditor.getState().setStructureLayer('elements') + + // Auto-select the wall tool if the level is empty (e.g., brand new project) + if (!firstLevel.children || firstLevel.children.length === 0) { + useEditor.getState().setMode('build') + useEditor.getState().setTool('wall') + } + } else { useEditor.getState().setPhase('site') useViewer.getState().setSelection({ buildingId: null, @@ -110,13 +114,42 @@ export function useProjectScene() { } catch (error) { // Fall back to clear scene useScene.getState().clearScene() - useEditor.getState().setPhase('site') - useViewer.getState().setSelection({ - buildingId: null, - levelId: null, - selectedIds: [], - zoneId: null, - }) + + // Auto-select the first building + level from the cleared scene + const sceneNodes = useScene.getState().nodes as Record + const sceneRootIds = useScene.getState().rootNodeIds + const siteNode = sceneRootIds[0] ? sceneNodes[sceneRootIds[0]] : null + const resolve = (child: any) => + typeof child === 'string' ? sceneNodes[child] : child + const firstBuilding = siteNode?.children?.map(resolve).find((n: any) => n?.type === 'building') + const firstLevel = firstBuilding?.children?.map(resolve).find((n: any) => n?.type === 'level') + + if (firstBuilding && firstLevel) { + useViewer.getState().setSelection({ + buildingId: firstBuilding.id, + levelId: firstLevel.id, + selectedIds: [], + zoneId: null, + }) + useEditor.getState().setPhase('structure') + useEditor.getState().setStructureLayer('elements') + + // Auto-select the wall tool if the level is empty (e.g., brand new project) + if (!firstLevel.children || firstLevel.children.length === 0) { + useEditor.getState().setMode('build') + useEditor.getState().setTool('wall') + } + } else { + useEditor.getState().setPhase('site') + useViewer.getState().setSelection({ + buildingId: null, + levelId: null, + selectedIds: [], + zoneId: null, + }) + } + } finally { + useProjectStore.getState().setIsSceneLoading(false) } // Allow auto-save again after a tick (let the store update propagate) diff --git a/apps/editor/features/community/lib/projects/store.ts b/apps/editor/features/community/lib/projects/store.ts index 1695f954..9b878a46 100644 --- a/apps/editor/features/community/lib/projects/store.ts +++ b/apps/editor/features/community/lib/projects/store.ts @@ -15,12 +15,14 @@ interface ProjectStore { activeProject: Project | null projects: Project[] isLoading: boolean + isSceneLoading: boolean error: string | null // Actions fetchProjects: () => Promise fetchActiveProject: () => Promise setActiveProject: (projectId: string) => Promise + setIsSceneLoading: (loading: boolean) => void initialize: () => Promise updateActiveThumbnail: (thumbnailUrl: string) => void } @@ -30,6 +32,7 @@ export const useProjectStore = create((set, get) => ({ activeProject: null, projects: [], isLoading: true, + isSceneLoading: false, error: null, // Fetch all projects @@ -79,6 +82,10 @@ export const useProjectStore = create((set, get) => ({ } }, + setIsSceneLoading: (loading: boolean) => { + set({ isSceneLoading: loading }) + }, + // Patch the active project's thumbnail URL in place (no refetch) updateActiveThumbnail: (thumbnailUrl: string) => { set((state) => ({ diff --git a/apps/editor/hooks/use-keyboard.ts b/apps/editor/hooks/use-keyboard.ts index d7616c5d..64da7074 100644 --- a/apps/editor/hooks/use-keyboard.ts +++ b/apps/editor/hooks/use-keyboard.ts @@ -16,6 +16,10 @@ export const useKeyboard = () => { e.preventDefault() emitter.emit('tool:cancel') + // Clear selections to close UI panels, but KEEP the active building and level context + useViewer.getState().setSelection({ selectedIds: [], zoneId: null }) + useEditor.getState().setSelectedReferenceId(null) + // If in build mode, switch back to select mode const { mode } = useEditor.getState() if (mode === 'build') { @@ -46,6 +50,36 @@ export const useKeyboard = () => { } else if (e.key === 'Z' && e.shiftKey && (e.metaKey || e.ctrlKey)) { e.preventDefault() useScene.temporal.getState().redo() + } else if (e.key === 'ArrowUp' && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + const { buildingId, levelId } = useViewer.getState().selection + if (buildingId) { + const building = useScene.getState().nodes[buildingId] + if (building && building.type === 'building' && building.children.length > 0) { + const currentIdx = levelId ? building.children.indexOf(levelId as any) : -1 + const nextIdx = currentIdx < building.children.length - 1 ? currentIdx + 1 : currentIdx + if (nextIdx !== -1 && nextIdx !== currentIdx) { + useViewer.getState().setSelection({ levelId: building.children[nextIdx] as any }) + } else if (currentIdx === -1) { + useViewer.getState().setSelection({ levelId: building.children[0] as any }) + } + } + } + } else if (e.key === 'ArrowDown' && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + const { buildingId, levelId } = useViewer.getState().selection + if (buildingId) { + const building = useScene.getState().nodes[buildingId] + if (building && building.type === 'building' && building.children.length > 0) { + const currentIdx = levelId ? building.children.indexOf(levelId as any) : -1 + const prevIdx = currentIdx > 0 ? currentIdx - 1 : currentIdx + if (prevIdx !== -1 && prevIdx !== currentIdx) { + useViewer.getState().setSelection({ levelId: building.children[prevIdx] as any }) + } else if (currentIdx === -1) { + useViewer.getState().setSelection({ levelId: building.children[building.children.length - 1] as any }) + } + } + } } else if (e.key === 'Delete' || e.key === 'Backspace') { e.preventDefault() diff --git a/apps/editor/package.json b/apps/editor/package.json index b7ef2fa5..c1cd4f89 100644 --- a/apps/editor/package.json +++ b/apps/editor/package.json @@ -29,38 +29,42 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@react-google-maps/api": "^2.20.8", - "@react-three/uikit-lucide": "^1.0.60", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.5.0", + "@react-three/uikit-lucide": "^1.0.62", "@repo/ui": "*", - "@supabase/supabase-js": "^2.95.3", + "@supabase/supabase-js": "^2.98.0", "@t3-oss/env-nextjs": "^0.13.10", - "@tailwindcss/postcss": "^4.1.18", - "@types/three": "^0.183.0", + "@tailwindcss/postcss": "^4.2.1", + "@types/three": "^0.183.1", "@vercel/analytics": "^1.6.1", "@vercel/speed-insights": "^1.3.1", "@vercel/toolbar": "^0.2.2", "@visual-json/react": "latest", - "better-auth": "^1.4.18", + "better-auth": "^1.4.19", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "howler": "^2.2.4", "lucide-react": "^0.562.0", - "motion": "^12.26.2", + "motion": "^12.34.3", "nanoid": "^5.1.6", - "next": "16.1.0", + "next": "16.1.6", "postcss": "^8.5.6", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "tailwind-merge": "^3.4.0", - "tailwindcss": "^4.1.18", - "zod": "^4.3.6" + "react": "^19.2.4", + "react-dom": "^19.2.4", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.1", + "three": "^0.183.1", + "zod": "^4.3.6", + "zustand": "^5.0.11" }, "devDependencies": { "@repo/typescript-config": "*", "@types/howler": "^2.2.12", - "@types/node": "^22.15.3", + "@types/node": "^22.19.12", "@types/react": "19.2.2", "@types/react-dom": "19.2.2", "tw-animate-css": "^1.4.0", - "typescript": "5.9.2" + "typescript": "5.9.3" } } diff --git a/apps/editor/public/cursor.svg b/apps/editor/public/cursor.svg new file mode 100644 index 00000000..d0611362 --- /dev/null +++ b/apps/editor/public/cursor.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/editor/public/icons/build.png b/apps/editor/public/icons/build.png new file mode 100644 index 00000000..cfaead98 Binary files /dev/null and b/apps/editor/public/icons/build.png differ diff --git a/apps/editor/public/icons/rotate.png b/apps/editor/public/icons/rotate.png new file mode 100644 index 00000000..4b840d2f Binary files /dev/null and b/apps/editor/public/icons/rotate.png differ diff --git a/apps/editor/public/icons/select.png b/apps/editor/public/icons/select.png new file mode 100644 index 00000000..9abd1db3 Binary files /dev/null and b/apps/editor/public/icons/select.png differ diff --git a/apps/editor/public/icons/topview.png b/apps/editor/public/icons/topview.png new file mode 100644 index 00000000..35c96248 Binary files /dev/null and b/apps/editor/public/icons/topview.png differ diff --git a/bun.lock b/bun.lock index eff6adff..5bb8e242 100644 --- a/bun.lock +++ b/bun.lock @@ -5,20 +5,14 @@ "": { "name": "editor", "dependencies": { - "@react-three/drei": "^10.7.7", - "@react-three/fiber": "^9.5.0", "portless": "^0.4.2", - "three": "^0.183.0", - "three-bvh-csg": "^0.0.17", - "three-mesh-bvh": "^0.9.8", - "zustand": "^5.0.11", }, "devDependencies": { - "@biomejs/biome": "^2.4.2", - "supabase": "2.75.3", - "turbo": "^2.8.10", - "typescript": "5.9.2", - "ultracite": "^7.2.3", + "@biomejs/biome": "^2.4.4", + "supabase": "2.76.15", + "turbo": "^2.8.11", + "typescript": "5.9.3", + "ultracite": "^7.2.4", }, }, "apps/editor": { @@ -43,39 +37,43 @@ "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", "@react-google-maps/api": "^2.20.8", - "@react-three/uikit-lucide": "^1.0.60", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.5.0", + "@react-three/uikit-lucide": "^1.0.62", "@repo/ui": "*", - "@supabase/supabase-js": "^2.95.3", + "@supabase/supabase-js": "^2.98.0", "@t3-oss/env-nextjs": "^0.13.10", - "@tailwindcss/postcss": "^4.1.18", - "@types/three": "^0.183.0", + "@tailwindcss/postcss": "^4.2.1", + "@types/three": "^0.183.1", "@vercel/analytics": "^1.6.1", "@vercel/speed-insights": "^1.3.1", "@vercel/toolbar": "^0.2.2", "@visual-json/react": "latest", - "better-auth": "^1.4.18", + "better-auth": "^1.4.19", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "howler": "^2.2.4", "lucide-react": "^0.562.0", - "motion": "^12.26.2", + "motion": "^12.34.3", "nanoid": "^5.1.6", - "next": "16.1.0", + "next": "16.1.6", "postcss": "^8.5.6", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "tailwind-merge": "^3.4.0", - "tailwindcss": "^4.1.18", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.1", + "three": "^0.183.1", "zod": "^4.3.6", + "zustand": "^5.0.11", }, "devDependencies": { "@repo/typescript-config": "*", "@types/howler": "^2.2.12", - "@types/node": "^22.15.3", + "@types/node": "^22.19.12", "@types/react": "19.2.2", "@types/react-dom": "19.2.2", "tw-animate-css": "^1.4.0", - "typescript": "5.9.2", + "typescript": "5.9.3", }, }, "packages/auth": { @@ -88,7 +86,7 @@ }, "devDependencies": { "@repo/typescript-config": "*", - "typescript": "5.9.2", + "typescript": "5.9.3", }, }, "packages/core": { @@ -99,7 +97,7 @@ "idb-keyval": "^6.2.2", "mitt": "^3.0.1", "nanoid": "^5.1.6", - "three-bvh-csg": "^0.0.17", + "three-bvh-csg": "^0.0.18", "three-mesh-bvh": "^0.9.8", "zod": "^4.3.5", "zundo": "^2.3.0", @@ -109,7 +107,7 @@ "@repo/typescript-config": "*", "@types/react": "^19.2.2", "@types/three": "^0.183.0", - "typescript": "5.9.2", + "typescript": "5.9.3", }, "peerDependencies": { "@react-three/drei": "^10", @@ -123,15 +121,15 @@ "version": "0.0.0", "dependencies": { "@supabase/supabase-js": "^2.95.3", - "drizzle-orm": "^0.39.0", - "drizzle-zod": "^0.5.1", + "drizzle-orm": "^0.45.1", + "drizzle-zod": "^0.8.3", "nanoid": "^5.0.9", "postgres": "^3.4.5", }, "devDependencies": { "@repo/typescript-config": "*", - "drizzle-kit": "^0.30.0", - "typescript": "5.9.2", + "drizzle-kit": "^0.31.9", + "typescript": "5.9.3", }, }, "packages/eslint-config": { @@ -169,7 +167,7 @@ "@types/react": "19.2.2", "@types/react-dom": "19.2.2", "eslint": "^9.39.1", - "typescript": "5.9.2", + "typescript": "5.9.3", }, }, "packages/viewer": { @@ -182,7 +180,7 @@ "@repo/typescript-config": "*", "@types/react": "^19.2.2", "@types/three": "^0.183.0", - "typescript": "5.9.2", + "typescript": "5.9.3", }, "peerDependencies": { "@pascal-app/core": "^0.1.4", @@ -198,31 +196,31 @@ "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], - "@better-auth/core": ["@better-auth/core@1.4.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.3.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.8", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-q+awYgC7nkLEBdx2sW0iJjkzgSHlIxGnOpsN1r/O1+a4m7osJNHtfK2mKJSL1I+GfNyIlxJF8WvD/NLuYMpmcg=="], + "@better-auth/core": ["@better-auth/core@1.4.19", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.3.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.8", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-uADLHG1jc5BnEJi7f6ijUN5DmPPRSj++7m/G19z3UqA3MVCo4Y4t1MMa4IIxLCqGDFv22drdfxescgW+HnIowA=="], - "@better-auth/telemetry": ["@better-auth/telemetry@1.4.18", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.18" } }, "sha512-e5rDF8S4j3Um/0LIVATL2in9dL4lfO2fr2v1Wio4qTMRbfxqnUDTa+6SZtwdeJrbc4O+a3c+IyIpjG9Q/6GpfQ=="], + "@better-auth/telemetry": ["@better-auth/telemetry@1.4.19", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.19" } }, "sha512-ApGNS7olCTtDpKF8Ow3Z+jvFAirOj7c4RyFUpu8axklh3mH57ndpfUAUjhgA8UVoaaH/mnm/Tl884BlqiewLyw=="], "@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="], "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="], - "@biomejs/biome": ["@biomejs/biome@2.4.2", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.2", "@biomejs/cli-darwin-x64": "2.4.2", "@biomejs/cli-linux-arm64": "2.4.2", "@biomejs/cli-linux-arm64-musl": "2.4.2", "@biomejs/cli-linux-x64": "2.4.2", "@biomejs/cli-linux-x64-musl": "2.4.2", "@biomejs/cli-win32-arm64": "2.4.2", "@biomejs/cli-win32-x64": "2.4.2" }, "bin": { "biome": "bin/biome" } }, "sha512-vVE/FqLxNLbvYnFDYg3Xfrh1UdFhmPT5i+yPT9GE2nTUgI4rkqo5krw5wK19YHBd7aE7J6r91RRmb8RWwkjy6w=="], + "@biomejs/biome": ["@biomejs/biome@2.4.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.4", "@biomejs/cli-darwin-x64": "2.4.4", "@biomejs/cli-linux-arm64": "2.4.4", "@biomejs/cli-linux-arm64-musl": "2.4.4", "@biomejs/cli-linux-x64": "2.4.4", "@biomejs/cli-linux-x64-musl": "2.4.4", "@biomejs/cli-win32-arm64": "2.4.4", "@biomejs/cli-win32-x64": "2.4.4" }, "bin": { "biome": "bin/biome" } }, "sha512-tigwWS5KfJf0cABVd52NVaXyAVv4qpUXOWJ1rxFL8xF1RVoeS2q/LK+FHgYoKMclJCuRoCWAPy1IXaN9/mS61Q=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3pEcKCP/1POKyaZZhXcxFl3+d9njmeAihZ17k8lL/1vk+6e0Cbf0yPzKItFiT+5Yh6TQA4uKvnlqe0oVZwRxCA=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jZ+Xc6qvD6tTH5jM6eKX44dcbyNqJHssfl2nnwT6vma6B1sj7ZLTGIk6N5QwVBs5xGN52r3trk5fgd3sQ9We9A=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-P7hK1jLVny+0R9UwyGcECxO6sjETxfPyBm/1dmFjnDOHgdDPjPqozByunrwh4xPKld8sxOr5eAsSqal5uKgeBg=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-Dh1a/+W+SUCXhEdL7TiX3ArPTFCQKJTI1mGncZNWfO+6suk+gYA4lNyJcBB+pwvF49uw0pEbUS49BgYOY4hzUg=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-DI3Mi7GT2zYNgUTDEbSjl3e1KhoP76OjQdm8JpvZYZWtVDRyLd3w8llSr2TWk1z+U3P44kUBWY3X7H9MD1/DGQ=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-V/NFfbWhsUU6w+m5WYbBenlEAz8eYnSqRMDMAW3K+3v0tYVkNyZn8VU0XPxk/lOqNXLSCCrV7FmV/u3SjCBShg=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-/x04YK9+7erw6tYEcJv9WXoBHcULI/wMOvNdAyE9S3JStZZ9yJyV67sWAI+90UHuDo/BDhq0d96LDqGlSVv7WA=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+sPAXq3bxmFwhVFJnSwkSF5Rw2ZAJMH3MF6C9IveAEOdSpgajPhoQhbbAK12SehN9j2QrHpk4J/cHsa/HqWaYQ=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-GK2ErnrKpWFigYP68cXiCHK4RTL4IUWhK92AFS3U28X/nuAL5+hTuy6hyobc8JZRSt+upXt1nXChK+tuHHx4mA=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-R4+ZCDtG9kHArasyBO+UBD6jr/FcFCTH8QkNTOCu0pRJzCWyWC4EtZa2AmUZB5h3e0jD7bRV2KvrENcf8rndBg=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.2", "", { "os": "linux", "cpu": "x64" }, "sha512-wbBmTkeAoAYbOQ33f6sfKG7pcRSydQiF+dTYOBjJsnXO2mWEOQHllKlC2YVnedqZFERp2WZhFUoO7TNRwnwEHQ=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gGvFTGpOIQDb5CQ2VC0n9Z2UEqlP46c4aNgHmAMytYieTGEcfqhfCFnhs6xjt0S3igE6q5GLuIXtdQt3Izok+g=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-k2uqwLYrNNxnaoiW3RJxoMGnbKda8FuCmtYG3cOtVljs3CzWxaTR+AoXwKGHscC9thax9R4kOrtWqWN0+KdPTw=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-trzCqM7x+Gn832zZHgr28JoYagQNX4CZkUZhMUac2YxvvyDRLJDrb5m9IA7CaZLlX6lTQmADVfLEKP1et1Ma4Q=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.2", "", { "os": "win32", "cpu": "x64" }, "sha512-9ma7C4g8Sq3cBlRJD2yrsHXB1mnnEBdpy7PhvFrylQWQb4PoyCmPucdX7frvsSBQuFtIiKCrolPl/8tCZrKvgQ=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.4", "", { "os": "win32", "cpu": "x64" }, "sha512-gnOHKVPFAAPrpoPt2t+Q6FZ7RPry/FDV3GcpU53P3PtLNnQjBmKyN2Vh/JtqXet+H4pme8CC76rScwdjDcT1/A=="], "@clack/core": ["@clack/core@1.0.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g=="], @@ -238,51 +236,57 @@ "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], @@ -390,25 +394,25 @@ "@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=="], - "@next/env": ["@next/env@16.1.0", "", {}, "sha512-Dd23XQeFHmhf3KBW76leYVkejHlCdB7erakC2At2apL1N08Bm+dLYNP+nNHh0tzUXfPQcNcXiQyacw0PG4Fcpw=="], + "@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="], "@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.5.9", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-kUzXx0iFiXw27cQAViE1yKWnz/nF8JzRmwgMRTMh8qMY90crNsdXJRh2e+R0vBpFR3kk1yvAR7wev7+fCCb79Q=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-onHq8dl8KjDb8taANQdzs3XmIqQWV3fYdslkGENuvVInFQzZnuBYYOG2HGHqqtvgmEU7xWzhgndXXxnhk4Z3fQ=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Am6VJTp8KhLuAH13tPrAoVIXzuComlZlMwGr++o2KDjWiKPe3VwpxYhgV6I4gKls2EnsIMggL4y7GdXyDdJcFA=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-fVicfaJT6QfghNyg8JErZ+EMNQ812IS0lmKfbmC01LF1nFBcKfcs4Q75Yy8IqnsCqH/hZwGhqzj3IGVfWV6vpA=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TojQnDRoX7wJWXEEwdfuJtakMDW64Q7NrxQPviUnfYJvAx5/5wcGE+1vZzQ9F17m+SdpFeeXuOr6v3jbyusYMQ=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-quhNFVySW4QwXiZkZ34SbfzNBm27vLrxZ2HwTfFFO1BBP0OY1+pI0nbyewKeq1FriqU+LZrob/cm26lwsiAi8Q=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-6JW0z2FZUK5iOVhUIWqE4RblAhUj1EwhZ/MwteGb//SpFTOHydnhbp3868gxalwea+mbOLWO6xgxj9wA9wNvNw=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-+DK/akkAvvXn5RdYN84IOmLkSy87SCmpofJPdB8vbLmf01BzntPBSYXnMvnEEv/Vcf3HYJwt24QZ/s6sWAwOMQ=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Tr0j94MphimCCks+1rtYPzQFK+faJuhHWCegU9S9gDlgyOk8Y3kPmO64UcjyzZAlligeBtYZ/2bEyrKq0d2wqQ=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A=="], "@noble/ciphers": ["@noble/ciphers@2.1.1", "", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="], @@ -432,13 +436,15 @@ "@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="], - "@pmndrs/msdfonts": ["@pmndrs/msdfonts@1.0.61", "", {}, "sha512-Nxk+3dAdsXFdx5pq1IHNe1j4zxogkAfhmBKUIHS+aJUs45q/XMtlADVrHLooiQ/Amlp9tXAvbvFYUOGd897POw=="], + "@pmndrs/msdfonts": ["@pmndrs/msdfonts@1.0.62", "", {}, "sha512-TGmAo0mFbdzPwwNCDzJoy6Khr3T/Z613ApwegYsRs6S+P+AeOSIS4+4aB5bL4G7hY6uNt5cyZVClESuEjYXt9g=="], - "@pmndrs/uikit": ["@pmndrs/uikit@1.0.61", "", { "dependencies": { "@pmndrs/msdfonts": "^1.0.61", "@pmndrs/uikit-pub-sub": "^1.0.61", "@preact/signals-core": "^1.5.1", "@zappar/msdf-generator": "^1.2.4", "yoga-layout": "^3.2.1" }, "peerDependencies": { "three": ">=0.162" } }, "sha512-xoNp2jKRoHa7INC3QaAL2ddpGmKy+eORMh4zoNds17EndqyBrqYzfWJK+PVmei/5MQNBTi0KrY2qxBGj6hB0cg=="], + "@pmndrs/pointer-events": ["@pmndrs/pointer-events@6.6.29", "", {}, "sha512-o4YD6VfJgDYjFgde/YyAw2X5KY454tdmOXrHGOvKTWJBHzkL90B5vH4rqmexwRVvaDfT3YLvVh/Dm5cBbgZXMg=="], - "@pmndrs/uikit-lucide": ["@pmndrs/uikit-lucide@1.0.61", "", { "dependencies": { "@pmndrs/uikit": "^1.0.61" } }, "sha512-pToRVLnOIuxs0RUm6UTa5HWgcDfhLATq1FtgKmzxiUuHVvYf4h8PIrFJswP8e/KeotzHvr5sTg/HIYvbvSo0/A=="], + "@pmndrs/uikit": ["@pmndrs/uikit@1.0.62", "", { "dependencies": { "@pmndrs/msdfonts": "^1.0.62", "@pmndrs/uikit-pub-sub": "^1.0.62", "@preact/signals-core": "^1.5.1", "@zappar/msdf-generator": "^1.2.4", "yoga-layout": "^3.2.1" }, "peerDependencies": { "three": ">=0.162" } }, "sha512-TRV7Q1hKmGIFEzhJYF4xaXowdgqGUQd+XKcpEfyvwShOWEWqiDAYoI5OYjDQPhVVshI2frb5XVPr6acGf2laFQ=="], - "@pmndrs/uikit-pub-sub": ["@pmndrs/uikit-pub-sub@1.0.61", "", { "dependencies": { "@preact/signals-core": "^1.8.0" } }, "sha512-J1r5faMzwoWgAsDTW/0B+M96BuZgCyrvpM91/+QENnBfFCRR6xM2LIqI3KB/HlaO/1L8zQQHQE5g8HA2tVT4xw=="], + "@pmndrs/uikit-lucide": ["@pmndrs/uikit-lucide@1.0.62", "", { "dependencies": { "@pmndrs/uikit": "^1.0.62" } }, "sha512-aGgFJixWnYf+Gb3anQr04G3MaDYf82AQSZZFYO/Xzo1Y8QxaAjeiQdIgZtq7rrEWWxzJbVepHN9YgHyfLmBPMg=="], + + "@pmndrs/uikit-pub-sub": ["@pmndrs/uikit-pub-sub@1.0.62", "", { "dependencies": { "@preact/signals-core": "^1.8.0" } }, "sha512-E/8z5ZnqGfHgsgyUtMujJwRM11JSsZpxYnSxDDOZ+Q5FcK7aLSBCfT80Nm32EdytpN+xQ76mDI8ROvWYg3abxg=="], "@preact/signals-core": ["@preact/signals-core@1.12.1", "", {}, "sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA=="], @@ -532,9 +538,9 @@ "@react-three/fiber": ["@react-three/fiber@9.5.0", "", { "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-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA=="], - "@react-three/uikit": ["@react-three/uikit@1.0.61", "", { "dependencies": { "@pmndrs/uikit": "^1.0.61", "@preact/signals-core": "^1.5.1", "suspend-react": "^0.1.3", "zustand": "^5.0.6" }, "peerDependencies": { "@react-three/fiber": ">=8", "react": ">=18" } }, "sha512-rZrgev90X0UwXQ9CvBVYvoiMDW2UrIlQEoBERDG7BO3pdvXGENuMG9hmodGBGe9c8IC0REO9u7xerMWPBz8zyQ=="], + "@react-three/uikit": ["@react-three/uikit@1.0.62", "", { "dependencies": { "@pmndrs/pointer-events": "^6.6.29", "@pmndrs/uikit": "^1.0.62", "@preact/signals-core": "^1.5.1", "suspend-react": "^0.1.3", "zustand": "^5.0.6" }, "peerDependencies": { "@react-three/fiber": ">=8", "react": ">=18" } }, "sha512-2kvZSsIp/S6aTkEl9RT1rDV+p2S4fmIoUZTvHIWwpiENorQfARC/FZEC4tFzIRKty8+7Pa8J7h8twvkF3vWZ4A=="], - "@react-three/uikit-lucide": ["@react-three/uikit-lucide@1.0.61", "", { "dependencies": { "@pmndrs/uikit-lucide": "^1.0.61", "@react-three/uikit": "^1.0.61" } }, "sha512-iZc71VDl90sL14JjL6DqX6a0vJ0HKotFRHU4plIfauMcyLxmjhgQ9jQHKXUNwyvJtJhzNm+We7DUz5hCPODntQ=="], + "@react-three/uikit-lucide": ["@react-three/uikit-lucide@1.0.62", "", { "dependencies": { "@pmndrs/uikit-lucide": "^1.0.62", "@react-three/uikit": "^1.0.62" } }, "sha512-/iC9yjUwEHNFRPvTIIXiMIvek39XA2QpFZjVsr3XOwF3jmZ3IFlEOtjxZz0OYpxVZQSMAIqolPE78Ndim7pSZg=="], "@repo/eslint-config": ["@repo/eslint-config@workspace:packages/eslint-config"], @@ -546,17 +552,17 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@supabase/auth-js": ["@supabase/auth-js@2.95.3", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-vD2YoS8E2iKIX0F7EwXTmqhUpaNsmbU6X2R0/NdFcs02oEfnHyNP/3M716f3wVJ2E5XHGiTFXki6lRckhJ0Thg=="], + "@supabase/auth-js": ["@supabase/auth-js@2.98.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-GBH361T0peHU91AQNzOlIrjUZw9TZbB9YDRiyFgk/3Kvr3/Z1NWUZ2athWTfHhwNNi8IrW00foyFxQD9IO/Trg=="], - "@supabase/functions-js": ["@supabase/functions-js@2.95.3", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-uTuOAKzs9R/IovW1krO0ZbUHSJnsnyJElTXIRhjJTqymIVGcHzkAYnBCJqd7468Fs/Foz1BQ7Dv6DCl05lr7ig=="], + "@supabase/functions-js": ["@supabase/functions-js@2.98.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-N/xEyiNU5Org+d+PNCpv+TWniAXRzxIURxDYsS/m2I/sfAB/HcM9aM2Dmf5edj5oWb9GxID1OBaZ8NMmPXL+Lg=="], - "@supabase/postgrest-js": ["@supabase/postgrest-js@2.95.3", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-LTrRBqU1gOovxRm1vRXPItSMPBmEFqrfTqdPTRtzOILV4jPSueFz6pES5hpb4LRlkFwCPRmv3nQJ5N625V2Xrg=="], + "@supabase/postgrest-js": ["@supabase/postgrest-js@2.98.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-v6e9WeZuJijzUut8HyXu6gMqWFepIbaeaMIm1uKzei4yLg9bC9OtEW9O14LE/9ezqNbSAnSLO5GtOLFdm7Bpkg=="], - "@supabase/realtime-js": ["@supabase/realtime-js@2.95.3", "", { "dependencies": { "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-D7EAtfU3w6BEUxDACjowWNJo/ZRo7sDIuhuOGKHIm9FHieGeoJV5R6GKTLtga/5l/6fDr2u+WcW/m8I9SYmaIw=="], + "@supabase/realtime-js": ["@supabase/realtime-js@2.98.0", "", { "dependencies": { "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-rOWt28uGyFipWOSd+n0WVMr9kUXiWaa7J4hvyLCIHjRFqWm1z9CaaKAoYyfYMC1Exn3WT8WePCgiVhlAtWC2yw=="], - "@supabase/storage-js": ["@supabase/storage-js@2.95.3", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-4GxkJiXI3HHWjxpC3sDx1BVrV87O0hfX+wvJdqGv67KeCu+g44SPnII8y0LL/Wr677jB7tpjAxKdtVWf+xhc9A=="], + "@supabase/storage-js": ["@supabase/storage-js@2.98.0", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-tzr2mG+v7ILSAZSfZMSL9OPyIH4z1ikgQ8EcQTKfMRz4EwmlFt3UnJaGzSOxyvF5b+fc9So7qdSUWTqGgeLokQ=="], - "@supabase/supabase-js": ["@supabase/supabase-js@2.95.3", "", { "dependencies": { "@supabase/auth-js": "2.95.3", "@supabase/functions-js": "2.95.3", "@supabase/postgrest-js": "2.95.3", "@supabase/realtime-js": "2.95.3", "@supabase/storage-js": "2.95.3" } }, "sha512-Fukw1cUTQ6xdLiHDJhKKPu6svEPaCEDvThqCne3OaQyZvuq2qjhJAd91kJu3PXLG18aooCgYBaB6qQz35hhABg=="], + "@supabase/supabase-js": ["@supabase/supabase-js@2.98.0", "", { "dependencies": { "@supabase/auth-js": "2.98.0", "@supabase/functions-js": "2.98.0", "@supabase/postgrest-js": "2.98.0", "@supabase/realtime-js": "2.98.0", "@supabase/storage-js": "2.98.0" } }, "sha512-Ohc97CtInLwZyiSASz7tT9/Abm/vqnIbO9REp+PivVUII8UZsuI3bngRQnYgJdFoOIwvaEII1fX1qy8x0CyNiw=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -564,35 +570,35 @@ "@t3-oss/env-nextjs": ["@t3-oss/env-nextjs@0.13.10", "", { "dependencies": { "@t3-oss/env-core": "0.13.10" }, "peerDependencies": { "arktype": "^2.1.0", "typescript": ">=5.0.0", "valibot": "^1.0.0-beta.7 || ^1.0.0", "zod": "^3.24.0 || ^4.0.0" }, "optionalPeers": ["arktype", "typescript", "valibot", "zod"] }, "sha512-JfSA2WXOnvcc/uMdp31paMsfbYhhdvLLRxlwvrnlPE9bwM/n0Z+Qb9xRv48nPpvfMhOrkrTYw1I5Yc06WIKBJQ=="], - "@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="], + "@tailwindcss/node": ["@tailwindcss/node@4.2.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.1" } }, "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.18", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.18", "@tailwindcss/oxide-darwin-arm64": "4.1.18", "@tailwindcss/oxide-darwin-x64": "4.1.18", "@tailwindcss/oxide-freebsd-x64": "4.1.18", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", "@tailwindcss/oxide-linux-x64-musl": "4.1.18", "@tailwindcss/oxide-wasm32-wasi": "4.1.18", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" } }, "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.1", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.1", "@tailwindcss/oxide-darwin-arm64": "4.2.1", "@tailwindcss/oxide-darwin-x64": "4.2.1", "@tailwindcss/oxide-freebsd-x64": "4.2.1", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", "@tailwindcss/oxide-linux-x64-musl": "4.2.1", "@tailwindcss/oxide-wasm32-wasi": "4.2.1", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.18", "", { "os": "android", "cpu": "arm64" }, "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.18", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18", "", { "os": "linux", "cpu": "arm" }, "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.18", "", { "os": "linux", "cpu": "x64" }, "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.18", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.0", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.1", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.18", "", { "os": "win32", "cpu": "x64" }, "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ=="], - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="], + "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2.1", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.1", "@tailwindcss/oxide": "4.2.1", "postcss": "^8.5.6", "tailwindcss": "4.2.1" } }, "sha512-OEwGIBnXnj7zJeonOh6ZG9woofIjGrd2BORfvE5p9USYKDCZoQmfqLcfNiRWoJlRWLdNPn2IgVZuWAOM4iTYMw=="], "@tinyhttp/accepts": ["@tinyhttp/accepts@1.3.0", "", { "dependencies": { "es-mime-types": "^0.0.16", "negotiator": "^0.6.2" } }, "sha512-YaJ4EMgVUI6JHzWO14lr6vn/BLJEoFN4Sqd20l0/oBcLLENkP8gnPtX1jB7OhIu0AE40VCweAqvSP+0/pgzB1g=="], @@ -638,7 +644,7 @@ "@types/md5": ["@types/md5@2.3.6", "", {}, "sha512-WD69gNXtRBnpknfZcb4TRQ0XJQbUPZcai/Qdhmka3sxUR3Et8NrXoeAoknG/LghYHTf4ve795rInVYHBTQdNVA=="], - "@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], + "@types/node": ["@types/node@22.19.12", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-0QEp0aPJYSyf6RrTjDB7HlKgNMTY+V2C7ESTaVt6G9gQ0rPLzTGz7OF2NXTLR5vcy7HJEtIUsyWLsfX0kTqJBA=="], "@types/offscreencanvas": ["@types/offscreencanvas@2019.7.3", "", {}, "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A=="], @@ -740,7 +746,7 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.9.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg=="], - "better-auth": ["better-auth@1.4.18", "", { "dependencies": { "@better-auth/core": "1.4.18", "@better-auth/telemetry": "1.4.18", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.8", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.3.5" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-bnyifLWBPcYVltH3RhS7CM62MoelEqC6Q+GnZwfiDWNfepXoQZBjEvn4urcERC7NTKgKq5zNBM8rvPvRBa6xcg=="], + "better-auth": ["better-auth@1.4.19", "", { "dependencies": { "@better-auth/core": "1.4.19", "@better-auth/telemetry": "1.4.19", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.8", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.3.5" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-3RlZJcA0+NH25wYD85vpIGwW9oSTuEmLIaGbT8zg41w/Pa2hVWHKedjoUHHJtnzkBXzDb+CShkLnSw7IThDdqQ=="], "better-call": ["better-call@1.1.8", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-XMQ2rs6FNXasGNfMjzbyroSwKwYbZ/T3IxruSS6U2MJRsSYh3wYtG3o6H00ZlKZ/C/UPOAD97tqgQJNsxyeTXw=="], @@ -850,15 +856,15 @@ "draco3d": ["draco3d@1.5.7", "", {}, "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ=="], - "drizzle-kit": ["drizzle-kit@0.30.6", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-U4wWit0fyZuGuP7iNmRleQyK2V8wCuv57vf5l3MnG4z4fzNTjY/U13M8owyQ5RavqvqxBifWORaR3wIUzlN64g=="], + "drizzle-kit": ["drizzle-kit@0.31.9", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-GViD3IgsXn7trFyBUUHyTFBpH/FsHTxYJ66qdbVggxef4UBPHRYxQaRzYLTuekYnk9i5FIEL9pbBIwMqX/Uwrg=="], - "drizzle-orm": ["drizzle-orm@0.39.3", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-EZ8ZpYvDIvKU9C56JYLOmUskazhad+uXZCTCRN4OnRMsL+xAJ05dv1eCpAG5xzhsm1hqiuC5kAZUCS924u2DTw=="], + "drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="], - "drizzle-zod": ["drizzle-zod@0.5.1", "", { "peerDependencies": { "drizzle-orm": ">=0.23.13", "zod": "*" } }, "sha512-C/8bvzUH/zSnVfwdSibOgFjLhtDtbKYmkbPbUCq46QZyZCH6kODIMSOgZ8R7rVjoI+tCj3k06MRJMDqsIeoS4A=="], + "drizzle-zod": ["drizzle-zod@0.8.3", "", { "peerDependencies": { "drizzle-orm": ">=0.36.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww=="], "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=="], - "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], + "enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -888,7 +894,7 @@ "es-vary": ["es-vary@0.0.8", "", {}, "sha512-fiERjQiCHrXUAToNRT/sh7MtXnfei9n7cF9oVQRUEp9L5BGXsTKSPaXq8L+4v0c/ezfvuTWd/f0JSl5IBRUvSg=="], - "esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="], + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], @@ -962,7 +968,7 @@ "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], - "framer-motion": ["framer-motion@12.26.2", "", { "dependencies": { "motion-dom": "^12.26.2", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA=="], + "framer-motion": ["framer-motion@12.34.3", "", { "dependencies": { "motion-dom": "^12.34.3", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-v81ecyZKYO/DfpTwHivqkxSUBzvceOpoI+wLfgCgoUIKxlFKEXdg0oR9imxwXumT4SFy8vRk9xzJ5l3/Du/55Q=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -1150,29 +1156,29 @@ "lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="], - "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="], - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="], - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="], - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="], - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="], - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="], - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="], - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="], - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="], - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="], - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="], - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="], "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], @@ -1214,11 +1220,11 @@ "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="], - "motion": ["motion@12.26.2", "", { "dependencies": { "framer-motion": "^12.26.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-2Q6g0zK1gUJKhGT742DAe42LgietcdiJ3L3OcYAHCQaC1UkLnn6aC8S/obe4CxYTLAgid2asS1QdQ/blYfo5dw=="], + "motion": ["motion@12.34.3", "", { "dependencies": { "framer-motion": "^12.34.3", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-xZIkBGO7v/Uvm+EyaqYd+9IpXu0sZqLywVlGdCFrrMiaO9JI4Kx51mO9KlHSWwll+gZUVY5OJsWgYI5FywJ/tw=="], - "motion-dom": ["motion-dom@12.26.2", "", { "dependencies": { "motion-utils": "^12.24.10" } }, "sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw=="], + "motion-dom": ["motion-dom@12.34.3", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-sYgFe+pR9aIM7o4fhs2aXtOI+oqlUd33N9Yoxcgo1Fv7M20sRkHtCmzE/VRNIcq7uNJ+qio+Xubt1FXH3pQ+eQ=="], - "motion-utils": ["motion-utils@12.24.10", "", {}, "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww=="], + "motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -1230,7 +1236,7 @@ "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], - "next": ["next@16.1.0", "", { "dependencies": { "@next/env": "16.1.0", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.0", "@next/swc-darwin-x64": "16.1.0", "@next/swc-linux-arm64-gnu": "16.1.0", "@next/swc-linux-arm64-musl": "16.1.0", "@next/swc-linux-x64-gnu": "16.1.0", "@next/swc-linux-x64-musl": "16.1.0", "@next/swc-win32-arm64-msvc": "16.1.0", "@next/swc-win32-x64-msvc": "16.1.0", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Y+KbmDbefYtHDDQKLNrmzE/YYzG2msqo2VXhzh5yrJ54tx/6TmGdkR5+kP9ma7i7LwZpZMfoY3m/AoPPPKxtVw=="], + "next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="], "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], @@ -1342,9 +1348,9 @@ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], - "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], - "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], + "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], @@ -1458,7 +1464,7 @@ "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], - "supabase": ["supabase@2.75.3", "", { "dependencies": { "bin-links": "^6.0.0", "https-proxy-agent": "^7.0.2", "node-fetch": "^3.3.2", "tar": "7.5.7" }, "bin": { "supabase": "bin/supabase" } }, "sha512-j2uHJfK8TE7SxL6cd+kQixwkl8H5ASBkQfOEVjf3d3XS1Wixy4UO8WudDYNVriu1KuKzsNnkkSC/jTyHNXNrcQ=="], + "supabase": ["supabase@2.76.15", "", { "dependencies": { "bin-links": "^6.0.0", "https-proxy-agent": "^7.0.2", "node-fetch": "^3.3.2", "tar": "7.5.9" }, "bin": { "supabase": "bin/supabase" } }, "sha512-m69o1XPAzZaIWfQiEeT+KY/Ci3OSA663RyoH9xECbXSxhr7dsipLCpCqT1E4MCob0mMhHh/7A+Eltx4y1qSwiQ=="], "supercluster": ["supercluster@8.0.1", "", { "dependencies": { "kdbush": "^4.0.2" } }, "sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ=="], @@ -1468,17 +1474,17 @@ "suspend-react": ["suspend-react@0.1.3", "", { "peerDependencies": { "react": ">=17.0" } }, "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ=="], - "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], + "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], - "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], + "tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="], "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], - "tar": ["tar@7.5.7", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ=="], + "tar": ["tar@7.5.9", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg=="], "three": ["three@0.183.1", "", {}, "sha512-Psv6bbd3d/M/01MT2zZ+VmD0Vj2dbWTNhfe4CuSg7w5TuW96M3NOyCVuh9SZQ05CpGmD7NEcJhZw4GVjhCYxfQ=="], - "three-bvh-csg": ["three-bvh-csg@0.0.17", "", { "peerDependencies": { "three": ">=0.151.0", "three-mesh-bvh": ">=0.6.6" } }, "sha512-iEkHDF8GRfGM6593Cuw8SnF1vfENCp46gIAtRzuL4nGXGWPcR1sbTBwM9ptDONb5twqlZp5WkAKya5aBKe2qcA=="], + "three-bvh-csg": ["three-bvh-csg@0.0.18", "", { "peerDependencies": { "three": ">=0.179.0", "three-mesh-bvh": ">=0.9.7" } }, "sha512-M3GCZMmGFgASGuDf+YMamM83nVlD/vdwzVHcYbFxgW+g1S7/nKPiuY00YVHOMbjmJPh8mLevGZL65ItHUuGt2w=="], "three-mesh-bvh": ["three-mesh-bvh@0.9.8", "", { "peerDependencies": { "three": ">= 0.159.0" } }, "sha512-YphYvdXEZSXdz6iNdWJo1RB6qvSCRyiXPEVSvNU6xVWbLDOdSrfEIsJOpgFOnefdmVEvZ6M+sY0cjh9gl7MvdA=="], @@ -1502,19 +1508,19 @@ "tunnel-rat": ["tunnel-rat@0.1.2", "", { "dependencies": { "zustand": "^4.3.2" } }, "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ=="], - "turbo": ["turbo@2.8.10", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.10", "turbo-darwin-arm64": "2.8.10", "turbo-linux-64": "2.8.10", "turbo-linux-arm64": "2.8.10", "turbo-windows-64": "2.8.10", "turbo-windows-arm64": "2.8.10" }, "bin": { "turbo": "bin/turbo" } }, "sha512-OxbzDES66+x7nnKGg2MwBA1ypVsZoDTLHpeaP4giyiHSixbsiTaMyeJqbEyvBdp5Cm28fc+8GG6RdQtic0ijwQ=="], + "turbo": ["turbo@2.8.11", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.11", "turbo-darwin-arm64": "2.8.11", "turbo-linux-64": "2.8.11", "turbo-linux-arm64": "2.8.11", "turbo-windows-64": "2.8.11", "turbo-windows-arm64": "2.8.11" }, "bin": { "turbo": "bin/turbo" } }, "sha512-H+rwSHHPLoyPOSoHdmI1zY0zy0GGj1Dmr7SeJW+nZiWLz2nex8EJ+fkdVabxXFMNEux+aywI4Sae8EqhmnOv4A=="], - "turbo-darwin-64": ["turbo-darwin-64@2.8.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-A03fXh+B7S8mL3PbdhTd+0UsaGrhfyPkODvzBDpKRY7bbeac4MDFpJ7I+Slf2oSkCEeSvHKR7Z4U71uKRUfX7g=="], + "turbo-darwin-64": ["turbo-darwin-64@2.8.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-XKaCWaz4OCt77oYYvGCIRpvYD4c/aNaKjRkUpv+e8rN3RZb+5Xsyew4yRO+gaHdMIUhQznXNXfHlhs+/p7lIhA=="], - "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sidzowgWL3s5xCHLeqwC9M3s9M0i16W1nuQF3Mc7fPHpZ+YPohvcbVFBB2uoRRHYZg6yBnwD4gyUHKTeXfwtXA=="], + "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VvynLHGUNvQ9k7GZjRPSsRcK4VkioTfFb7O7liAk4nHKjEcMdls7GqxzjVWgJiKz3hWmQGaP9hRa9UUnhVWCxA=="], - "turbo-linux-64": ["turbo-linux-64@2.8.10", "", { "os": "linux", "cpu": "x64" }, "sha512-YK9vcpL3TVtqonB021XwgaQhY9hJJbKKUhLv16osxV0HkcQASQWUqR56yMge7puh6nxU67rQlTq1b7ksR1T3KA=="], + "turbo-linux-64": ["turbo-linux-64@2.8.11", "", { "os": "linux", "cpu": "x64" }, "sha512-cbSn37dcm+EmkQ7DD0euy7xV7o2el4GAOr1XujvkAyKjjNvQ+6QIUeDgQcwAx3D17zPpDvfDMJY2dLQadWnkmQ=="], - "turbo-linux-arm64": ["turbo-linux-arm64@2.8.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-3+j2tL0sG95iBJTm+6J8/45JsETQABPqtFyYjVjBbi6eVGdtNTiBmHNKrbvXRlQ3ZbUG75bKLaSSDHSEEN+btQ=="], + "turbo-linux-arm64": ["turbo-linux-arm64@2.8.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-+trymp2s2aBrhS04l6qFxcExzZ8ffndevuUB9c5RCeqsVpZeiWuGQlWNm5XjOmzoMayxRARZ5ma7yiWbGMiLqQ=="], - "turbo-windows-64": ["turbo-windows-64@2.8.10", "", { "os": "win32", "cpu": "x64" }, "sha512-hdeF5qmVY/NFgiucf8FW0CWJWtyT2QPm5mIsX0W1DXAVzqKVXGq+Zf+dg4EUngAFKjDzoBeN6ec2Fhajwfztkw=="], + "turbo-windows-64": ["turbo-windows-64@2.8.11", "", { "os": "win32", "cpu": "x64" }, "sha512-3kJjFSM4yw1n9Uzmi+XkAUgCae19l/bH6RJ442xo7mnZm0tpOjo33F+FYHoSVpIWVMd0HG0LDccyafPSdylQbA=="], - "turbo-windows-arm64": ["turbo-windows-arm64@2.8.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-QGdr/Q8LWmj+ITMkSvfiz2glf0d7JG0oXVzGL3jxkGqiBI1zXFj20oqVY0qWi+112LO9SVrYdpHS0E/oGFrMbQ=="], + "turbo-windows-arm64": ["turbo-windows-arm64@2.8.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-JOM4uF2vuLsJUvibdR6X9QqdZr6BhC6Nhlrw4LKFPsXZZI/9HHLoqAiYRpE4MuzIwldCH/jVySnWXrI1SKto0g=="], "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], @@ -1528,11 +1534,11 @@ "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], - "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript-eslint": ["typescript-eslint@8.53.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.53.0", "@typescript-eslint/parser": "8.53.0", "@typescript-eslint/typescript-estree": "8.53.0", "@typescript-eslint/utils": "8.53.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw=="], - "ultracite": ["ultracite@7.2.3", "", { "dependencies": { "@clack/prompts": "^1.0.1", "commander": "^14.0.3", "deepmerge": "^4.3.1", "glob": "^13.0.3", "jsonc-parser": "^3.3.1", "nypm": "^0.6.5" }, "peerDependencies": { "oxlint": "^1.0.0" }, "optionalPeers": ["oxlint"], "bin": { "ultracite": "dist/index.js" } }, "sha512-WKNS2sKAZe4BHu+JGbZebXvy/A1QagDaBnndrK/zwOJAze/mQ8jeHfdG2bPlv3qcJ5fdS3w2Kd7c/eIcH78HvA=="], + "ultracite": ["ultracite@7.2.4", "", { "dependencies": { "@clack/prompts": "^1.0.1", "commander": "^14.0.3", "deepmerge": "^4.3.1", "glob": "^13.0.3", "jsonc-parser": "^3.3.1", "nypm": "^0.6.5" }, "peerDependencies": { "oxlint": "^1.0.0" }, "optionalPeers": ["oxlint"], "bin": { "ultracite": "dist/index.js" } }, "sha512-3b2g2pwZMDSd+PBK8pxYCjEoYaqVSgXD22HS22BxR8GfbmRXJ3VpNu5Z5lNywIxZXsJleWdpGPHibOPYr/xmKg=="], "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], @@ -1586,8 +1592,6 @@ "zustand": ["zustand@5.0.11", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg=="], - "@better-auth/core/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], - "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -1618,8 +1622,6 @@ "@react-three/fiber/zustand": ["zustand@5.0.10", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg=="], - "@react-three/uikit/zustand": ["zustand@5.0.10", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg=="], - "@repo/ui/@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], @@ -1634,6 +1636,8 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@types/ws/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -1654,8 +1658,6 @@ "@vercel/toolbar/fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - "better-auth/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], - "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], diff --git a/package.json b/package.json index 4f177702..e176605a 100644 --- a/package.json +++ b/package.json @@ -27,20 +27,14 @@ "release:major": "gh workflow run release.yml -f package=both -f bump=major" }, "dependencies": { - "@react-three/drei": "^10.7.7", - "@react-three/fiber": "^9.5.0", - "portless": "^0.4.2", - "three": "^0.183.0", - "three-bvh-csg": "^0.0.17", - "three-mesh-bvh": "^0.9.8", - "zustand": "^5.0.11" + "portless": "^0.4.2" }, "devDependencies": { - "@biomejs/biome": "^2.4.2", - "supabase": "2.75.3", - "turbo": "^2.8.10", - "typescript": "5.9.2", - "ultracite": "^7.2.3" + "@biomejs/biome": "^2.4.4", + "supabase": "2.76.15", + "turbo": "^2.8.11", + "typescript": "5.9.3", + "ultracite": "^7.2.4" }, "engines": { "node": ">=18" diff --git a/packages/auth/package.json b/packages/auth/package.json index c354edfc..f1e951f6 100644 --- a/packages/auth/package.json +++ b/packages/auth/package.json @@ -19,6 +19,6 @@ }, "devDependencies": { "@repo/typescript-config": "*", - "typescript": "5.9.2" + "typescript": "5.9.3" } } diff --git a/packages/core/package.json b/packages/core/package.json index 85d35be2..b877f284 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,7 +32,7 @@ "idb-keyval": "^6.2.2", "mitt": "^3.0.1", "nanoid": "^5.1.6", - "three-bvh-csg": "^0.0.17", + "three-bvh-csg": "^0.0.18", "three-mesh-bvh": "^0.9.8", "zod": "^4.3.5", "zundo": "^2.3.0", @@ -41,7 +41,7 @@ "devDependencies": { "@repo/typescript-config": "*", "@types/react": "^19.2.2", - "typescript": "5.9.2", + "typescript": "5.9.3", "@types/three": "^0.183.0" }, "keywords": [ diff --git a/packages/db/package.json b/packages/db/package.json index 92af857d..612ab320 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -20,14 +20,14 @@ }, "dependencies": { "@supabase/supabase-js": "^2.95.3", - "drizzle-orm": "^0.39.0", - "drizzle-zod": "^0.5.1", + "drizzle-orm": "^0.45.1", + "drizzle-zod": "^0.8.3", "nanoid": "^5.0.9", "postgres": "^3.4.5" }, "devDependencies": { "@repo/typescript-config": "*", - "drizzle-kit": "^0.30.0", - "typescript": "5.9.2" + "drizzle-kit": "^0.31.9", + "typescript": "5.9.3" } } diff --git a/packages/ui/package.json b/packages/ui/package.json index 715430ca..638b47fe 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -17,7 +17,7 @@ "@types/react": "19.2.2", "@types/react-dom": "19.2.2", "eslint": "^9.39.1", - "typescript": "5.9.2" + "typescript": "5.9.3" }, "dependencies": { "react": "^19.2.0", diff --git a/packages/viewer/package.json b/packages/viewer/package.json index 80ae189b..508a27d2 100644 --- a/packages/viewer/package.json +++ b/packages/viewer/package.json @@ -34,7 +34,7 @@ "devDependencies": { "@repo/typescript-config": "*", "@types/react": "^19.2.2", - "typescript": "5.9.2", + "typescript": "5.9.3", "@types/three": "^0.183.0" }, "keywords": [ diff --git a/packages/viewer/src/components/viewer/selection-manager.tsx b/packages/viewer/src/components/viewer/selection-manager.tsx index 5fa67574..adfabdae 100644 --- a/packages/viewer/src/components/viewer/selection-manager.tsx +++ b/packages/viewer/src/components/viewer/selection-manager.tsx @@ -75,7 +75,7 @@ const pointInPolygonWithTolerance = ( interface SelectionStrategy { types: SelectableNodeType[] - handleClick: (node: AnyNode) => void + handleClick: (node: AnyNode, nativeEvent?: MouseEvent) => void handleDeselect: () => void isValid: (node: AnyNode) => boolean } @@ -159,6 +159,21 @@ const isNodeInZone = (node: AnyNode, levelId: string, zoneId: string): boolean = const getStrategy = (): SelectionStrategy | null => { const { buildingId, levelId, zoneId } = useViewer.getState().selection + const computeNextIds = (node: AnyNode, selectedIds: string[], event?: any): string[] => { + const isMeta = event?.metaKey || event?.nativeEvent?.metaKey; + const isCtrl = event?.ctrlKey || event?.nativeEvent?.ctrlKey; + + if (isMeta || isCtrl) { + if (selectedIds.includes(node.id)) { + return selectedIds.filter((id) => id !== node.id); + } else { + return [...selectedIds, node.id]; + } + } + + return [node.id]; + }; + // No building selected -> can select buildings if (!buildingId) { return { @@ -204,16 +219,9 @@ const getStrategy = (): SelectionStrategy | null => { // Zone selected -> can select/hover contents (walls, items, slabs, ceilings, roofs, windows, doors) return { types: ['wall', 'item', 'slab', 'ceiling', 'roof', 'window', 'door'], - handleClick: (node) => { + handleClick: (node, nativeEvent) => { const { selectedIds } = useViewer.getState().selection - // Toggle selection - if already selected, deselect; otherwise select - if (selectedIds.includes(node.id)) { - useViewer - .getState() - .setSelection({ selectedIds: selectedIds.filter((id) => id !== node.id) }) - } else { - useViewer.getState().setSelection({ selectedIds: [node.id] }) - } + useViewer.getState().setSelection({ selectedIds: computeNextIds(node, selectedIds, nativeEvent) }) }, handleDeselect: () => { const { selectedIds } = useViewer.getState().selection @@ -262,7 +270,7 @@ export const SelectionManager = () => { event.stopPropagation() clickHandledRef.current = true - strategy.handleClick(event.node) + strategy.handleClick(event.node, event.nativeEvent as unknown as MouseEvent) // Clear hover immediately after clicking on building/level/zone useViewer.setState({ hoveredId: null }) } diff --git a/packages/viewer/src/hooks/use-node-events.ts b/packages/viewer/src/hooks/use-node-events.ts index 77f9650c..f619707b 100644 --- a/packages/viewer/src/hooks/use-node-events.ts +++ b/packages/viewer/src/hooks/use-node-events.ts @@ -69,11 +69,13 @@ export function useNodeEvents(node: NodeConfig[T]['node'], t if (useViewer.getState().cameraDragging) return if (e.button !== 0) return emit('pointerup', e) + // Synthesize a click event on pointer up to be more forgiving than R3F's default onClick + // which often fails if the mouse moves even 1 pixel. + emit('click', e) }, onClick: (e: ThreeEvent) => { - if (useViewer.getState().cameraDragging) return - if (e.button !== 0) return - emit('click', e) + // Disable default R3F click since we synthesize it on pointerup + // This prevents double-clicks from firing twice. }, onPointerEnter: (e: ThreeEvent) => { if (useViewer.getState().cameraDragging) return