diff --git a/src/components/ui/GameUI.tsx b/src/components/ui/GameUI.tsx
index 6b3482a..9926cf5 100644
--- a/src/components/ui/GameUI.tsx
+++ b/src/components/ui/GameUI.tsx
@@ -2,12 +2,14 @@ import { Crosshair } from "@/components/ui/Crosshair";
import { DebugOverlayLayout } from "@/components/ui/debug/DebugOverlayLayout";
import { HandTrackingVisualizer } from "@/components/ui/HandTrackingVisualizer";
import { InteractPrompt } from "@/components/ui/InteractPrompt";
+import { RepairMovementLockIndicator } from "@/components/ui/RepairMovementLockIndicator";
export function GameUI(): React.JSX.Element {
return (
<>
+
>
diff --git a/src/components/ui/RepairMovementLockIndicator.tsx b/src/components/ui/RepairMovementLockIndicator.tsx
new file mode 100644
index 0000000..4f8d825
--- /dev/null
+++ b/src/components/ui/RepairMovementLockIndicator.tsx
@@ -0,0 +1,20 @@
+import { useCameraMode } from "@/hooks/debug/useCameraMode";
+import { useRepairMovementLocked } from "@/hooks/gameplay/useRepairMovementLocked";
+
+export function RepairMovementLockIndicator(): React.JSX.Element | null {
+ const cameraMode = useCameraMode();
+ const movementLocked = useRepairMovementLocked();
+
+ if (cameraMode !== "player") return null;
+ if (!movementLocked) return null;
+
+ return (
+
+
+ Déplacement verrouillé pendant la réparation
+
+ );
+}
diff --git a/src/hooks/gameplay/useRepairMovementLocked.ts b/src/hooks/gameplay/useRepairMovementLocked.ts
new file mode 100644
index 0000000..11953ce
--- /dev/null
+++ b/src/hooks/gameplay/useRepairMovementLocked.ts
@@ -0,0 +1,28 @@
+import { useGameStore } from "@/managers/stores/useGameStore";
+import type { MissionStep } from "@/types/gameplay/repairMission";
+
+export function useRepairMovementLocked(): boolean {
+ return useGameStore((state) => {
+ switch (state.mainState) {
+ case "bike":
+ return isRepairMovementLocked(state.bike.currentStep);
+ case "pylone":
+ return isRepairMovementLocked(state.pylone.currentStep);
+ case "ferme":
+ return isRepairMovementLocked(state.ferme.currentStep);
+ case "intro":
+ case "outro":
+ return false;
+ }
+ });
+}
+
+function isRepairMovementLocked(step: MissionStep): boolean {
+ return (
+ step === "inspected" ||
+ step === "fragmented" ||
+ step === "scanning" ||
+ step === "repairing" ||
+ step === "reassembling"
+ );
+}
diff --git a/src/index.css b/src/index.css
index 67f39df..25383a5 100644
--- a/src/index.css
+++ b/src/index.css
@@ -397,6 +397,35 @@ canvas {
letter-spacing: 0.03em;
}
+.repair-movement-lock-indicator {
+ position: fixed;
+ top: 22px;
+ left: 50%;
+ z-index: 10;
+ display: inline-flex;
+ align-items: center;
+ gap: 9px;
+ padding: 9px 13px;
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ border-radius: 999px;
+ background: rgba(5, 9, 16, 0.72);
+ color: rgba(255, 255, 255, 0.88);
+ font-size: 12px;
+ font-weight: 650;
+ letter-spacing: 0.02em;
+ pointer-events: none;
+ transform: translateX(-50%);
+ backdrop-filter: blur(10px);
+}
+
+.repair-movement-lock-indicator__dot {
+ width: 7px;
+ height: 7px;
+ border-radius: 999px;
+ background: #38bdf8;
+ box-shadow: 0 0 14px rgba(56, 189, 248, 0.86);
+}
+
.scene-loading-overlay {
position: fixed;
inset: 0;
diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx
index 2f045dd..f97b446 100644
--- a/src/world/player/PlayerController.tsx
+++ b/src/world/player/PlayerController.tsx
@@ -23,9 +23,8 @@ import {
PLAYER_WALK_SPEED,
PLAYER_XZ_DAMPING_FACTOR,
} from "@/data/player/playerConfig";
+import { useRepairMovementLocked } from "@/hooks/gameplay/useRepairMovementLocked";
import { InteractionManager } from "@/managers/InteractionManager";
-import { useGameStore } from "@/managers/stores/useGameStore";
-import type { MissionStep } from "@/types/gameplay/repairMission";
import type { Vector3Tuple } from "@/types/three/three";
type Keys = {
@@ -75,32 +74,6 @@ function setMovementKey(keys: Keys, key: string, pressed: boolean): boolean {
}
}
-function isRepairMovementLocked(step: MissionStep): boolean {
- return (
- step === "inspected" ||
- step === "fragmented" ||
- step === "scanning" ||
- step === "repairing" ||
- step === "reassembling"
- );
-}
-
-function useRepairMovementLocked(): boolean {
- return useGameStore((state) => {
- switch (state.mainState) {
- case "bike":
- return isRepairMovementLocked(state.bike.currentStep);
- case "pylone":
- return isRepairMovementLocked(state.pylone.currentStep);
- case "ferme":
- return isRepairMovementLocked(state.ferme.currentStep);
- case "intro":
- case "outro":
- return false;
- }
- });
-}
-
export function PlayerController({
octree,
spawnPosition,