aboutsummaryrefslogtreecommitdiff
path: root/public
diff options
context:
space:
mode:
authordax <me@dax.ist>2026-08-04 08:31:33 +0000
committerdax <me@dax.ist>2026-08-04 08:31:33 +0000
commit3b57a64799205f3b8c3ef2a4b74e1a967d36d1e8 (patch)
tree500e54f5b180df87cc57e7c4b810f993dd4e7865 /public
parent861ccce517f8c879b7c4f3c162ccced856486205 (diff)
pathways: scroll pans / pinch zooms; ghost preview on hover of destination space or arrow (not the zone band); invisible dropzones for current tile
Diffstat (limited to 'public')
-rw-r--r--public/app.css1
-rw-r--r--public/app.js52
-rw-r--r--public/index.html6
3 files changed, 47 insertions, 12 deletions
diff --git a/public/app.css b/public/app.css
index e1f3db0..30187b7 100644
--- a/public/app.css
+++ b/public/app.css
@@ -49,6 +49,7 @@ html, body {
pointer-events: none;
box-shadow: 0 0 0 2px rgba(125, 211, 252, .65);
}
+.dropzone { position: absolute; }
@keyframes lock {
0% { transform: scale(.2); opacity: 0; }
diff --git a/public/app.js b/public/app.js
index 9f27b87..aa2eef3 100644
--- a/public/app.js
+++ b/public/app.js
@@ -191,6 +191,7 @@ function setCurrent(id, rect) {
tiles.get(id).el.classList.add("current")
if (!userZoomed) scale = worldScale()
center()
+ renderDropzones(id)
getExits(id)
}
@@ -211,8 +212,8 @@ function renderNav(id) {
b.innerHTML = ARROW_SVG[ex.dir]
b.addEventListener("pointerdown", (e) => e.stopPropagation())
b.addEventListener("click", (e) => { e.stopPropagation(); moveFrom(id, ex.dir) })
- zone.addEventListener("pointerenter", () => showGhost(choosePlacement(id, ex.dir)))
- zone.addEventListener("pointerleave", () => hideGhost())
+ b.addEventListener("pointerenter", () => showGhost(choosePlacement(id, ex.dir)))
+ b.addEventListener("pointerleave", () => hideGhost())
zone.appendChild(b)
nav.appendChild(zone)
}
@@ -232,6 +233,7 @@ async function getExits(id) {
const exits = data.exits || []
exitsCache.set(id, exits)
renderNav(id)
+ if (cur && cur.id === id) renderDropzones(id)
return exits
} catch {
return null
@@ -309,6 +311,34 @@ function hideGhost() {
}
}
+let dropzones = []
+function clearDropzones() {
+ for (const z of dropzones) world.removeChild(z)
+ dropzones = []
+}
+
+function renderDropzones(id) {
+ clearDropzones()
+ const t = tiles.get(id)
+ if (!t) return
+ for (const dir of ["N", "E", "S", "W"]) {
+ const p = choosePlacement(id, dir)
+ if (!p) continue
+ const z = document.createElement("div")
+ z.className = "dropzone"
+ z.style.left = p.rect.x + "px"
+ z.style.top = p.rect.y + "px"
+ z.style.width = p.rect.w + "px"
+ z.style.height = p.rect.h + "px"
+ z.addEventListener("pointerenter", () => showGhost(p))
+ z.addEventListener("pointerleave", () => hideGhost())
+ z.addEventListener("pointerdown", (e) => e.stopPropagation())
+ z.addEventListener("click", () => moveFrom(id, dir))
+ world.appendChild(z)
+ dropzones.push(z)
+ }
+}
+
stage.addEventListener("pointerover", (e) => {
const tileEl = e.target.closest(".tile")
if (tileEl) getExits(Number(tileEl.dataset.id))
@@ -350,15 +380,19 @@ stage.addEventListener("pointerup", (e) => {
let zoomTimer = 0
stage.addEventListener("wheel", (e) => {
e.preventDefault()
- const rect = stage.getBoundingClientRect()
- const mx = e.clientX - rect.left
- const my = e.clientY - rect.top
+ let dx = e.deltaX
let dy = e.deltaY
- if (e.deltaMode === 1) dy *= 16
- else if (e.deltaMode === 2) dy *= window.innerHeight
- const factor = Math.exp(-dy * (e.ctrlKey ? 0.012 : 0.0018))
+ if (e.deltaMode === 1) { dx *= 16; dy *= 16 }
+ else if (e.deltaMode === 2) { dx *= window.innerWidth; dy *= window.innerHeight }
world.style.transition = "none"
- zoomAt(mx, my, factor)
+ if (e.ctrlKey) {
+ const rect = stage.getBoundingClientRect()
+ zoomAt(e.clientX - rect.left, e.clientY - rect.top, Math.exp(-dy * 0.01))
+ } else {
+ panX -= dx
+ panY -= dy
+ applyPan()
+ }
clearTimeout(zoomTimer)
zoomTimer = setTimeout(() => { world.style.transition = "" }, 120)
}, { passive: false })
diff --git a/public/index.html b/public/index.html
index 5e31bbf..7f378d3 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>pathways</title>
- <link rel="stylesheet" href="/app.css?v=7">
+ <link rel="stylesheet" href="/app.css?v=8">
</head>
<body>
<main id="stage">
@@ -13,9 +13,9 @@
</main>
<div id="splash">
<h1>pathways<span class="dot">.</span></h1>
- <p>four paths, one per direction. walk them.<br><span class="hint">drag to pan · scroll to zoom · double-click empty space or press 0 to fit</span></p>
+ <p>four paths, one per direction. walk them.<br><span class="hint">drag or scroll to pan · pinch to zoom · double-click empty space or press 0 to fit</span></p>
<button id="start">start</button>
</div>
- <script src="/app.js?v=7"></script>
+ <script src="/app.js?v=8"></script>
</body>
</html>