diff --git a/neode-ui/src/components/cloud/FileCardGrid.vue b/neode-ui/src/components/cloud/FileCardGrid.vue index 330e1fc6..352a9176 100644 --- a/neode-ui/src/components/cloud/FileCardGrid.vue +++ b/neode-ui/src/components/cloud/FileCardGrid.vue @@ -6,7 +6,7 @@ @click="handleClick" > -
+
audioPlaying.value && currentSrc.value // Uniform card cover ratio across every file type so folders, images, videos // and documents all render at the same height in the grid (previously images/ // videos were square while folders were 4/3, giving a ragged, mismatched grid). -const aspectClass = computed(() => 'aspect-[4/3]') +// Aspect is now driven entirely by .cloud-grid-card-cover CSS (4/3 desktop, +// square on mobile) so the ratio is deterministic regardless of Tailwind layer +// ordering. const coverBg = computed(() => { if (props.item.isDir) return 'bg-amber-500/10' diff --git a/neode-ui/src/style.css b/neode-ui/src/style.css index 4677bdf6..90e919f1 100644 --- a/neode-ui/src/style.css +++ b/neode-ui/src/style.css @@ -1827,6 +1827,22 @@ html.modal-scroll-locked .dashboard-scroll-panel { } } +/* Mobile: square, tappable tiles + bottom clearance so the last row scrolls + above the tab bar / back button (matches .mobile-scroll-pad). */ +@media (max-width: 767px) { + .cloud-card-grid, + .cloud-file-list { + padding-bottom: calc( + var(--mobile-tab-bar-height, 88px) + + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)) + + var(--audio-player-height, 0px) + 24px + ); + } + .cloud-grid-card-cover { + aspect-ratio: 1 / 1; + } +} + .cloud-grid-card { display: flex; flex-direction: column; @@ -1856,6 +1872,9 @@ html.modal-scroll-locked .dashboard-scroll-panel { .cloud-grid-card-cover { position: relative; width: 100%; + /* Fallback aspect when the Tailwind aspect-[4/3] utility is unavailable, so + the cover never collapses to zero height. */ + aspect-ratio: 4 / 3; overflow: hidden; border-radius: 0.625rem; } diff --git a/neode-ui/src/views/Dashboard.vue b/neode-ui/src/views/Dashboard.vue index 34d85cc4..bc6b1580 100644 --- a/neode-ui/src/views/Dashboard.vue +++ b/neode-ui/src/views/Dashboard.vue @@ -290,14 +290,19 @@ function activeNetKey(): string { let touchStartX = 0 let touchStartY = 0 let touchStartTime = 0 +let swipeSuppressed = false function onContentTouchStart(e: TouchEvent) { const t = e.touches[0] if (!t) return + // Don't begin a tab swipe when the gesture starts on an app icon — let the + // icon handle the tap/long-press. Swiping anywhere else still changes tabs. + swipeSuppressed = !!(e.target instanceof Element && e.target.closest('.app-icon-item')) touchStartX = t.clientX touchStartY = t.clientY touchStartTime = e.timeStamp } function onContentTouchEnd(e: TouchEvent) { + if (swipeSuppressed) { swipeSuppressed = false; return } const t = e.changedTouches[0] if (!t) return const dx = t.clientX - touchStartX