The pin-hint pill: centred in the room the corner ☰ leaves (from --map-fab-beside to 12px), 40px, two lines, just above the legend

This commit is contained in:
Doxa Maps 2026-09-30 10:00:51 +00:00
parent 55ca562ed5
commit 3f3f2fa359

View file

@ -278,18 +278,13 @@ function creditFloor(pill) {
} catch (_) { return Infinity }
}
// Whether the layout's round ☰ is shown (it publishes --map-fab-beside only then): the pill takes its row.
const withFab = ref(false)
function clampIntoView() {
const el = hintEl.value
if (!el || !onScreen.value || !paneActive.value) return
// The row decides the base the clamp measures from, so settle it first and measure after the re-render.
const fab = !!getComputedStyle(el).getPropertyValue('--map-fab-beside').trim()
if (fab !== withFab.value) { withFab.value = fab; nextTick(clampIntoView); return }
try {
// ★ ADJUST `bottom`, NEVER `transform`. My first attempt set el.style.transform and cleared it
// between measurements — both wrong here, and the diagnosis took two failed builds:
// · `.pin-hint` already uses `transform: translateX(-50%)` to CENTRE itself, so writing a
// · `.pin-hint` used `transform: translateX(-50%)` to CENTRE itself (it is a centred band now), so writing a
// translateY would have silently destroyed the horizontal centring.
// · the pill's computed transform also carries the `pinhint-bob` keyframe animation, so a
// computed-transform reading is never just "my offset".
@ -477,25 +472,23 @@ useShadowStyles(`
drawer. 12 keeps it above the map's own base layers (the canvas, the scroll edges at 2, the control
column at 10) and below all of that chrome. It is never meant to cover anything else: its placement
already keeps it above the credit strip and the legend sheet (clampIntoView).
★ IN THE ROW OF THE ROUND MENU BUTTON, never touching it. While the gallery's round ☰ is shown, the
layout publishes its footprint (--map-fab-beside: where a sibling may start). The pill is capped to
the width left between TWO such footprints, so it stays centred with equal room on both sides and
cannot reach the ☰ in either direction, and it sits in the ☰'s row (the pin-hint--fab rule below:
the ☰'s own 12px bottom; the clamp then lifts both kinds of control to 8px above the legend sheet,
the gap the layout keeps for the ☰). With no ☰ (desktop, a partner page) it is simply centred.
★ IT NEVER READS THE ☰'s LIFT. The layout moves the ☰ by a scroll-tracked lift so it stays over
the legend while the pane scrolls; this pill lives INSIDE the map, beside the legend, so it stays
over it by itself. Positioned from that lift, it moved against the legend on every scroll. */
.pin-hint { position: absolute; left: 50%;
bottom: calc(max(var(--pin-hint-attrib-clear, 20px) + 12px, 8px) + var(--pin-hint-lift, 0px)); transform: translateX(-50%);
z-index: 12; pointer-events: none;
width: max-content; max-width: calc(100% - 2 * var(--map-fab-beside, 16px)); box-sizing: border-box;
★ CENTRED IN THE ROOM THE CORNER ☰ LEAVES. The gallery's round ☰ sits fixed in the bottom start
corner and publishes the room it takes on that side as --map-fab-beside, only while it shows. The pill
is a band from that edge (12px with no ☰) to 12px from the other, and centres itself in it, so it can
never reach the ☰ and has equal room on both sides; the legend sheet pads its collapsed strip by the
same variable, so the ☰, the legend and the pill agree by construction. Vertically the clamp keeps it
just above the legend sheet, clear of the legend's own caret and title.
★ IT NEVER READS A MOVING VARIABLE. It lives inside the map, beside the legend, so it stays over the
legend by itself while the pane scrolls or a tab switches. */
.pin-hint { position: absolute; inset-inline-start: var(--map-fab-beside, 12px); inset-inline-end: 12px;
bottom: calc(max(var(--pin-hint-attrib-clear, 20px) + 12px, 8px) + var(--pin-hint-lift, 0px));
display: flex; justify-content: center;
z-index: 12; pointer-events: none; box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
animation: pinhint-in .5s ease both; }
.pin-hint.pin-hint--fab { bottom: calc(12px + env(safe-area-inset-bottom, 0px) + var(--pin-hint-lift, 0px)); }
/* 44px, the round ☰'s height, with its two lines one above the other. */
/* 40px, the corner ☰'s size, with its two lines one above the other. */
.pin-hint-pill { pointer-events: auto; display: inline-flex; align-items: center; gap: 8px; text-align: center;
min-height: 44px; box-sizing: border-box; padding: 4px 12px 4px 10px; border-radius: 22px;
min-height: 40px; max-width: 100%; box-sizing: border-box; padding: 3px 12px 3px 10px; border-radius: 20px;
background: rgba(23, 29, 38, .92); color: #fff; font-size: 12px; line-height: 1.25; font-weight: 600;
letter-spacing: .01em; box-shadow: 0 4px 18px rgba(0,0,0,.30);
animation: pinhint-bob 2.6s ease-in-out 1s infinite; }
@ -522,7 +515,7 @@ useShadowStyles(`
.pin-hint.dark .pin-hint-x:hover { color: #1b212b; }
@keyframes pinhint-pulse { 0% { transform: scale(.6); opacity: .9 } 70% { transform: scale(1.5); opacity: 0 } 100% { opacity: 0 } }
@keyframes pinhint-bob { 0%,100% { transform: translateY(0) } 50% { transform: translateY(-3px) } }
@keyframes pinhint-in { from { opacity: 0; transform: translateX(-50%) translateY(10px) } to { opacity: 1; transform: translateX(-50%) translateY(0) } }
@keyframes pinhint-in { from { opacity: 0; transform: translateY(10px) } to { opacity: 1; transform: translateY(0) } }
@media (prefers-reduced-motion: reduce) {
.pin-hint, .pin-hint-pill, .pin-hint-dot::after { animation: none !important; }
}
@ -530,7 +523,7 @@ useShadowStyles(`
</script>
<template>
<div v-if="visible" v-show="onScreen && paneActive" ref="hintEl" class="pin-hint" :class="{ dark: isDark, 'pin-hint--fab': withFab }" role="status">
<div v-if="visible" v-show="onScreen && paneActive" ref="hintEl" class="pin-hint" :class="{ dark: isDark }" role="status">
<span class="pin-hint-pill">
<span class="pin-hint-dot" aria-hidden="true"></span>
<span class="pin-hint-text"><span v-for="(l, i) in lines" :key="i" class="pin-hint-line">{{ l }}</span></span>