init
All checks were successful
Deploy / build (push) Successful in 51s

This commit is contained in:
alsaze 2025-11-27 17:54:01 +03:00
parent 5f3c4057e7
commit 5f250b8b6c
6 changed files with 169 additions and 30 deletions

View File

@ -6,6 +6,14 @@
</UApp> </UApp>
</template> </template>
<script setup lang="ts">
import gsap from 'gsap'
import { ScrollToPlugin } from 'gsap/ScrollToPlugin'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger, ScrollToPlugin)
</script>
<style lang="scss"> <style lang="scss">
@use '@/assets/scss/main' as *; @use '@/assets/scss/main' as *;
</style> </style>

View File

@ -6,7 +6,7 @@
:ui="{ container: '!p-0' }" :ui="{ container: '!p-0' }"
> >
<template #body> <template #body>
<div class="benefits"> <div ref="benefitsRef" class="benefits">
<div <div
v-for="benefit in benefits" v-for="benefit in benefits"
:key="benefit.title" :key="benefit.title"
@ -30,6 +30,41 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import gsap from 'gsap'
import ScrollTrigger from 'gsap/ScrollTrigger'
import { onMounted, onUnmounted, ref } from 'vue'
gsap.registerPlugin(ScrollTrigger)
const benefitsRef = ref()
let benefitsCtx
onMounted(() => {
desktopAnimation()
})
function desktopAnimation() {
benefitsCtx = gsap.context((self) => {
const boxes = self.selector('.benefit')
const tl = gsap.timeline({
scrollTrigger: {
trigger: benefitsRef.value,
start: 'top 80%',
toggleActions: 'play none none reverse',
},
})
tl.from(boxes, {
y: 50,
opacity: 0,
duration: 0.8,
ease: 'power3.out',
stagger: 0.25,
})
}, benefitsRef.value)
}
const benefits = [ const benefits = [
{ {
icon: '⏱️', icon: '⏱️',
@ -62,6 +97,10 @@ const benefits = [
description: 'Более 10 лет на рынке консьерж-услуг', description: 'Более 10 лет на рынке консьерж-услуг',
}, },
] ]
onUnmounted(() => {
benefitsCtx?.revert()
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -6,25 +6,22 @@
:ui="{ container: '!p-0' }" :ui="{ container: '!p-0' }"
> >
<template #body> <template #body>
<div class="how-works"> <div ref="howWorksRef" class="how-works">
<UCard <UCard
v-for="item in items" v-for="item in items"
:key="item.title" :key="item.title"
class="how-work" class="how-work"
:ui="{ body: 'flex flex-col gap-4 h-[350px] !pb-15' }" :ui="cardUi"
> >
<div class="how-work__number"> <div class="how-work__number">
{{ item.number }} {{ item.number }}
</div> </div>
<div class="how-work__icon"> <div class="how-work__icon">
{{ item.icon }} {{ item.icon }}
</div> </div>
<div class="how-work__title"> <div class="how-work__title">
{{ item.title }} {{ item.title }}
</div> </div>
<div class="how-work__description"> <div class="how-work__description">
{{ item.description }} {{ item.description }}
</div> </div>
@ -35,6 +32,50 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useMediaQuery } from '@vueuse/core'
import gsap from 'gsap'
import { onMounted, onUnmounted, ref } from 'vue'
const isMobile = useMediaQuery('(max-width: 1280px)')
const cardUi = computed(() => {
return isMobile.value
? { body: 'flex flex-col gap-2 h-[270px] !pb-5' }
: { body: 'flex flex-col gap-4 h-[350px] !pb-15' }
})
const howWorksRef = ref()
let ctx
onMounted(() => {
desktopAnimation()
})
function desktopAnimation() {
ctx = gsap.context((self) => {
const boxes = self.selector('.how-work')
const tl = gsap.timeline({
scrollTrigger: {
trigger: howWorksRef.value,
start: 'top 50%',
toggleActions: 'play none none reverse',
},
})
tl.from(boxes, {
x: -80,
opacity: 0,
duration: 0.8,
ease: 'power3.out',
stagger: 0.25,
})
}, howWorksRef.value)
}
onUnmounted(() => {
ctx.revert()
})
const items = [ const items = [
{ {
icon: '💬', icon: '💬',
@ -72,7 +113,7 @@ const items = [
@include mobile { @include mobile {
gap: 10px; gap: 10px;
grid-template-columns: repeat(2, minmax(150px, 350px)); grid-template-columns: repeat(1, minmax(150px, 350px));
} }
} }

View File

@ -1,11 +1,12 @@
<template> <template>
<UPageSection <UPageSection
id="services"
title="Что мы предлагаем" title="Что мы предлагаем"
description="Полный спектр консьерж-услуг для работы с недвижимостью и автомобилями" description="Полный спектр консьерж-услуг для работы с недвижимостью и автомобилями"
:ui="{ container: '!p-0' }" :ui="{ container: '!p-0' }"
> >
<template #body> <template #body>
<div class="services"> <div ref="servicesRef" class="services">
<NuxtLink <NuxtLink
v-for="service in services" v-for="service in services"
:key="service.name" :key="service.name"
@ -41,6 +42,52 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import gsap from 'gsap'
import ScrollTrigger from 'gsap/ScrollTrigger'
import { onMounted, onUnmounted, ref } from 'vue'
gsap.registerPlugin(ScrollTrigger)
const servicesRef = ref()
let servicesCtx
onMounted(() => {
servicesAnimation()
})
function servicesAnimation() {
servicesCtx = gsap.context((self) => {
const cards = self.selector('.service')
cards.forEach((card) => {
gsap.from(card, {
y: 80,
rotateY: 15,
opacity: 0,
duration: 1,
ease: 'power4.out',
scrollTrigger: {
trigger: card,
start: 'top 70%',
toggleActions: 'play none none reverse',
},
})
})
cards.forEach((card) => {
const hover = gsap.to(card, {
scale: 1.01,
duration: 0.3,
paused: true,
ease: 'power2.out',
})
card.addEventListener('mouseenter', () => hover.play())
card.addEventListener('mouseleave', () => hover.reverse())
})
}, servicesRef.value)
}
const services = [ const services = [
{ {
name: 'nedvizhimost', name: 'nedvizhimost',
@ -87,6 +134,10 @@ const services = [
], ],
}, },
] ]
onUnmounted(() => {
servicesCtx?.revert()
})
</script> </script>
<style lang="scss"> <style lang="scss">
@ -109,11 +160,6 @@ const services = [
border-radius: 24px; border-radius: 24px;
overflow: hidden; overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.6); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.6);
transition: 0.15s ease-out;
&:hover {
transform: scale(1.01);
}
img { img {
width: 100%; width: 100%;

View File

@ -21,6 +21,9 @@ export default defineNuxtConfig({
], ],
], ],
css: ['~/assets/css/main.css', '~/assets/scss/main.scss'], css: ['~/assets/css/main.css', '~/assets/scss/main.scss'],
build: {
transpile: ['gsap'],
},
i18n: { i18n: {
locales: [ locales: [
{ code: 'en', name: 'English', file: 'en.json' }, { code: 'en', name: 'English', file: 'en.json' },

View File

@ -3,9 +3,26 @@
<UPageHero <UPageHero
title="Консьерж-сервис премиум класса" title="Консьерж-сервис премиум класса"
:description="`Недвижимость и авто под ключ\nМы берем на себя все заботы по поиску, проверке и оформлению недвижимости и автомобилей. Экономьте время — доверьтесь профессионалам.`" :description="`Недвижимость и авто под ключ\nМы берем на себя все заботы по поиску, проверке и оформлению недвижимости и автомобилей. Экономьте время — доверьтесь профессионалам.`"
:links="links"
class="index-page__hero" class="index-page__hero"
/> >
<template #footer>
<div class="flex gap-2 place-content-center">
<UButton
size="xl"
label="Узанть больше"
href="#services"
/>
<UButton
size="xl"
label="Почему выбирают нас"
href="#benefits"
color="neutral"
variant="subtle"
/>
</div>
</template>
</UPageHero>
<UContainer class="flex flex-col gap-32 my-32"> <UContainer class="flex flex-col gap-32 my-32">
<Services /> <Services />
@ -19,21 +36,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Services from '~/components/Services.vue' import Services from '~/components/Services.vue'
const links = computed(() => [
{
label: 'Начать работу',
to: '/docs/getting-started',
icon: 'i-lucide-square-play',
},
{
label: 'Узнать больше',
to: '/docs/getting-started/theme/design-system',
color: 'neutral',
variant: 'subtle',
trailingIcon: 'i-lucide-arrow-right',
},
])
</script> </script>
<style lang="scss"> <style lang="scss">