aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/app.css19
-rw-r--r--public/app.js143
-rw-r--r--public/index.html4
3 files changed, 56 insertions, 110 deletions
diff --git a/public/app.css b/public/app.css
index bbc2655..fd7612e 100644
--- a/public/app.css
+++ b/public/app.css
@@ -48,26 +48,17 @@ 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);
-}
.dropzone { position: absolute; cursor: pointer; }
-.dropzone.hidden { visibility: hidden; }
-.dropzone .preview.hide { visibility: hidden; }
-.dropzone .preview {
- width: 100%;
- height: 100%;
- opacity: .5;
+.preview {
+ position: absolute;
+ opacity: .35;
pointer-events: none;
border-radius: 6px;
box-shadow: 0 0 0 2px rgba(125, 211, 252, .5);
transition: opacity .15s;
}
-.dropzone:hover .preview,
-.dropzone:active .preview { opacity: .85; }
-.dropzone .preview img {
+.preview.hot { opacity: .85; }
+.preview img {
width: 100%;
height: 100%;
object-fit: cover;
diff --git a/public/app.js b/public/app.js
index cab053e..f323b22 100644
--- a/public/app.js
+++ b/public/app.js
@@ -210,30 +210,15 @@ function renderNav(id) {
t.el.querySelectorAll(".nav").forEach((n) => n.remove())
const exits = exitsCache.get(id)
if (!exits) return
- const isCurrent = cur && cur.id === id
- if (TOUCH && !isCurrent) return
+ if (cur && cur.id === id) return
+ if (TOUCH) 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}`
- 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) {
- const edge = document.createElement("div")
- edge.className = `nav-edge nav-edge-${ex.dir}`
- zone.appendChild(edge)
- }
nav.appendChild(zone)
}
t.el.appendChild(nav)
@@ -341,70 +326,21 @@ function traverseFrom(id, dir) {
followEnabled = false
const back = adjacentTile(id, dir)
if (back) {
- hideGhost()
setCurrent(back, tiles.get(back).rect)
return
}
const chosen = choosePlacement(id, dir)
if (!chosen) return
- hideGhost()
addTile(chosen.info, chosen.rect)
setCurrent(chosen.id, chosen.rect)
}
-let ghost = null
-let ghostKey = ""
-let ghostVisible = false
-function setPreviewHidden(hidden) {
- ghostVisible = hidden
- if (!cur) return
- const entry = zones.get(cur.id)
- if (entry) entry.prevEl.classList.toggle("hide", hidden)
-}
-function showGhost(p) {
- if (!p) return
- const key = p.id + "|" + p.rect.x + "," + p.rect.y
- if (ghost && ghostKey === key) return
- hideGhost()
- ghostKey = key
- 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 = ""
- el.appendChild(img)
- world.appendChild(el)
- ghost = el
- setPreviewHidden(true)
-}
-
-function hideGhost() {
- if (ghost) {
- world.removeChild(ghost)
- ghost = null
- }
- ghostKey = ""
- setPreviewHidden(false)
-}
-
-let hideGhostTimer = 0
-function scheduleHideGhost() {
- clearTimeout(hideGhostTimer)
- hideGhostTimer = setTimeout(hideGhost, 80)
-}
-function cancelHideGhost() {
- clearTimeout(hideGhostTimer)
-}
-
const zones = new Map()
function clearTileZones(id) {
const entry = zones.get(id)
if (entry) {
for (const z of entry.els) world.removeChild(z)
+ if (entry.preview) world.removeChild(entry.preview)
zones.delete(id)
}
}
@@ -413,6 +349,12 @@ function clearAllZones() {
for (const id of [...zones.keys()]) clearTileZones(id)
}
+function resetPreview() {
+ if (!cur) return
+ const entry = zones.get(cur.id)
+ if (entry && entry.reset) entry.reset()
+}
+
function renderTileZones(id) {
if (!cur || id !== cur.id) return
if (!tiles.has(id) || !exitsCache.has(id)) return
@@ -429,28 +371,30 @@ function renderTileZones(id) {
const key = dirs.map((x) => x.dir + x.p.info.id + "@" + x.p.rect.x + "," + x.p.rect.y).join("|")
let best = null
for (const x of dirs) if (!best || x.d < best.d) best = x
- const entry = zones.get(id)
- if (entry && entry.key === key) {
- if (best && best.dir !== entry.bestDir) {
- const z = entry.els.find((el) => el.dataset.dir === best.dir)
- if (z) {
- z.appendChild(entry.prevEl)
- entry.img.src = best.p.info.thumb
- entry.bestDir = best.dir
- }
- }
- return
- }
+ const existing = zones.get(id)
+ if (existing && existing.key === key) return
clearTileZones(id)
if (!best) return
const els = []
- const prevEl = document.createElement("div")
- prevEl.className = "preview"
- const img = document.createElement("img")
- img.src = best.p.info.thumb
- img.alt = ""
- img.loading = "lazy"
- prevEl.appendChild(img)
+ const preview = document.createElement("div")
+ preview.className = "preview"
+ const previewImg = document.createElement("img")
+ previewImg.alt = ""
+ previewImg.loading = "lazy"
+ preview.appendChild(previewImg)
+ let hotDir = null
+ const showAt = (x) => {
+ preview.style.left = x.p.rect.x + "px"
+ preview.style.top = x.p.rect.y + "px"
+ preview.style.width = x.p.rect.w + "px"
+ preview.style.height = x.p.rect.h + "px"
+ previewImg.src = x.p.info.thumb
+ }
+ const reset = () => {
+ hotDir = null
+ preview.classList.remove("hot")
+ if (best) showAt(best)
+ }
for (const x of dirs) {
const z = document.createElement("div")
z.className = "dropzone"
@@ -461,17 +405,27 @@ function renderTileZones(id) {
z.style.height = x.p.rect.h + "px"
z.addEventListener("pointerdown", (e) => e.stopPropagation())
z.addEventListener("click", () => traverseFrom(id, x.dir))
- if (!TOUCH && x.dir !== best.dir) {
- z.addEventListener("pointermove", () => { cancelHideGhost(); showGhost(x.p) })
- z.addEventListener("pointerleave", () => scheduleHideGhost())
+ if (!TOUCH) {
+ z.addEventListener("pointermove", () => {
+ if (hotDir !== x.dir) {
+ hotDir = x.dir
+ showAt(x)
+ preview.classList.add("hot")
+ }
+ })
+ z.addEventListener("pointerleave", () => {
+ if (hotDir === x.dir) {
+ hotDir = null
+ preview.classList.remove("hot")
+ }
+ })
}
world.appendChild(z)
els.push(z)
}
- const bestEl = els.find((el) => el.dataset.dir === best.dir)
- bestEl.appendChild(prevEl)
- zones.set(id, { els, key, bestDir: best.dir, prevEl, img })
- setPreviewHidden(ghostVisible)
+ world.appendChild(preview)
+ showAt(best)
+ zones.set(id, { els, key, preview, reset })
}
function refreshPreviews() {
@@ -528,7 +482,7 @@ stage.addEventListener("pointerdown", (e) => {
if (e.target.closest("#import-state")) return
pointers.set(e.pointerId, { x: e.clientX, y: e.clientY })
try { stage.setPointerCapture(e.pointerId) } catch { /* ignore */ }
- hideGhost()
+ resetPreview()
world.style.transition = "none"
if (pointers.size === 2) {
followEnabled = true
@@ -621,6 +575,7 @@ stage.addEventListener("wheel", (e) => {
const rect = stage.getBoundingClientRect()
zoomAt(e.clientX - rect.left, e.clientY - rect.top, Math.exp(-dy * 0.01))
} else {
+ resetPreview()
panX -= dx
panY -= dy
applyPan()
diff --git a/public/index.html b/public/index.html
index 1fab56b..b31e8ce 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=46">
+ <link rel="stylesheet" href="/app.css?v=52">
</head>
<body>
<main id="stage">
@@ -29,6 +29,6 @@
<span id="progress-text"></span>
</div>
<div id="toast" role="status" aria-live="polite"></div>
- <script src="/app.js?v=46"></script>
+ <script src="/app.js?v=52"></script>
</body>
</html>