diff --git a/client/src/pages/collections/useCollections.ts b/client/src/pages/collections/useCollections.ts
index 254f2c7df..4834d2fc4 100644
--- a/client/src/pages/collections/useCollections.ts
+++ b/client/src/pages/collections/useCollections.ts
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useMemo, useState } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useTranslation } from '../../i18n'
import { useElementSize } from '../../hooks/useElementSize'
@@ -70,6 +70,24 @@ export function useCollections() {
// Initial load.
useEffect(() => { loadAll() }, [])
+ // When the list↔map split toggles, its grid columns animate; keep nudging the
+ // mounted map to re-layout during the transition (Leaflet's trackResize hooks
+ // window resize; MapLibre observes its own box). Skip the initial mount — the
+ // map already sizes itself there, and nudging would fight its first fit.
+ const firstViewRun = useRef(true)
+ useEffect(() => {
+ if (firstViewRun.current) { firstViewRun.current = false; return }
+ if (!isWide) return
+ const start = performance.now()
+ let raf = 0
+ const tick = () => {
+ window.dispatchEvent(new Event('resize'))
+ if (performance.now() - start < 440) raf = requestAnimationFrame(tick)
+ }
+ raf = requestAnimationFrame(tick)
+ return () => cancelAnimationFrame(raf)
+ }, [view, isWide])
+
// Keep the active list in sync with the URL (/collections/:id, or the
// "All saved" union at /collections).
useEffect(() => {
diff --git a/client/src/store/collectionStore.ts b/client/src/store/collectionStore.ts
index ddb5dc307..b1076d14b 100644
--- a/client/src/store/collectionStore.ts
+++ b/client/src/store/collectionStore.ts
@@ -15,7 +15,7 @@ export type IncomingCollectionInvite = CollectionListResponse['incomingInvites']
export const ALL_SAVED = 'all' as const
export type ActiveCollectionId = number | typeof ALL_SAVED | null
-export type CollectionView = 'grid' | 'list' | 'map'
+export type CollectionView = 'list' | 'map'
export type StatusFilter = CollectionStatus | 'all'
interface CollectionState {
@@ -69,7 +69,7 @@ export const useCollectionStore = create((set, get) => ({
places: [],
members: [],
incomingInvites: [],
- view: 'grid',
+ view: 'list',
statusFilter: 'all',
search: '',
selectedPlaceId: null,
diff --git a/client/src/styles/collections.css b/client/src/styles/collections.css
index 2b33a86de..2d1f50af0 100644
--- a/client/src/styles/collections.css
+++ b/client/src/styles/collections.css
@@ -195,40 +195,26 @@
.trek-dash .col-selbar-btn.danger { color: oklch(0.6 0.2 25); }
.trek-dash .col-selbar-btn.danger:hover { background: oklch(0.6 0.2 25 / .12); }
-/* ----------------- place grid + cards ----------------- */
-.trek-dash .col-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(214px, 1fr)); gap: 20px; }
-.trek-dash .col-card {
- position: relative; isolation: isolate; overflow: hidden; cursor: pointer; text-align: left; width: 100%;
- border-radius: var(--r-xl); background: var(--glass-bg); border: 1px solid var(--glass-border);
- box-shadow: var(--glass-shadow), var(--glass-highlight);
- transition: transform .25s cubic-bezier(.2,.7,.2,1), box-shadow .25s, border-color .25s;
+/* ----------------- map panel + floating controls ----------------- */
+.trek-dash .col-map-shell { position: relative; width: 100%; height: 100%; }
+.trek-dash .col-map-toggle {
+ position: absolute; top: 12px; left: 12px; z-index: 20;
+ width: 38px; height: 38px; border-radius: 11px; display: grid; place-items: center; color: var(--ink);
+ background: var(--glass-bg); border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow);
+ backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
+ transition: transform .12s, background .15s;
}
-.trek-dash .col-card:hover { transform: translateY(-4px); box-shadow: var(--glass-shadow-hover), var(--glass-highlight); border-color: oklch(0.8 0.01 70 / .8); }
-.dark .trek-dash .col-card:hover { border-color: oklch(1 0 0 / .2); }
-.trek-dash .col-card.sel { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent), var(--glass-shadow); }
-.trek-dash .col-cover { position: relative; aspect-ratio: 4 / 3; overflow: hidden; }
-.trek-dash .col-cover-media { position: absolute; inset: 0; width: 100%; height: 100%; }
-.trek-dash .col-cover-media img { width: 100%; height: 100%; object-fit: cover; transition: transform .6s cubic-bezier(.2,.7,.2,1); }
-.trek-dash .col-card:hover .col-cover-media img { transform: scale(1.05); }
-.trek-dash .col-cover-fallback { background-size: cover; background-position: center; }
-.trek-dash .col-cover::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(0,0,0,0) 44%, rgba(0,0,0,.58) 100%); }
-.trek-dash .col-card-name {
- position: absolute; left: 14px; right: 14px; bottom: 11px; z-index: 1; color: #fff;
- font-size: 15px; font-weight: 600; letter-spacing: -.01em; line-height: 1.18;
- text-shadow: 0 1px 6px rgba(0,0,0,.42), 0 1px 2px rgba(0,0,0,.5);
- overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
+.trek-dash .col-map-toggle:hover { transform: translateY(-1px); background: var(--surface); }
+.trek-dash .col-map-search {
+ position: absolute; top: 12px; right: 12px; z-index: 20;
+ display: flex; align-items: center; gap: 8px; height: 38px; padding: 0 14px 0 12px; border-radius: 11px; color: var(--ink-3);
+ background: var(--glass-bg); border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow);
+ backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
}
-.trek-dash .col-card-status { position: absolute; top: 10px; right: 10px; z-index: 2; }
-.trek-dash .col-card-check {
- position: absolute; top: 10px; left: 10px; z-index: 2; width: 22px; height: 22px; border-radius: 7px;
- display: grid; place-items: center; background: rgba(255,255,255,.86); border: 1px solid rgba(0,0,0,.1); color: transparent;
+.trek-dash .col-map-search input {
+ width: 180px; background: none; border: 0; outline: none; color: var(--ink); font-size: 13.5px; font-family: inherit;
}
-.trek-dash .col-card-check.on { background: var(--accent); border-color: var(--accent); color: #fff; }
-.trek-dash .col-card-body { display: flex; align-items: center; gap: 8px; padding: 11px 13px; min-width: 0; }
-.trek-dash .col-card-cat { display: inline-flex; align-items: center; gap: 7px; min-width: 0; font-size: 12px; color: var(--ink-2); font-weight: 500; }
-.trek-dash .col-card-cat .cdot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
-.trek-dash .col-card-cat span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.trek-dash .col-card-cat svg { flex-shrink: 0; color: var(--ink-3); }
+.trek-dash .col-map-search input::placeholder { color: var(--ink-3); }
/* ----------------- list view ----------------- */
.trek-dash .col-listview { display: flex; flex-direction: column; gap: 7px; }
@@ -255,9 +241,16 @@
/* list + map split (list view on wide screens): scrollable list left, sticky
map right. Clicking a place pans the map (selectedPlaceId drives it). Only
- rendered above 1024px (the page gates it in JS), so no mobile override needed. */
-.trek-dash .col-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(360px, 1.06fr); gap: 20px; align-items: start; }
-.trek-dash .col-split-list { min-width: 0; }
+ rendered above 1024px (the page gates it in JS). The columns animate between
+ the split and the collapsed-list "map full" state; the map stays mounted so
+ the grow/shrink is smooth (the page nudges it to re-layout during the anim). */
+.trek-dash .col-split {
+ display: grid; grid-template-columns: 1fr 1.05fr; gap: 20px; align-items: start;
+ transition: grid-template-columns .4s cubic-bezier(.2,.7,.2,1), gap .4s cubic-bezier(.2,.7,.2,1);
+}
+.trek-dash .col-split.map-full { grid-template-columns: 0fr 1fr; gap: 0; }
+.trek-dash .col-split-list { min-width: 0; overflow: hidden; transition: opacity .25s; }
+.trek-dash .col-split.map-full .col-split-list { opacity: 0; pointer-events: none; }
.trek-dash .col-split-map {
position: sticky; top: calc(var(--nav-h) + 22px);
height: calc(100vh - var(--nav-h) - 210px); min-height: 440px;
@@ -314,8 +307,6 @@
.trek-dash .col-hero-stats { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; margin: 0 -18px; padding: 0 18px; }
.trek-dash .col-hero-stats::-webkit-scrollbar { display: none; }
.trek-dash .col-chip { flex-shrink: 0; }
- .trek-dash .col-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
- .trek-dash .col-card-name { font-size: 13.5px; }
.trek-dash .col-search input { width: 100%; }
.trek-dash .col-search { flex: 1; }
.trek-dash .col-mapwrap { height: calc(100vh - 260px); min-height: 320px; }
diff --git a/shared/src/i18n/ar/collection.ts b/shared/src/i18n/ar/collection.ts
index 90cecc59d..32315d6df 100644
--- a/shared/src/i18n/ar/collection.ts
+++ b/shared/src/i18n/ar/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'إظهار القائمة',
+ 'collections.expandMap': 'توسيع الخريطة',
'collections.hero.mine': 'مجموعتي',
'collections.hero.shared': 'تمت مشاركتها معك',
'collections.hero.all': 'مكتبتك',
diff --git a/shared/src/i18n/br/collection.ts b/shared/src/i18n/br/collection.ts
index 552c3ad6f..b67a9487d 100644
--- a/shared/src/i18n/br/collection.ts
+++ b/shared/src/i18n/br/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Mostrar lista',
+ 'collections.expandMap': 'Ampliar mapa',
'collections.hero.mine': 'Minha coleção',
'collections.hero.shared': 'Compartilhada com você',
'collections.hero.all': 'Sua biblioteca',
diff --git a/shared/src/i18n/cs/collection.ts b/shared/src/i18n/cs/collection.ts
index 169105967..e09b264d2 100644
--- a/shared/src/i18n/cs/collection.ts
+++ b/shared/src/i18n/cs/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Zobrazit seznam',
+ 'collections.expandMap': 'Zvětšit mapu',
'collections.hero.mine': 'Moje sbírka',
'collections.hero.shared': 'Sdíleno s vámi',
'collections.hero.all': 'Vaše knihovna',
diff --git a/shared/src/i18n/de/collection.ts b/shared/src/i18n/de/collection.ts
index d4bed63d2..10d307714 100644
--- a/shared/src/i18n/de/collection.ts
+++ b/shared/src/i18n/de/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Liste zeigen',
+ 'collections.expandMap': 'Karte vergrößern',
'collections.hero.mine': 'Meine Sammlung',
'collections.hero.shared': 'Mit dir geteilt',
'collections.hero.all': 'Deine Bibliothek',
diff --git a/shared/src/i18n/en/collection.ts b/shared/src/i18n/en/collection.ts
index d5d45a6f6..0bcd92b10 100644
--- a/shared/src/i18n/en/collection.ts
+++ b/shared/src/i18n/en/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Show list',
+ 'collections.expandMap': 'Expand map',
'collections.hero.mine': 'My collection',
'collections.hero.shared': 'Shared with you',
'collections.hero.all': 'Your library',
diff --git a/shared/src/i18n/es/collection.ts b/shared/src/i18n/es/collection.ts
index 4f1b87531..9e26e9e33 100644
--- a/shared/src/i18n/es/collection.ts
+++ b/shared/src/i18n/es/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Mostrar lista',
+ 'collections.expandMap': 'Ampliar mapa',
'collections.hero.mine': 'Mi colección',
'collections.hero.shared': 'Compartida contigo',
'collections.hero.all': 'Tu biblioteca',
diff --git a/shared/src/i18n/fr/collection.ts b/shared/src/i18n/fr/collection.ts
index a3532605f..c6d4bdcbb 100644
--- a/shared/src/i18n/fr/collection.ts
+++ b/shared/src/i18n/fr/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Afficher la liste',
+ 'collections.expandMap': 'Agrandir la carte',
'collections.hero.mine': 'Ma collection',
'collections.hero.shared': 'Partagée avec vous',
'collections.hero.all': 'Votre bibliothèque',
diff --git a/shared/src/i18n/gr/collection.ts b/shared/src/i18n/gr/collection.ts
index 9c981873c..e7bfb07db 100644
--- a/shared/src/i18n/gr/collection.ts
+++ b/shared/src/i18n/gr/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Εμφάνιση λίστας',
+ 'collections.expandMap': 'Μεγέθυνση χάρτη',
'collections.hero.mine': 'Η συλλογή μου',
'collections.hero.shared': 'Κοινή χρήση με εσάς',
'collections.hero.all': 'Η βιβλιοθήκη σας',
diff --git a/shared/src/i18n/hu/collection.ts b/shared/src/i18n/hu/collection.ts
index 056dca8c6..ef1d385ed 100644
--- a/shared/src/i18n/hu/collection.ts
+++ b/shared/src/i18n/hu/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Lista megjelenítése',
+ 'collections.expandMap': 'Térkép nagyítása',
'collections.hero.mine': 'Saját gyűjtemény',
'collections.hero.shared': 'Megosztva veled',
'collections.hero.all': 'A könyvtárad',
diff --git a/shared/src/i18n/id/collection.ts b/shared/src/i18n/id/collection.ts
index 396f5975c..d78e5363b 100644
--- a/shared/src/i18n/id/collection.ts
+++ b/shared/src/i18n/id/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Tampilkan daftar',
+ 'collections.expandMap': 'Perbesar peta',
'collections.hero.mine': 'Koleksi saya',
'collections.hero.shared': 'Dibagikan dengan Anda',
'collections.hero.all': 'Pustaka Anda',
diff --git a/shared/src/i18n/it/collection.ts b/shared/src/i18n/it/collection.ts
index 85118b079..b30021ab1 100644
--- a/shared/src/i18n/it/collection.ts
+++ b/shared/src/i18n/it/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Mostra elenco',
+ 'collections.expandMap': 'Espandi mappa',
'collections.hero.mine': 'La mia raccolta',
'collections.hero.shared': 'Condivisa con te',
'collections.hero.all': 'La tua libreria',
diff --git a/shared/src/i18n/ja/collection.ts b/shared/src/i18n/ja/collection.ts
index 89903e9c6..2f41cc2ed 100644
--- a/shared/src/i18n/ja/collection.ts
+++ b/shared/src/i18n/ja/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'リストを表示',
+ 'collections.expandMap': '地図を拡大',
'collections.hero.mine': 'マイコレクション',
'collections.hero.shared': '共有されています',
'collections.hero.all': 'ライブラリ',
diff --git a/shared/src/i18n/ko/collection.ts b/shared/src/i18n/ko/collection.ts
index e54fe8ece..6457443db 100644
--- a/shared/src/i18n/ko/collection.ts
+++ b/shared/src/i18n/ko/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': '목록 표시',
+ 'collections.expandMap': '지도 확대',
'collections.hero.mine': '내 컬렉션',
'collections.hero.shared': '나와 공유됨',
'collections.hero.all': '내 라이브러리',
diff --git a/shared/src/i18n/nl/collection.ts b/shared/src/i18n/nl/collection.ts
index 101b8ac07..af2450b57 100644
--- a/shared/src/i18n/nl/collection.ts
+++ b/shared/src/i18n/nl/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Lijst tonen',
+ 'collections.expandMap': 'Kaart vergroten',
'collections.hero.mine': 'Mijn collectie',
'collections.hero.shared': 'Gedeeld met jou',
'collections.hero.all': 'Jouw bibliotheek',
diff --git a/shared/src/i18n/pl/collection.ts b/shared/src/i18n/pl/collection.ts
index b2ecb8e33..9a495fa2e 100644
--- a/shared/src/i18n/pl/collection.ts
+++ b/shared/src/i18n/pl/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Pokaż listę',
+ 'collections.expandMap': 'Powiększ mapę',
'collections.hero.mine': 'Moja kolekcja',
'collections.hero.shared': 'Udostępniono Tobie',
'collections.hero.all': 'Twoja biblioteka',
diff --git a/shared/src/i18n/ru/collection.ts b/shared/src/i18n/ru/collection.ts
index c70120a91..e28e55c30 100644
--- a/shared/src/i18n/ru/collection.ts
+++ b/shared/src/i18n/ru/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Показать список',
+ 'collections.expandMap': 'Развернуть карту',
'collections.hero.mine': 'Моя коллекция',
'collections.hero.shared': 'Доступно вам',
'collections.hero.all': 'Ваша библиотека',
diff --git a/shared/src/i18n/sv/collection.ts b/shared/src/i18n/sv/collection.ts
index c87373be1..ef6a34096 100644
--- a/shared/src/i18n/sv/collection.ts
+++ b/shared/src/i18n/sv/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Visa lista',
+ 'collections.expandMap': 'Förstora karta',
'collections.hero.mine': 'Min samling',
'collections.hero.shared': 'Delad med dig',
'collections.hero.all': 'Ditt bibliotek',
diff --git a/shared/src/i18n/tr/collection.ts b/shared/src/i18n/tr/collection.ts
index f6c93a284..99ed10b44 100644
--- a/shared/src/i18n/tr/collection.ts
+++ b/shared/src/i18n/tr/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Listeyi göster',
+ 'collections.expandMap': 'Haritayı genişlet',
'collections.hero.mine': 'Koleksiyonum',
'collections.hero.shared': 'Sizinle paylaşıldı',
'collections.hero.all': 'Kitaplığınız',
diff --git a/shared/src/i18n/uk/collection.ts b/shared/src/i18n/uk/collection.ts
index 2c9714e78..3f542af65 100644
--- a/shared/src/i18n/uk/collection.ts
+++ b/shared/src/i18n/uk/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Показати список',
+ 'collections.expandMap': 'Розгорнути карту',
'collections.hero.mine': 'Моя колекція',
'collections.hero.shared': 'Поділено з вами',
'collections.hero.all': 'Ваша бібліотека',
diff --git a/shared/src/i18n/vi/collection.ts b/shared/src/i18n/vi/collection.ts
index 3da9fd233..cf482e29a 100644
--- a/shared/src/i18n/vi/collection.ts
+++ b/shared/src/i18n/vi/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': 'Hiện danh sách',
+ 'collections.expandMap': 'Mở rộng bản đồ',
'collections.hero.mine': 'Bộ sưu tập của tôi',
'collections.hero.shared': 'Được chia sẻ với bạn',
'collections.hero.all': 'Thư viện của bạn',
diff --git a/shared/src/i18n/zh-TW/collection.ts b/shared/src/i18n/zh-TW/collection.ts
index 570e4f385..eb52ca0cb 100644
--- a/shared/src/i18n/zh-TW/collection.ts
+++ b/shared/src/i18n/zh-TW/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': '顯示清單',
+ 'collections.expandMap': '展開地圖',
'collections.hero.mine': '我的收藏',
'collections.hero.shared': '與你共享',
'collections.hero.all': '你的收藏庫',
diff --git a/shared/src/i18n/zh/collection.ts b/shared/src/i18n/zh/collection.ts
index 40d8bb61c..27a734aff 100644
--- a/shared/src/i18n/zh/collection.ts
+++ b/shared/src/i18n/zh/collection.ts
@@ -1,6 +1,8 @@
import type { TranslationStrings } from '../types';
const collection: TranslationStrings = {
+ 'collections.showList': '显示列表',
+ 'collections.expandMap': '展开地图',
'collections.hero.mine': '我的收藏',
'collections.hero.shared': '与你共享',
'collections.hero.all': '你的收藏库',