diff --git a/package.json b/package.json index de5c7c4a..b346c227 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "react-input-mask": "^2.0.4", "react-rangeslider": "^2.2.0", "react-swipeable": "^7.0.1", + "react-transition-group": "^4.4.5", "react-usestateref": "^1.0.9", "usehooks-ts": "^3.1.0", "zustand": "^4.5.4" @@ -29,6 +30,7 @@ "@types/react-dom": "^18", "@types/react-input-mask": "^3.0.5", "@types/react-rangeslider": "^2.2.7", + "@types/react-transition-group": "^4.4.11", "autoprefixer": "^10.4.19", "eslint": "^8", "eslint-config-next": "14.2.5", diff --git a/public/img/components/products/highlight.svg b/public/img/components/products/highlight.svg new file mode 100644 index 00000000..15f384ed --- /dev/null +++ b/public/img/components/products/highlight.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/img/components/products/stream.png b/public/img/components/products/stream.png index 37fe026c..bd33cd8e 100644 Binary files a/public/img/components/products/stream.png and b/public/img/components/products/stream.png differ diff --git a/public/img/pages/home/showreel/preview.png b/public/img/pages/home/showreel/preview.png new file mode 100644 index 00000000..15cfe3ba Binary files /dev/null and b/public/img/pages/home/showreel/preview.png differ diff --git a/public/videos/pages/home/showreel_1080p_4000k_h264.mp4 b/public/videos/pages/home/showreel.mp4 similarity index 100% rename from public/videos/pages/home/showreel_1080p_4000k_h264.mp4 rename to public/videos/pages/home/showreel.mp4 diff --git a/public/videos/pages/home/technology.mp4 b/public/videos/pages/home/technology.mp4 new file mode 100644 index 00000000..be90d697 Binary files /dev/null and b/public/videos/pages/home/technology.mp4 differ diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index 20690892..ce485998 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -38,7 +38,7 @@ export function Header() { {width >= 1280 && } -
+
О компании Блог @@ -48,7 +48,7 @@ export function Header() {
diff --git a/src/components/Layout/ModalContainer.tsx b/src/components/Layout/ModalContainer.tsx index 801626d0..95370325 100644 --- a/src/components/Layout/ModalContainer.tsx +++ b/src/components/Layout/ModalContainer.tsx @@ -7,10 +7,10 @@ export function ModalContainer() { return ( modal && ( -
-
e.stopPropagation()} className="cursor-default"> - {modal} -
+
+ {/*
e.stopPropagation()} className="cursor-default"> */} + {modal} + {/*
*/}
) ); diff --git a/src/components/Layout/ModalWithProducts.tsx b/src/components/Layout/ModalWithProducts.tsx index 92c27d07..49313174 100644 --- a/src/components/Layout/ModalWithProducts.tsx +++ b/src/components/Layout/ModalWithProducts.tsx @@ -1,7 +1,9 @@ import Products from '@/consts/products.json'; import { useModalStore } from '@/stores/useModalStore'; +import { AnimatePresence, motion } from 'framer-motion'; import Image from 'next/image'; -import { useEffect } from 'react'; +import { useEffect, useRef, useState } from 'react'; +import { useOnClickOutside } from 'usehooks-ts'; import { CloseIcon } from '../icons/CloseIcon'; interface IProduct { @@ -11,40 +13,66 @@ interface IProduct { image: string; } -export default function ModalWithProducts() { +export function ModalWithProducts() { const { setModal } = useModalStore(); + const [show, setShow] = useState(true); + const ref = useRef(null); + + useOnClickOutside(ref, () => setShow(false)); useEffect(() => { const listener = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - setModal(false); - } + if (e.key === 'Escape') setShow(false); }; document.addEventListener('keydown', listener); return () => document.removeEventListener('keydown', listener); - }, [setModal]); + }, []); return ( -
-

- GRAFF.estate - -

-
- {Products.map((product, index) => ( - - ))} -
-
+

+ GRAFF.estate + +

+
+ {Products.map((product, index) => ( + + ))} +
+ + )} + ); } @@ -55,9 +83,13 @@ function ProductItem({ className, }: Omit & { className?: string }) { return ( -
@@ -76,9 +108,11 @@ function ProductItem({ src={image} alt={title} fill - className="object-contain object-bottom !relative" + priority + sizes="100% 100%" + className="object-contain !relative object-bottom" />
-
+ ); } diff --git a/src/components/Layout/ProductsList.tsx b/src/components/Layout/ProductsList.tsx index 2a23871c..ded6a34d 100644 --- a/src/components/Layout/ProductsList.tsx +++ b/src/components/Layout/ProductsList.tsx @@ -1,17 +1,29 @@ 'use client'; import { useModalStore } from '@/stores/useModalStore'; +import { useEffect, useState } from 'react'; import { ChevronDownIcon } from '../icons/ChevronDownIcon'; import { ChevronUpIcon } from '../icons/ChevronUpIcon'; -import ModalWithProducts from './ModalWithProducts'; +import { ModalWithProducts } from './ModalWithProducts'; export function ProductsList() { const { setModal, modal } = useModalStore(); + const [show, setShow] = useState(false); + + useEffect(() => { + setShow(!!modal); + }, [modal]); return ( +
+
+ ); +} diff --git a/src/components/icons/ChevronUpIcon.tsx b/src/components/icons/ChevronUpIcon.tsx index 5cec8fd5..1abfc7e1 100644 --- a/src/components/icons/ChevronUpIcon.tsx +++ b/src/components/icons/ChevronUpIcon.tsx @@ -8,8 +8,8 @@ export function ChevronUpIcon() { xmlns="http://www.w3.org/2000/svg" > diff --git a/src/components/pages/MainPage/Availables/AvailableItem.tsx b/src/components/pages/MainPage/Availables/AvailableItem.tsx index a25275d9..8a1962e0 100644 --- a/src/components/pages/MainPage/Availables/AvailableItem.tsx +++ b/src/components/pages/MainPage/Availables/AvailableItem.tsx @@ -26,7 +26,7 @@ export const AvailableItem = forwardRef( minHeight: '17.6vw', } } - transition={{ duration: 1, type: 'just' }} + transition={{ duration: 1, type: 'just', delay: 0.5 }} exit={{ minWidth: '31.6vw', minHeight: '17.6vw', diff --git a/src/components/pages/MainPage/Integrations/IntegrationItem.tsx b/src/components/pages/MainPage/Integrations/IntegrationItem.tsx index 9004c000..d430dfc0 100644 --- a/src/components/pages/MainPage/Integrations/IntegrationItem.tsx +++ b/src/components/pages/MainPage/Integrations/IntegrationItem.tsx @@ -9,38 +9,66 @@ export interface IIntegration { company: string; } -export const IntegrationItem = forwardRef( - ({ img, title, year, company }, ref) => { - return ( - - - {''} -
-
-

{title}

-

{year}

-
-

{company}

+export const IntegrationItem = forwardRef< + HTMLDivElement, + IIntegration & { index: number } +>(({ img, title, year, company, index }, ref) => { + // const transitionStyles: Map = new Map([ + // ['entering', { minWidth: '48vw', minHeight: '48vw' }], + // ['entered', { minWidth: '48vw', minHeight: '48vw' }], + // ['exiting', { minWidth: '31.6vw', minHeight: '31.8vw' }], + // ['exited', { minWidth: '31.6vw', minHeight: '31.8vw' }], + // ]); + + return ( + // console.log('enter')} + // onEntering={() => console.log('entering')} + // onEntered={() => console.log('entered')} + // onExit={() => console.log('exit')} + // onExiting={() => console.log('exiting')} + // onExited={() => console.log('exited')} + // > + // {state => ( + console.log('exit')}> + 3 ? '456' : '')} + initial={{ minWidth: '31.6vw', minHeight: '31.8vw' }} + transition={{ duration: 0.5, type: 'just', delay: 0.5 }} + animate={!!ref && { minWidth: '48vw', minHeight: '48vw' }} + // exit={{ minWidth: '31.6vw', minHeight: '31.8vw' }} + className={'flex flex-col relative transition-all pointer-events-none'} + // style={{ + // transition: 'all 0.5s ease-out', + // ...transitionStyles.get(state), + // }} + > + {''} +
+
+

{title}

+

{year}

- - - ); - }, -); +

{company}

+
+
+
+ // )} + //
+ ); +}); IntegrationItem.displayName = 'IntegrationItem'; diff --git a/src/components/pages/MainPage/Integrations/IntegrationsSlider.tsx b/src/components/pages/MainPage/Integrations/IntegrationsSlider.tsx index 90694cc7..18e9374a 100644 --- a/src/components/pages/MainPage/Integrations/IntegrationsSlider.tsx +++ b/src/components/pages/MainPage/Integrations/IntegrationsSlider.tsx @@ -1,7 +1,6 @@ 'use client'; import { SliderWithScaling } from '@/ui/SliderWithScaling'; -import 'react-circular-progressbar/dist/styles.css'; import { IIntegration, IntegrationItem } from './IntegrationItem'; export function IntegrationsSlider({ diff --git a/src/components/pages/MainPage/Showreel.tsx b/src/components/pages/MainPage/Showreel.tsx index 2c5a1c58..d6853c51 100644 --- a/src/components/pages/MainPage/Showreel.tsx +++ b/src/components/pages/MainPage/Showreel.tsx @@ -1,77 +1,35 @@ 'use client'; import { LoadingIcon } from '@/components/icons/LoadingIcon'; -import { PauseIcon } from '@/components/icons/PauseIcon'; import { PlayIcon } from '@/components/icons/PlayIcon'; +import { VideoModal } from '@/components/Layout/VideoModal'; import { ClassNameWrapper } from '@/hocs/ClassNameWrapper'; -import { useRef, useState } from 'react'; -import { useHover } from 'usehooks-ts'; +import { useModalStore } from '@/stores/useModalStore'; export function Showreel() { - const [isPlaying, setIsPlaying] = useState(true); - const [isBuffering, setIsBuffering] = useState(false); - - function handleOnPlaying() { - setIsBuffering(false); - } - - function handleOnWaiting() { - setIsBuffering(true); - } - - const ref = useRef(null); - const videoRef = useRef(null); - - const hovered = useHover(ref); + const { modal, setModal } = useModalStore(); return (
-
-
diff --git a/src/components/pages/MainPage/Statistics.tsx b/src/components/pages/MainPage/Statistics.tsx index e57001ed..9c629602 100644 --- a/src/components/pages/MainPage/Statistics.tsx +++ b/src/components/pages/MainPage/Statistics.tsx @@ -1,5 +1,4 @@ import { Descriptor } from '@/ui/Descriptor'; -import { LineThrow } from '@/ui/LineThrow'; import { Title } from '@/ui/Title'; import { Manrope } from 'next/font/google'; import Image from 'next/image'; @@ -9,10 +8,13 @@ const manrope = Manrope({ subsets: ['latin'] }); export function Statistics() { return (
- + <Title className="mb-20 leading-[-2em]"> Продавайте недвижимость <br /> <span className="text-gradient">проще и </span> - <LineThrow>быстрее</LineThrow> + <span className="relative"> + <span className="text-[#52587A]">быстрее</span> + <span className="absolute top-[55%] -left-[2.5%] 2xl:h-1.5 h-1 w-[105%] bg-white" /> + </span> <span className="text-gradient"> дороже</span>
diff --git a/src/components/pages/MainPage/Technology.tsx b/src/components/pages/MainPage/Technology.tsx index 7b933469..605ea509 100644 --- a/src/components/pages/MainPage/Technology.tsx +++ b/src/components/pages/MainPage/Technology.tsx @@ -1,7 +1,6 @@ import { ArrowMoreIcon } from '@/components/icons/ArrowMoreIcon'; import { PlusIcon } from '@/components/icons/PlusIcon'; import { ClassNameWrapper } from '@/hocs/ClassNameWrapper'; -import Image from 'next/image'; import Link from 'next/link'; export function Technology() { @@ -33,13 +32,7 @@ export function Technology() { презентовать объект покупателю из любой точки мира

- {''} +

diff --git a/src/consts/reviews.json b/src/consts/reviews.json index f3e44b3c..9433fb06 100644 --- a/src/consts/reviews.json +++ b/src/consts/reviews.json @@ -4,14 +4,14 @@ "text": "«Эффективность инструмента была подтверждена буквально в первый день после его внедрения»", "author": "Егор Бобров, Коммерческий директор авторского квартала «Машаров»" }, - { - "image": "/img/pages/home/reviews/2.jpg", - "text": "«Клиенты особенно ценят возможность легко выбрать квартиру с помощью 3D-модель жилого комплекса»", - "author": "Олег Бондорев, Ведущий менеджер компании «ЭНКО»" - }, { "image": "/img/pages/home/reviews/3.jpg", "text": "«Клиенты особенно ценят возможность легко выбрать квартиру с помощью 3D-модель жилого комплекса»", "author": "Алина Веселова, Ведущий специалист отдела продаж" + }, + { + "image": "/img/pages/home/reviews/2.jpg", + "text": "«Клиенты особенно ценят возможность легко выбрать квартиру с помощью 3D-модель жилого комплекса»", + "author": "Олег Бондорев, Ведущий менеджер компании «ЭНКО»" } ] diff --git a/src/ui/LineThrow.tsx b/src/ui/LineThrow.tsx deleted file mode 100644 index d6048fb4..00000000 --- a/src/ui/LineThrow.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { PropsWithChildren } from 'react'; - -export function LineThrow({ - className = '', - children, -}: PropsWithChildren<{ className?: string }>) { - return ( - - {children} - - ); -} diff --git a/src/ui/NavLink.tsx b/src/ui/NavLink.tsx index e0aaa3d7..f41b291e 100644 --- a/src/ui/NavLink.tsx +++ b/src/ui/NavLink.tsx @@ -1,7 +1,6 @@ 'use client'; import Link from 'next/link'; -import { usePathname } from 'next/navigation'; export function NavLink({ href, @@ -12,8 +11,6 @@ export function NavLink({ children: React.ReactNode; className?: string; }) { - const pathname = usePathname(); - return ( - {/* } - className={pathname.startsWith(href) ? '' : 'invisible'} - /> */} {children} ); diff --git a/src/ui/SliderWithScaling.tsx b/src/ui/SliderWithScaling.tsx index 1e60c022..0da267ba 100644 --- a/src/ui/SliderWithScaling.tsx +++ b/src/ui/SliderWithScaling.tsx @@ -22,7 +22,9 @@ export function SliderWithScaling({ controlsPosition, }: { slides: T[]; - SlideElement: ForwardRefExoticComponent>; + SlideElement: ForwardRefExoticComponent< + T & { index: number } & RefAttributes + >; className?: string; alignItems?: 'start' | 'center' | 'end'; title: string; @@ -32,7 +34,6 @@ export function SliderWithScaling({ const width = useWindowWidth(); const baseoffset = (-width / 1600) * 507 + 8; - // const [itemRef, scale] = useAnimate(); const itemRef = useRef(null); const [slide, setSlide] = useState(0); @@ -57,8 +58,6 @@ export function SliderWithScaling({ ); const nextSlide = () => { - // itemRef.current!.style.minWidth = minWidth; - // itemRef.current!.style.minHeight = minHeight; dispatch('next'); }; @@ -66,7 +65,9 @@ export function SliderWithScaling({ setSliderOffset(baseoffset); }, [baseoffset, order, slide]); - const prevSlide = () => {}; + const prevSlide = () => { + dispatch('prev'); + }; const handlers = useSwipeable({ onSwipedLeft: nextSlide, @@ -84,20 +85,23 @@ export function SliderWithScaling({
- {order.map((slide, index) => ( - - ))} + {order.map((slide, index) => { + return ( + + ); + })}
diff --git a/tailwind.config.ts b/tailwind.config.ts index 01cfbf80..42bf13eb 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -14,12 +14,27 @@ const config: Config = { }, animation: { 'infinite-scroll': 'infinite-scroll 25s linear infinite', + 'highlight-product': 'highlight-product 0.1s ease-in 0s', }, keyframes: { 'infinite-scroll': { from: { transform: 'translateX(0)' }, to: { transform: 'translateX(-100%)' }, }, + 'highlight-product': { + '100%': { + backgroundImage: 'url(/img/components/products/highlight.svg)', + }, + }, + scaling: { + '0%': { + transform: 'min-width 31.6vw min-height 31.8vw', + transition: 'transform 500ms', + }, + '100%': { + transform: 'min-width 48vw min-height 48vw', + }, + }, }, }, }, diff --git a/yarn.lock b/yarn.lock index 94f1bb8a..0d0870dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,13 @@ resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== +"@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" + integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + dependencies: + regenerator-runtime "^0.14.0" + "@eslint-community/eslint-utils@^4.2.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -241,6 +248,13 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.4.11": + version "4.4.11" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5" + integrity sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA== + dependencies: + "@types/react" "*" + "@types/react@*", "@types/react@^18": version "18.3.3" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" @@ -795,6 +809,14 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -2230,7 +2252,7 @@ prettier@^3.3.3: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== -prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -2288,6 +2310,16 @@ react-swipeable@^7.0.1: resolved "https://registry.yarnpkg.com/react-swipeable/-/react-swipeable-7.0.1.tgz#cd299f5986c5e4a7ee979839658c228f660e1e0c" integrity sha512-RKB17JdQzvECfnVj9yDZsiYn3vH0eyva/ZbrCZXZR0qp66PBRhtg4F9yJcJTWYT5Adadi+x4NoG53BxKHwIYLQ== +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-usestateref@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/react-usestateref/-/react-usestateref-1.0.9.tgz#d40bc54db116e786b6b2bb1cd20fe06e7f8187f3" @@ -2327,6 +2359,11 @@ reflect.getprototypeof@^1.0.4: globalthis "^1.0.3" which-builtin-type "^1.1.3" +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.5.1, regexp.prototype.flags@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334"