Hover mini-map marker in box coordinates

This commit is contained in:
2026-08-21 10:20:38 -04:00
parent d26355cebb
commit fdf3c51cb0

View File

@@ -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 {