diff --git a/package.json b/package.json
index 24a31af..c680586 100644
--- a/package.json
+++ b/package.json
@@ -15,8 +15,12 @@
     "@fontsource/material-icons": "^4.5.4",
     "@fontsource/roboto-mono": "^4.5.8",
     "@pixi/app": "~6.3.2",
+    "@pixi/constants": "~6.3.2",
     "@pixi/core": "~6.3.2",
-    "@pixi/particle-emitter": "^5.0.4",
+    "@pixi/display": "~6.3.2",
+    "@pixi/math": "~6.3.2",
+    "@pixi/particle-emitter": "^5.0.7",
+    "@pixi/sprite": "~6.3.2",
     "@pixi/ticker": "~6.3.2",
     "@vitejs/plugin-vue": "^2.3.3",
     "@vitejs/plugin-vue-jsx": "^1.3.10",
@@ -28,7 +32,7 @@
     "vite-tsconfig-paths": "^3.5.0",
     "vue": "^3.2.26",
     "vue-next-select": "^2.10.2",
-    "vue-panzoom": "^1.1.6",
+    "vue-panzoom": "https://github.com/thepaperpilot/vue-panzoom.git",
     "vue-textarea-autosize": "^1.1.1",
     "vue-toastification": "^2.0.0-rc.1",
     "vue-transition-expand": "^0.1.0",
diff --git a/src/features/boards/Board.vue b/src/features/boards/Board.vue
index 90f4e79..c7e7d9d 100644
--- a/src/features/boards/Board.vue
+++ b/src/features/boards/Board.vue
@@ -61,17 +61,15 @@ import type {
 import { getNodeProperty } from "features/boards/board";
 import type { StyleValue } from "features/feature";
 import { Visibility } from "features/feature";
-import { PersistentState } from "game/persistence";
 import type { ProcessedComputable } from "util/computed";
 import { computed, ref, Ref, toRefs, unref } from "vue";
-import panZoom from "vue-panzoom";
 import BoardLinkVue from "./BoardLink.vue";
 import BoardNodeVue from "./BoardNode.vue";
 
 const _props = defineProps<{
     nodes: Ref<BoardNode[]>;
     types: Record<string, GenericNodeType>;
-    [PersistentState]: Ref<BoardData>;
+    state: Ref<BoardData>;
     visibility: ProcessedComputable<Visibility>;
     width?: ProcessedComputable<string>;
     height?: ProcessedComputable<string>;
@@ -80,6 +78,7 @@ const _props = defineProps<{
     links: Ref<BoardNodeLink[] | null>;
     selectedAction: Ref<GenericBoardNodeAction | null>;
     selectedNode: Ref<BoardNode | null>;
+    mousePosition: Ref<{ x: number; y: number } | null>;
 }>();
 const props = toRefs(_props);
 
@@ -170,13 +169,13 @@ function mouseDown(e: MouseEvent | TouchEvent, nodeID: number | null = null, dra
         }
     }
     if (nodeID != null) {
-        props[PersistentState].value.selectedNode = null;
-        props[PersistentState].value.selectedAction = null;
+        props.state.value.selectedNode = null;
+        props.state.value.selectedAction = null;
     }
 }
 
 function drag(e: MouseEvent | TouchEvent) {
-    const zoom = stage.value.$panZoomInstance.getTransform().scale;
+    const { x, y, scale } = stage.value.panZoomInstance.getTransform();
 
     let clientX, clientY;
     if ("touches" in e) {
@@ -185,6 +184,7 @@ function drag(e: MouseEvent | TouchEvent) {
             clientY = e.touches[0].clientY;
         } else {
             endDragging(dragging.value);
+            props.mousePosition.value = null;
             return;
         }
     } else {
@@ -192,9 +192,14 @@ function drag(e: MouseEvent | TouchEvent) {
         clientY = e.clientY;
     }
 
+    props.mousePosition.value = {
+        x: (clientX - x) / scale,
+        y: (clientY - y) / scale
+    };
+
     dragged.value = {
-        x: dragged.value.x + (clientX - lastMousePosition.value.x) / zoom,
-        y: dragged.value.y + (clientY - lastMousePosition.value.y) / zoom
+        x: dragged.value.x + (clientX - lastMousePosition.value.x) / scale,
+        y: dragged.value.y + (clientY - lastMousePosition.value.y) / scale
     };
     lastMousePosition.value = {
         x: clientX,
@@ -229,8 +234,8 @@ function endDragging(nodeID: number | null) {
 
         dragging.value = null;
     } else if (!hasDragged.value) {
-        props[PersistentState].value.selectedNode = null;
-        props[PersistentState].value.selectedAction = null;
+        props.state.value.selectedNode = null;
+        props.state.value.selectedAction = null;
     }
 }
 </script>
@@ -239,7 +244,11 @@ function endDragging(nodeID: number | null) {
 .vue-pan-zoom-scene {
     width: 100%;
     height: 100%;
-    cursor: move;
+    cursor: grab;
+}
+
+.vue-pan-zoom-scene:active {
+    cursor: grabbing;
 }
 
 .g1 {
diff --git a/src/features/boards/board.ts b/src/features/boards/board.ts
index 4743370..8d4545e 100644
--- a/src/features/boards/board.ts
+++ b/src/features/boards/board.ts
@@ -21,9 +21,12 @@ import type {
 } from "util/computed";
 import { processComputable } from "util/computed";
 import { createLazyProxy } from "util/proxies";
-import { computed, Ref, unref } from "vue";
+import { computed, ref, Ref, unref } from "vue";
+import panZoom from "vue-panzoom";
 import type { Link } from "../links/links";
 
+globalBus.on("setupVue", app => panZoom.install(app));
+
 export const BoardType = Symbol("Board");
 
 export type NodeComputable<T> = Computable<T> | ((node: BoardNode) => T);
@@ -166,12 +169,14 @@ export interface BoardOptions {
     types: Record<string, NodeTypeOptions>;
 }
 
-export interface BaseBoard extends Persistent<BoardData> {
+export interface BaseBoard {
     id: string;
+    state: Persistent<BoardData>;
     links: Ref<BoardNodeLink[] | null>;
     nodes: Ref<BoardNode[]>;
     selectedNode: Ref<BoardNode | null>;
     selectedAction: Ref<GenericBoardNodeAction | null>;
+    mousePosition: Ref<{ x: number; y: number } | null>;
     type: typeof BoardType;
     [Component]: typeof BoardComponent;
     [GatherProps]: () => Record<string, unknown>;
@@ -199,140 +204,138 @@ export type GenericBoard = Replace<
 export function createBoard<T extends BoardOptions>(
     optionsFunc: OptionsFunc<T, BaseBoard, GenericBoard>
 ): Board<T> {
-    return createLazyProxy(
-        persistent => {
-            const board = Object.assign(persistent, optionsFunc());
-            board.id = getUniqueID("board-");
-            board.type = BoardType;
-            board[Component] = BoardComponent;
+    return createLazyProxy(() => {
+        const board = optionsFunc();
+        board.id = getUniqueID("board-");
+        board.type = BoardType;
+        board[Component] = BoardComponent;
 
-            board.nodes = computed(() => processedBoard[PersistentState].value.nodes);
-            board.selectedNode = computed(
-                () =>
-                    processedBoard.nodes.value.find(
-                        node => node.id === board[PersistentState].value.selectedNode
-                    ) || null
-            );
-            board.selectedAction = computed(() => {
-                const selectedNode = processedBoard.selectedNode.value;
-                if (selectedNode == null) {
-                    return null;
-                }
-                const type = processedBoard.types[selectedNode.type];
-                if (type.actions == null) {
-                    return null;
-                }
-                return (
-                    type.actions.find(
-                        action => action.id === processedBoard[PersistentState].value.selectedAction
-                    ) || null
-                );
-            });
-            board.links = computed(() => {
-                if (processedBoard.selectedAction.value == null) {
-                    return null;
-                }
-                if (
-                    processedBoard.selectedAction.value.links &&
-                    processedBoard.selectedNode.value
-                ) {
-                    return getNodeProperty(
-                        processedBoard.selectedAction.value.links,
-                        processedBoard.selectedNode.value
-                    );
-                }
-                return null;
-            });
-            processComputable(board as T, "visibility");
-            setDefault(board, "visibility", Visibility.Visible);
-            processComputable(board as T, "width");
-            setDefault(board, "width", "100%");
-            processComputable(board as T, "height");
-            setDefault(board, "height", "400px");
-            processComputable(board as T, "classes");
-            processComputable(board as T, "style");
-
-            for (const type in board.types) {
-                const nodeType: NodeTypeOptions & Partial<BaseNodeType> = board.types[type];
-
-                processComputable(nodeType as NodeTypeOptions, "title");
-                processComputable(nodeType as NodeTypeOptions, "label");
-                processComputable(nodeType as NodeTypeOptions, "size");
-                setDefault(nodeType, "size", 50);
-                processComputable(nodeType as NodeTypeOptions, "draggable");
-                setDefault(nodeType, "draggable", false);
-                processComputable(nodeType as NodeTypeOptions, "shape");
-                setDefault(nodeType, "shape", Shape.Circle);
-                processComputable(nodeType as NodeTypeOptions, "canAccept");
-                setDefault(nodeType, "canAccept", false);
-                processComputable(nodeType as NodeTypeOptions, "progress");
-                processComputable(nodeType as NodeTypeOptions, "progressDisplay");
-                setDefault(nodeType, "progressDisplay", ProgressDisplay.Fill);
-                processComputable(nodeType as NodeTypeOptions, "progressColor");
-                setDefault(nodeType, "progressColor", "none");
-                processComputable(nodeType as NodeTypeOptions, "fillColor");
-                processComputable(nodeType as NodeTypeOptions, "outlineColor");
-                processComputable(nodeType as NodeTypeOptions, "titleColor");
-                processComputable(nodeType as NodeTypeOptions, "actionDistance");
-                setDefault(nodeType, "actionDistance", Math.PI / 6);
-                nodeType.nodes = computed(() =>
-                    board[PersistentState].value.nodes.filter(node => node.type === type)
-                );
-                setDefault(nodeType, "onClick", function (node: BoardNode) {
-                    board[PersistentState].value.selectedNode = node.id;
-                });
-
-                if (nodeType.actions) {
-                    for (const action of nodeType.actions) {
-                        processComputable(action, "visibility");
-                        setDefault(action, "visibility", Visibility.Visible);
-                        processComputable(action, "icon");
-                        processComputable(action, "fillColor");
-                        processComputable(action, "tooltip");
-                        processComputable(action, "links");
-                    }
-                }
-            }
-
-            board[GatherProps] = function (this: GenericBoard) {
-                const {
-                    nodes,
-                    types,
-                    [PersistentState]: state,
-                    visibility,
-                    width,
-                    height,
-                    style,
-                    classes,
-                    links,
-                    selectedAction,
-                    selectedNode
-                } = this;
-                return {
-                    nodes,
-                    types,
-                    [PersistentState]: state,
-                    visibility,
-                    width,
-                    height,
-                    style: unref(style),
-                    classes,
-                    links,
-                    selectedAction,
-                    selectedNode
-                };
-            };
-
-            // This is necessary because board.types is different from T and Board
-            const processedBoard = board as unknown as Board<T>;
-            return processedBoard;
-        },
-        persistent<BoardData>({
+        board.state = persistent<BoardData>({
             nodes: [],
             selectedNode: null,
             selectedAction: null
-        })
-    );
+        });
+        board.nodes = computed(() => processedBoard.state.value.nodes);
+        board.selectedNode = computed(
+            () =>
+                processedBoard.nodes.value.find(
+                    node => node.id === processedBoard.state.value.selectedNode
+                ) || null
+        );
+        board.selectedAction = computed(() => {
+            const selectedNode = processedBoard.selectedNode.value;
+            if (selectedNode == null) {
+                return null;
+            }
+            const type = processedBoard.types[selectedNode.type];
+            if (type.actions == null) {
+                return null;
+            }
+            return (
+                type.actions.find(
+                    action => action.id === processedBoard.state.value.selectedAction
+                ) || null
+            );
+        });
+        board.mousePosition = ref(null);
+        board.links = computed(() => {
+            if (processedBoard.selectedAction.value == null) {
+                return null;
+            }
+            if (processedBoard.selectedAction.value.links && processedBoard.selectedNode.value) {
+                return getNodeProperty(
+                    processedBoard.selectedAction.value.links,
+                    processedBoard.selectedNode.value
+                );
+            }
+            return null;
+        });
+        processComputable(board as T, "visibility");
+        setDefault(board, "visibility", Visibility.Visible);
+        processComputable(board as T, "width");
+        setDefault(board, "width", "100%");
+        processComputable(board as T, "height");
+        setDefault(board, "height", "400px");
+        processComputable(board as T, "classes");
+        processComputable(board as T, "style");
+
+        for (const type in board.types) {
+            const nodeType: NodeTypeOptions & Partial<BaseNodeType> = board.types[type];
+
+            processComputable(nodeType as NodeTypeOptions, "title");
+            processComputable(nodeType as NodeTypeOptions, "label");
+            processComputable(nodeType as NodeTypeOptions, "size");
+            setDefault(nodeType, "size", 50);
+            processComputable(nodeType as NodeTypeOptions, "draggable");
+            setDefault(nodeType, "draggable", false);
+            processComputable(nodeType as NodeTypeOptions, "shape");
+            setDefault(nodeType, "shape", Shape.Circle);
+            processComputable(nodeType as NodeTypeOptions, "canAccept");
+            setDefault(nodeType, "canAccept", false);
+            processComputable(nodeType as NodeTypeOptions, "progress");
+            processComputable(nodeType as NodeTypeOptions, "progressDisplay");
+            setDefault(nodeType, "progressDisplay", ProgressDisplay.Fill);
+            processComputable(nodeType as NodeTypeOptions, "progressColor");
+            setDefault(nodeType, "progressColor", "none");
+            processComputable(nodeType as NodeTypeOptions, "fillColor");
+            processComputable(nodeType as NodeTypeOptions, "outlineColor");
+            processComputable(nodeType as NodeTypeOptions, "titleColor");
+            processComputable(nodeType as NodeTypeOptions, "actionDistance");
+            setDefault(nodeType, "actionDistance", Math.PI / 6);
+            nodeType.nodes = computed(() =>
+                processedBoard.state.value.nodes.filter(node => node.type === type)
+            );
+            setDefault(nodeType, "onClick", function (node: BoardNode) {
+                processedBoard.state.value.selectedNode = node.id;
+            });
+
+            if (nodeType.actions) {
+                for (const action of nodeType.actions) {
+                    processComputable(action, "visibility");
+                    setDefault(action, "visibility", Visibility.Visible);
+                    processComputable(action, "icon");
+                    processComputable(action, "fillColor");
+                    processComputable(action, "tooltip");
+                    processComputable(action, "links");
+                }
+            }
+        }
+
+        board[GatherProps] = function (this: GenericBoard) {
+            const {
+                nodes,
+                types,
+                state,
+                visibility,
+                width,
+                height,
+                style,
+                classes,
+                links,
+                selectedAction,
+                selectedNode,
+                mousePosition
+            } = this;
+            return {
+                nodes,
+                types,
+                state,
+                visibility,
+                width,
+                height,
+                style: unref(style),
+                classes,
+                links,
+                selectedAction,
+                selectedNode,
+                mousePosition
+            };
+        };
+
+        // This is necessary because board.types is different from T and Board
+        const processedBoard = board as unknown as Board<T>;
+        return processedBoard;
+    });
 }
 
 export function getNodeProperty<T>(property: NodeComputable<T>, node: BoardNode): T {