aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/app.css5
-rw-r--r--public/app.js42
-rw-r--r--public/index.html4
3 files changed, 46 insertions, 5 deletions
diff --git a/public/app.css b/public/app.css
index ceac2aa..e1f3db0 100644
--- a/public/app.css
+++ b/public/app.css
@@ -44,6 +44,11 @@ html, body {
}
.tile img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 6px; }
.tile.lock { animation: lock .5s cubic-bezier(.22, 1, .36, 1); }
+.tile.ghost {
+ opacity: .5;
+ pointer-events: none;
+ box-shadow: 0 0 0 2px rgba(125, 211, 252, .65);
+}
@keyframes lock {
0% { transform: scale(.2); opacity: 0; }
diff --git a/public/app.js b/public/app.js
index 2797347..9f27b87 100644
--- a/public/app.js
+++ b/public/app.js
@@ -211,6 +211,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())
zone.appendChild(b)
nav.appendChild(zone)
}
@@ -240,10 +242,10 @@ async function getExits(id) {
return p
}
-async function moveFrom(id, dir) {
+function choosePlacement(id, dir) {
const exits = exitsCache.get(id)
const ex = exits && exits.find((e) => e.dir === dir)
- if (!ex) return
+ if (!ex) return null
const from = tiles.get(id).rect
const seen = new Set()
const candIds = []
@@ -265,15 +267,48 @@ async function moveFrom(id, dir) {
const p = bestPlacement(tileW(info), tileH(info), from, dir)
if (p) scored.push({ id: cid, info, rect: p.rect, cost: p.cost })
}
- if (!scored.length) return
+ if (!scored.length) return null
scored.sort((a, b) => a.cost - b.cost)
const best = scored[0]
const next = ex.next && !rectOf.has(ex.next.id) ? scored.find((s) => s.id === ex.next.id) : null
const chosen = next && next.cost - best.cost <= FIT_TOLERANCE ? next : best
+ return { id: chosen.id, info: chosen.info, rect: chosen.rect }
+}
+
+function moveFrom(id, dir) {
+ const chosen = choosePlacement(id, dir)
+ if (!chosen) return
+ hideGhost()
addTile(chosen.info, chosen.rect)
setCurrent(chosen.id, chosen.rect)
}
+let ghost = null
+function showGhost(p) {
+ hideGhost()
+ if (!p) return
+ const el = document.createElement("div")
+ el.className = "tile ghost"
+ el.style.left = p.rect.x + "px"
+ el.style.top = p.rect.y + "px"
+ el.style.width = p.rect.w + "px"
+ el.style.height = p.rect.h + "px"
+ const img = document.createElement("img")
+ img.src = p.info.thumb
+ img.alt = ""
+ img.loading = "lazy"
+ el.appendChild(img)
+ world.appendChild(el)
+ ghost = el
+}
+
+function hideGhost() {
+ if (ghost) {
+ world.removeChild(ghost)
+ ghost = null
+ }
+}
+
stage.addEventListener("pointerover", (e) => {
const tileEl = e.target.closest(".tile")
if (tileEl) getExits(Number(tileEl.dataset.id))
@@ -281,6 +316,7 @@ stage.addEventListener("pointerover", (e) => {
stage.addEventListener("pointerdown", (e) => {
if (e.target.closest(".nav-btn")) return
+ 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)
diff --git a/public/index.html b/public/index.html
index 8aa5ed7..5e31bbf 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=6">
+ <link rel="stylesheet" href="/app.css?v=7">
</head>
<body>
<main id="stage">
@@ -16,6 +16,6 @@
<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>
<button id="start">start</button>
</div>
- <script src="/app.js?v=6"></script>
+ <script src="/app.js?v=7"></script>
</body>
</html>