diff --git a/public/img/pages/web/demo/clouds.png b/public/img/pages/web/demo/clouds.png index 3052064d..e508650b 100644 Binary files a/public/img/pages/web/demo/clouds.png and b/public/img/pages/web/demo/clouds.png differ diff --git a/src/app/(main)/layout.tsx b/src/app/(main)/layout.tsx index 0e7a2f1b..845b9240 100644 --- a/src/app/(main)/layout.tsx +++ b/src/app/(main)/layout.tsx @@ -1,14 +1,9 @@ import { Feedback } from "@/components/Layout/Feedback"; import { Footer } from "@/components/Layout/Footer"; -import dynamic from "next/dynamic"; +import { Header } from "@/components/Layout/Header"; import { PropsWithChildren } from "react"; export default function MainLayout({ children }: PropsWithChildren) { - const Header = dynamic( - () => import("@/components/Layout/Header").then((mod) => mod.Header), - { ssr: false } - ); - return (
diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx index 44514296..fda6bbd8 100644 --- a/src/components/Layout/Header.tsx +++ b/src/components/Layout/Header.tsx @@ -2,7 +2,6 @@ "use client"; import { api } from "@/api"; -import { useMediaQueries } from "@/hooks/useMediaQueries"; import { useScroll } from "@/hooks/useScroll"; import { useCheckAuthQuery } from "@/queries/checkAuth"; import { HeaderLink } from "@/ui/HeaderLink"; @@ -39,8 +38,6 @@ export function Header() { const [burgerOpened, setBurgerOpened] = useState(false); const [productsOpened, setProductsOpened] = useState(false); - const { isLg, isXs, isSm, isMd } = useMediaQueries(); - const pathname = usePathname(); const burgerRef = useRef(null); @@ -60,8 +57,15 @@ export function Header() { const scroll = useScroll(logoRef); + const [headerWidth, setHeaderWidth] = useState(); + return ( -
+
{ + if (el) setHeaderWidth(el.clientWidth + 12); + }} + > - {((isLg && scroll < -logoRef.current?.clientHeight!) || !isLg) && ( + {((headerWidth && + headerWidth >= 1440 && + scroll < -logoRef.current?.clientHeight!) || + (headerWidth && headerWidth < 1440)) && ( )} - {burgerOpened && (isXs || isSm) && ( + {burgerOpened && headerWidth && headerWidth < 768 && ( <> - {productsOpened && (isMd || isLg) && ( - <> - setProductsOpened(false)} - ref={productsRef} - initial={{ opacity: 0 }} - animate={{ - width: isLg ? "max(68.889vw,360px)" : "calc(100vw - 32px)", - opacity: 100, - }} - exit={{ opacity: 0 }} - className="fixed max-md:hidden top-[6.944vw] left-1/2 -translate-x-1/2 lg:rounded-[1.111vw] md:max-lg:rounded-2xl bg-[#37393B99] backdrop-blur-2xl p-1 z-[13]" - > - - -
- - )} + {productsOpened && + headerWidth && + (headerWidth >= 768 || headerWidth < 1440) && ( + <> + setProductsOpened(false)} + ref={productsRef} + initial={{ opacity: 0 }} + animate={{ + width: + headerWidth && headerWidth >= 1440 + ? "max(68.889vw,360px)" + : "calc(100vw - 32px)", + opacity: 100, + }} + exit={{ opacity: 0 }} + className="fixed max-md:hidden top-[6.944vw] left-1/2 -translate-x-1/2 lg:rounded-[1.111vw] md:max-lg:rounded-2xl bg-[#37393B99] backdrop-blur-2xl p-1 z-[13]" + > + + +
+ + )}
{pathname.startsWith("/projects") ? ( diff --git a/src/components/displacement/DisplacementCard.tsx b/src/components/displacement/DisplacementCard.tsx index 8adb28a4..b11b4697 100644 --- a/src/components/displacement/DisplacementCard.tsx +++ b/src/components/displacement/DisplacementCard.tsx @@ -1,74 +1,76 @@ import { useMediaQueries } from "@/hooks/useMediaQueries"; -import { motion } from "framer-motion"; -import React, { useEffect, useRef, useState } from "react"; +import { motion, useScroll, useTransform } from "framer-motion"; +import React, { useRef } from "react"; export default function DisplacementCard({ children, className, + index = 0, }: { children: React.ReactNode; className?: string; + index?: number; }) { const cardRef = useRef(null); - const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); - const [cardTransform, setCardTransform] = useState({ x: 0, y: 0 }); const { isLg } = useMediaQueries(); - useEffect(() => { - const handleMouseMove = (e: MouseEvent) => { - setMousePosition({ x: e.clientX, y: e.clientY }); - }; + // Получаем прогресс скролла относительно элемента карточки + const { scrollYProgress } = useScroll({ + target: cardRef, + offset: ["start end", "end start"], + }); - window.addEventListener("mousemove", handleMouseMove); - return () => window.removeEventListener("mousemove", handleMouseMove); - }, []); + // Создаем уникальные параметры для каждой карточки на основе её индекса + const phaseX = index * 0.7; // Фазовое смещение для X + const phaseY = index * 1.2; // Фазовое смещение для Y + const phaseRotate = index * 0.3; // Фазовое смещение для поворота - useEffect(() => { - if (!cardRef.current || !isLg) return; + // Создаем трансформации для X и Y на основе скролла + // Используем функции для динамического вычисления значений + // Альтернативный подход с массивами значений + const x = useTransform( + scrollYProgress, + [0, 0.25, 0.5, 0.75, 1], + isLg + ? [ + 0, + Math.sin(phaseX) * 8, + Math.sin(phaseX + Math.PI) * 8, + Math.sin(phaseX + Math.PI * 1.5) * 8, + 0, + ] + : [0, 0, 0, 0, 0] + ); - const card = cardRef.current; - const rect = card!.getBoundingClientRect(); - const cardCenterX = rect.left + rect.width / 2; - const cardCenterY = rect.top + rect.height / 2; + const y = useTransform( + scrollYProgress, + [0, 0.25, 0.5, 0.75, 1], + isLg + ? [ + 0, + Math.cos(phaseY) * 25, + Math.cos(phaseY + Math.PI) * 25, + Math.cos(phaseY + Math.PI * 1.5) * 25, + 0, + ] + : [0, 0, 0, 0, 0] + ); - const deltaX = mousePosition.x - cardCenterX; - const deltaY = mousePosition.y - cardCenterY; - const distance = Math.hypot(deltaX, deltaY); - - const maxDistance = 1000; - const maxOffset = 20; - - if (distance < maxDistance) { - const strength = 1 - distance / maxDistance; - const offsetX = (deltaX / distance) * strength * maxOffset; - const offsetY = (deltaY / distance) * strength * maxOffset; - - setCardTransform({ - x: isNaN(offsetX) ? 0 : offsetX, - y: isNaN(offsetY) ? 0 : offsetY, - }); - } else { - setCardTransform({ x: 0, y: 0 }); - } - }, [isLg, mousePosition]); + // Добавляем легкое вращение для более динамичного эффекта + const rotate = useTransform( + scrollYProgress, + [0, 0.5, 1], + isLg ? [0, Math.sin(phaseRotate) * 2, 0] : [0, 0, 0] + ); return ( - + - + - + - + - + - + +
-
+ poster="/img/pages/about/reel_template.png" + className="absolute inset-0 object-cover w-full lg:h-dvh rounded-b-[1.111vw] z-[10] top-0 max-lg:relative md:max-lg:aspect-[736/441] max-md:aspect-[340/512] max-md:rounded-b-[4.444vw]" + /> +
-

+

GRAFF.estate —
IT компания,
разрабатывающая
{" "} передовые @@ -34,21 +25,21 @@ export function AboutHero() {

AI
VR
diff --git a/src/components/pages/AboutPage/AboutMain.tsx b/src/components/pages/AboutPage/AboutMain.tsx index 8aadae6c..83055671 100644 --- a/src/components/pages/AboutPage/AboutMain.tsx +++ b/src/components/pages/AboutPage/AboutMain.tsx @@ -1,9 +1,9 @@ "use client"; import React, { useEffect } from "react"; -import { AboutHero } from "./AboutHero"; import { Statistics } from "../MainPage/Statistics"; import AboutTeam from "./AboutTeam"; import dynamic from "next/dynamic"; +import { AboutHero } from "./AboutHero"; export default function AboutMain() { useEffect(() => { diff --git a/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideCards.tsx b/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideCards.tsx index 35bc815b..1cdf69b8 100644 --- a/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideCards.tsx +++ b/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideCards.tsx @@ -22,7 +22,7 @@ export default function ContentSlideCards({ return ( {active && ( @@ -55,20 +55,20 @@ export default function ContentSlideCards({ initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - transition={{ duration: 0.3, ease: "easeInOut" }} + transition={{ duration: 0.5, type: "just" }} className="absolute inset-0 w-full h-full z-[4]" > Для соцсетей:

- Вертикальные изображения с идеально выстоенным кадром + Вертикальные изображения с идеально выстоенным кадром

@@ -76,7 +76,7 @@ export default function ContentSlideCards({ initial={{ x: "-4.167vw", y: "4.167vw" }} animate={{ x: 0, y: 0 }} exit={{ x: "-4.167vw", y: "4.167vw" }} - transition={{ duration: 0.6, ease: "easeInOut" }} + transition={{ duration: 0.5, type: "just" }} className="lg:w-[13.403vw] md:w-[18.229vw] w-[38.889vw] flex flex-col lg:gap-[0.694vw] gap-2 lg:p-[1.389vw] p-4 bg-[#37393B99] backdrop-blur-[5px] rounded-[1.111vw] absolute lg:right-[5.25vw] lg:top-[2.625vw] right-0 top-0 max-md:top-[15vw] max-md:right-[5vw] max-md:rounded-[4.444vw] md:max-lg:p-[2.083vw] md:max-lg:right-[2.083vw] md:max-lg:top-[2.083vw]" > @@ -96,7 +96,7 @@ export default function ContentSlideCards({ translateY: active ? "-3.819vw" : "-0.347vw", width: cardsSize, }} - transition={{ duration: 0.2, ease: "easeInOut" }} + transition={{ duration: 0.5, type: "just" }} className="absolute z-[3] lg:rounded-[0.417vw] rounded-2xl" src="/img/pages/web/content/card_top.png" alt="" @@ -108,7 +108,7 @@ export default function ContentSlideCards({ translateY: "0px", width: cardsSize, }} - transition={{ duration: 0.4, ease: "easeInOut" }} + transition={{ duration: 0.5, type: "just" }} className="absolute z-[2] lg:rounded-[0.417vw] rounded-2xl" src="/img/pages/web/content/card_md.png" alt="" @@ -120,7 +120,7 @@ export default function ContentSlideCards({ translateY: active ? "3.819vw" : "0.347vw", width: cardsSize, }} - transition={{ duration: 0.5, ease: "easeInOut" }} + transition={{ duration: 0.5, type: "just" }} className="absolute z-[1] lg:rounded-[0.417vw] rounded-2xl" src="/img/pages/web/content/card_bt.png" alt="" diff --git a/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideHall.tsx b/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideHall.tsx index 156fe8fc..ef59ae69 100644 --- a/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideHall.tsx +++ b/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideHall.tsx @@ -42,7 +42,7 @@ export default function ContentSlideHall({ return (
diff --git a/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideIphone.tsx b/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideIphone.tsx index 77f874c9..da6c66a5 100644 --- a/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideIphone.tsx +++ b/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/ContentSlideIphone.tsx @@ -78,9 +78,9 @@ export default function ContentSlideIphone({ : "94.444vw", }} transition={{ duration: 0.5, type: "just" }} - className="lg:h-[37.361vw] md:h-[68.448vw] h-[151.111vw] relative flex-shrink-0 w-full select-none" + className="lg:h-[37.361vw] md:h-[68.448vw] h-[151.111vw] relative flex-shrink-0 w-full select-none pointer-events-none" > -
+
Яркие видео

- Дайте клиентам почувствовать атмосферу жизни в жилом комплексе + Дайте клиентам почувствовать атмосферу жизни в жилом комплексе

@@ -103,11 +104,12 @@ export default function ContentSlideIphone({ animate={{ opacity: active ? 1 : 0, top: - (isLg && (active ? "16.736vw" : "8.736vw")) || - (isMd && (active ? "60.307vw" : "50.307vw")) || + (isLg && (active ? "15.736vw" : "8.736vw")) || + (isMd && (active ? "59.307vw" : "50.307vw")) || "134.722vw", left: isLg ? (active ? "1.667vw" : "5.667vw") : "100%", }} + transition={{ duration: 0.5, type: "just" }} className="max-lg:-translate-y-full z-[9] max-lg:-translate-x-full lg:w-[13.403vw] md:w-[18.229vw] w-[38.889vw] flex flex-col lg:p-[1.389vw] p-4 lg:gap-[0.694vw] gap-2 bg-[#37393B99] backdrop-blur-[5px] lg:rounded-[1.111vw] rounded-2xl absolute max-md:rounded-[4.444vw] md:max-lg:p-[2.083vw] max-md:p-[4.444vw] max-md:w-[38.889vw]" > @@ -132,12 +134,13 @@ export default function ContentSlideIphone({ ? "5.859vw" : "5px", }} + transition={{ duration: 0.5, type: "just" }} className="lg:w-[13.403vw] z-[9] md:w-[18.229vw] w-[38.889vw] flex flex-col lg:p-[1.389vw] p-4 lg:gap-[0.694vw] gap-2 bg-[#37393B99] backdrop-blur-[5px] lg:rounded-[1.111vw] rounded-2xl absolute md:max-lg:p-[2.083vw] max-md:rounded-[4.444vw] max-md:p-[4.444vw] max-md:w-[38.889vw]" > Экстерьер

- Запечатлим лучшие места для досуга и отдыха в вашем проекте + Запечатлим лучшие места для досуга и отдыха в вашем проекте

@@ -150,13 +153,14 @@ export default function ContentSlideIphone({ "40.278vw", right: isLg ? (active ? "1.736vw" : "5.583vw") : "0px", }} + transition={{ duration: 0.5, type: "just" }} className="lg:w-[13.403vw] z-[9] md:w-[18.229vw] w-[38.889vw] flex flex-col lg:p-[1.389vw] p-4 lg:gap-[0.694vw] gap-2 bg-[#37393B99] backdrop-blur-[5px] lg:rounded-[1.111vw] rounded-2xl absolute md:max-lg:p-[2.083vw] max-md:rounded-[4.444vw] max-md:p-[4.444vw] max-md:w-[38.889vw]" > Архитектура

- Сделаем видеооблеты, чтобы показать клиентам жилой комплекс в - инфраструктуре города + Сделаем видеооблеты, чтобы показать клиентам жилой комплекс + в инфраструктуре города

diff --git a/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/WebInfiniteSlider.tsx b/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/WebInfiniteSlider.tsx index e1417ca3..f8d8a8d6 100644 --- a/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/WebInfiniteSlider.tsx +++ b/src/components/pages/WebPage/PageComponents/WebInfiniteSlider/WebInfiniteSlider.tsx @@ -11,12 +11,13 @@ export default function InfiniteSlider() { const { isLg, isMd } = useMediaQueries(); const offset = isLg ? 0 : isMd ? 10.677 : 95.833; const slideWidth = isLg - ? 23.661 + 0.486 + ? 23.611 + 0.5 : isMd - ? 31.25 + 1.0415 + ? 31.25 + 1.0416 : 94.444 + 1.389; const [slideOffset, setSlideOffset] = useState(-slideWidth); const [isAnimating, setIsAnimating] = useState(false); + const [slides, setSlides] = useState([ { type: "hall", id: Math.random() }, { type: "cards", id: Math.random() }, @@ -53,17 +54,14 @@ export default function InfiniteSlider() { return (
-
+
setIsAnimating(false)} className="flex lg:gap-[2vw] md:gap-[2.083vw] gap-[2.778vw]" style={{ transform: `translateX(${slideOffset}vw)`, transitionDuration: `${ - slideOffset === -offset || - slideOffset === -slideWidth * 2 - offset - ? 0 - : 0.5 + slideOffset % (-2 * slideWidth) === -offset ? 0 : 0.5 }s`, }} > diff --git a/src/components/pages/WebPage/WebDemo.tsx b/src/components/pages/WebPage/WebDemo.tsx index b21b3a34..fb3f2f62 100644 --- a/src/components/pages/WebPage/WebDemo.tsx +++ b/src/components/pages/WebPage/WebDemo.tsx @@ -8,6 +8,22 @@ import DisplacementCardsWrapper from "@/components/displacement/DisplacementCard import DisplacementCard from "@/components/displacement/DisplacementCard"; import { useMediaQuery } from "usehooks-ts"; +// CSS стили для анимации облаков +const cloudsAnimationStyles = ` + @keyframes cloudMove { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(0%); + } + } + + .cloud-animation { + animation: cloudMove 15s linear infinite; + } +`; + export default function WebDemo() { const [demoActive, setDemoActive] = useState(false); const iframeRef = useRef(null); @@ -65,14 +81,14 @@ export default function WebDemo() {

Ваш жилой комплекс
становится{" "} -
+
частью живого
города

- -
+ +