Index: src/assets/js/ward-sky.js
===================================================================
--- src/assets/js/ward-sky.js	(revision 39dab6b0c3b5fd70bb2f06743430f0014384b3b7)
+++ src/assets/js/ward-sky.js	(revision 3866994fe8be998ed18b62cee35531e7d013b18d)
@@ -254,51 +254,111 @@
   }
 
+  /** De schaal binnen zijn grenzen. Eén plek, zodat wiel en pinch gelijk zijn. */
+  _clampScale(s) { return Math.min(Math.max(s, 0.2), 2.5); }
+
+  /**
+   * Zoomen ROND een schermpunt, niet rond het canvas-midden: de wereld onder
+   * je vingers (of je muis) blijft staan, zodat de hemel zich als een kaart
+   * gedraagt in plaats van onder je weg te schuiven.
+   */
+  _zoomAround(px, py, factor) {
+    const next = this._clampScale(this.scale * factor);
+    const cx = this.w / 2, cy = this.h / 2;
+    // Het wereldpunt onder (px,py) mag na de zoom op dezelfde plek blijven.
+    const worldX = (px - cx - this.panX) / this.scale;
+    const worldY = (py - cy - this.panY) / this.scale;
+    this.panX = px - cx - worldX * next;
+    this.panY = py - cy - worldY * next;
+    this.scale = next;
+  }
+
   _wire() {
     const c = this.canvas;
-    let dragging = false, lastX = 0, lastY = 0, moved = 0;
-    let pinch = null;
+    // Alle vingers die nu op de hemel liggen. Eén = pannen of tikken, twee =
+    // knijpen. Een Map want pointers komen en gaan in willekeurige volgorde.
+    const pts = new Map();
+    let moved = 0;                 // hoeveel de ENE vinger bewoog (tik vs sleep)
+    let pinchDist = 0;             // vingerafstand bij het begin van een knijp
+    let becamePinch = false;       // ooit twee vingers? dan geen tik aan het eind
+
+    const local = (e) => {
+      const r = c.getBoundingClientRect();
+      return { x: e.clientX - r.left, y: e.clientY - r.top };
+    };
+    const twoPointerState = () => {
+      const [a, b] = [...pts.values()];
+      return {
+        dist: Math.hypot(a.x - b.x, a.y - b.y),
+        mx: (a.x + b.x) / 2, my: (a.y + b.y) / 2,
+      };
+    };
 
     c.addEventListener('pointerdown', (e) => {
       if (!this.interactive) return;
-      dragging = true; moved = 0;
-      lastX = e.clientX; lastY = e.clientY;
       c.setPointerCapture(e.pointerId);
+      pts.set(e.pointerId, local(e));
+      moved = 0;
+      if (pts.size === 2) { becamePinch = true; pinchDist = twoPointerState().dist; }
     });
+
     c.addEventListener('pointermove', (e) => {
-      if (!this.interactive || !dragging) return;
-      const dx = e.clientX - lastX, dy = e.clientY - lastY;
-      moved += Math.abs(dx) + Math.abs(dy);
-      this.panX += dx; this.panY += dy;
-      lastX = e.clientX; lastY = e.clientY;
+      if (!this.interactive || !pts.has(e.pointerId)) return;
+      const prev = pts.get(e.pointerId);
+      const cur = local(e);
+      pts.set(e.pointerId, cur);
+
+      if (pts.size >= 2) {
+        // Knijpen: schaal volgt de verhouding van de vingerafstand, en we
+        // zoomen om het midden tussen de vingers.
+        const { dist, mx, my } = twoPointerState();
+        if (pinchDist > 0 && dist > 0) {
+          this._zoomAround(mx, my, dist / pinchDist);
+          pinchDist = dist;
+        }
+      } else {
+        // Eén vinger: pannen.
+        const dx = cur.x - prev.x, dy = cur.y - prev.y;
+        moved += Math.abs(dx) + Math.abs(dy);
+        this.panX += dx; this.panY += dy;
+      }
     });
-    const end = (e) => {
-      if (!this.interactive || !dragging) return;
-      dragging = false;
-      // Nauwelijks bewogen? Dan was het een tik en geen sleep.
-      if (moved < 6) {
-        const r = c.getBoundingClientRect();
-        const x = e.clientX - r.left;
-        const y = e.clientY - r.top;
+
+    const lift = (e) => {
+      if (!pts.has(e.pointerId)) return;
+      const cur = pts.get(e.pointerId);
+      pts.delete(e.pointerId);
+      // Nog één vinger over na een knijp: zijn afstand als nieuw ijkpunt, en
+      // niet meteen als tik tellen.
+      if (pts.size === 1) { pinchDist = 0; return; }
+      if (pts.size > 1) return;
+
+      // Laatste vinger los. Een tik alleen als er nooit geknepen is en de
+      // vinger nauwelijks bewoog.
+      if (!becamePinch && moved < 6) {
+        const { x, y } = cur;
         const hit = nearestFace(x, y, this.spots, {
           cx: this.w / 2, cy: this.h / 2, cell: CELL, scale: this.scale,
           panX: this.panX, panY: this.panY,
         });
-        if (hit != null) { this.onPick?.(hit); return; }
-        // Geen gezicht? Dan misschien de (+): zelfde middelpunt als getekend,
-        // met een raakzone die meeschaalt zodat mikken bij elke zoom klopt.
-        const s2 = Math.min(Math.max(this.scale, 0.5), 1.6);
-        const d = Math.hypot(x - (this.w / 2 + this.panX), y - (this.h / 2 + this.panY));
-        if (d <= 26 * s2) this.onNew?.();
+        if (hit != null) { this.onPick?.(hit); }
+        else {
+          // Geen gezicht? Dan misschien de (+): zelfde middelpunt als getekend,
+          // met een raakzone die meeschaalt zodat mikken bij elke zoom klopt.
+          const s2 = Math.min(Math.max(this.scale, 0.5), 1.6);
+          const d = Math.hypot(x - (this.w / 2 + this.panX), y - (this.h / 2 + this.panY));
+          if (d <= 26 * s2) this.onNew?.();
+        }
       }
-    };
-    c.addEventListener('pointerup', end);
-    c.addEventListener('pointercancel', () => { dragging = false; });
-
-    // Knijpen op een telefoon, wiel op een desktop; allebei zoomen om het
-    // midden, zodat de hemel zich als een kaart gedraagt.
+      becamePinch = false;
+    };
+    c.addEventListener('pointerup', lift);
+    c.addEventListener('pointercancel', (e) => { pts.delete(e.pointerId); if (pts.size < 2) pinchDist = 0; });
+
+    // Het wiel op een desktop: zoomen om de muis, dezelfde weg als de pinch.
     c.addEventListener('wheel', (e) => {
       if (!this.interactive) return;
       e.preventDefault();
-      this.scale = Math.min(Math.max(this.scale * (e.deltaY < 0 ? 1.1 : 0.9), 0.2), 2.5);
+      const { x, y } = local(e);
+      this._zoomAround(x, y, e.deltaY < 0 ? 1.1 : 0.9);
     }, { passive: false });
 
