diff --git a/client/package-lock.json b/client/package-lock.json index c369f15..8c93526 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,10 +10,12 @@ "dependencies": { "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.4.2", + "@react-three/postprocessing": "^3.0.4", "axios": "^1.13.2", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "^7.9.6", + "react-router-dom": "^7.11.0", "three": "^0.182.0" }, "devDependencies": { @@ -1106,6 +1108,7 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.4.2.tgz", "integrity": "sha512-H4B4+FDNHpvIb4FmphH4ubxOfX5bxmfOw0+3pkQwR9u9wFiyMS7wUDkNn0m4RqQuiLWeia9jfN1eBvtyAVGEog==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/react-reconciler": "^0.32.0", @@ -1157,6 +1160,32 @@ "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", "license": "MIT" }, + "node_modules/@react-three/postprocessing": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-3.0.4.tgz", + "integrity": "sha512-e4+F5xtudDYvhxx3y0NtWXpZbwvQ0x1zdOXWTbXMK6fFLVDd4qucN90YaaStanZGS4Bd5siQm0lGL/5ogf8iDQ==", + "license": "MIT", + "dependencies": { + "maath": "^0.6.0", + "n8ao": "^1.9.4", + "postprocessing": "^6.36.6" + }, + "peerDependencies": { + "@react-three/fiber": "^9.0.0", + "react": "^19.0", + "three": ">= 0.156.0" + } + }, + "node_modules/@react-three/postprocessing/node_modules/maath": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz", + "integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.144.0", + "three": ">=0.144.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", @@ -4273,6 +4302,16 @@ "dev": true, "license": "MIT" }, + "node_modules/n8ao": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/n8ao/-/n8ao-1.10.1.tgz", + "integrity": "sha512-hhI1pC+BfOZBV1KMwynBrVlIm8wqLxj/abAWhF2nZ0qQKyzTSQa1QtLVS2veRiuoBQXojxobcnp0oe+PUoxf/w==", + "license": "ISC", + "peerDependencies": { + "postprocessing": ">=6.30.0", + "three": ">=0.137" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -4596,6 +4635,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postprocessing": { + "version": "6.38.2", + "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.38.2.tgz", + "integrity": "sha512-7DwuT7Tkst41ZjSj287g7C9c5/D3Xx5rMgBosg0dadbUPoZD2HNzkadKPol1d2PJAoI9f+Jeh1/v9YfLzpFGVw==", + "license": "Zlib", + "peer": true, + "peerDependencies": { + "three": ">= 0.157.0 < 0.183.0" + } + }, "node_modules/potpack": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", @@ -4712,9 +4761,9 @@ } }, "node_modules/react-router": { - "version": "7.9.6", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz", - "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==", + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz", + "integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -4733,6 +4782,22 @@ } } }, + "node_modules/react-router-dom": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz", + "integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==", + "license": "MIT", + "dependencies": { + "react-router": "7.11.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/react-use-measure": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", diff --git a/client/package.json b/client/package.json index 5d35635..22b64e4 100644 --- a/client/package.json +++ b/client/package.json @@ -12,10 +12,12 @@ "dependencies": { "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.4.2", + "@react-three/postprocessing": "^3.0.4", "axios": "^1.13.2", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router": "^7.9.6", + "react-router-dom": "^7.11.0", "three": "^0.182.0" }, "devDependencies": { diff --git a/client/src/CorridorScene.jsx b/client/src/CorridorScene.jsx new file mode 100644 index 0000000..60363c3 --- /dev/null +++ b/client/src/CorridorScene.jsx @@ -0,0 +1,58 @@ +/* eslint-disable react/no-unknown-property */ + +/** + * רכיב CorridorScene + * תפקידו להציג שכבת ממשק (UI Overlay) בזמן מעבר בין חדרים. + * הוא מציג טקסט מרכזי עם אפקט "זכוכית חלבית" (Glassmorphism). + */ +export default function CorridorScene({ text }) { + return ( +
+
+ {/* הטקסט הדינמי המועבר מה-ThreeDemo (למשל: "starting Mission 1") */} + {text} +
+
+ ); +} \ No newline at end of file diff --git a/client/src/Missions/Mission1.jsx b/client/src/Missions/Mission1.jsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/Missions/Mission2.jsx b/client/src/Missions/Mission2.jsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/Missions/Mission3.jsx b/client/src/Missions/Mission3.jsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/Missions/Mission4.jsx b/client/src/Missions/Mission4.jsx new file mode 100644 index 0000000..e69de29 diff --git a/client/src/Scene.jsx b/client/src/Scene.jsx new file mode 100644 index 0000000..97898cb --- /dev/null +++ b/client/src/Scene.jsx @@ -0,0 +1,387 @@ +/* eslint-disable react/no-unknown-property */ +import { useEffect, useMemo, useRef } from "react"; +import { useFrame } from "@react-three/fiber"; +import { OrbitControls } from "@react-three/drei"; +import * as THREE from "three"; +import Robot from "./components/Robot"; +import { useKeyboard } from "./useKeyboard"; + +// --- פונקציות עזר מתמטיות --- +// הגבלת ערך בין מינימום למקסימום (למשל כדי שהרובוט לא יצא מהקירות) +function clamp(v, min, max) { + return Math.max(min, Math.min(max, v)); +} + +// מעבר רך בין ערכים (Linear Interpolation) +function lerp(a, b, t) { + return a + (b - a) * t; +} + +// --- רכיב האייקון של המשימה (MissionIcon) --- +function MissionIcon({ position, tint = "#28f0e6" }) { + const groupRef = useRef(); + const linesRef = useRef(); + const glowRef = useRef(); + const auraRef = useRef(); + const tRef = useRef(Math.random() * 10); // זמן התחלתי רנדומלי כדי שהאייקונים לא יפעמו בסינכרון מושלם + + // יצירת טקסטורה של הילה (Aura) על הרצפה באמצעות Canvas + const auraTex = useMemo(() => { + const c = document.createElement("canvas"); + c.width = 256; + c.height = 256; + const ctx = c.getContext("2d"); + + // יצירת גרדיאנט מעגלי (Radial Gradient) + const g = ctx.createRadialGradient(128, 128, 12, 128, 128, 120); + g.addColorStop(0.0, "rgba(255,255,255,0.0)"); + g.addColorStop(0.25, "rgba(255,255,255,0.22)"); + g.addColorStop(0.55, "rgba(255,255,255,0.10)"); + g.addColorStop(1.0, "rgba(255,255,255,0.0)"); + + ctx.fillStyle = g; + ctx.fillRect(0, 0, 256, 256); + + const tex = new THREE.CanvasTexture(c); + tex.needsUpdate = true; + return tex; + }, []); + + // לוגיקת האנימציה של האייקון (מתבצע בכל פריים) + useFrame((_, delta) => { + tRef.current += delta; + if (!groupRef.current) return; + + // 1. אפקט ריחוף (Hover) מעלה ומטה בעזרת פונקציית Sin + const baseHover = 3; + const floatAmp = 0.18; + const floatSpeed = 1.0; + const floatY = Math.sin(tRef.current * floatSpeed) * floatAmp; + groupRef.current.position.y = position[1] + baseHover + floatY; + + // 2. סיבוב איטי סביב ציר ה-Y וטיה (Tilt) עדין למראה דינמי + groupRef.current.rotation.y += delta * 0.6; + groupRef.current.rotation.x = Math.sin(tRef.current * 0.6) * 0.06; + groupRef.current.rotation.z = Math.cos(tRef.current * 0.6) * 0.04; + + // 3. אפקט פעימה (Pulse) המשפיע על השקיפות והגודל + const pulse = 0.5 + 0.5 * Math.sin(tRef.current * 2.2); + + if (linesRef.current?.material) { + linesRef.current.material.opacity = 0.55 + pulse * 0.25; + } + + if (glowRef.current?.material) { + glowRef.current.material.opacity = 0.10 + pulse * 0.10; + const s = 1.02 + pulse * 0.03; + glowRef.current.scale.set(s, s, s); + } + + if (auraRef.current?.material) { + auraRef.current.material.opacity = 0.16 + pulse * 0.10; + auraRef.current.rotation.z += delta * 0.15; // סיבוב ההילה שעל הרצפה + } + }); + + return ( + + {/* אור ניאון קטן שבוקע ממרכז האייקון */} + + + {/* קווי המתאר של הקוביה (Wireframe) */} + + + + + + {/* נפח "Glow" פנימי שקוף */} + + + + + + {/* מישור ההילה על הרצפה */} + + + + + + ); +} + +// --- רכיב הסצנה המרכזי (Scene) --- +export default function Scene({ + roomId = "main", + spawnKey, + spawn, + inputEnabled = true, + onPose, + onIconsForMap, + onNearLabel, + onMissionTrigger, +}) { + const keys = useKeyboard(); + + // הגדרות מימדי החדר ומרחק בטיחות מהקירות + const ROOM_W = 70; + const ROOM_D = 70; + const WALL_PADDING = 2.5; + + const minX = -ROOM_W / 2 + WALL_PADDING; + const maxX = ROOM_W / 2 - WALL_PADDING; + const minZ = -ROOM_D / 2 + WALL_PADDING; + const maxZ = ROOM_D / 2 - WALL_PADDING; + + const robotRef = useRef(); + const controlsRef = useRef(); + + // מיקום וסיבוב הרובוט (נשמר ב-Ref כדי למנוע רנדר מיותר של כל ה-React) + const pos = useRef(new THREE.Vector3(0, -1.15, 0)); + const yaw = useRef(0); + + const SPEED = 7; + + // הגדרות מצלמה (גובה, מרחק והיסט צידי למראה קולנועי) + const CAM_HEIGHT = 4.1; + const CAM_DISTANCE = 12.0; + const CAM_SIDE = 10.2; + const CAM_LERP = 0.07; // מהירות ה"מרדף" של המצלמה אחרי הרובוט + + const camTargetPos = useRef(new THREE.Vector3()); + const camLookAt = useRef(new THREE.Vector3()); + + // הגדרת מיקומי כל האייקונים בעולם + const ALL_ICONS = useMemo(() => [ + { id: "task1", label: "Mission 1", pos: new THREE.Vector3(16.5, -1.15, 14.0) }, + { id: "task2", label: "Mission 2", pos: new THREE.Vector3(-20.0, -1.15, 10.5) }, + { id: "task3", label: "Mission 3", pos: new THREE.Vector3(-14.5, -1.15, -21.0) }, + { id: "task4", label: "Mission 4", pos: new THREE.Vector3(22.0, -1.15, -10.0) }, + ], []); + + // פילטור האייקונים להצגה לפי החדר הנוכחי + const iconsToShow = useMemo(() => { + if (roomId === "main") return ALL_ICONS; + const found = ALL_ICONS.find((x) => x.id === roomId); + return found ? [found] : []; + }, [roomId, ALL_ICONS]); + + // עדכון המפה החיצונית במיקומי האייקונים + useEffect(() => { + onIconsForMap?.( + iconsToShow.map((ic) => ({ + id: ic.id, + label: ic.label, + x: ic.pos.x, + z: ic.pos.z, + })) + ); + }, [iconsToShow, onIconsForMap]); + + const triggerCooldown = useRef(0); + const poseTick = useRef(0); + const lastNear = useRef("None"); + + // אתחול מיקום הרובוט בעת כניסה לחדר (Spawn) + useEffect(() => { + pos.current.set(spawn?.x ?? 0, spawn?.y ?? -1.15, spawn?.z ?? 0); + yaw.current = spawn?.yaw ?? 0; + triggerCooldown.current = 1.6; + lastNear.current = "None"; + onNearLabel?.("None"); + }, [spawnKey, spawn, onNearLabel]); + + // לולאת העדכון הראשית של ה-3D + useFrame(({ camera }, delta) => { + const k = keys.current; + if (triggerCooldown.current > 0) triggerCooldown.current -= delta; + + if (!inputEnabled) { + // אם התנועה חסומה (למשל בזמן מעבר חדר), הרובוט עובר למצב המתנה + if (robotRef.current?.userData?.setAction) robotRef.current.userData.setAction("Idle"); + } else { + // 1. חישוב סיבוב (Rotation) + let turn = 0; + if (k.ArrowLeft || k.KeyA) turn += 1; + if (k.ArrowRight || k.KeyD) turn -= 1; + yaw.current += turn * 2.2 * delta; + + // 2. חישוב תנועה (Movement) + let forward = 0; + if (k.ArrowUp || k.KeyW) forward = 1; + if (k.ArrowDown || k.KeyS) forward = -1; + + const moving = forward !== 0; + if (moving) { + // המרת זווית הסיבוב לוקטור תנועה במרחב (Trigonometry) + const vx = Math.sin(yaw.current) * forward; + const vz = Math.cos(yaw.current) * forward; + + const nextX = pos.current.x + vx * SPEED * delta; + const nextZ = pos.current.z + vz * SPEED * delta; + + // מניעת יציאה מגבולות החדר + pos.current.x = clamp(nextX, minX, maxX); + pos.current.z = clamp(nextZ, minZ, maxZ); + + if (robotRef.current?.userData?.setAction) robotRef.current.userData.setAction("Walk"); + } else { + if (robotRef.current?.userData?.setAction) robotRef.current.userData.setAction("Idle"); + } + } + + // עדכון המודל התלת-ממדי של הרובוט + if (robotRef.current) { + robotRef.current.position.set(pos.current.x, pos.current.y, pos.current.z); + robotRef.current.rotation.y = yaw.current; + } + + // 3. בדיקת קרבה למשימות (Collision/Trigger Logic) + if (inputEnabled) { + let nearestDist = Infinity; + let nearestLabel = "None"; + const triggerDist = 4.6; + + for (const ic of iconsToShow) { + const d = pos.current.distanceTo(ic.pos); + if (d < nearestDist) { nearestDist = d; nearestLabel = ic.label; } + + // הפעלת המשימה אם הרובוט מספיק קרוב + if (d < triggerDist && triggerCooldown.current <= 0) { + triggerCooldown.current = 1.4; + onMissionTrigger?.(ic.id); + } + } + + // עדכון ה-UI על המשימה הקרובה ביותר + const nearLabel = nearestDist < 11 ? nearestLabel : "None"; + if (nearLabel !== lastNear.current) { + lastNear.current = nearLabel; + onNearLabel?.(nearLabel); + } + } + + // דיווח מיקום למפה החיצונית (בתדירות נמוכה יותר לשיפור ביצועים) + poseTick.current += delta; + if (poseTick.current > 0.06) { + poseTick.current = 0; + onPose?.({ x: pos.current.x, z: pos.current.z, yaw: yaw.current, roomW: ROOM_W, roomD: ROOM_D, roomId }); + } + + // 4. לוגיקת מצלמה עוקבת (Chase Camera) + camLookAt.current.set(pos.current.x, 1.6, pos.current.z); // המצלמה תמיד מסתכלת על הרובוט + + // חישוב המיקום האידיאלי של המצלמה מאחורי ובצד הרובוט + const backX = Math.sin(yaw.current) * CAM_DISTANCE; + const backZ = Math.cos(yaw.current) * CAM_DISTANCE; + const rightX = Math.sin(yaw.current + Math.PI / 2) * CAM_SIDE; + const rightZ = Math.cos(yaw.current + Math.PI / 2) * CAM_SIDE; + + camTargetPos.current.set( + pos.current.x - backX + rightX, + CAM_HEIGHT, + pos.current.z - backZ + rightZ + ); + + // הגבלת המצלמה שלא תצא מהקירות + const CAM_PADDING = 2.5; + camTargetPos.current.x = clamp(camTargetPos.current.x, -ROOM_W / 2 + CAM_PADDING, ROOM_W / 2 - CAM_PADDING); + camTargetPos.current.z = clamp(camTargetPos.current.z, -ROOM_D / 2 + CAM_PADDING, ROOM_D / 2 - CAM_PADDING); + + // תנועת מצלמה חלקה (Lerp) ומיקוד + camera.position.lerp(camTargetPos.current, CAM_LERP); + camera.lookAt(camLookAt.current); + }); + + // יצירת חומר שקוף לקירות (משותף לכל הקירות) + const wallMat = useMemo(() => { + return new THREE.MeshBasicMaterial({ + color: "#ffffff", + transparent: true, + opacity: 0.12, + depthWrite: false, + side: THREE.DoubleSide, + }); + }, []); + + return ( + <> + {/* תאורה סביבתית ותאורת כיוון (שמש) ליצירת צללים */} + + + + + {/* בניית ארבעת קירות החדר */} + {(() => { + const WALL_VIS_H = 220; // גובה ויזואלי של הקירות + const yCenter = -1.15 + WALL_VIS_H / 2; + const W = ROOM_W; + const D = ROOM_D; + return ( + <> + + + + + + + + + + + + + + + + + + ); + })()} + + {/* הרצפה - מקבלת צללים מהרובוט */} + + + + + + {/* רינדור האייקונים */} + {iconsToShow.map((ic) => ( + + ))} + + {/* מודל הרובוט */} + + + {/* בקרת מצלמה (מושבתת לשליטה ידנית, משמשת רק למיקוד) */} + + + ); +} \ No newline at end of file diff --git a/client/src/ThreeDemo.jsx b/client/src/ThreeDemo.jsx index 5e0d29c..cf7a49c 100644 --- a/client/src/ThreeDemo.jsx +++ b/client/src/ThreeDemo.jsx @@ -1,150 +1,310 @@ /* eslint-disable react/no-unknown-property */ -import { Canvas, useFrame } from "@react-three/fiber"; -import { OrbitControls } from "@react-three/drei"; -import Robot from "./Robot"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { Canvas } from "@react-three/fiber"; import * as THREE from "three"; -import { useRef, useEffect } from "react"; -import { useKeyboard } from "./useKeyboard"; +import Scene from "./Scene"; +import CorridorScene from "./CorridorScene"; -// תנועת הרובוט -function MovingRobot({ robotRef }) { - const keys = useKeyboard(); - const velocity = useRef(new THREE.Vector3()); +export default function ThreeDemo() { + // --- ניהול מצבי החדרים --- + const [room, setRoom] = useState("main"); // החדר הנוכחי שבו נמצא המשתמש + const [targetRoom, setTargetRoom] = useState(null); // חדר היעד בזמן מעבר - useFrame((_, delta) => { - if (!robotRef.current) return; + // --- מצבי ויזואליזציה ומעברים --- + const [fade, setFade] = useState(0); // רמת השקיפות של המסך השחור (0 עד 1) + const [transitionText, setTransitionText] = useState(""); // הטקסט שיוצג בזמן המעבר - const moveSpeed = 3.5; - const direction = new THREE.Vector3(); + // --- נתוני הרובוט והסביבה (עבור המפה) --- + const [pose, setPose] = useState(null); // מיקום וזווית הרובוט (x, z, yaw) + const [iconsForMap, setIconsForMap] = useState([]); // רשימת נקודות העניין (משימות) למפה + const [nearLabel, setNearLabel] = useState("None"); // שם המשימה הקרובה ביותר לרובוט - if (keys.current.KeyW || keys.current.ArrowUp) direction.z -= 1; - if (keys.current.KeyS || keys.current.ArrowDown) direction.z += 1; - if (keys.current.KeyA || keys.current.ArrowLeft) direction.x -= 1; - if (keys.current.KeyD || keys.current.ArrowRight) direction.x += 1; + // --- ניהול לוגיקת המעבר (State Machine) --- + const [isTransitioning, setIsTransitioning] = useState(false); // האם מתבצע כרגע מעבר? + const stageRef = useRef("idle"); // השלב הנוכחי במעבר: fadeOut, hold, או fadeIn + const startTimeRef = useRef(0); // זמן תחילת השלב הנוכחי - direction.normalize(); + // מפתח לריענון הרובוט במיקום ההתחלתי כשעוברים חדר + const [spawnKey, setSpawnKey] = useState(0); - if (direction.length() > 0) { - if (robotRef.current.userData.setAction) { - robotRef.current.userData.setAction("Walking"); - } + // הגדרת מיקום התחלתי יציב (Memoized) כדי למנוע רינדורים מיותרים + const spawn = useMemo(() => { + return { x: 0, y: -1.15, z: 0, yaw: 0 }; + }, []); - velocity.current.copy(direction).multiplyScalar(moveSpeed * delta); - robotRef.current.position.add(velocity.current); + // פונקציות עזר להמרת מזהי חדרים לשמות ידידותיים + function roomLabel(r) { + if (r === "main") return "Main Room"; + if (r === "task1") return "Mission 1"; + if (r === "task2") return "Mission 2"; + if (r === "task3") return "Mission 3"; + if (r === "task4") return "Mission 4"; + return "Room"; + } - const angle = Math.atan2(direction.x, direction.z); - robotRef.current.rotation.y = angle; - } else { - if (robotRef.current.userData.setAction) { - robotRef.current.userData.setAction("Idle"); - } + function missionLabelFromId(id) { + if (id === "task1") return "Mission 1"; + if (id === "task2") return "Mission 2"; + if (id === "task3") return "Mission 3"; + if (id === "task4") return "Mission 4"; + return "Mission"; + } + + // התחלת תהליך המעבר בין חדרים + function beginTransition(next, message) { + if (isTransitioning) return; // מניעת התחלת מעבר חדש אם כבר יש אחד פעיל + setTargetRoom(next); + setTransitionText(message); + setIsTransitioning(true); + stageRef.current = "fadeOut"; // מתחילים בהחשכת המסך + startTimeRef.current = performance.now(); + } + + // פונקציה שנקראת כשהרובוט נכנס לטווח של משימה + function onMissionTrigger(missionId) { + if (room === "main") { + // אם אנחנו בלובי, כניסה למשימה + const msg = `starting ${missionLabelFromId(missionId)}`; + beginTransition(missionId, msg); + return; } - }); - return ; -} + if (room === missionId) { + // אם אנחנו בתוך משימה, חזרה ללובי + const msg = " going back to Main Room "; + beginTransition("main", msg); + } + } -// מצלמה שעוקבת אחרי הרובוט -function FollowCamera({ targetRef }) { - useFrame(({ camera }) => { - if (!targetRef.current) return; + // --- מנוע המעברים (Animation Loop) --- + useEffect(() => { + if (!isTransitioning) return; - const targetPos = targetRef.current.position; + let raf = 0; + const fadeOutMs = 850; // משך זמן ההחשכה + const holdMs = 850; // זמן ההמתנה בחושך (טעינה) + const fadeInMs = 950; // משך זמן הופעת החדר החדש - // מיקום יחסי של המצלמה מאחורי הרובוט - const offset = new THREE.Vector3(0, 3, 8); - const desiredPos = targetPos.clone().add(offset); + // פונקציית החלקה למעברים רכים (Easing) + function easeInOut(t) { + return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; + } - // תנועה רכה למיקום הרצוי - camera.position.lerp(desiredPos, 0.05); - camera.lookAt(targetPos.x, targetPos.y + 1.5, targetPos.z); - }); + function tick(now) { + const stage = stageRef.current; + const elapsed = now - startTimeRef.current; - return null; -} + // שלב 1: החשכה (Fade Out) + if (stage === "fadeOut") { + const t = Math.min(1, elapsed / fadeOutMs); + setFade(easeInOut(t)); + if (t >= 1) { + stageRef.current = "hold"; + startTimeRef.current = now; + } + raf = requestAnimationFrame(tick); + return; + } -export default function ThreeDemo() { - const robotRef = useRef(); + // שלב 2: החזקת המסך החשוך והחלפת החדר בפועל + if (stage === "hold") { + setFade(1); + if (elapsed >= holdMs) { + if (targetRoom) { + setRoom(targetRoom); + setSpawnKey((k) => k + 1); // גורם ל-Scene לאפס את מיקום הרובוט + } + stageRef.current = "fadeIn"; + startTimeRef.current = now; + } + raf = requestAnimationFrame(tick); + return; + } - // ביטול גלילת חיצים - useEffect(() => { - const preventArrowScroll = (e) => { - if ( - ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.code) - ) { - e.preventDefault(); + // שלב 3: חשיפת החדר החדש (Fade In) + if (stage === "fadeIn") { + const t = Math.min(1, elapsed / fadeInMs); + setFade(1 - easeInOut(t)); + if (t >= 1) { + setFade(0); + setTransitionText(""); + setTargetRoom(null); + setIsTransitioning(false); + stageRef.current = "idle"; + return; + } + raf = requestAnimationFrame(tick); + return; } - }; - window.addEventListener("keydown", preventArrowScroll); - return () => window.removeEventListener("keydown", preventArrowScroll); - }, []); + + raf = requestAnimationFrame(tick); + } + + raf = requestAnimationFrame(tick); + return () => cancelAnimationFrame(raf); + }, [isTransitioning, targetRoom]); return ( -
+
+ {/* עולם התלת-ממד */} { scene.background = new THREE.Color("#ffffff"); - scene.fog = new THREE.Fog("#e0eefe", 10, 90); + scene.fog = new THREE.Fog("#e3e4f2", 25, 120); // הוספת ערפל לעומק }} > - {/* קירות */} - - - - - - {/* תאורה */} - - + + {/* ממשק משתמש - מפה קטנה */} + {pose && ( + - } + + {/* השכבה השחורה שיוצרת את אפקט ה-Fade */} +
+
+ ); +} + +// --- רכיב המפה הקטנה (MiniMap) --- +function MiniMap({ pose, icons, roomTitle, nearLabel }) { + const size = 128; // גודל המפה בפיקסלים + const pad = 10; // ריפוד פנימי + + // פונקציות המרה ממיקומי תלת-ממד (X, Z) למיקומי פיקסלים בדו-ממד + function toMapX(x) { + return ((x + pose.roomW / 2) / pose.roomW) * (size - 2 * pad) + pad; + } + function toMapZ(z) { + return ((z + pose.roomD / 2) / pose.roomD) * (size - 2 * pad) + pad; + } + + const px = toMapX(pose.x); + const pz = toMapZ(pose.z); + + return ( +
+ {/* כותרת החדר הנוכחי */} +
+ {roomTitle} +
+ + {/* עיגול המפה */} +
+ {/* אפקט תאורה פנימי למפה */} +
- {/* רצפה */} - - - - - - {/* הרובוט + מצלמה עוקבת */} - - - - {/* ביטול שליטה עם העכבר */} - { + const ix = toMapX(ic.x); + const iz = toMapZ(ic.z); + return ( +
+ ); + })} + + {/* הסמן הכחול המייצג את הרובוט */} +
- +
+ + {/* חיווי טקסטואלי על משימה קרובה */} +
+ Nearby: {nearLabel || "None"} +
); -} +} \ No newline at end of file diff --git a/client/src/components/RandomDuck/RandomDuck.jsx b/client/src/components/RandomDuck/RandomDuck.jsx index 2d93857..a6ab329 100644 --- a/client/src/components/RandomDuck/RandomDuck.jsx +++ b/client/src/components/RandomDuck/RandomDuck.jsx @@ -29,4 +29,4 @@ const RandomDuck = () => { ); }; -export default RandomDuck; +export default RandomDuck; \ No newline at end of file diff --git a/client/src/components/RandomDuck/RandomDuck.module.css b/client/src/components/RandomDuck/RandomDuck.module.css index 322abe5..18fefa8 100644 --- a/client/src/components/RandomDuck/RandomDuck.module.css +++ b/client/src/components/RandomDuck/RandomDuck.module.css @@ -32,4 +32,4 @@ font-size: 24px; font-weight: bold; color: #333; -} +} \ No newline at end of file diff --git a/client/src/Robot.jsx b/client/src/components/Robot.jsx similarity index 100% rename from client/src/Robot.jsx rename to client/src/components/Robot.jsx diff --git a/client/src/components/common/FirstButton/FirstButton.jsx b/client/src/components/common/FirstButton.jsx similarity index 100% rename from client/src/components/common/FirstButton/FirstButton.jsx rename to client/src/components/common/FirstButton.jsx diff --git a/client/src/components/common/FirstButton/FirstButton.module.css b/client/src/components/common/FirstButton.module.css similarity index 100% rename from client/src/components/common/FirstButton/FirstButton.module.css rename to client/src/components/common/FirstButton.module.css diff --git a/client/src/pages/HomePage/HomePage.jsx b/client/src/pages/HomePage/HomePage.jsx index 1304b7a..2fb3ed4 100644 --- a/client/src/pages/HomePage/HomePage.jsx +++ b/client/src/pages/HomePage/HomePage.jsx @@ -1,14 +1,35 @@ -import styles from './Home.module.css'; -import RandomDuck from '../../components/RandomDuck/RandomDuck.jsx'; +import { Link } from "react-router-dom"; - -const Home = () => { +export default function Home() { return ( -
-

Duck It

- +
+

ברוכה הבאה

+ + + +
); -}; - -export default Home; +} diff --git a/client/src/useKeyboard.js b/client/src/useKeyboard.js index 5115d25..8507ef6 100644 --- a/client/src/useKeyboard.js +++ b/client/src/useKeyboard.js @@ -14,14 +14,21 @@ export function useKeyboard() { useEffect(() => { const down = (e) => { - if (e.code in keys.current) keys.current[e.code] = true; + if (e.code in keys.current) { + e.preventDefault(); + keys.current[e.code] = true; + } }; const up = (e) => { - if (e.code in keys.current) keys.current[e.code] = false; + if (e.code in keys.current) { + e.preventDefault(); + keys.current[e.code] = false; + } }; - window.addEventListener("keydown", down); - window.addEventListener("keyup", up); + window.addEventListener("keydown", down, { passive: false }); + window.addEventListener("keyup", up, { passive: false }); + return () => { window.removeEventListener("keydown", down); window.removeEventListener("keyup", up); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a59f182 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,361 @@ +{ + "name": "hakathon", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@react-three/postprocessing": "^3.0.4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "license": "Apache-2.0" + }, + "node_modules/@react-three/fiber": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz", + "integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==", + "license": "MIT", + "peer": true, + "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" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-file-system": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/postprocessing": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@react-three/postprocessing/-/postprocessing-3.0.4.tgz", + "integrity": "sha512-e4+F5xtudDYvhxx3y0NtWXpZbwvQ0x1zdOXWTbXMK6fFLVDd4qucN90YaaStanZGS4Bd5siQm0lGL/5ogf8iDQ==", + "license": "MIT", + "dependencies": { + "maath": "^0.6.0", + "n8ao": "^1.9.4", + "postprocessing": "^6.36.6" + }, + "peerDependencies": { + "@react-three/fiber": "^9.0.0", + "react": "^19.0", + "three": ">= 0.156.0" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-reconciler": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.182.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.182.0.tgz", + "integrity": "sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.22.0" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "license": "MIT" + }, + "node_modules/@webgpu/types": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.68.tgz", + "integrity": "sha512-3ab1B59Ojb6RwjOspYLsTpCzbNB3ZaamIAxBMmvnNkiDoLTZUOBXZ9p5nAYVEkQlDdf6qAZWi1pqj9+ypiqznA==", + "license": "BSD-3-Clause" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/its-fine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", + "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.9" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/maath": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.6.0.tgz", + "integrity": "sha512-dSb2xQuP7vDnaYqfoKzlApeRcR2xtN8/f7WV/TMAkBC8552TwTLtOO0JTcSygkYMjNDPoo6V01jTw/aPi4JrMw==", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.144.0", + "three": ">=0.144.0" + } + }, + "node_modules/meshoptimizer": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz", + "integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==", + "license": "MIT" + }, + "node_modules/n8ao": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/n8ao/-/n8ao-1.10.1.tgz", + "integrity": "sha512-hhI1pC+BfOZBV1KMwynBrVlIm8wqLxj/abAWhF2nZ0qQKyzTSQa1QtLVS2veRiuoBQXojxobcnp0oe+PUoxf/w==", + "license": "ISC", + "peerDependencies": { + "postprocessing": ">=6.30.0", + "three": ">=0.137" + } + }, + "node_modules/postprocessing": { + "version": "6.38.2", + "resolved": "https://registry.npmjs.org/postprocessing/-/postprocessing-6.38.2.tgz", + "integrity": "sha512-7DwuT7Tkst41ZjSj287g7C9c5/D3Xx5rMgBosg0dadbUPoZD2HNzkadKPol1d2PJAoI9f+Jeh1/v9YfLzpFGVw==", + "license": "Zlib", + "peer": true, + "peerDependencies": { + "three": ">= 0.157.0 < 0.183.0" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-use-measure": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", + "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, + "node_modules/three": { + "version": "0.182.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.182.0.tgz", + "integrity": "sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==", + "license": "MIT", + "peer": true + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz", + "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b7eec46 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@react-three/postprocessing": "^3.0.4" + } +} diff --git a/server/eslint.config.js b/server/eslint.config.js index 5d777cd..7d4c7e1 100644 --- a/server/eslint.config.js +++ b/server/eslint.config.js @@ -22,6 +22,7 @@ export default [ ...pluginN.configs['flat/recommended'].rules, ...pluginImport.flatConfigs.recommended.rules, ...pluginPromise.configs['flat/recommended'].rules, + 'react/no-unknown-property': ['error', { ignore: ['object', 'intensity', 'position', 'castShadow', 'args', 'rotation'] }] }, }, ]; \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 2871d8d..991ce21 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -20,10 +20,286 @@ "eslint-plugin-import": "^2.32.0", "eslint-plugin-n": "^17.23.1", "eslint-plugin-promise": "^7.2.1", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", "globals": "^16.5.0", "nodemon": "^3.1.11" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -247,6 +523,56 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -294,6 +620,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -405,6 +732,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.findlastindex": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", @@ -465,6 +813,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", @@ -510,6 +875,16 @@ "dev": true, "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -571,6 +946,41 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -638,6 +1048,27 @@ "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001762", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", + "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -752,6 +1183,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", @@ -966,6 +1404,13 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -990,9 +1435,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, "license": "MIT", "dependencies": { @@ -1076,6 +1521,34 @@ "node": ">= 0.4" } }, + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -1135,6 +1608,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1160,6 +1643,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -1415,6 +1899,87 @@ "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -1764,6 +2329,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -1993,6 +2568,23 @@ "node": ">= 0.4" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -2520,6 +3112,31 @@ "dev": true, "license": "ISC" }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2533,6 +3150,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2567,6 +3197,22 @@ "json5": "lib/cli.js" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2614,6 +3260,29 @@ "dev": true, "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -2714,6 +3383,13 @@ "node": ">= 0.6" } }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, "node_modules/nodemon": { "version": "3.1.11", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", @@ -2805,6 +3481,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/object.fromentries": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", @@ -3006,6 +3698,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3039,6 +3738,18 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3108,6 +3819,13 @@ "node": ">= 0.10" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3519,6 +4237,45 @@ "node": ">= 0.4" } }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -3873,6 +4630,37 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -4013,6 +4801,13 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -4025,6 +4820,30 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/server/package.json b/server/package.json index 4e38aa9..2117a95 100644 --- a/server/package.json +++ b/server/package.json @@ -17,13 +17,15 @@ "express": "^5.1.0" }, "devDependencies": { - "globals": "^16.5.0", - "nodemon": "^3.1.11", - "eslint": "^9.39.1", - "brace-expansion": "^1.1.12", "@eslint/js": "^9.39.1", - "eslint-plugin-n": "^17.23.1", + "brace-expansion": "^1.1.12", + "eslint": "^9.39.1", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-promise": "^7.2.1" + "eslint-plugin-n": "^17.23.1", + "eslint-plugin-promise": "^7.2.1", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "globals": "^16.5.0", + "nodemon": "^3.1.11" } }