diff --git a/src/client/app.ts b/src/client/app.ts index 4613fa4..44fbd18 100644 --- a/src/client/app.ts +++ b/src/client/app.ts @@ -32,6 +32,7 @@ let scheduledRenderTimer = null; let scheduledRenderOptions = { fitMap: false }; let lastRenderAt = 0; const SHARE_PARAM_NAME = "share"; +let overlayMapView = null; const map = L.map("map", { zoomControl: true }).setView([52.5208, 13.4095], 13); const lightMapLayer = L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", { @@ -248,6 +249,27 @@ function setMapTheme(theme) { }); } +function captureMapView() { + const center = map.getCenter(); + return { + center: [center.lat, center.lng], + zoom: map.getZoom() + }; +} + +function rememberOverlayMapView() { + overlayMapView = captureMapView(); +} + +function restoreOverlayMapView() { + if (!overlayMapView) { + return; + } + + map.setView(overlayMapView.center, overlayMapView.zoom, { animate: false }); + overlayMapView = null; +} + function setRouteVisible(visible) { routeVisible = Boolean(visible); localStorage.setItem(ROUTE_VISIBLE_STORAGE_KEY, routeVisible ? "true" : "false"); @@ -494,6 +516,7 @@ function closeOverlayView() { overlayLoading.classList.remove("error"); overlayLoadingText.textContent = "Loading full-size image..."; overlayImage.removeAttribute("src"); + restoreOverlayMapView(); } closeOverlay.addEventListener("click", closeOverlayView); @@ -503,7 +526,7 @@ overlay.addEventListener("click", (event) => { } }); -function updateRouteView(photos) { +function updateRouteView(photos, options = {}) { const routePoints = photos .filter((photo) => photo.latitude !== null && photo.longitude !== null && photo.capturedAt) .sort((a, b) => String(a.capturedAt).localeCompare(String(b.capturedAt))) @@ -511,6 +534,10 @@ function updateRouteView(photos) { route.setLatLngs(routePoints); + if (options.preserveMapView) { + return; + } + if (routePoints.length === 1) { map.setView(routePoints[0], 13); } else if (routePoints.length > 1) { @@ -671,7 +698,8 @@ function renderVisiblePhotos(options = {}) { marker.on("mouseover", () => marker.openPopup()); marker.on("click", () => { state.activePhotoId = photo.id; - renderVisiblePhotos({ fitMap: false }); + rememberOverlayMapView(); + renderVisiblePhotos({ fitMap: false, preserveMapView: true }); openOverlay(photo); }); } @@ -685,14 +713,15 @@ function renderVisiblePhotos(options = {}) { '' + escapeHtml(photo.name) + ''; item.addEventListener("click", () => { state.activePhotoId = photo.id; - renderVisiblePhotos({ fitMap: false }); + rememberOverlayMapView(); + renderVisiblePhotos({ fitMap: false, preserveMapView: true }); openOverlay(photo); }); photoList.appendChild(item); } } - updateRouteView(visiblePhotos); + updateRouteView(visiblePhotos, options); renderTimeline(); if (options.fitMap && visiblePhotos.length) { diff --git a/src/server/app-assets.ts b/src/server/app-assets.ts index 2e1ab72..63644fb 100644 --- a/src/server/app-assets.ts +++ b/src/server/app-assets.ts @@ -37,7 +37,7 @@ export const APP_STYLES = ` :root { --photo-active-bg: rgba(51, 65, 85, 0.76); --photo-active-border: rgba(148, 163, 184, 0.34); --empty-border: rgba(148, 163, 184, 0.22); - --overlay-bg: rgba(2, 6, 23, 0.9); + --overlay-bg: rgba(2, 6, 23, 0.7); --card-border: rgba(148, 163, 184, 0.16); --timeline-surface: rgba(15, 23, 42, 0.88); --timeline-surface-soft: rgba(15, 23, 42, 0.76); @@ -97,7 +97,7 @@ export const APP_STYLES = ` :root { --photo-active-bg: rgba(100, 116, 139, 0.12); --photo-active-border: rgba(100, 116, 139, 0.32); --empty-border: rgba(15, 23, 42, 0.16); - --overlay-bg: rgba(8, 12, 18, 0.78); + --overlay-bg: rgba(8, 12, 18, 0.7); --card-border: rgba(15, 23, 42, 0.11); --timeline-surface: rgba(255, 255, 255, 0.84); --timeline-surface-soft: rgba(255, 255, 255, 0.94); @@ -587,6 +587,7 @@ export const APP_STYLES = ` :root { display: none; place-items: center; background: var(--overlay-bg); + backdrop-filter: blur(4px); z-index: 1000; padding: 24px; } @@ -598,11 +599,13 @@ export const APP_STYLES = ` :root { .overlay-card { display: inline-grid; width: fit-content; - max-width: 100%; + max-width: min(100%, calc(100vw - 48px)); + gap: 12px; overflow: hidden; border-radius: 0; - background: var(--timeline-surface-soft); - border-color: var(--timeline-border); + background: transparent; + border: 0; + box-shadow: none; color: var(--timeline-text); } @@ -610,25 +613,32 @@ export const APP_STYLES = ` :root { position: relative; width: fit-content; max-width: 100%; - min-height: 48vh; - background: var(--timeline-surface-strong); + min-height: 0; + background: transparent; } .overlay-card header { - padding: 16px 18px; - border-bottom: 1px solid var(--timeline-border); + padding: 0 4px; display: flex; - justify-content: space-between; + justify-content: flex-start; align-items: center; - background: var(--timeline-surface-strong); + background: transparent; color: var(--timeline-text); } + .overlay-card header strong { + display: inline-flex; + padding: 8px 12px; + background: var(--timeline-surface-soft); + border: 1px solid var(--timeline-border); + backdrop-filter: blur(14px); + } + .overlay-card img { display: block; width: auto; - max-width: min(1120px, calc(100vw - 48px)); - max-height: 78vh; + max-width: min(1440px, calc(100vw - 48px)); + max-height: calc(100vh - 112px); object-fit: contain; background: var(--timeline-surface-strong); transition: opacity 120ms ease; @@ -677,6 +687,27 @@ export const APP_STYLES = ` :root { color: var(--timeline-text); } + .overlay-close { + position: fixed; + top: 16px; + right: 16px; + z-index: 1001; + width: 40px; + height: 40px; + padding: 0; + justify-content: center; + background: var(--timeline-surface-soft); + color: var(--timeline-text); + border: 1px solid var(--timeline-border); + box-shadow: var(--shadow-soft); + backdrop-filter: blur(14px); + } + + .overlay-close:hover { + background: var(--timeline-surface-strong); + border-color: var(--timeline-border-strong); + } + .thumb { width: min(210px, 100%); box-sizing: border-box; diff --git a/src/server/page-template.ts b/src/server/page-template.ts index fd8d15c..51442d9 100644 --- a/src/server/page-template.ts +++ b/src/server/page-template.ts @@ -140,12 +140,12 @@ ${APP_STYLES}