diff --git a/dashboard/src/components/shared/ReactorBg.vue b/dashboard/src/components/shared/ReactorBg.vue
new file mode 100644
index 000000000..54927ecc9
--- /dev/null
+++ b/dashboard/src/components/shared/ReactorBg.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
diff --git a/dashboard/src/composables/useReactorBg.ts b/dashboard/src/composables/useReactorBg.ts
new file mode 100644
index 000000000..4bb560e7f
--- /dev/null
+++ b/dashboard/src/composables/useReactorBg.ts
@@ -0,0 +1,36 @@
+/**
+ * Global reactor background mouse tracking
+ * Shares mouse position across all components so the canvas
+ * background can react to UI element proximity.
+ */
+import { ref, readonly } from "vue";
+
+const mouseX = ref(-9999);
+const mouseY = ref(-9999);
+const isOverUI = ref(false);
+
+function onMouseMove(e: MouseEvent) {
+ mouseX.value = e.clientX;
+ mouseY.value = e.clientY;
+}
+
+function onMouseLeave() {
+ mouseX.value = -9999;
+ mouseY.value = -9999;
+ isOverUI.value = false;
+}
+
+function setIsOverUI(val: boolean) {
+ isOverUI.value = val;
+}
+
+export function useReactorBg() {
+ return {
+ mouseX: readonly(mouseX),
+ mouseY: readonly(mouseY),
+ isOverUI: readonly(isOverUI),
+ onMouseMove,
+ onMouseLeave,
+ setIsOverUI,
+ };
+}
diff --git a/dashboard/src/layouts/full/FullLayout.vue b/dashboard/src/layouts/full/FullLayout.vue
index 74ed9a83e..abb4afe4b 100644
--- a/dashboard/src/layouts/full/FullLayout.vue
+++ b/dashboard/src/layouts/full/FullLayout.vue
@@ -7,6 +7,7 @@ import VerticalHeaderVue from "./vertical-header/VerticalHeader.vue";
import MigrationDialog from "@/components/shared/MigrationDialog.vue";
import ReadmeDialog from "@/components/shared/ReadmeDialog.vue";
import Chat from "@/components/chat/Chat.vue";
+import ReactorBg from "@/components/shared/ReactorBg.vue";
import { useCustomizerStore } from "@/stores/customizer";
import { useRouterLoadingStore } from "@/stores/routerLoading";
import { useI18n } from "@/i18n/composables";
@@ -108,6 +109,7 @@ onMounted(() => {
customizer.inputBg ? 'inputWithbg' : '',
]"
>
+