diff options
Diffstat (limited to 'public')
| -rw-r--r-- | public/app.css | 18 | ||||
| -rw-r--r-- | public/app.js | 76 | ||||
| -rw-r--r-- | public/index.html | 4 |
3 files changed, 83 insertions, 15 deletions
diff --git a/public/app.css b/public/app.css index a2df38d..6d5e992 100644 --- a/public/app.css +++ b/public/app.css @@ -91,6 +91,24 @@ html, body { filter: drop-shadow(0 0 8px rgba(125, 211, 252, .9)); } +@media (hover: none) { + #stage { + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; + } + .nav-zone-N { height: 25%; } + .nav-zone-S { height: 25%; } + .nav-zone-E { top: 26%; height: 48%; width: 25%; } + .nav-zone-W { top: 26%; height: 48%; width: 25%; } + .nav-edge { + opacity: 1; + filter: drop-shadow(0 0 6px rgba(125, 211, 252, .45)); + } + .nav-zone:active .nav-edge { + filter: drop-shadow(0 0 12px rgba(125, 211, 252, 1)); + } +} + #splash { position: fixed; inset: 0; diff --git a/public/app.js b/public/app.js index 95f64d4..1c920ae 100644 --- a/public/app.js +++ b/public/app.js @@ -26,6 +26,8 @@ let scale = 1 let userZoomed = false let drag = null +const TOUCH = typeof window.matchMedia === "function" && window.matchMedia("(hover: none)").matches + function aspect(info) { return info.h > 0 ? info.w / info.h : 1 } @@ -197,19 +199,22 @@ function renderNav(id) { const exits = exitsCache.get(id) if (!exits) return const isCurrent = cur && cur.id === id + if (TOUCH && !isCurrent) return const nav = document.createElement("div") nav.className = "nav" for (const ex of exits) { const zone = document.createElement("div") zone.className = `nav-zone nav-zone-${ex.dir}` - zone.addEventListener("pointermove", () => { - if (adjacentTile(id, ex.dir)) return - cancelHideGhost() - showGhost(choosePlacement(id, ex.dir)) - }) - zone.addEventListener("pointerleave", () => { - scheduleHideGhost() - }) + if (!TOUCH) { + zone.addEventListener("pointermove", () => { + if (adjacentTile(id, ex.dir)) return + cancelHideGhost() + showGhost(choosePlacement(id, ex.dir)) + }) + zone.addEventListener("pointerleave", () => { + scheduleHideGhost() + }) + } zone.addEventListener("pointerdown", (e) => e.stopPropagation()) zone.addEventListener("click", (e) => { e.stopPropagation(); traverseFrom(id, ex.dir) }) if (!adjacentTile(id, ex.dir) && isCurrent) { @@ -389,6 +394,7 @@ function clearAllZones() { function renderTileZones(id) { clearTileZones(id) + if (TOUCH) return if (!tiles.has(id) || !exitsCache.has(id)) return const els = [] for (const dir of ["N", "E", "S", "W"]) { @@ -425,15 +431,37 @@ stage.addEventListener("pointerover", (e) => { if (tileEl) getExits(Number(tileEl.dataset.id)) }) +const pointers = new Map() +let pinchDist = 0 + stage.addEventListener("pointerdown", (e) => { if (e.target.closest(".nav-zone")) return + pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }) + stage.setPointerCapture(e.pointerId) hideGhost() - drag = { x: e.clientX, y: e.clientY, px: panX, py: panY, moved: false, tile: e.target.closest(".tile") } world.style.transition = "none" - stage.setPointerCapture(e.pointerId) + if (pointers.size === 2) { + drag = null + const [a, b] = [...pointers.values()] + pinchDist = Math.hypot(b.x - a.x, b.y - a.y) + return + } + drag = { x: e.clientX, y: e.clientY, px: panX, py: panY, moved: false, tile: e.target.closest(".tile") } }) stage.addEventListener("pointermove", (e) => { + if (pointers.has(e.pointerId)) { + pointers.set(e.pointerId, { x: e.clientX, y: e.clientY }) + if (pointers.size === 2 && pinchDist > 0) { + const [a, b] = [...pointers.values()] + const d = Math.hypot(b.x - a.x, b.y - a.y) + if (d > 0) { + zoomAt((a.x + b.x) / 2, (a.y + b.y) / 2, d / pinchDist) + pinchDist = d + } + return + } + } if (!drag) return const dx = e.clientX - drag.x const dy = e.clientY - drag.y @@ -446,23 +474,45 @@ stage.addEventListener("pointermove", (e) => { }) stage.addEventListener("pointerup", (e) => { + const wasPinch = pointers.size >= 2 + pointers.delete(e.pointerId) + if (wasPinch && pointers.size === 1) { + const p = [...pointers.values()][0] + drag = { x: p.x, y: p.y, px: panX, py: panY, moved: true, tile: null } + } + pinchDist = 0 if (!drag) return const wasDrag = drag.moved const tile = drag.tile drag = null world.style.transition = "" - if (wasDrag || !tile) return + if (wasDrag || !tile) { + if (!wasDrag && !tile && e.pointerType === "touch") bgTap() + return + } const id = Number(tile.dataset.id) if (cur && cur.id === id) return setCurrent(id, tiles.get(id).rect) }) -stage.addEventListener("pointercancel", () => { - if (!drag) return +stage.addEventListener("pointercancel", (e) => { + pointers.delete(e.pointerId) + pinchDist = 0 drag = null world.style.transition = "" }) +let lastBgTap = -Infinity +function bgTap() { + const now = performance.now() + if (now - lastBgTap < 350) { + lastBgTap = 0 + resetZoom() + return + } + lastBgTap = now +} + let zoomTimer = 0 stage.addEventListener("wheel", (e) => { e.preventDefault() diff --git a/public/index.html b/public/index.html index 4c13a5a..0beff50 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=20"> + <link rel="stylesheet" href="/app.css?v=21"> </head> <body> <main id="stage"> @@ -16,6 +16,6 @@ <p>four paths, one per direction. walk them.</p> <button id="start">start</button> </div> - <script src="/app.js?v=20"></script> + <script src="/app.js?v=21"></script> </body> </html> |
