From 46bf4aa2c36c4f982f31b6e5ff9811349684f076 Mon Sep 17 00:00:00 2001 From: Meleki Date: Sun, 22 Feb 2026 19:02:49 +0100 Subject: [PATCH] chain block click: anchored zoom drill-down, entry mode at max zoom --- wwwroot/app.js | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/wwwroot/app.js b/wwwroot/app.js index ba1d187..81fd5e6 100644 --- a/wwwroot/app.js +++ b/wwwroot/app.js @@ -304,7 +304,47 @@ function renderChainBlocks(chains) { `; block.querySelector('.block-header').addEventListener('click', () => { - openModal(null, null, c.chainId); + if (entryMode) { + openModal(null, null, c.chainId); + } else if (zoomIndex >= ZOOM_LEVELS.length - 1) { + // Already at max zoom (1 Tag) - enter entry mode for the bucket containing this chain + const chainMid = new Date((new Date(c.first).getTime() + new Date(c.last).getTime()) / 2); + const totalMinutes = (viewTo - viewFrom) / 60000; + const bucketMinutes = Math.max(1, Math.round(totalMinutes / BAR_COUNT)); + const bucketMs = bucketMinutes * 60000; + const elapsed = chainMid - viewFrom; + const bucketIndex = Math.floor(elapsed / bucketMs); + const bucketStart = new Date(viewFrom.getTime() + bucketIndex * bucketMs); + const bucketEnd = new Date(bucketStart.getTime() + bucketMs); + zoomStack.push({ from: new Date(viewFrom), to: new Date(viewTo), zoom: zoomIndex, entry: false }); + viewFrom = bucketStart; + viewTo = bucketEnd; + entryMode = true; + loadView(); + } else { + const chainMid = new Date((new Date(c.first).getTime() + new Date(c.last).getTime()) / 2); + zoomStack.push({ from: new Date(viewFrom), to: new Date(viewTo), zoom: zoomIndex, entry: false }); + const nextZoom = Math.min(zoomIndex + 1, ZOOM_LEVELS.length - 1); + const nextLevel = ZOOM_LEVELS[nextZoom]; + + if (nextLevel.weeks) { + const anchor = getLastAnchor(chainMid); + viewFrom = anchor; + viewTo = new Date(anchor.getTime() + nextLevel.weeks * 7 * 86400000); + } else { + // Day slots within current week + const weekStart = getLastAnchor(chainMid); + const slotMs = nextLevel.days * 86400000; + const elapsed = chainMid - weekStart; + const slotIndex = Math.floor(elapsed / slotMs); + viewFrom = new Date(weekStart.getTime() + slotIndex * slotMs); + viewTo = new Date(viewFrom.getTime() + slotMs); + } + + zoomIndex = nextZoom; + entryMode = false; + loadView(); + } }); container.appendChild(block); });