diff --git a/vault.html b/vault.html
index aca2edf..ea0e421 100644
--- a/vault.html
+++ b/vault.html
@@ -13,7 +13,8 @@
--accent: #FFD700; --link: #FFD700; --border: #313244;
--green: #a6e3a1; --red: #f38ba8; --blue: #89b4fa; --teal: #94e2d5;
}
-* { margin: 0; padding: 0; box-sizing: border-box; }
+* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
+html, body { touch-action: manipulation; }
body { background: var(--bg-primary); color: var(--text-primary); font-family: -apple-system, 'Segoe UI', Inter, sans-serif; height: 100vh; overflow: hidden; }
.vault-app {
@@ -47,7 +48,8 @@ body { background: var(--bg-primary); color: var(--text-primary); font-family: -
/* Sidebar file tree */
.vault-sidebar {
background: var(--bg-secondary); border-right: 1px solid var(--border);
- overflow-y: auto; padding: 8px 0; font-size: 13px;
+ overflow-y: auto; -webkit-overflow-scrolling: touch;
+ touch-action: pan-y; padding: 8px 0; font-size: 13px;
}
.tree-folder { cursor: pointer; user-select: none; }
.tree-folder-header {
@@ -68,7 +70,8 @@ body { background: var(--bg-primary); color: var(--text-primary); font-family: -
/* Content area */
.vault-content {
- overflow-y: auto; padding: 32px 48px; max-width: 900px;
+ overflow-y: auto; -webkit-overflow-scrolling: touch;
+ touch-action: pan-y; padding: 32px 48px; max-width: 900px;
line-height: 1.7; font-size: 15px;
}
.vault-content h1 { color: var(--accent); font-size: 28px; margin: 24px 0 12px; }
@@ -94,9 +97,9 @@ body { background: var(--bg-primary); color: var(--text-primary); font-family: -
/* Graph panel */
.vault-graph {
background: var(--bg-secondary); border-left: 1px solid var(--border);
- position: relative; overflow: hidden;
+ position: relative; overflow: hidden; touch-action: none;
}
-.vault-graph canvas { width: 100% !important; height: 100% !important; }
+.vault-graph canvas { width: 100% !important; height: 100% !important; touch-action: none; }
/* Search results overlay */
#search-results {
@@ -321,23 +324,65 @@ async function loadGraph() {
graphScene.add(new THREE.Line(geo, mat));
});
- // Orbit
+ // Orbit (mouse + touch support)
let isDrag = false, lastX = 0, lastY = 0, rotX = 0, rotY = 0;
- canvas.addEventListener('mousedown', e => { isDrag = true; lastX = e.clientX; lastY = e.clientY; });
- canvas.addEventListener('mousemove', e => {
+ function getPos(e) {
+ if (e.touches && e.touches.length) {
+ return { x: e.touches[0].clientX, y: e.touches[0].clientY };
+ }
+ return { x: e.clientX, y: e.clientY };
+ }
+ function onDragStart(e) {
+ e.preventDefault();
+ isDrag = true;
+ const p = getPos(e);
+ lastX = p.x; lastY = p.y;
+ }
+ function onDragMove(e) {
if (!isDrag) return;
- rotY += (e.clientX - lastX) * 0.005;
- rotX += (e.clientY - lastY) * 0.005;
- lastX = e.clientX; lastY = e.clientY;
- });
- canvas.addEventListener('mouseup', () => isDrag = false);
- canvas.addEventListener('wheel', e => {
+ e.preventDefault();
+ const p = getPos(e);
+ rotY += (p.x - lastX) * 0.005;
+ rotX += (p.y - lastY) * 0.005;
+ lastX = p.x; lastY = p.y;
+ }
+ function onDragEnd(e) {
+ if (e && e.preventDefault) e.preventDefault();
+ isDrag = false;
+ }
+ function onWheel(e) {
graphCamera.position.z += e.deltaY * 0.5;
graphCamera.position.z = Math.max(50, Math.min(800, graphCamera.position.z));
- });
+ }
+ canvas.addEventListener('mousedown', onDragStart);
+ canvas.addEventListener('mousemove', onDragMove);
+ window.addEventListener('mouseup', onDragEnd);
+ canvas.addEventListener('touchstart', onDragStart, { passive: false });
+ canvas.addEventListener('touchmove', onDragMove, { passive: false });
+ canvas.addEventListener('touchend', onDragEnd, { passive: false });
+ canvas.addEventListener('touchcancel', onDragEnd, { passive: false });
+ canvas.addEventListener('wheel', onWheel, { passive: true });
+
+ // Auto-rotate: when user is not interacting, slow rotation.
+ // Pauses when tab is hidden, when user is dragging, and after page visibility loss.
+ let autoRotate = true;
+ let lastInteractionTime = 0;
+ const AUTO_ROTATE_RESUME_MS = 3000; // resume auto-rotation 3s after user stops dragging
function animateGraph() {
requestAnimationFrame(animateGraph);
+ if (document.hidden) return; // skip rendering when tab is in background
+ const now = Date.now();
+ const userJustInteracted = (now - lastInteractionTime) < AUTO_ROTATE_RESUME_MS;
+ if (isDrag) {
+ lastInteractionTime = now;
+ autoRotate = false;
+ } else if (!userJustInteracted) {
+ autoRotate = true;
+ }
+ if (autoRotate && !isDrag) {
+ rotY += 0.002;
+ }
graphScene.rotation.y = rotY;
graphScene.rotation.x = rotX;
graphRenderer.render(graphScene, graphCamera);