LegendMobile: the card image holds its seat through the snap to full open, and the reduced-motion rule names its fade

This commit is contained in:
Doxa Maps 2026-10-01 10:50:17 +00:00
parent 3b32c09617
commit 3901bf219f

View file

@ -24,10 +24,11 @@ useShadowStyles(`
/* 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-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 holds still while a finger drags the
sheet (script: IMG_SEAT_VAR). Dragged up, the sheet (z 1000) slides over the seated 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 shadow root's reduced-motion reset turns the fade off for those who ask. */
/* 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;}
@ -358,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')
@ -766,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
@ -850,6 +854,14 @@ 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
@ -862,9 +874,7 @@ onMounted(() => {
const h = sheet.offsetHeight
if (h <= 0) return
_sheetVarTarget.style.setProperty(SHEET_H_VAR, `${h}px`)
// The floating image's seat follows the sheet EXCEPT while a finger drags it: the credit and the camera
// keep reading the live height above, and the snap after a drag moves the seat frame by frame.
if (!isDragging.value) _sheetVarTarget.style.setProperty(IMG_SEAT_VAR, `${h}px`)
seatImage(h)
_sheetVarTarget.__legendSheetVarOwner = sheet
})
_sheetResizeObserver.observe(sheet)