diff --git a/src/App.vue b/src/App.vue index f0090b6..d60c43d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -112,6 +112,7 @@ import { import { useDraggableFloatingButton } from '@/utils/useDraggableFloatingButton' import AppCreditLink from '@/components/AppCreditLink.vue' import { useRoute } from 'vue-router' +import { recoverMobileViewportAfterAuth } from '@/layouts/composables/useMobileScrollReset' const VersionUpdateModal = defineAsyncComponent( () => import('@/modules/version/VersionUpdateModal.vue') @@ -251,13 +252,26 @@ watch(showSplash, (visible) => { } }) +watch( + () => route.name, + (toName, fromName) => { + if (isDesktop.value) return + if (fromName === 'login' || toName === 'home') { + recoverMobileViewportAfterAuth() + } + }, + { flush: 'post' }, +) + diff --git a/src/layouts/mobile/mobile-layout.less b/src/layouts/mobile/mobile-layout.less index 894db9f..51809d9 100644 --- a/src/layouts/mobile/mobile-layout.less +++ b/src/layouts/mobile/mobile-layout.less @@ -131,8 +131,16 @@ opacity: 0; } +.mobile-app-layout { + height: 100%; + max-height: 100%; + overflow: hidden; + display: flex; + flex-direction: column; +} + .main-with-bottom-chrome { - height: calc(100vh - var(--app-bottom-chrome-height, 108px)); + height: calc(100dvh - var(--app-bottom-chrome-height, 108px)); overflow: hidden; display: flex; flex-direction: column; @@ -148,7 +156,11 @@ overscroll-behavior-y: contain; } +.main-with-bottom-chrome-inner--contained { + overflow: hidden; +} + .main-with-bottom-chrome--no-nav, .main-with-bottom-chrome--no-nav .main-with-bottom-chrome-inner { - height: calc(100vh - var(--app-credit-footer-offset, 28px)); + height: calc(100dvh - var(--app-credit-footer-offset, 28px)); } diff --git a/src/layouts/styles/mobile-frame.less b/src/layouts/styles/mobile-frame.less index 1dc33dd..ee3aba8 100644 --- a/src/layouts/styles/mobile-frame.less +++ b/src/layouts/styles/mobile-frame.less @@ -5,6 +5,33 @@ */ html:not(.app-layout--desktop) { + html, + body, + #app { + min-height: 0; + height: 100dvh; + max-height: 100dvh; + overflow: hidden; + } + + body { + overscroll-behavior: none; + } + + .desktopConfig, + .app-main { + min-height: 0; + height: 100dvh; + max-height: 100dvh; + overflow: hidden; + } + + .app-main > * { + height: 100%; + max-height: 100%; + overflow: hidden; + } + /* Teleport to body — dock bar must span viewport, not shrink to content */ .app-dock-add-bar, .wallet-card-slider { diff --git a/src/layouts/styles/mobile-page-common.less b/src/layouts/styles/mobile-page-common.less index 417ab3e..f912a37 100644 --- a/src/layouts/styles/mobile-page-common.less +++ b/src/layouts/styles/mobile-page-common.less @@ -1,10 +1,35 @@ -/* Shared mobile page chrome */ +/* Shared mobile page chrome (inner pages — not home .app-header) */ + +:root { + --mpage-header-bar: 2.75rem; + --mpage-header-offset: calc(4rem + var(--app-safe-top, env(safe-area-inset-top, 0px))); + --mpage-subheader-offset: calc(var(--mpage-header-bar) + var(--app-safe-top, env(safe-area-inset-top, 0px))); + --mpage-content-with-subheader: calc(6.5rem + var(--app-safe-top, env(safe-area-inset-top, 0px))); + --mpage-scroll-max-height: calc(100dvh - 50px - var(--app-safe-top, env(safe-area-inset-top, 0px))); + --mobile-info-support-block: calc(5rem + var(--app-safe-bottom, env(safe-area-inset-bottom, 0px))); +} + +/* Fixed top bar: background fills under status bar; content sits below safe area */ +.mobile-top-chrome { + top: 0; + box-sizing: border-box; + padding-top: calc(0.75rem + var(--app-safe-top, env(safe-area-inset-top, 0px))); +} + +.mobile-top-chrome.h-12, +.mobile-top-chrome.min-h-12 { + height: auto; + min-height: calc(3rem + var(--app-safe-top, env(safe-area-inset-top, 0px))); +} .mpage-header { display: flex; align-items: center; gap: 0.5rem; + top: 0; + box-sizing: border-box; padding: 0.75rem; + padding-top: calc(0.75rem + var(--app-safe-top, env(safe-area-inset-top, 0px))); background: #fff; color: @App-primary-color; font-size: 1rem; @@ -38,7 +63,7 @@ } .mpage-subheader { - top: 2.75rem; + top: var(--mpage-subheader-offset); z-index: 1999; padding: 0.75rem; background: #fff; @@ -47,14 +72,137 @@ } .mpage-content { - padding-top: 4rem; + padding-top: var(--mpage-header-offset); } .mpage-content--with-subheader { - padding-top: 6.5rem; + padding-top: var(--mpage-content-with-subheader); } .mpage-scroll { overflow-y: auto; - max-height: calc(100vh - 50px); + max-height: var(--mpage-scroll-max-height); +} + +.mobile-page-body--pt10 { + padding-top: calc(2.5rem + var(--app-safe-top, env(safe-area-inset-top, 0px))); +} + +.mobile-page-body--pt15 { + padding-top: calc(3.75rem + var(--app-safe-top, env(safe-area-inset-top, 0px))); +} + +.mobile-page-body--pt20 { + padding-top: calc(5rem + var(--app-safe-top, env(safe-area-inset-top, 0px))); +} + +/* Info pages (about, educate): fixed shell, scroll inside content box only */ +.mobile-info-page { + position: relative; + display: flex; + flex-direction: column; + height: 100%; + min-height: 0; + max-height: 100%; + overflow: hidden; +} + +.mobile-info-page__header { + z-index: 1999; +} + +/* Decorative page background — fixed to viewport, never scrolls on iOS */ +.mobile-page-bg, +.mobile-info-page__bg { + pointer-events: none; + position: fixed; + z-index: 0; + top: 0; + left: 0; + right: 0; + bottom: var(--app-bottom-chrome-height, 108px); + overflow: hidden; + max-width: 100vw; + box-sizing: border-box; + + img { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: auto; + } +} + +.mobile-page-bg--no-nav, +.mobile-info-page__bg { + bottom: var(--app-credit-footer-offset, 28px); +} + +.mobile-page-bg--cover { + background-size: cover; + background-position: center; + background-repeat: no-repeat; +} + +html:not(.app-layout--desktop) { + @media screen and (min-width: 426px) and (max-width: 767.98px) { + .mobile-page-bg, + .mobile-info-page__bg { + width: 426px; + max-width: 426px; + left: 50%; + right: auto; + transform: translateX(-50%); + } + } +} + +.mobile-info-page__main { + position: relative; + z-index: 1; + flex: 1 1 0; + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; + padding-top: calc(var(--mpage-header-offset) + 1rem); + padding-right: 2.5rem; + padding-left: 2.5rem; + padding-bottom: calc( + var(--mobile-info-support-block, 5rem) + var(--app-credit-footer-offset, 28px) + 1.25rem + ); + pointer-events: none; + + > * { + pointer-events: auto; + } +} + +.mobile-info-page__support { + bottom: var(--app-credit-footer-offset, 28px); + box-sizing: border-box; + padding: 1rem 2.5rem; + padding-bottom: calc(1rem + var(--app-safe-bottom, env(safe-area-inset-bottom, 0px))); + background: #fff; + box-shadow: 0 -2px 8px rgba(15, 23, 42, 0.06); + z-index: 1998; +} + +.mobile-content-box { + display: flex; + flex-direction: column; + flex: 1 1 0; + min-height: 0; + overflow: hidden; + margin-bottom: 0.75rem; +} + +.mobile-content-box__scroll { + flex: 1 1 0; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; } diff --git a/src/views/base/about/components/AboutSectionsContent.vue b/src/views/base/about/components/AboutSectionsContent.vue index 92bdcea..5b45ac9 100644 --- a/src/views/base/about/components/AboutSectionsContent.vue +++ b/src/views/base/about/components/AboutSectionsContent.vue @@ -1,13 +1,16 @@