diff --git a/library/components/legends/LegendMobile.vue b/library/components/legends/LegendMobile.vue
index d30c35c89..6e869b176 100644
--- a/library/components/legends/LegendMobile.vue
+++ b/library/components/legends/LegendMobile.vue
@@ -23,7 +23,14 @@ useShadowStyles(`
mobile chrome (pull tab, collapse/expand caret, touch drag). ── */
/* bottom = the sheet's height (it shows only while the sheet is open, whose height fits its content)
+ a 16px gap. */
-.floating-mini-image{position:absolute;inset-inline-start:16px;bottom:calc(var(--legend-sheet-h,30%) + 16px);z-index:999;pointer-events:auto;cursor:pointer;}
+.floating-mini-image{position:absolute;inset-inline-start:16px;bottom:calc(var(--legend-img-seat,var(--legend-sheet-h,30%)) + 16px);z-index:999;pointer-events:auto;cursor:pointer;}
+/* The image SITS on --legend-img-seat, a copy of the sheet's height that moves only while the image shows (the
+ sheet open, no finger on it; script: IMG_SEAT_VAR). Dragged up, and through the snap to full open, the sheet
+ (z 1000) slides over the still image (z 999), so it goes INTO the legend instead of riding its top edge; dragged
+ down, it waits and then follows the snap. It fades in and out instead of popping; the reduced-motion rule below
+ turns the fade off for those who ask. */
+.lm-img-enter-active,.lm-img-leave-active{transition:opacity .2s ease;}
+.lm-img-enter-from,.lm-img-leave-to{opacity:0;}
.mini-image{width:120px;height:150px;object-fit:cover;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.25);display:block;}
/* Containment root: clamps absolute children to the map area. */
@@ -352,7 +359,8 @@ useShadowStyles(`
that animates must carry its own. Additive only: it removes motion when the OS asks and
changes nothing otherwise. */
@media (prefers-reduced-motion: reduce) {
- .legend-sheet, .legend-content, .legend-handle, .legend-toggle { transition: none !important; animation: none !important; }
+ .legend-sheet, .legend-content, .legend-handle, .legend-toggle,
+ .lm-img-enter-active, .lm-img-leave-active { transition: none !important; animation: none !important; }
}
`, 'legend-mobile-sheet')
@@ -760,6 +768,8 @@ function onSheetTouchEnd() {
// Free-drag tabs: leave the sheet where it was released (custom px stays,
// state unchanged) — the user reads the legend and the map together.
if (h < openH * 0.5) uiStore.collapseLegend()
+ // The height stops where the finger did, so the observer may never fire again: seat the image here.
+ else seatImage()
return
}
if (Math.abs(travel) < SNAP_MIN_PX) { snapTo(drag.startState); return } // hysteresis
@@ -839,10 +849,19 @@ onBeforeUnmount(() => {
// display:none desktop slot in detail mode (both profiles) observes 0 and must
// never zero the var under the visible sheet. The owner clears it on unmount.
const SHEET_H_VAR = '--legend-sheet-h'
+const IMG_SEAT_VAR = '--legend-img-seat'
let _sheetResizeObserver = null
let _sheetVarTarget = null
let _observedSheet = null
+// The floating image's seat follows the sheet only while the image shows: open, and no finger on it. The credit
+// and the camera keep reading the live height (SHEET_H_VAR); a drag holds the seat, and so does the snap to full
+// open, where the sheet slides over the fading image instead of carrying it up.
+function seatImage(h = sheetRef.value ? sheetRef.value.offsetHeight : 0) {
+ if (!_sheetVarTarget || h <= 0 || isDragging.value || legendState.value !== 'open') return
+ _sheetVarTarget.style.setProperty(IMG_SEAT_VAR, `${h}px`)
+}
+
onMounted(() => {
const sheet = sheetRef.value
if (!sheet || typeof ResizeObserver === 'undefined') return
@@ -855,6 +874,7 @@ onMounted(() => {
const h = sheet.offsetHeight
if (h <= 0) return
_sheetVarTarget.style.setProperty(SHEET_H_VAR, `${h}px`)
+ seatImage(h)
_sheetVarTarget.__legendSheetVarOwner = sheet
})
_sheetResizeObserver.observe(sheet)
@@ -864,6 +884,7 @@ onBeforeUnmount(() => {
if (_sheetResizeObserver) { _sheetResizeObserver.disconnect(); _sheetResizeObserver = null }
if (_sheetVarTarget && _sheetVarTarget.__legendSheetVarOwner === _observedSheet) {
_sheetVarTarget.style.removeProperty(SHEET_H_VAR)
+ _sheetVarTarget.style.removeProperty(IMG_SEAT_VAR)
_sheetVarTarget.__legendSheetVarOwner = null
}
_sheetVarTarget = null
@@ -907,7 +928,8 @@ onBeforeUnmount(() => { if (_creditTimer) { clearTimeout(_creditTimer); _creditT
beside the legend, so it shows in no resting state (collapsed, or open with nothing selected). A
tapped dot's detail view keeps its note inside the sheet, where it explains shared dots; the tree's
own copy stays off (:pin-note="false" below). Desktop is unchanged. -->
-
+
+
t(i,l,void 0,r));else{const i=Object.keys(e);a=new Array(i.length);for(let l=0,c=i.length;l t(i,l,void 0,r));else{const i=Object.keys(e);a=new Array(i.length);for(let l=0,c=i.length;l