Files
intotheeast-com/docs/working/learnings/2026-06-22-mobile-ux-learnings.md
T

118 lines
3.6 KiB
Markdown

# Mobile UX Session Learnings — 2026-06-22
Discoveries from the mobile polish session (stat scaling, map fullscreen, panel toggles, shared partials).
## MapLibre GL JS v4 — Attribution starts expanded despite compact: true
**Problem:** `new maplibregl.AttributionControl({ compact: true })` renders a `<details>` element. In MapLibre v4, this element has `open` set after `map.on('load')` fires, so the attribution panel starts expanded even though `compact: true` was passed.
**Fix:** In the `load` handler, explicitly remove the `open` attribute:
```js
map.on('load', function () {
var attrib = map.getContainer().querySelector('.maplibregl-ctrl-attrib');
if (attrib) attrib.removeAttribute('open');
});
```
**Also:** To avoid the default attribution control conflicting with a custom button in `bottom-right`, disable it in the constructor and add it manually to `bottom-left`:
```js
var map = new maplibregl.Map({ ..., attributionControl: false });
map.addControl(new maplibregl.AttributionControl({ compact: true }), 'bottom-left');
```
## CSS Panel Animation — max-height beats grid-template-rows: 0fr
**Problem:** `grid-template-rows: 0fr → 1fr` transition fails when the direct grid child has `overflow: hidden`. The child creates a Block Formatting Context (BFC) that prevents `0fr` from collapsing to zero height.
**Fix:** Use `max-height` transition on the outer container:
```css
.panel {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease;
}
.panel.is-open {
max-height: 600px;
}
```
## Fluid Font Sizing with clamp()
```css
.stat-value {
font-size: clamp(2rem, 6vw, var(--text-3xl));
}
```
- `clamp(min, preferred, max)`: scales linearly between min and max
- `6vw` at 333px viewport = 20px = 1.25rem, but floor is 2rem (32px)
- Keep labels at `--text-xs` (0.75rem) intentionally — the contrast makes values pop
## CSS Grid — Spanning the Lone Last Item in a 2-Column Grid
```css
@media (max-width: 600px) {
.my-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.my-grid .item:last-child:nth-child(odd) { grid-column: 1 / -1; }
}
```
- `minmax(0, 1fr)` — strictly equal columns (bare `1fr` has a hidden `auto` minimum)
- `:last-child:nth-child(odd)` — matches an item that is both last and in an odd position
## PhotoSwipe v5 — Correct Element for CSS Animations
**Problem:** `pswp.currSlide.el` is `undefined` in PhotoSwipe v5.
**Fix:** Use `pswp.currSlide.container` — the DOM wrapper for the current slide:
```js
var el = pswp.currSlide && pswp.currSlide.container;
if (!el) return;
el.classList.add('pswp-key-from-right');
```
## Mobile Fullscreen Map Pattern
```css
.map-col.is-fullscreen {
position: fixed !important;
inset: 0;
z-index: 9999;
height: 100dvh !important;
}
```
```js
fsBtn.addEventListener('click', function() {
var isFs = mapCol.classList.toggle('is-fullscreen');
document.body.style.overflow = isFs ? 'hidden' : '';
setTimeout(function() { map.resize(); }, 50);
});
```
**Marker click while fullscreen:** Exit fullscreen first, then scroll after the transition:
```js
if (isFullscreen) {
fsBtn.click();
setTimeout(scrollAndHighlight, 450);
} else {
scrollAndHighlight();
}
```
## Shared Twig Partial Pattern
```twig
{% include 'partials/feed-map.html.twig' with {
'map_entries': map_entries,
'map_id': 'feed-map',
'map_var': 'feedMap',
'link_href': page.parent().url ~ '/map',
'card_prefix': 'entry-',
'trip_page': trip_page,
'show_journey': true
} only %}
```
Grav's global Twig functions (`url()`, `theme_var()`) remain available with `only`. Only parent template variables are excluded.