diff --git a/frontend/src/components/LocationMapTooltip.vue b/frontend/src/components/LocationMapTooltip.vue index 37e95d8..e2e3bb3 100644 --- a/frontend/src/components/LocationMapTooltip.vue +++ b/frontend/src/components/LocationMapTooltip.vue @@ -97,11 +97,15 @@ const blueprintUrl = computed(() => { // the wrong level's dimensions is precisely how a marker ends up plausibly // placed and wrong. const markerX = computed(() => { - return (props.left / dimensionsFor(props.levelid).width) * 100 + const fraction = props.left / dimensionsFor(props.levelid).width + const { offsetX, scaleX } = paintedBox.value + return (offsetX + fraction * scaleX) * 100 }) const markerY = computed(() => { - return (props.top / dimensionsFor(props.levelid).height) * 100 + const fraction = props.top / dimensionsFor(props.levelid).height + const { offsetY, scaleY } = paintedBox.value + return (offsetY + fraction * scaleY) * 100 }) // Marker style with counter-scale to maintain constant size @@ -120,9 +124,33 @@ const markerStyle = computed(() => ({ // lands in the grey gutter beside the map. Levels near 390:300 looked correct, // which is why it only affected some assets. // -// The fix is to let the IMAGE size the frame: the frame shrink-wraps the drawing, -// so a percentage of the frame IS a percentage of the drawing, whatever the -// level's aspect ratio. The letterboxing moves out to the preview around it. +// The preview is a fixed box and object-fit: contain paints the drawing inside it +// at the drawing's own aspect ratio, so the drawing does not fill the box: it is +// letterboxed on one axis. A marker placed at a percentage of the BOX therefore +// lands somewhere else than the same percentage of the DRAWING, on any level +// whose shape is not the box's. +// +// So convert: work out where the painted drawing sits inside the box, then map +// the marker into box coordinates. Doing it here rather than by resizing the +// frame keeps the centring transform below honest - its percentages resolve +// against the element, so the element has to stay the size of the box. +const PREVIEW_WIDTH = 390 // keep in step with .map-preview in the styles +const PREVIEW_HEIGHT = 300 +const PREVIEW_ASPECT = PREVIEW_WIDTH / PREVIEW_HEIGHT + +// Fraction of the box the drawing covers, and the gap before it, per axis. +const paintedBox = computed(() => { + const { width, height } = dimensionsFor(props.levelid) + const levelAspect = width / height + if (levelAspect >= PREVIEW_ASPECT) { + // Wider than the box: full width, bars above and below. + const scale = PREVIEW_ASPECT / levelAspect + return { scaleX: 1, offsetX: 0, scaleY: scale, offsetY: (1 - scale) / 2 } + } + // Taller than the box: full height, bars left and right. + const scale = levelAspect / PREVIEW_ASPECT + return { scaleX: scale, offsetX: (1 - scale) / 2, scaleY: 1, offsetY: 0 } +}) // Transform style that centers on the marker and zooms toward it const transformStyle = computed(() => { // Calculate translation to center the marker in the preview @@ -265,12 +293,6 @@ watch(currentTheme, () => { .map-preview { position: relative; - /* Centres the aspect-fitted frame, which is where the letterboxing now - happens. The frame carries the drawing AND the marker, so the two can no - longer disagree. */ - display: flex; - align-items: center; - justify-content: center; /* Smaller than it was: this is a glance-and-move-on preview, and at 500px it covered the row it was launched from. Aspect ratio kept. */ width: 390px; @@ -281,23 +303,21 @@ watch(currentTheme, () => { .map-transform { position: relative; - /* Shrink-wraps the image rather than filling the preview, so the box the - marker is positioned in is the box the drawing is painted in. */ - max-width: 100%; - max-height: 100%; + /* Fills the preview. The centring transform translates by percentages, which + resolve against THIS element, so it must stay the size of the box it is + centring within. The letterbox is accounted for in the marker maths. */ + width: 100%; + height: 100%; transition: transform 0.15s ease-out; } .map-image { - /* Sizes itself from the drawing, scaled down to fit the preview. NOT - width/height 100% with object-fit: contain - that painted the drawing - letterboxed inside a box the marker was measured against, which put the two - in different coordinate spaces on any level that is not 390:300. */ + width: 100%; + height: 100%; + /* contain, so a level of any shape is shown whole and undistorted. Where the + bars fall is computed in paintedBox and folded into the marker position. */ + object-fit: contain; display: block; - max-width: 100%; - max-height: 100%; - width: auto; - height: auto; } .marker-dot {