273 lines
8.1 KiB
Vue
273 lines
8.1 KiB
Vue
<template>
|
|
<q-layout view="lHr lpr lFr" class="bg-grey-11 relative-position">
|
|
<q-header
|
|
class="main-content bg-transparent"
|
|
reveal
|
|
style="transition: all 0.5s ease"
|
|
ref="headerRef"
|
|
>
|
|
<div
|
|
class="q-mx-md q-mt-lg q-py-sm q-px-md"
|
|
:class="isHeroScroll ? 'text-white' : 'text-white glass shadow-6'"
|
|
:style="{
|
|
'backdrop-filter': isHeroScroll ? 'none' : 'blur(12px) saturate(180%)',
|
|
'-webkit-backdrop-filter': isHeroScroll ? 'none' : 'blur(12px) saturate(180%)',
|
|
'border-radius': 'var(--base-radius)',
|
|
'transition': 'all 0.5s ease'
|
|
}"
|
|
>
|
|
<div
|
|
ref="headerContainer"
|
|
class="flex justify-between no-wrap items-center"
|
|
>
|
|
<base-logo ref="logo" class="text-h6" />
|
|
<div
|
|
ref="menuContainer"
|
|
class="row items-center q-ml-md no-wrap"
|
|
style="min-width: 42px"
|
|
>
|
|
<div
|
|
ref="buttonsContainer"
|
|
:class="{ 'invisible absolute': !showFullMenu }"
|
|
class="flex row no-wrap"
|
|
>
|
|
<q-btn
|
|
v-for="item in menuItems"
|
|
:key="item.id"
|
|
flat
|
|
no-caps
|
|
@click="scrollToElement(item.ref)"
|
|
ref="menuButtons"
|
|
:color="isHeroScroll ? 'white' : 'black'"
|
|
>
|
|
<span class="text-no-wrap">{{ $t(item.title) }}</span>
|
|
</q-btn>
|
|
</div>
|
|
|
|
<q-btn
|
|
v-if="!showFullMenu"
|
|
flat
|
|
round
|
|
icon="menu"
|
|
@click="showDrawer = !showDrawer"
|
|
:color="isHeroScroll ? 'white' : 'black'"
|
|
/>
|
|
|
|
<q-btn
|
|
outline
|
|
:color="isHeroScroll ? 'white' : 'black'"
|
|
class="q-ml-sm"
|
|
>
|
|
<div class="flex items-center no-wrap">
|
|
<span class="text-bold">{{ locale.split('-')[0] }}</span>
|
|
</div>
|
|
|
|
<q-menu>
|
|
<q-list style="min-width: 100px">
|
|
<q-item
|
|
v-for="lang in langNames"
|
|
:key="lang.locale"
|
|
clickable
|
|
v-close-popup
|
|
@click="setLocale(lang)"
|
|
>
|
|
<q-item-section>
|
|
<q-item-label :class="isCurrentLang(lang.locale) ? 'text-primary' : ''">
|
|
{{ lang.label }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-btn>
|
|
</div>
|
|
<q-resize-observer @resize="checkSpace" />
|
|
</div>
|
|
</div>
|
|
</q-header>
|
|
|
|
<q-drawer v-model="showDrawer" side="right" overlay>
|
|
<div class="flex column items-end q-gutter-y-md q-pa-md">
|
|
<q-btn
|
|
flat
|
|
round
|
|
icon="mdi-close"
|
|
@click="showDrawer = !showDrawer"
|
|
/>
|
|
<q-btn
|
|
v-for="item in menuItems"
|
|
:key="item.id"
|
|
flat
|
|
no-caps
|
|
@click="scrollToElement(item.ref)"
|
|
>
|
|
<span class="text-no-wrap text-h6">{{ $t(item.title) }}</span>
|
|
</q-btn>
|
|
</div>
|
|
</q-drawer>
|
|
|
|
<q-page-container class="main-content q-pa-none q-ma-none bg-transparent">
|
|
<q-scroll-observer axis="vertical" @scroll="updateHeaderStyle" />
|
|
<q-page class="column">
|
|
<hero-banner
|
|
class="q-pa-none q-pt-xl"
|
|
style="margin-top: -100px"
|
|
id="hero_banner"
|
|
/>
|
|
<problem-section id="problems"/>
|
|
<how-works-section id="how_works" />
|
|
<price-section id="price" />
|
|
<faq-section id="FAQ" />
|
|
<footer-section id="contacts" />
|
|
</q-page>
|
|
</q-page-container>
|
|
</q-layout>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted, nextTick } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
import { scroll } from 'quasar'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { setGlobalLocale } from 'src/boot/i18n'
|
|
|
|
import baseLogo from 'components/BaseLogo.vue'
|
|
import HeroBanner from 'components/HeroBanner.vue'
|
|
import ProblemSection from 'components/ProblemSection.vue'
|
|
import HowWorksSection from 'components/HowWorksSection.vue'
|
|
import PriceSection from 'components/PriceSection.vue'
|
|
import FaqSection from 'components/FAQSection.vue'
|
|
import FooterSection from 'components/FooterSection.vue'
|
|
|
|
const { locale } = useI18n({ useScope: 'global' })
|
|
const { getScrollTarget, setVerticalScrollPosition } = scroll
|
|
const route = useRoute()
|
|
|
|
const isHeroScroll = ref(true)
|
|
const showDrawer = ref(false)
|
|
const showFullMenu = ref(true)
|
|
const isManualScrolling = ref(false)
|
|
|
|
const menuButtons = ref([])
|
|
const headerContainer = ref(null)
|
|
const logo = ref(null)
|
|
const menuContainer = ref(null)
|
|
const headerRef = ref(null)
|
|
|
|
const HEADER_OFFSET = 60
|
|
|
|
const menuItems = [
|
|
{ id: 0, title: 'main__how_it_works', ref: 'how_works' },
|
|
{ id: 1, title: 'main__price', ref: 'price' },
|
|
{ id: 2, title: 'main__faq', ref: 'FAQ' },
|
|
{ id: 3, title: 'main__contacts', ref: 'contacts' }
|
|
]
|
|
|
|
const allSections = [
|
|
'hero_banner',
|
|
'problems',
|
|
'how_works',
|
|
'price',
|
|
'FAQ',
|
|
'contacts'
|
|
]
|
|
|
|
const langNames = [
|
|
{ locale: 'en-US', label: 'English' },
|
|
{ locale: 'ru-RU', label: 'Русский' }
|
|
]
|
|
|
|
const updateHeaderStyle = (e) => {
|
|
isHeroScroll.value = e.position.top <= 15
|
|
|
|
if (isManualScrolling.value) return
|
|
|
|
for (const id of allSections) {
|
|
const el = document.getElementById(id)
|
|
if (el) {
|
|
const rect = el.getBoundingClientRect()
|
|
if (rect.top >= -100 && rect.top <= 150) {
|
|
|
|
if (id === 'hero_banner') {
|
|
if (window.location.hash !== '') {
|
|
history.replaceState(null, '', window.location.pathname + window.location.search)
|
|
}
|
|
} else {
|
|
const newHash = `#${id}`
|
|
if (window.location.hash !== newHash) {
|
|
history.replaceState(null, '', newHash)
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const checkSpace = () => {
|
|
if (!headerContainer.value || !logo.value || !menuContainer.value) return
|
|
const headerWidth = headerContainer.value.offsetWidth
|
|
const logoWidth = logo.value.$el.offsetWidth
|
|
const menuMargin = parseFloat(getComputedStyle(menuContainer.value).marginLeft) || 0
|
|
const availableWidth = headerWidth - logoWidth - menuMargin - 80
|
|
const buttonsWidth = menuButtons.value.reduce(
|
|
(total, btn) => total + (btn?.$el.offsetWidth || 0), 0
|
|
)
|
|
showFullMenu.value = buttonsWidth <= availableWidth
|
|
}
|
|
|
|
const scrollToElement = (id, animate = true) => {
|
|
const el = document.getElementById(id)
|
|
if (!el) return
|
|
|
|
const target = getScrollTarget(el)
|
|
const headerNativeEl = headerRef.value?.$el
|
|
|
|
if (animate) isManualScrolling.value = true
|
|
|
|
const duration = animate ? 350 : 0
|
|
setVerticalScrollPosition(target, el.offsetTop, duration)
|
|
|
|
const checkAction = () => {
|
|
if (headerNativeEl?.classList.contains('q-header--hidden')) {
|
|
setVerticalScrollPosition(target, el.offsetTop + HEADER_OFFSET, 100)
|
|
}
|
|
if (animate) {
|
|
setTimeout(() => { isManualScrolling.value = false }, 100)
|
|
}
|
|
}
|
|
|
|
if (animate) {
|
|
setTimeout(checkAction, 350)
|
|
showDrawer.value = false
|
|
history.pushState(null, '', `#${id}`)
|
|
} else {
|
|
setTimeout(checkAction, 350)
|
|
}
|
|
}
|
|
|
|
const isCurrentLang = (lang) => locale.value === lang
|
|
const setLocale = (newLocale) => setGlobalLocale(newLocale.locale)
|
|
|
|
onMounted(async () => {
|
|
await nextTick()
|
|
checkSpace()
|
|
if (route.hash) {
|
|
const id = route.hash.replace('#', '')
|
|
setTimeout(() => {
|
|
scrollToElement(id, false)
|
|
}, 450)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.invisible {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
.glass {
|
|
background-color: rgba(255, 255, 255, 0.451) !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
}
|
|
</style> |