aboutsummaryrefslogtreecommitdiff
path: root/public
diff options
context:
space:
mode:
authordax <me@dax.ist>2026-08-04 07:25:52 +0000
committerdax <me@dax.ist>2026-08-04 07:25:52 +0000
commita2315c4349dcca405b762898652d3d067f3001e9 (patch)
tree67b843844e6e10cfb5eec98f1f5a85e786ea3bc0 /public
parent1b24afa90bbcc9311cf19ea0aad29dc05f102bac (diff)
pathways: start tile fits both window dims, placement packs tight via slack scoring
Diffstat (limited to 'public')
-rw-r--r--public/app.js63
1 files changed, 49 insertions, 14 deletions
diff --git a/public/app.js b/public/app.js
index 6411bf7..181c583 100644
--- a/public/app.js
+++ b/public/app.js
@@ -74,38 +74,69 @@ function baseRect(w, h, from, dir) {
return { x: from.x - w - GAP, y: from.y + (from.h - h) / 2, w, h }
}
+function slackScore(rect, slide) {
+ let lo = -Infinity
+ let hi = Infinity
+ for (const t of tiles.values()) {
+ const r = t.rect
+ if (slide === "x") {
+ if (r.y + r.h <= rect.y - GAP || rect.y + rect.h <= r.y - GAP) continue
+ if (r.x + r.w <= rect.x) lo = Math.max(lo, r.x + r.w)
+ else if (r.x >= rect.x + rect.w) hi = Math.min(hi, r.x)
+ } else {
+ if (r.x + r.w <= rect.x - GAP || rect.x + rect.w <= r.x - GAP) continue
+ if (r.y + r.h <= rect.y) lo = Math.max(lo, r.y + r.h)
+ else if (r.y >= rect.y + rect.h) hi = Math.min(hi, r.y)
+ }
+ }
+ const start = slide === "x" ? rect.x : rect.y
+ const end = slide === "x" ? rect.x + rect.w : rect.y + rect.h
+ const gLo = lo === -Infinity ? 0 : start - lo
+ const gHi = hi === Infinity ? 0 : hi - end
+ return Math.max(0, gLo) + Math.max(0, gHi)
+}
+
function placeRect(w, h, from, dir) {
const base = baseRect(w, h, from, dir)
if (!collides(base)) return base
const horiz = dir === "N" || dir === "S"
+ const slide = horiz ? "x" : "y"
const fixed = horiz ? base.y : base.x
const slide0 = horiz ? base.x : base.y
- let best = null
+ const at = (c) => (horiz ? { x: c, y: fixed, w, h } : { x: fixed, y: c, w, h })
+ let nearest = null
for (let k = 1; k < 1000; k++) {
for (const off of [k * 2, -k * 2]) {
- const rect = horiz ? { x: slide0 + off, y: fixed, w, h } : { x: fixed, y: slide0 + off, w, h }
- if (!collides(rect)) {
- best = rect
+ if (!collides(at(slide0 + off))) {
+ nearest = at(slide0 + off)
break
}
}
- if (best) break
+ if (nearest) break
}
- if (!best) return base
- let snap = null
+ if (!nearest) return base
+ const cands = new Map()
+ cands.set(horiz ? nearest.x : nearest.y, nearest)
for (const t of tiles.values()) {
const r = t.rect
const lines = horiz
? [r.x - w - GAP, r.x + r.w + GAP, r.x + (r.w - w) / 2]
: [r.y - h - GAP, r.y + r.h + GAP, r.y + (r.h - h) / 2]
for (const c of lines) {
- const at = horiz ? best.x : best.y
- if (Math.abs(c - at) > GAP) continue
- const rect = horiz ? { x: c, y: fixed, w, h } : { x: fixed, y: c, w, h }
- if (!collides(rect)) snap = rect
+ const rect = at(c)
+ if (!collides(rect)) cands.set(c, rect)
+ }
+ }
+ let best = null
+ let bestScore = Infinity
+ for (const [c, rect] of cands) {
+ const score = slackScore(rect, slide) + 0.35 * Math.abs(c - slide0)
+ if (score < bestScore) {
+ best = rect
+ bestScore = score
}
}
- return snap || best
+ return best || nearest
}
function addTile(info, rect) {
@@ -263,8 +294,12 @@ startBtn.addEventListener("click", async () => {
const data = await r.json()
const photo = data.photo
photoInfo.set(photo.id, photo)
- const minDim = Math.min(window.innerWidth, window.innerHeight)
- U = Math.max(100, Math.round((minDim * 0.92) / Math.max(aspect(photo), 1)))
+ const a = aspect(photo)
+ const wf = a >= 1 ? a : 1
+ const hf = a >= 1 ? 1 : 1 / a
+ const uW = (window.innerWidth * 0.92) / wf
+ const uH = (window.innerHeight * 0.92) / hf
+ U = Math.max(100, Math.min(640, Math.round(Math.min(uW, uH))))
const rect = { x: 0, y: 0, w: tileW(photo), h: tileH(photo) }
addTile(photo, rect)
setCurrent(photo.id, rect)