From ca92296b5161cea48892d46f49017d577b3b6387 Mon Sep 17 00:00:00 2001 From: Nicolay Bennett Rennemo Date: Mon, 9 Nov 2026 21:50:22 +0100 Subject: [PATCH] Map preview and zoom in on location --- apps/frontend/src/assets/map-poster-dark.avif | Bin 0 -> 7149 bytes .../frontend/src/assets/map-poster-light.avif | Bin 0 -> 8069 bytes apps/frontend/src/assets/map-poster.avif | Bin 29435 -> 0 bytes apps/frontend/src/components/ClientMap.tsx | 29 ++-- apps/frontend/src/components/Map.tsx | 161 +++++++++++++----- .../src/components/MockLocationNotice.tsx | 1 - .../src/components/__tests__/Map.test.tsx | 59 ++++--- .../__tests__/MockLocationNotice.test.tsx | 1 - apps/frontend/src/routes/index.tsx | 7 - apps/frontend/src/styles.css | 41 +++++ 10 files changed, 212 insertions(+), 87 deletions(-) create mode 100644 apps/frontend/src/assets/map-poster-dark.avif create mode 100644 apps/frontend/src/assets/map-poster-light.avif delete mode 100644 apps/frontend/src/assets/map-poster.avif diff --git a/apps/frontend/src/assets/map-poster-dark.avif b/apps/frontend/src/assets/map-poster-dark.avif new file mode 100644 index 0000000000000000000000000000000000000000..2c0f5d4ed8baed1de0d31509960014add621c936 GIT binary patch literal 7149 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 xu0Z^p;)B*v`v(sMRuZ!tdW=j`G2&Cd#(Tg literal 0 HcmV?d00001 diff --git a/apps/frontend/src/assets/map-poster.avif b/apps/frontend/src/assets/map-poster.avif deleted file mode 100644 index 42f9cb6ccc2856e00d0499f54b14188c743564f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29435 zcmXtf1B@t5)Aic6ZSUT-ZQHhO+vZ){wr$(CZT{0RKJzjR1hi000hW|6&RY3)}xK`o9X+zr+OSzvh2E z2CfW@LUz`6|69w_!rsyTA1-BKZ)Eq+qgXiEoBY=Z002DpuRZ=>fM9RoZt*_^0s`Wn zhcR$v6bc~u4*@_nva@!yHL`a9H!~~%nEyWr-NN3+;J?~G`?oO=K%gLyf4jP~F*a~U z0)T?zAmjG-5{EEn_Gel-1jMj{j7my32nCh&&jawUhzB6&+P1Mz$yW$kB-4EiFM-jn z&7-t^!%@qILtC>_+tRZjQN=)8Qc!gCXoZG+Kp#kQ%U%n~AoBoHe@}U;Jy5kc$bDnP zuCes!ZHXup5c!(9la8nc$y^~47S`onirT1?oG{pWb%s}^T?3i2=;-dyb@m+;Zms$A zrhxfJ@H6!Ha;$BDxBZ~RH&3h;5TF4_NmeV%kgZtPLo;VXJVp&# zy~S-H5^f(Q zhjVP!%Y|CBuV17(nk@TGQtKT{wINNnpx*p-5d zzJxd4ELHN}ObP)x-L(#TU3wi>8sM9|^BGf5g>3lsjn;9)Ni$Q~{!JW1R4{PVy6$p; zV1A!WnM%N%UwZK{r@ns3Ug+q|6JfjCV@A@|8~xNMt13b%ie#M^sm`_-5(v{pE(<&w z@?@N5E~QPWjSa^Xz(9o6Q(7y=Vs_d^9ZX?7!GCnW$>Wriq1t{5Ar*X?7J}8M3?t0d z+~VbImh@FyMvE;%UZwd%E_qaVQQg#>inowVqZ*2N=eos?-o_6(}o_w1|Cn!%C&wy;&qm;GBb&`)%(3yu5sxsR+ zLAje408hh}P5;>(T!k65t}utbKBK_i2?L=|yUlg^YWQKk{Ib_K;n+VpcSKQ}q@~JR5I( zL6Ika98$-RZYgUt`Ap=d>bR%*ZQwIUTP#9P!`&J?g&PrHE% zp-LDjp0i|u(>^HM2AS`C!1hxkVBL*t4;CjOvdTPN65I+}x5lkh#$^|zML<>N{-B(i z5TVFcnq}V@^Z@7<+FPE9y-#&hI+hWeFRNX@biSJt-6=q9iWrWmi4*&m;NbRhbMIj) zVYqd$A>2V4h$>A$NDL3n2EFC5iEhizZ&`}q=m+Hfu%kQiffk!bt8aTRtZ_JQd#ZzH z<=`suTY-tq%p?Nki>OM}XT$EDU^W>p->d*(%+}b&2?jWhp~ri|&)28wIMGQ#l_H_j z4tdDDAfXDn+y$b)vH=?;cKbNtJ1_$-jACx6hma!1hGr12^sRtk0Q9w60x$0 zwU|hPCE_!*D5M+jjo9oG)taw57v3hzkBGr4YTCVlkAf{i95fpz2*pnvsujJBwnCgJ zhIdR)^^S2P_mHMAr2fH3-RK^5p@LQdxzbo51FQLSI+Q?AGGDU)^{U~I`P3Ng zj;p6C&6Db9yiHN>1!DE2O-0m`UhzSH$~M3bH?HnfbXVm;x4%!w@uICk+(4I!&*#Vtj zie2F!J~T$R+`T`R^;C5G^48y|o;qquJay!yTvN4w&v_v9@|N#G1Dne_C61tNJd{EL zexLNd0MxxxB|=vomG>C4yh;3tKqZYl4L52p3?&?ib!igpg0?OQf`_JI1<<5++N@A+S4h;r8Odv|+DG2FqB}yMTE~L~#Jr%Y8QFn*KKbxnduD_Iygz`xCnlXYg%rLsnp!TOM zyw7o;Va+N2W4-$TR<*9kPWk%`ny0=<^#FVLw+6-&wggM^Ys5=a@ySy-lsz?Qo|i}F zOx{ZC%K?@7=V%ol8XX*OCFu#o7mDV@6+G9b<|m0eiKWxr5~W&7&8#!oM2rel%P&@& zvEjE`com17mL{&BVV3F8_Z*z4pm2uZtO%kdqoD7Lsr+=-RyJgE@q44vB;7EV{OQNi zA1{3#2sJxnu*k+CNZ-^4La)w%RGj^ujIhpV3Fs1e?JAE9WtsXxBf3jV)t3WBl)vE) zM2ENWXV+G(EzF}|d=HOBrdhtGs)+&x+IVHqYuJj+y+B-DjE*1U7aw$Q;$Bljy|=LS z(uPS)UvM906rF(Us)@azf%%)xu^=;A!}>5;o;eB$RIT2VTMar#F?pUcpN)<|Qpk$3 z)3E?N10PF8sxlW4LUSfxL9FemrltP^DT+Im3ZmO$*O^ZS!RG`x|AD44PG+q{786`k zX;b75rUf1CS*06c&RC&?XcWvq1%My-x-ExmO>qOjRI}#6ND9<9y%AE>`nlIKGr9+u zK*p#_97_rUe!dR!{I>(xuE48q9v6a`!=w9DX(z`mz3U>7u~wI4QH?bU zsHWVz=1l!KcKE(2pbum$XCJ5c2<*O^dJy|a?_mK30yfVTknyXSJ zW7lY&Q7Evo0T}TpW*J_!?!^i)_<>mfe6{;L=7+42=l8A>J>`CQ>{QCp;)Rh@6StdF zJA3FA4QNTQwJo6oy5%(^w?^x6`ITs4fpq;Aw+#H77{FC%8uwy`Rp zYaw`mZLPHe^VIKo5Ng-&Dfq}TM9Gm+vY+^+3>TUs9z(;Xo8S23bwCBii

Srg4U% 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 Go9uwB!5$3& diff --git a/apps/frontend/src/components/ClientMap.tsx b/apps/frontend/src/components/ClientMap.tsx index d041a8a..a2e0309 100644 --- a/apps/frontend/src/components/ClientMap.tsx +++ b/apps/frontend/src/components/ClientMap.tsx @@ -1,7 +1,9 @@ import { lazy, Suspense, useEffect, useState } from 'react' import type { ComponentProps } from 'react' -import mapPoster from '#/assets/map-poster.avif' +import mapPosterDark from '#/assets/map-poster-dark.avif' +import mapPosterLight from '#/assets/map-poster-light.avif' +import { MAP_ATTRIBUTION } from '#/lib/map-styles' import type MapComponent from './Map' // Leaflet touches `window` while its module is evaluated, so it may only be @@ -16,30 +18,37 @@ const LazyMap = mapModulePromise ? lazy(() => mapModulePromise) : null type MapProps = ComponentProps /** - * A picture of the map's default view (Trondheim, zoom 15) rendered on the - * server. The map is the largest thing on the first screen, so pulling that - * paint out of the JavaScript bundle lets the browser show it immediately - * instead of waiting for Leaflet and a handful of map tiles. + * Pictures of the map's default view (Trondheim, zoom 3) rendered on the + * server, one per theme. The map is the largest thing on the first screen, so + * pulling that paint out of the JavaScript bundle lets the browser show it + * immediately instead of waiting for Leaflet and a handful of map tiles. * - * Regenerate it by screenshotting `#map` at a 1112px viewport width (the widest - * the container gets) with no location available, then `avifenc -q 40 -s 6`. + * Regenerate them by screenshotting `#map` at a 1280px viewport width with the + * mock location (968x400), then encode each to AVIF at quality ~35. */ function MapPoster({ ready }: { ready: boolean }) { return ( <> + {/* The live map brings its own attribution control. */} ) diff --git a/apps/frontend/src/components/Map.tsx b/apps/frontend/src/components/Map.tsx index 453e044..a23244c 100644 --- a/apps/frontend/src/components/Map.tsx +++ b/apps/frontend/src/components/Map.tsx @@ -1,13 +1,7 @@ 'use client' import { useEffect } from 'react' -import { - CircleMarker, - MapContainer, - Marker, - Tooltip, - useMap, -} from 'react-leaflet' +import { MapContainer, Marker, Tooltip, useMap } from 'react-leaflet' import MarkerClusterGroup from 'react-leaflet-cluster' import L from 'leaflet' import 'leaflet/dist/leaflet.css' @@ -16,24 +10,37 @@ import { useNavigate } from '@tanstack/react-router' import { formatPrice } from '../lib/format-price' import harpIconUrl from '#/assets/harp.svg?url' import harpShadowUrl from '#/assets/harp_shadow.svg?url' -import { MOCK_LOCATION_NAME } from '../lib/location' +import { MOCK_LOCATION, MOCK_LOCATION_NAME } from '../lib/location' import type { Coordinates } from '../lib/location' import { MAP_STYLE_URLS } from '../lib/map-styles' import { useThemeMode } from '../lib/use-theme-mode' import type { SearchResult } from '../types/search' import { VectorTileLayer } from './VectorTileLayer' -const DEFAULT_CENTER: [number, number] = [63.4305, 10.3951] -const DEFAULT_ZOOM = 15 +// Both SSR posters are centred on the mock location at this zoom, so the live +// map starts on exactly the same frame and then flies to the user. +const POSTER_CENTER: [number, number] = [MOCK_LOCATION.lat, MOCK_LOCATION.lon] +const POSTER_ZOOM = 3 +const USER_ZOOM = 13 -function FlyToResult({ coordinates }: { coordinates: number[] | undefined }) { +function FlyToUser({ location }: { location?: Coordinates | null }) { const map = useMap() + const lat = location?.lat + const lon = location?.lon useEffect(() => { - if (coordinates && coordinates.length == 2) { - map.flyTo([coordinates[0], coordinates[1]], 12) - } - }, [coordinates, map]) + if (lat == null || lon == null) return + + // Let the poster's first frame paint, then fly in from its view. + const timeout = window.setTimeout(() => { + map.flyTo([lat, lon], USER_ZOOM, { + duration: 1.6, + easeLinearity: 0.25, + }) + }, 300) + + return () => window.clearTimeout(timeout) + }, [lat, lon, map]) return null } @@ -48,6 +55,97 @@ const harpIcon = new L.Icon({ popupAnchor: [0, -25], }) +const userLocationIcon = L.divIcon({ + className: 'gspotter-user-location', + html: '', + iconSize: [20, 20], + iconAnchor: [10, 10], +}) + +function UserLocationMarker({ + location, + isMockLocation, +}: { + location: Coordinates + isMockLocation: boolean +}) { + const map = useMap() + const label = isMockLocation + ? `Mock location: ${MOCK_LOCATION_NAME}` + : 'Your location' + + useEffect(() => { + const container = map.getContainer() + const setDotScale = (scale: number) => { + container.style.setProperty('--gspotter-dot-scale', String(scale)) + } + + let frame = 0 + let animating = false + + // Leaflet animates a marker by scaling its icon element with a CSS + // transition. Read that live scale each frame and apply its inverse to the + // dot so it keeps the same on-screen size throughout the zoom. + const readIconScale = () => { + const icon = container.querySelector( + '.gspotter-user-location', + ) + if (!icon) return 1 + + const transform = window.getComputedStyle(icon).transform + const values = transform.match(/matrix(?:3d)?\(([^)]+)\)/)?.[1] + if (!values) return 1 + + const [a = 1, b = 0] = values.split(',').map(Number) + const scale = Math.hypot(a, b) + return scale > 0 ? scale : 1 + } + + const tick = () => { + setDotScale(1 / readIconScale()) + frame = window.requestAnimationFrame(tick) + } + + const start = () => { + if (animating) return + animating = true + frame = window.requestAnimationFrame(tick) + } + + const stop = () => { + if (!animating) return + animating = false + window.cancelAnimationFrame(frame) + setDotScale(1) + } + + map.on('zoomstart', start) + map.on('zoomanim', start) + map.on('zoomend', stop) + + return () => { + map.off('zoomstart', start) + map.off('zoomanim', start) + map.off('zoomend', stop) + window.cancelAnimationFrame(frame) + setDotScale(1) + } + }, [map]) + + return ( + + + {label} + + + ) +} + /** * Overlapping pins are merged into a single, larger target. This keeps the map * usable on touch screens: without clustering the pins on top of each other @@ -69,25 +167,19 @@ function createClusterIcon(cluster: L.MarkerCluster) { export interface MapProps { nearby?: SearchResult[] userLocation?: Coordinates | null - flyTo?: number[] isMockLocation?: boolean } export default function Map({ nearby = [], userLocation, - flyTo, isMockLocation = false, }: MapProps) { const navigate = useNavigate() const themeMode = useThemeMode() - // Start at the user's position when we already know it. Otherwise Leaflet - // first downloads tiles for the default view and immediately replaces them - // after flying to the user, wasting both time and bandwidth. - const initialCenter: [number, number] = userLocation - ? [userLocation.lat, userLocation.lon] - : DEFAULT_CENTER - const initialZoom = userLocation ? 12 : DEFAULT_ZOOM + // Always start on the poster's view, then fly to the user once it is known. + const initialCenter: [number, number] = POSTER_CENTER + const initialZoom = POSTER_ZOOM const markers = nearby.filter( (venue, index, all) => all.findIndex( @@ -108,7 +200,7 @@ export default function Map({ className="h-100 w-full rounded-2xl" > - + {userLocation && ( - - - {isMockLocation - ? `Mock location: ${MOCK_LOCATION_NAME}` - : 'Your location'} - - + )} diff --git a/apps/frontend/src/components/MockLocationNotice.tsx b/apps/frontend/src/components/MockLocationNotice.tsx index 83b6f84..11d05f1 100644 --- a/apps/frontend/src/components/MockLocationNotice.tsx +++ b/apps/frontend/src/components/MockLocationNotice.tsx @@ -13,7 +13,6 @@ const REPOSITORY_URL = 'https://git.ntnu.no/IT2810-H26/T09-Project-2' export default function MockLocationNotice() { return (