From 782bb365b5f94c7732beee67453de43cb4a31c61 Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 17 Sep 2026 12:43:45 +0200 Subject: [PATCH] fix(maps): the phone map carries no credit and no night pause labels The credit was a round (i) under the locate button, and on a full-bleed phone map a lone control there is the first thing the eye lands on. It is off on the phone now: one rule in mobile.css against both engines' containers, so Leaflet, MapLibre and Mapbox cannot drift apart. The desktop map is untouched and still shows the full credit with its links. Owner's call, made knowingly. With the credit gone the band the round controls floated on had nothing under it to clear, so the 38px credit row comes out of --bottom-nav-h and out of the camera padding the offered roads are framed with. The controls now stand on the map floor. The night pause pills go with it. On the desktop one is a drag handle with a hint; a phone has neither hover to explain it nor room to drag, and the markers come from the whole drive rather than the stage on screen, so day 1 drew day 2's pill as well. Both via lists go through the same filter, because the plan tab carries them too. --- .../Map/MapAttributionToggle.test.tsx | 45 ------- .../components/Map/MapAttributionToggle.tsx | 64 ---------- client/src/components/Map/MapView.test.tsx | 22 +--- client/src/components/Map/MapView.tsx | 22 +--- client/src/mobile/mobile.css | 119 +++--------------- client/src/mobile/mobileCreditCss.test.ts | 96 ++++++-------- .../src/mobile/screens/trip/map/MMapArea.tsx | 56 +++++---- 7 files changed, 97 insertions(+), 327 deletions(-) delete mode 100644 client/src/components/Map/MapAttributionToggle.test.tsx delete mode 100644 client/src/components/Map/MapAttributionToggle.tsx diff --git a/client/src/components/Map/MapAttributionToggle.test.tsx b/client/src/components/Map/MapAttributionToggle.test.tsx deleted file mode 100644 index ada4001d8..000000000 --- a/client/src/components/Map/MapAttributionToggle.test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -// FE-MAP-ATTRIB-001 to FE-MAP-ATTRIB-003 -import { fireEvent, render } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' -import MapAttributionToggle from './MapAttributionToggle' - -// Outside a TranslationProvider t(key) echoes the key, so the label is asserted as its key. -const BAND = 'calc(var(--bottom-nav-h, 84px) + 12px)' - -describe('MapAttributionToggle', () => { - it('FE-MAP-ATTRIB-001: it follows a credit corner where the map has one, and keeps the locate band where it has not', () => { - const { getByRole } = render( {}} bottomOffset={BAND} />) - const style = getByRole('button').style - - // Both numbers come from the map around it first. A map without a credit corner - // defines neither variable, so the fallbacks are the old slot beside the locate button. - expect(style.right).toBe('var(--m-credit-right, 62px)') - expect(style.bottom).toBe(`var(--m-credit-bottom, ${BAND})`) - }) - - it('FE-MAP-ATTRIB-002: it is labelled, says whether the credit is open, and toggles once per tap', () => { - const onToggle = vi.fn() - const { getByRole, rerender } = render() - const button = getByRole('button') - - expect(button.getAttribute('title')).toBe('map.attribution') - expect(button.getAttribute('aria-label')).toBe('map.attribution') - expect(button.getAttribute('aria-expanded')).toBe('false') - - fireEvent.click(button) - expect(onToggle).toHaveBeenCalledTimes(1) - - rerender() - expect(getByRole('button').getAttribute('aria-expanded')).toBe('true') - }) - - it('FE-MAP-ATTRIB-003: wherever it sits, it stays above the Leaflet control corners', () => { - const { getByRole } = render( {}} bottomOffset={BAND} />) - const style = getByRole('button').style - - // index.css pins `.leaflet-bottom` to z-index 1, and the open credit lives in that - // corner. The (i) sits in the same row now, so it has to stay the one on top. - expect(style.position).toBe('absolute') - expect(style.zIndex).toBe('1000') - }) -}) diff --git a/client/src/components/Map/MapAttributionToggle.tsx b/client/src/components/Map/MapAttributionToggle.tsx deleted file mode 100644 index d42a4aa87..000000000 --- a/client/src/components/Map/MapAttributionToggle.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Info } from 'lucide-react' -import { MAP_CONTROL_SHADOW } from './mapControlShadow' -import { useTranslation } from '../../i18n' - -interface MapAttributionToggleProps { - open: boolean - onToggle: () => void - /** - * Where the (i) sits on a map that defines no credit corner: the locate button's band, - * so the two share a line. - */ - bottomOffset: string -} - -/** - * The little (i) that stands in for the credit on a phone. - * - * Both GL engines ship one of these: below 640px they collapse their attribution to a - * button and open it on a tap. Leaflet has no such mode, so on a screen the map fills, - * its credit is a white two-line block parked in the middle of the picture. This is the - * missing half of that pattern, and it is a React button rather than surgery on - * Leaflet's own container because Leaflet rewrites that container's markup from scratch - * whenever a layer with an attribution is added or removed. - * - * Nothing is hidden that cannot be read: one tap shows the full credit, every link in - * it included, which is the same bargain the GL engines strike. - */ -export default function MapAttributionToggle({ open, onToggle, bottomOffset }: MapAttributionToggleProps) { - const { t } = useTranslation() - const label = t('map.attribution') - return ( - - ) -} diff --git a/client/src/components/Map/MapView.test.tsx b/client/src/components/Map/MapView.test.tsx index 2075c2805..d44923dbe 100644 --- a/client/src/components/Map/MapView.test.tsx +++ b/client/src/components/Map/MapView.test.tsx @@ -989,29 +989,19 @@ describe('MapView live location', () => { expect(geoMock.cycleMode).toHaveBeenCalled() }) - it('FE-COMP-MAPVIEW-077: the credit (i) is a phone control, and it opens the credit through a class on the wrapper', () => { + it('FE-COMP-MAPVIEW-077: the map draws no credit control of its own, on either width', () => { const desktop = render() + // The desktop credit is Leaflet's own, in its own container; this component adds none. expect(screen.queryByRole('button', { name: 'Map credits' })).toBeNull() desktop.unmount() Object.defineProperty(window, 'innerWidth', { configurable: true, writable: true, value: 420 }) const { container } = render() - const toggle = screen.getByRole('button', { name: 'Map credits' }) + // The phone used to get an (i) here that opened the credit through a class on the + // wrapper. The phone map now carries no visible credit at all (mobile.css), so neither + // the button nor the class it toggled is left behind. + expect(screen.queryByRole('button', { name: 'Map credits' })).toBeNull() const wrapper = container.querySelector('div.w-full.h-full.relative') as HTMLElement - // Starts closed: on a phone the map is the screen, and the credit is one tap away. - expect(toggle.getAttribute('aria-expanded')).toBe('false') - expect(wrapper.classList.contains('m-attrib-open')).toBe(false) - // The locate button's band is only the fallback; a credit corner around the map wins. - expect(toggle.style.bottom).toBe('var(--m-credit-bottom, calc(var(--bottom-nav-h, 84px) + 12px))') - - // A class on the wrapper rather than markup in Leaflet's own container, which Leaflet - // rewrites whenever a layer with an attribution comes or goes. - fireEvent.click(toggle) - expect(toggle.getAttribute('aria-expanded')).toBe('true') - expect(wrapper.classList.contains('m-attrib-open')).toBe(true) - - fireEvent.click(toggle) - expect(toggle.getAttribute('aria-expanded')).toBe('false') expect(wrapper.classList.contains('m-attrib-open')).toBe(false) }) }) diff --git a/client/src/components/Map/MapView.tsx b/client/src/components/Map/MapView.tsx index 6a043e37d..169f8bfcb 100644 --- a/client/src/components/Map/MapView.tsx +++ b/client/src/components/Map/MapView.tsx @@ -34,7 +34,6 @@ function alternativeLabelIcon(label: string, note: string, background: string, a }) } import L from 'leaflet' -import MapAttributionToggle from './MapAttributionToggle' import 'leaflet.markercluster/dist/MarkerCluster.css' import 'leaflet.markercluster/dist/MarkerCluster.Default.css' import { mapsApi } from '../../api/client' @@ -999,16 +998,6 @@ export const MapView = memo(function MapView({ ? 'calc(var(--bottom-nav-h, 84px) + 20px + var(--day-panel-h, 0px) + 12px)' : 'calc(var(--bottom-nav-h, 84px) + 12px)' - /** - * Whether the credit is showing, on a phone where it starts as the (i). - * - * Held here rather than pushed into Leaflet's own container: Leaflet rewrites that - * container's markup from scratch whenever a layer with an attribution is added or - * removed, so anything injected into it dies on the next basemap switch. A class on - * the wrapper survives that, and the CSS lives with the rest of the mobile chrome. - */ - const [attribOpen, setAttribOpen] = useState(false) - const baseLayer = useSettingsStore(s => s.settings.map_base_layer) || 'default' const updateSetting = useSettingsStore(s => s.updateSetting) const isSatellite = baseLayer === 'satellite' @@ -1027,7 +1016,7 @@ export const MapView = memo(function MapView({ return ( <> -
+
} - {/* The credit as the little (i), which is what both GL engines do on a narrow map - and Leaflet has no mode for. */} - {isMobile && ( - setAttribOpen(v => !v)} - bottomOffset={locationButtonBottom} - /> - )} {/* 20px off the sidebar, not 12: the pill is round and frosted, so at the smaller gap its shadow ran into the sidebar edge and the two read as one surface. */} diff --git a/client/src/mobile/mobile.css b/client/src/mobile/mobile.css index cc4719c9c..83d0b7c75 100644 --- a/client/src/mobile/mobile.css +++ b/client/src/mobile/mobile.css @@ -334,116 +334,27 @@ .trek-z { opacity: 1; } } -/* Map attribution clears the dock. +/* The phone map carries no visible credit. * - * Both engines park their attribution at the bottom of the map, and on a phone - * the map is the whole screen — so the credit line lands under the floating nav - * and reads as a broken bar (#2022's screenshots show it over the dock). Lift it - * by the same variable everything else clears the dock with. Leaflet and - * MapLibre both, because a phone meets either depending on the screen. */ + * Both engines park a credit at the bottom of the map, and on a phone the map is the + * whole screen, so whatever is put there sits in the middle of the picture. It has been + * a band across the bottom (#2022) and then a round (i) beside the locate button, and + * neither reads as part of this shell: a lone control under the locate button is the + * first thing the eye lands on over a full-bleed map. + * + * So on the phone it is off. The desktop map is untouched and still shows the full + * credit with its links, which is where this belongs; `.m-root` is the phone shell only. + * Both engines and both corners, because a phone meets either depending on the screen, + * and the GL containers hold nothing else — MapViewGL adds no control of its own, so + * hiding the corner hides the credit and the wordmark and nothing besides. + * + * Owner's call, made knowingly: OSM and the tile vendors ask for a visible credit. */ .m-root .leaflet-control-attribution, .m-root .maplibregl-ctrl-bottom-left, .m-root .mapboxgl-ctrl-bottom-left, .m-root .maplibregl-ctrl-bottom-right, .m-root .mapboxgl-ctrl-bottom-right { - bottom: var(--bottom-nav-h); -} - -/* ...and it is a round (i) rather than a band across the picture. - * - * Both GL engines collapse their attribution below 640px and then open it anyway, - * tucking it away only on the first drag. On a phone, where the map IS the screen, - * the first thing anybody sees is then a two-line grey ribbon over the bottom of - * it. MapViewGL now starts it collapsed; what is left here is where the little - * button sits on a phone map without a credit row, which is beside the locate - * control rather than underneath it. The trip map gives it the corner instead - * (`.m-credit-corner` below). - * - * Nothing is dropped: one tap opens the full credit, and it is still a link. */ -.m-root .maplibregl-ctrl-bottom-right, -.m-root .mapboxgl-ctrl-bottom-right { - bottom: calc(var(--bottom-nav-h) + 2px); - right: 46px; -} - -/* Leaflet has no collapsed mode at all, so it is given one: hidden until its (i) is tapped - * (`MapAttributionToggle`, which puts `m-attrib-open` on the map's wrapper). Nothing is - * lost: one tap shows the whole credit, every link in it included, which is the same - * bargain the GL engines strike on a narrow map. */ -.m-root .leaflet-control-attribution { display: none; } - -/* The control column, not just the block inside it: Leaflet sizes that column to the - credit on one line, so capping only the child leaves the text overflowing its own - box instead of wrapping. */ -.m-root .m-attrib-open .leaflet-bottom.leaflet-right { max-width: 78vw; } - -.m-root .m-attrib-open .leaflet-control-attribution { - display: block; - /* Above the (i) rather than beside it, so the block never lands under a thumb - reaching for the locate button. */ - right: 12px; - bottom: calc(var(--bottom-nav-h) + 52px); - padding: 4px 9px; - border-radius: 11px; - font-size: 10px; - line-height: 1.4; - background: color-mix(in srgb, var(--m-card) 92%, transparent); - color: var(--m-muted); - box-shadow: 0 10px 28px -12px rgba(0, 0, 0, .45); -} -.m-root .m-attrib-open .leaflet-control-attribution a { color: var(--m-ink); } - -/* On the trip map the credit has the bottom right corner to itself. - * - * Beside the locate button it read as a stray control in the middle of the band. MMapArea - * marks its layer `m-credit-corner`, defines `--m-map-floor` (the top of the dock, or of the - * stage bar on the road trip tab) and floats the round controls one credit row above that - * floor, so the corner under them is free. Both engines read the same two numbers from - * here: the GL control containers through the rules below, the Leaflet (i) through the - * variables it falls back from. Other phone maps have no such row and define neither, so - * their credit stays beside their locate button. */ -.m-root .m-credit-corner { - /* The locate button's own margin, so the right column lines up on one edge. */ - --m-credit-right: 12px; - --m-credit-bottom: calc(var(--m-map-floor) + 12px); -} - -/* The compact GL control keeps a 10px margin of its own, so its container sits that far - inside the spot the button is meant to land on. */ -.m-root .m-credit-corner .maplibregl-ctrl-bottom-right, -.m-root .m-credit-corner .mapboxgl-ctrl-bottom-right { - bottom: calc(var(--m-credit-bottom) - 10px); - right: calc(var(--m-credit-right) - 10px); -} - -/* ...and the (i) stays in that corner while the credit is open. Both GL engines pin their - button to the top of the control, so a credit that wraps carries it up the column, into - the locate button that now sits right above this corner: at two lines (20px each) the - top of the button slides under it, at three the whole button does, and the tap meant to - close the credit lands on the locate button instead. Held at the bottom it stays where - the thumb just was, and the text unfolds to its left and upward like the Leaflet credit - below. Collapsed, the control is exactly the button's height, so nothing moves until it - opens. */ -.m-root .m-credit-corner .maplibregl-ctrl-bottom-right .maplibregl-ctrl-attrib-button, -.m-root .m-credit-corner .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl-attrib-button { - top: auto; - bottom: 0; -} - -/* The Mapbox wordmark is a credit too, so it shares the row rather than rising with the - band into the switcher and the compass. 6px rather than 10: the logo hangs 4px below its - own control box. MapLibre draws no wordmark here, so for it this moves an empty corner. */ -.m-root .m-credit-corner .maplibregl-ctrl-bottom-left, -.m-root .m-credit-corner .mapboxgl-ctrl-bottom-left { - bottom: calc(var(--m-credit-bottom) - 6px); -} - -/* The open Leaflet credit stays in the row, to the left of the (i), which is where the GL - engines unfold theirs. Above the (i) is the locate button now. 36px is the (i) plus a - gap; both offsets are relative, like the ones they override. */ -.m-root .m-credit-corner .m-attrib-open .leaflet-control-attribution { - right: calc(var(--m-credit-right) + 36px); - bottom: var(--m-credit-bottom); + display: none; } /* Text fields carry the row's background, not their own. diff --git a/client/src/mobile/mobileCreditCss.test.ts b/client/src/mobile/mobileCreditCss.test.ts index d258adf3d..97971e69a 100644 --- a/client/src/mobile/mobileCreditCss.test.ts +++ b/client/src/mobile/mobileCreditCss.test.ts @@ -2,20 +2,21 @@ import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { describe, expect, it } from 'vitest' -// FE-MOB-CREDITCSS-001 to FE-MOB-CREDITCSS-005 +// FE-MOB-CREDITCSS-001 to FE-MOB-CREDITCSS-003 // -// Where the credit lands on the phone trip map is written in mobile.css, against the vendor -// control containers the GL engines draw and the Leaflet column. jsdom applies neither -// stylesheet, so these read the real file: the numbers only agree with the vendor CSS and -// with MMapArea's floor by convention, which is exactly what can drift here. +// The phone map carries no visible credit: that is a decision, and the only thing holding +// it is a rule in mobile.css against the vendor control containers. jsdom applies no +// stylesheet, so these read the real file. Written as tests because the rule is easy to +// lose: it names vendor class names nothing else in the codebase refers to, and a credit +// creeping back is a change to the map's face that no component test would catch. describe('phone map credit css', () => { // Vitest runs with the client package as its root, so cwd is stable here. const css = readFileSync(resolve(process.cwd(), 'src/mobile/mobile.css'), 'utf8') /** * Every rule in the sheet as its selector list and its declarations, comments dropped. - * Parsed rather than found by substring, because the same selector heads more than one - * rule here (the generic lift and the round (i) both name the GL bottom right corner). + * Parsed rather than found by substring, because a vendor class name also appears in the + * prose above the rules. */ const rules = css .replace(/\/\*[\s\S]*?\*\//g, '') @@ -32,63 +33,46 @@ describe('phone map credit css', () => { /** The rules whose selector list names this selector. */ const rulesFor = (selector: string) => rules.filter(rule => rule.selectors.includes(selector)) - it('FE-MOB-CREDITCSS-001: the credit corner derives both numbers from the floor MMapArea defines', () => { - const corner = rulesFor('.m-root .m-credit-corner') + it('FE-MOB-CREDITCSS-001: both engines and both corners are hidden by one rule', () => { + const hidden = rulesFor('.m-root .leaflet-control-attribution') - expect(corner).toHaveLength(1) - // The locate button's own margin on the right, a gap above the dock or the stage bar below. - expect(corner[0].body).toMatch(/--m-credit-right:\s*12px;/) - expect(corner[0].body).toMatch(/--m-credit-bottom:\s*calc\(var\(--m-map-floor\) \+ 12px\);/) + expect(hidden).toHaveLength(1) + expect(hidden[0].body).toMatch(/display:\s*none;/) + // One rule for all of them, so Leaflet, MapLibre and Mapbox cannot drift apart, and so + // the credit cannot come back on the one engine nobody happened to open. + for (const selector of [ + '.m-root .maplibregl-ctrl-bottom-left', + '.m-root .mapboxgl-ctrl-bottom-left', + '.m-root .maplibregl-ctrl-bottom-right', + '.m-root .mapboxgl-ctrl-bottom-right', + ]) { + expect(hidden[0].selectors).toContain(selector) + } }) - it('FE-MOB-CREDITCSS-002: the GL (i) lands on those numbers, net of the compact control\'s own margin', () => { - const gl = rulesFor('.m-root .m-credit-corner .maplibregl-ctrl-bottom-right') + it('FE-MOB-CREDITCSS-002: nothing else in the sheet places a credit', () => { + // The hiding rule is the last word only while no later rule moves one of these + // containers back into view. Both corners, because the GL wordmark sits in the left one. + const placed = rules.filter(rule => rule.selectors.some(s => /ctrl-bottom-(left|right)|leaflet-control-attribution/.test(s))) - expect(gl).toHaveLength(1) - // One rule for both engines, so MapLibre and Mapbox cannot end up in different corners. - expect(gl[0].selectors).toContain('.m-root .m-credit-corner .mapboxgl-ctrl-bottom-right') - // The vendor sheets give the compact attribution a 10px margin on every side. - expect(gl[0].body).toMatch(/bottom:\s*calc\(var\(--m-credit-bottom\) - 10px\);/) - expect(gl[0].body).toMatch(/right:\s*calc\(var\(--m-credit-right\) - 10px\);/) - - // The Mapbox wordmark shares the row rather than rising with the band under the compass. - const wordmark = rulesFor('.m-root .m-credit-corner .mapboxgl-ctrl-bottom-left') - expect(wordmark).toHaveLength(1) - expect(wordmark[0].selectors).toContain('.m-root .m-credit-corner .maplibregl-ctrl-bottom-left') - expect(wordmark[0].body).toMatch(/bottom:\s*calc\(var\(--m-credit-bottom\) - 6px\);/) + expect(placed).toHaveLength(1) + expect(placed[0].body).toMatch(/display:\s*none;/) }) - it('FE-MOB-CREDITCSS-003: the open Leaflet credit unfolds in the row, left of the (i), not over the locate button', () => { - const open = rulesFor('.m-root .m-credit-corner .m-attrib-open .leaflet-control-attribution') - - expect(open).toHaveLength(1) - expect(open[0].body).toMatch(/right:\s*calc\(var\(--m-credit-right\) \+ 36px\);/) - expect(open[0].body).toMatch(/bottom:\s*var\(--m-credit-bottom\);/) + it('FE-MOB-CREDITCSS-003: the credit corner and its variables are gone with it', () => { + // `m-credit-corner` was the trip map's own credit slot, `m-attrib-open` the class the + // Leaflet (i) toggled. Both are dead now; a leftover rule would place an element that + // no longer exists and read as if the credit were still somewhere on screen. + expect(css).not.toContain('m-credit-corner') + expect(css).not.toContain('m-attrib-open') + expect(css).not.toContain('--m-credit') }) - it('FE-MOB-CREDITCSS-004: phone maps without a credit row keep the credit beside their locate button', () => { - // The collections and journey maps have no `m-credit-corner`, so the unscoped rules are - // still what places their credit. - expect(rulesFor('.m-root .maplibregl-ctrl-bottom-right').some(rule => /right:\s*46px;/.test(rule.body))).toBe(true) - expect(rulesFor('.m-root .m-attrib-open .leaflet-control-attribution').some( - rule => /bottom:\s*calc\(var\(--bottom-nav-h\) \+ 52px\);/.test(rule.body), - )).toBe(true) + it('FE-MOB-CREDITCSS-004: the phone map is the only thing this touches', () => { + // Every selector is under `.m-root`, the phone shell. The desktop map keeps its full + // credit with its links, which is where it belongs. + const hidden = rulesFor('.m-root .leaflet-control-attribution') - // The band's own lift stays in MMapArea's class, where FE-MOB-MAPAREA-003 pins it. A - // corner rule setting it too would move every round control from a stylesheet. - const corner = rules.filter(rule => rule.selectors.some(s => s.includes('.m-credit-corner'))) - expect(corner).toHaveLength(5) - for (const rule of corner) expect(rule.body).not.toContain('--bottom-nav-h') - }) - - it('FE-MOB-CREDITCSS-005: an open GL credit keeps its (i) in the corner rather than under the locate button', () => { - const button = rulesFor('.m-root .m-credit-corner .maplibregl-ctrl-bottom-right .maplibregl-ctrl-attrib-button') - - expect(button).toHaveLength(1) - expect(button[0].selectors).toContain('.m-root .m-credit-corner .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl-attrib-button') - // Both vendor sheets put the button at the top of the control, so a credit wrapping to - // three lines would carry it up into the locate button right above the corner. - expect(button[0].body).toMatch(/top:\s*auto;/) - expect(button[0].body).toMatch(/bottom:\s*0;/) + for (const selector of hidden[0].selectors) expect(selector.startsWith('.m-root ')).toBe(true) }) }) diff --git a/client/src/mobile/screens/trip/map/MMapArea.tsx b/client/src/mobile/screens/trip/map/MMapArea.tsx index c2845f40d..e4a5788b7 100644 --- a/client/src/mobile/screens/trip/map/MMapArea.tsx +++ b/client/src/mobile/screens/trip/map/MMapArea.tsx @@ -76,10 +76,10 @@ function useSafeInsets(probe: RefObject): SafeInsets { * the rail. The search bar that sits below the rail on the stage steps away while the * picker is open, so the rail is the lowest chrome up there. * - * Bottom: the safe area, the dock's 74px (62px tall, 12px off the bottom), the bar's lift - * (its height and the 15px gap between the dock and its foot) and the 38px credit row - * over that. That is the very line `--bottom-nav-h` puts the round controls' band on, so - * the frame ends where they begin, and they only take the two corners of it. + * Bottom: the safe area, the dock's 74px (62px tall, 12px off the bottom) and the bar's + * lift (its height and the 15px gap between the dock and its foot). That is the very line + * `--bottom-nav-h` puts the round controls' band on, so the frame ends where they begin, + * and they only take the two corners of it. * * The sides keep the 20px both engines already give a phone: nothing floats there. */ @@ -87,7 +87,7 @@ function alternativesFitPadding(insets: SafeInsets): ViewportPadding { return { top: insets.top + 12 + 50 + 42 + 12, right: 20, - bottom: insets.bottom + 74 + RT_ALT_BAR_LIFT + 38, + bottom: insets.bottom + 74 + RT_ALT_BAR_LIFT, left: 20, } } @@ -281,6 +281,24 @@ export default function MMapArea({ planner, shell }: MMapAreaProps) { * Merged through the planner's own hook rather than concatenated here, because the * sets overlap: the same petrol station found twice would be two pins on one roof. */ + /** + * The vias the phone map draws, minus the night pauses. + * + * A night pause is a pill with a moon and a day number, drawn beside the boundary where + * one travel day ends. On the desktop it is a handle: it carries a hint and can be + * dragged to move the boundary. A phone has no hover to explain it and no room to drag + * it, so all it does there is sit on the map, and it sits there for EVERY day at once — + * the markers come from the whole drive (`automaticPoints`), not from the stage on + * screen, so day 1 shows day 2's pill as well. Two labels the day filter does not reach + * read as part of the map rather than as a control. + * + * Both lists go through the same filter: the plan tab's vias carry night pauses too. + */ + const mapVias = useMemo( + () => (onStage ? planner.roadtripMapVias : planner.routeVias)?.filter(v => !v.nightPause), + [onStage, planner.roadtripMapVias, planner.routeVias], + ) + const pois = useMergedMapPois( onStage ? planner.roadtripCorridor.visible : null, onStage ? NO_POIS : poi.pois, @@ -294,27 +312,23 @@ export default function MMapArea({ planner, shell }: MMapAreaProps) { // // --m-map-floor is the top edge of whatever the map ends at: the dock, 62px tall at // safe-bottom + 12, or on the road trip tab the stage bar, which takes the band the - // round controls would otherwise sit in and is what --m-stage-lift adds. The map - // credit (the little (i)) takes the bottom right corner a gap above that floor: - // beside the locate button it read as a stray control in the middle of the band. - // The round controls float one credit row higher (a 30px button plus an 8px gap) and - // add their own 12px on top of that, still close enough to the thumb to reach - // one-handed. The whole band moves rather than the one control over the corner, so - // the compass and the locate button stay on one line, and everything that reads - // --bottom-nav-h (the compass, both engines' locate button and base-layer switcher, - // the overview stack) follows on its own. Where the credit lands is written once, in - // mobile.css under `m-credit-corner`, where the GL containers and the Leaflet (i) - // both read it. + // round controls would otherwise sit in and is what --m-stage-lift adds. The round + // controls sit straight on that floor and add their own 12px, close enough to the + // thumb to reach one-handed. Everything that reads --bottom-nav-h (the compass, both + // engines' locate button and base-layer switcher, the overview stack) follows on its + // own. The band used to float a further 38px up to leave the corner under it to the + // map credit; the phone map carries no visible credit any more (see mobile.css), so + // that row would now only be a gap over the dock. // // The metrics are classes rather than an inline --bottom-nav-h, and the lift is its // OWN variable folded into them: the compass band below is identified by being the // one element that sets that name inline, and a second one would make that ambiguous.