This commit is contained in:
2025-03-10 17:56:12 +05:00
14 changed files with 215 additions and 205 deletions
@@ -1,10 +1,8 @@
'use client';
import Lenis from 'lenis';
import ReactLenis, { useLenis } from 'lenis/react';
import { videos } from '@/consts/presentation/videos';
import { Title } from '@/ui/Title';
import { useInView, useMotionValueEvent, useScroll } from 'framer-motion';
import { useMotionValueEvent, useScroll } from 'framer-motion';
import { useRef, useState } from 'react';
import { Engine } from '../../../slides/Engine';
import { Infrastructure } from '../../../slides/Infrastructure';
@@ -16,24 +14,15 @@ import { VideoLayerMain } from '../../../slides/VideoLayerMain';
import { PrimeProgressItem } from '@/ui/PrimeProgressItem';
export function PresentationDesktop() {
const container = useRef<HTMLDivElement>(null);
const target = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ container });
const inView = useInView(container, { margin: '80% 0% -80% 0%' });
const { scrollYProgress } = useScroll({ target });
const [slide, setSlide] = useState(0);
const [completed, setCompleted] = useState(false);
const lenis = useLenis(({}) => {
console.log('asd');
});
useMotionValueEvent(scrollYProgress, 'change', (value) => {
setCompleted(value >= 0.99);
setSlide(Math.min(Math.trunc(value * videos.length), videos.length - 1));
});
useMotionValueEvent(scrollYProgress, 'change', (value) =>
setSlide(Math.min(Math.trunc(value * videos.length), videos.length - 1))
);
return (
<div className="mt-25 mb-60 max-lg:hidden relative">
@@ -42,18 +31,8 @@ export function PresentationDesktop() {
<span className="text-gradient">улучшает опыт выбора недвижимости</span>{' '}
и&nbsp;увеличивает темпы продаж квартир в&nbsp;жилом комплексе
</Title>
<div className="relative h-[38.889vw]">
<div
ref={container}
className={`h-full relative z-10 [scrollbar-width:none] ${
inView ? 'overflow-y-scroll' : 'overflow-hidden'
} ${!completed ? 'snap-mandatory snap-y' : ''}`}
>
{Array.from({ length: videos.length }).map((_, index) => (
<div key={index} className="snap-start snap-always h-full" />
))}
</div>
<ReactLenis className="absolute top-0 w-full h-full overflow-hidden">
<div className="relative h-[272.223vw]">
<div className="top-40 w-full h-[38.889vw] sticky">
<VideoLayerMain scroll={scrollYProgress} />
<SearchAndSelect scrollProgress={scrollYProgress} page="main" />
<ThreeDTour scrollProgress={scrollYProgress} page="main" />
@@ -61,23 +40,23 @@ export function PresentationDesktop() {
<Insolation scrollProgress={scrollYProgress} page="main" />
<Engine scroll={scrollYProgress} />
<IntegrationCRM scrollProgress={scrollYProgress} page="main" />
</ReactLenis>
</div>
<div className="flex absolute p-[0.556vw] rounded-[1.875vw] bg-[#37393B99] backdrop-blur-[20px] left-1/2 -translate-1/2 z-10">
{videos.map(({ src, anchorImg }, index) => (
<PrimeProgressItem
onClick={() => {
container.current?.scrollTo({
top: (index * container.current?.scrollHeight) / videos.length,
behavior: slide === videos.length - 1 ? 'auto' : 'smooth',
});
}}
active={index === slide}
src={anchorImg}
title={src}
key={src}
/>
))}
<div className="flex absolute bottom-0 p-[0.556vw] rounded-[1.875vw] bg-[#37393B99] backdrop-blur-[20px] left-1/2 -translate-x-1/2 translate-y-1/2 z-50">
{videos.map(({ src, anchorImg }, index) => (
<PrimeProgressItem
onClick={() => {}}
active={index === slide}
src={anchorImg}
title={src}
key={src}
/>
))}
</div>
</div>
<div ref={target} className={'h-full w-1/10 absolute top-40'}>
{Array.from({ length: videos.length + 1 }).map((_, index) => (
<div key={index} className="w-full h-1/7" />
))}
</div>
</div>
</div>
);
@@ -1,3 +1,4 @@
/* eslint-disable @next/next/no-img-element */
import { motion } from 'framer-motion';
export function AvatarCard({ slide }: { slide: number }) {
@@ -10,7 +11,7 @@ export function AvatarCard({ slide }: { slide: number }) {
className="p-[1.389vw] rounded-[1.389vw] flex flex-col justify-between items-center absolute bg-[#37393B99] w-[8.75vw] h-[10.625vw] right-[16.111vw]"
>
<img src="/img/pages/prime/avatar.png" alt="" />
<p>Аватар клиента</p>
<p className="btns font-medium">Аватар клиента</p>
</motion.div>
);
}
@@ -1,3 +1,4 @@
/* eslint-disable @next/next/no-img-element */
import { motion } from 'framer-motion';
export function InteractiveWindowCard({ slide }: { slide: number }) {
@@ -14,7 +15,7 @@ export function InteractiveWindowCard({ slide }: { slide: number }) {
className="absolute left-[2.014vw] bottom-0 h-full"
alt=""
/>
<p className="bnts font-medium z-1">Интерактивное окно</p>
<p className="btns font-medium z-1">Интерактивное окно</p>
</motion.div>
);
}
+104 -116
View File
@@ -27,17 +27,15 @@ import { AvatarCard } from './DesktopPresentation/AvatarCard';
import { ScenarioCard } from './DesktopPresentation/ScenarioCard';
export function PrimeAnimations({
primePresentationContainer,
primePresentationSlide,
}: {
primePresentationContainer: RefObject<HTMLDivElement>;
primePresentationSlide: number;
}) {
const [slide, setSlide] = useState(0);
const container = useRef<HTMLDivElement>(null);
const target = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ container });
const { scrollYProgress } = useScroll({ target });
const slidesCount = 9;
@@ -45,122 +43,112 @@ export function PrimeAnimations({
setSlide(Math.min(Math.trunc(value * slidesCount), slidesCount - 1))
);
const inView = useInView(container, { margin: '60% 0% -60% 0%' });
const inView = useInView(target, { margin: '60% 0% -60% 0%' });
return (
<div className="relative h-[49.444vw]">
<PackageTitle slide={slide} />
<DesignCard slide={slide} />
<EnvironmentCard slide={slide} />
<ThreeDReelsCard slide={slide} />
<EquipmentCard slide={slide} />
<StreamingCard slide={slide} />
<ArchVisCard slide={slide} />
<InterierConfiguratorCard slide={slide} />
<EngineCard slide={slide} />
<AnaliticsCard slide={slide} />
<ExcursionVRCard slide={slide} />
<FinanceToolCard slide={slide} />
<ExtraInterestPointsCard slide={slide} />
<SeasonsCard slide={slide} />
<InteractiveWindowCard slide={slide} />
<AvatarCard slide={slide} />
<ScenarioCard slide={slide} />
<motion.img
animate={
slide > 0
? {
bottom: slide > 2 ? '2.986vw' : '20vw',
width: '13.958vw',
height: '12.014vw',
scale: slide > 1 ? 0.83 : 1,
}
: { bottom: '10.972vw', width: '15.417vw', height: '13.194vw' }
}
transition={{ bounce: 'none' }}
src="/icons/folderBack.svg"
className="left-1/2 absolute -translate-x-1/2"
/>
<motion.div
animate={
slide > 1
? {
backgroundColor: 'transparent',
scale: 0.83,
bottom: slide > 2 ? '12.889vw' : '29.722vw',
}
: slide > 0
? {
backgroundColor: 'transparent',
y: '14.722vw',
}
: {
backgroundColor: '#37393B99',
backdropFilter: 'blur(20px)',
y: inView ? '14.722vw' : '-50%',
}
}
transition={{ bounce: 'none' }}
className="flex absolute p-[0.556vw] z-10 rounded-[1.875vw] bg-[#37393B99] left-1/2 -translate-x-1/2"
>
{primeVideos.map(({ src, anchorImg }, index) => (
<PrimeProgressItem
canAnimate={slide > 0}
{...primeProgressItemsTranslates[index]}
onClick={() => {
primePresentationContainer.current?.scrollTo({
top:
(index * primePresentationContainer.current?.scrollHeight) /
primeVideos.length,
behavior: 'smooth',
});
}}
active={index === primePresentationSlide}
src={anchorImg}
title={src}
key={src}
/>
))}
</motion.div>
<motion.img
animate={
slide > 0
? {
bottom: slide > 2 ? '2.986vw' : '20vw',
width: '15.833vw',
height: '9.792vw',
scale: slide > 1 ? 0.83 : 1,
}
: { width: '17.361vw', height: '10.764vw', bottom: '10.972vw' }
}
transition={{ bounce: 'none' }}
src="/icons/folderFront.svg"
className="left-1/2 absolute z-10 -translate-x-1/2"
/>
<motion.div
animate={
slide > 2
? { bottom: 0 }
: { opacity: +(slide > 1), bottom: '17.014vw' }
}
transition={{ bounce: 'none' }}
className="rounded-[1.389vw] border border-[#37393B] aspect-square w-[15.972vw] absolute left-1/2 -translate-x-1/2 p-[1.111vw] flex items-end"
>
<motion.p
className="btns font-medium"
animate={{ opacity: +(slide > 1) }}
<div className="h-[395.552vw] relative">
<div className="sticky top-0 h-[49.444vw]">
<PackageTitle slide={slide} />
<DesignCard slide={slide} />
<EnvironmentCard slide={slide} />
<ThreeDReelsCard slide={slide} />
<EquipmentCard slide={slide} />
<StreamingCard slide={slide} />
<ArchVisCard slide={slide} />
<InterierConfiguratorCard slide={slide} />
<EngineCard slide={slide} />
<AnaliticsCard slide={slide} />
<ExcursionVRCard slide={slide} />
<FinanceToolCard slide={slide} />
<ExtraInterestPointsCard slide={slide} />
<SeasonsCard slide={slide} />
<InteractiveWindowCard slide={slide} />
<AvatarCard slide={slide} />
<ScenarioCard slide={slide} />
<motion.img
animate={
slide > 0
? {
bottom: slide > 2 ? '2.986vw' : '20vw',
width: '13.958vw',
height: '12.014vw',
scale: slide > 1 ? 0.83 : 1,
}
: { bottom: '10.972vw', width: '15.417vw', height: '13.194vw' }
}
transition={{ bounce: 'none' }}
src="/icons/folderBack.svg"
className="left-1/2 absolute -translate-x-1/2"
/>
{/* <motion.div
animate={
slide > 1
? {
backgroundColor: 'transparent',
scale: 0.83,
bottom: slide > 2 ? '12.889vw' : '29.722vw',
}
: slide > 0
? {
backgroundColor: 'transparent',
y: '14.722vw',
}
: {
backgroundColor: '#37393B99',
backdropFilter: 'blur(20px)',
y: inView ? '14.722vw' : '-50%',
}
}
transition={{ bounce: 'none' }}
className="flex absolute p-[0.556vw] z-10 rounded-[1.875vw] bg-[#37393B99] left-1/2 -translate-x-1/2"
>
Базовый функционал
</motion.p>
</motion.div>
<div
className={`snap-y snap-mandatory relative h-full [scrollbar-width:none] ${
inView ? 'overflow-y-scroll z-12' : 'overflow-hidden'
}`}
ref={container}
>
{primeVideos.map(({ src, anchorImg }, index) => (
<PrimeProgressItem
canAnimate={slide > 0}
{...primeProgressItemsTranslates[index]}
onClick={() => {}}
active={index === primePresentationSlide}
src={anchorImg}
title={src}
key={src}
/>
))}
</motion.div> */}
<motion.img
animate={
slide > 0
? {
bottom: slide > 2 ? '2.986vw' : '20vw',
width: '15.833vw',
height: '9.792vw',
scale: slide > 1 ? 0.83 : 1,
}
: { width: '17.361vw', height: '10.764vw', bottom: '10.972vw' }
}
transition={{ bounce: 'none' }}
src="/icons/folderFront.svg"
className="left-1/2 absolute z-10 -translate-x-1/2"
/>
<motion.div
animate={
slide > 2
? { bottom: 0 }
: { opacity: +(slide > 1), bottom: '17.014vw' }
}
transition={{ bounce: 'none' }}
className="rounded-[1.389vw] border border-[#37393B] aspect-square w-[15.972vw] absolute left-1/2 -translate-x-1/2 p-[1.111vw] flex items-end"
>
<motion.p
className="btns font-medium"
animate={{ opacity: +(slide > 1) }}
>
Базовый функционал
</motion.p>
</motion.div>
</div>
<div className={'absolute h-full'} ref={target}>
{Array.from({ length: 9 }).map((_, index) => (
<div className="snap-start snap-always h-full" key={index} />
<div className="h-full" key={index} />
))}
</div>
</div>
+56 -23
View File
@@ -3,7 +3,12 @@
import { primeVideos } from '@/consts/presentation/videos';
import { Title } from '@/ui/Title';
import { useInView, useMotionValueEvent, useScroll } from 'framer-motion';
import {
motion,
useInView,
useMotionValueEvent,
useScroll,
} from 'framer-motion';
import { useRef, useState } from 'react';
import { CommercialOffer } from '../../slides/CommercialOffer';
import { Favorites } from '../../slides/Favorites';
@@ -14,22 +19,24 @@ import { ThreeDTour } from '../../slides/ThreeDTour';
import { VideoLayerPrime } from '../../slides/VideoLayerPrime';
import { Insolation } from '../../slides/Insolation';
import { PrimeAnimations } from './PrimeAnimations';
import { PrimeProgressItem } from '@/ui/PrimeProgressItem';
import { primeProgressItemsTranslates } from '@/consts/primeProgressItemsTranslates';
export function PrimeDesktopPage() {
const container = useRef<HTMLDivElement>(null);
const target = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ container });
const { scrollYProgress } = useScroll({ target });
const inView = useInView(container, { margin: '75% 0% -75% 0%' });
const [presentationSlide, setPresentationSlide] = useState(0);
const [primePresentationSlide, setPrimePresentationSlide] = useState(0);
useMotionValueEvent(scrollYProgress, 'change', (value) =>
setPresentationSlide(
setPrimePresentationSlide(
Math.min(Math.trunc(value * primeVideos.length), primeVideos.length - 1)
)
);
const [slide, setSlide] = useState(0);
return (
<div className="max-lg:hidden space-y-6">
<Title headerLevel={2}>
@@ -37,18 +44,8 @@ export function PrimeDesktopPage() {
<span className="text-gradient">опыт выбора недвижимости</span> и
увеличивает темпы продаж квартир в&nbsp;жилом комплексе
</Title>
<div className="relative h-[40.556vw]">
<div
ref={container}
className={`snap-y snap-mandatory h-full z-10 relative [scrollbar-width:none] ${
inView ? 'overflow-y-scroll' : 'overflow-hidden'
}`}
>
{Array.from({ length: primeVideos.length }).map((_, index) => (
<div key={index} className="snap-start snap-always h-full" />
))}
</div>
<div className="absolute inset-0 overflow-hidden">
<div className="relative h-[324.448vw]">
<div className="h-[40.556vw] overflow-hiddean sticky top-20 -mx-[1.389vw] px-[1.389vw]">
<VideoLayerPrime scroll={scrollYProgress} />
<ThreeDTour scrollProgress={scrollYProgress} page="prime" />
<SearchAndSelect scrollProgress={scrollYProgress} page="prime" />
@@ -57,12 +54,48 @@ export function PrimeDesktopPage() {
<IntegrationCRM scrollProgress={scrollYProgress} page="prime" />
<Insolation scrollProgress={scrollYProgress} page="prime" />
<CommercialOffer scroll={scrollYProgress} />
<motion.div
// animate={
// slide > 1
// ? {
// backgroundColor: 'transparent',
// scale: 0.83,
// bottom: slide > 2 ? '12.889vw' : '29.722vw',
// }
// : slide > 0
// ? {
// backgroundColor: 'transparent',
// y: '14.722vw',
// }
// : {
// backgroundColor: '#37393B99',
// backdropFilter: 'blur(20px)',
// y: inView ? '14.722vw' : '-50%',
// }
// }
// transition={{ bounce: 'none' }}
className="flex absolute bottom-0 p-[0.556vw] z-10 rounded-[1.875vw] bg-[#37393B99] left-1/2 -translate-x-1/2 translate-y-1/2"
>
{primeVideos.map(({ src, anchorImg }, index) => (
<PrimeProgressItem
canAnimate={slide > 0}
// {...primeProgressItemsTranslates[index]}
onClick={() => {}}
active={index === primePresentationSlide}
src={anchorImg}
title={src}
key={src}
/>
))}
</motion.div>
</div>
<div ref={target} className="h-full relative">
{Array.from({ length: primeVideos.length + 1 }).map((_, index) => (
<div key={index} className="h-1/8" />
))}
</div>
</div>
<PrimeAnimations
primePresentationSlide={presentationSlide}
primePresentationContainer={container}
/>
<PrimeAnimations primePresentationSlide={primePresentationSlide} />
</div>
);
}
+1 -1
View File
@@ -9,7 +9,7 @@ export function Engine({
scroll: MotionValue<number>;
top?: number;
}) {
const opacity = useTransform(scroll, [3 / 5, 4 / 5, 5 / 5], [0, 1, 0]);
const opacity = useTransform(scroll, [3 / 5, 4 / 5, 4.5 / 5], [0, 1, 0]);
const x = useTransform(scroll, [3 / 5, 4 / 5], ['-100%', '0%']);
+2 -2
View File
@@ -26,7 +26,7 @@ export function Infrastructure({
const opacityMain = useTransform(
scrollProgress,
[1 / 5, 2 / 5, 3 / 5],
[1 / 5, 2 / 5, 1 / 2],
[0, 1, 0]
);
@@ -45,7 +45,7 @@ export function Infrastructure({
x: page === 'main' ? xMain : xPrime,
opacity: page === 'main' ? opacityMain : opacityPrime,
}}
className={`absolute max-lg:hidden right-0 rounded-[1.111vw] p-[1.667vw] h-full bg-radial-[at_0%_100%] from-[#7A7A7A99] flex flex-col gap-2 backdrop-blur-[500px] select-none ${
className={`absolute max-lg:hidden right-[1.389vw] rounded-[1.111vw] p-[1.667vw] h-full bg-radial-[at_0%_100%] from-[#7A7A7A99] flex flex-col gap-2 backdrop-blur-[500px] select-none ${
page === 'main' ? 'w-[31.944vw]' : 'w-[23.611vw]'
}`}
>
+3 -1
View File
@@ -13,6 +13,8 @@ export function Insolation({
}) {
const y = useTransform(scrollProgress, [2 / 5, 3 / 5], ['100%', '0%']);
const xMain = useTransform(scrollProgress, [3 / 5, 4 / 5], ['0%', '100%']);
const x = useTransform(
scrollProgress,
[4 / 6, 5 / 6, 6 / 6],
@@ -39,7 +41,7 @@ export function Insolation({
style={{
y: page === 'main' ? y : undefined,
opacity: page === 'main' ? opacityMain : opacityPrime,
x: page === 'prime' ? x : undefined,
x: page === 'prime' ? x : xMain,
}}
className={`absolute max-lg:hidden h-full rounded-[1.111vw] p-[1.667vw] bg-radial-[at_0%_100%] from-[#7A7A7A99] flex flex-col gap-2 backdrop-blur-[500px] select-none ${
page === 'main' ? 'w-[31.944vw] right-0' : 'w-[23.611vw]'
+1 -1
View File
@@ -37,7 +37,7 @@ export function IntegrationCRM({
opacity: page === 'main' ? opacityMain : opacityPrime,
}}
className={`absolute hidden h-full p-[1.667vw] rounded-[1.111vw] bg-radial-[at_100%_100%] from-[#7A7A7A66] backdrop-blur-[500px] bottom-0 to-transparent lg:flex flex-col justify-between select-none ${
page === 'main' ? 'w-[29.167vw]' : 'w-[23.611vw] right-0'
page === 'main' ? 'w-[29.167vw]' : 'w-[23.611vw] right-[1.389vw]'
}`}
>
<p className="heading2 font-medium">
+1 -1
View File
@@ -16,7 +16,7 @@ export function SearchAndSelect({
}) {
const opacityMain = useTransform(
scrollProgress,
[0, 1 / (videos.length - 1)],
[0, 1 / (videos.length - 1) / 2],
[1, 0]
);
+1 -1
View File
@@ -58,7 +58,7 @@ export function ThreeDTour({
x: page === 'main' ? xMain : xPrime,
}}
className={`p-[1.667vw] max-lg:hidden rounded-[1.111vw] bg-radial-[at_100%_100%] from-[#7A7A7A66] backdrop-blur-[500px] aspect-[340/368] flex flex-col justify-between gap-[4.444vw] absolute w-[23.611vw] ${
page === 'prime' ? ' right-0 top-1/2 -translate-y-1/2' : ''
page === 'prime' ? ' right-[1.389vw] top-1/2 -translate-y-1/2' : ''
}`}
>
<p className="heading2 font-medium">
+1 -1
View File
@@ -74,7 +74,7 @@ export function VideoLayerMain({ scroll }: { scroll: MotionValue<number> }) {
loop
muted
playsInline
animate={{ y: slide === videos.length - 1 ? '7.292vw' : '0vws' }}
animate={{ y: slide === videos.length - 1 ? '7.292vw' : '0vw' }}
transition={{ duration: 0.1 }}
style={{ zIndex: videos.length - index }}
className={`absolute w-full h-full duration-500 transition-[opacity,transform]${
+6 -6
View File
@@ -7,14 +7,14 @@ import { useState } from 'react';
export const Providers = ({ children }: { children: React.ReactNode }) => {
const [queryClient] = useState(() => new QueryClient());
// const lenis = new Lenis();
const lenis = new Lenis();
// function raf(time: number) {
// lenis.raf(time);
// requestAnimationFrame(raf);
// }
function raf(time: number) {
lenis.raf(time);
requestAnimationFrame(raf);
}
// requestAnimationFrame(raf);
requestAnimationFrame(raf);
return (
<QueryClientProvider client={queryClient}>
+10 -4
View File
@@ -26,8 +26,7 @@ export function PrimeProgressItem({
<motion.div
animate={canAnimate ? { x, y, rotateZ, zIndex } : {}}
transition={{ bounce: 'none' }}
onClick={onClick}
className="group max-lg:hidden flex items-center cursor-pointer"
className="group max-lg:hidden flex items-center z-10"
>
<motion.div
animate={{ opacity: +!canAnimate }}
@@ -39,10 +38,17 @@ export function PrimeProgressItem({
}
/>
<motion.div
onClick={onClick}
animate={{
borderColor: canAnimate ? '#37393B00' : !active ? '#37393B' : 'white',
borderColor: canAnimate
? '#37393B00'
: !active
? '#37393B'
: '#FFFFFF',
}}
className={'p-[0.278vw] border relative rounded-[1.111vw]'}
className={
'p-[0.278vw] border relative cursor-pointer rounded-[1.111vw]'
}
>
<img
src={src}