diff --git a/public/img/components/modals/BuildUP/1.png b/public/img/components/modals/BuildUP/1.png new file mode 100644 index 00000000..f9c76eb1 Binary files /dev/null and b/public/img/components/modals/BuildUP/1.png differ diff --git a/public/img/components/modals/BuildUP/2.png b/public/img/components/modals/BuildUP/2.png new file mode 100644 index 00000000..2af30c13 Binary files /dev/null and b/public/img/components/modals/BuildUP/2.png differ diff --git a/public/img/components/modals/Dropfile/1.png b/public/img/components/modals/Dropfile/1.png new file mode 100644 index 00000000..8c73ad1a Binary files /dev/null and b/public/img/components/modals/Dropfile/1.png differ diff --git a/public/img/components/modals/Dropfile/2.png b/public/img/components/modals/Dropfile/2.png new file mode 100644 index 00000000..e4d3e9dd Binary files /dev/null and b/public/img/components/modals/Dropfile/2.png differ diff --git a/public/img/components/modals/Dropfile/3.png b/public/img/components/modals/Dropfile/3.png new file mode 100644 index 00000000..3f96648f Binary files /dev/null and b/public/img/components/modals/Dropfile/3.png differ diff --git a/public/img/components/modals/Dropfile/4.png b/public/img/components/modals/Dropfile/4.png new file mode 100644 index 00000000..b40bf186 Binary files /dev/null and b/public/img/components/modals/Dropfile/4.png differ diff --git a/public/img/pages/about/experience/sites.png b/public/img/pages/about/experience/sites.png index f07443e3..0064e011 100644 Binary files a/public/img/pages/about/experience/sites.png and b/public/img/pages/about/experience/sites.png differ diff --git a/public/img/pages/results/garland_tablet.svg b/public/img/pages/results/garland_tablet.svg new file mode 100644 index 00000000..2496e1f0 --- /dev/null +++ b/public/img/pages/results/garland_tablet.svg @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/img/pages/results/projects/badaevsky.png b/public/img/pages/results/projects/badaevsky.png new file mode 100644 index 00000000..d604697c Binary files /dev/null and b/public/img/pages/results/projects/badaevsky.png differ diff --git a/public/img/pages/results/projects/rozhdestvenka.png b/public/img/pages/results/projects/rozhdestvenka.png new file mode 100644 index 00000000..541c8d50 Binary files /dev/null and b/public/img/pages/results/projects/rozhdestvenka.png differ diff --git a/public/img/pages/web/faq/faq_1.png b/public/img/pages/web/faq/faq_1.png index d36611ba..61ef59cc 100644 Binary files a/public/img/pages/web/faq/faq_1.png and b/public/img/pages/web/faq/faq_1.png differ diff --git a/src/components/displacement/DisplacementCard.tsx b/src/components/displacement/DisplacementCard.tsx index b11b4697..eb4189f2 100644 --- a/src/components/displacement/DisplacementCard.tsx +++ b/src/components/displacement/DisplacementCard.tsx @@ -6,10 +6,12 @@ export default function DisplacementCard({ children, className, index = 0, + onClick, }: { children: React.ReactNode; className?: string; index?: number; + onClick?: () => void; }) { const cardRef = useRef(null); const { isLg } = useMediaQueries(); @@ -75,6 +77,7 @@ export default function DisplacementCard({ `w-[15.486vw] aspect-[223/240] rounded-[16px] bg-[radial-gradient(circle,rgba(0,0,0,1)_-70%,rgba(34,36,37,1)_100%);] flex flex-col items-center justify-between p-[1.111vw] lg:absolute max-lg:relative max-lg:w-full max-lg:h-full overflow-hidden md:max-lg:p-[2.083vw] max-md:p-[4.444vw] max-md:aspect-[165/240] ` + className } + onClick={onClick} > {children} diff --git a/src/components/modals/AchievmentsSliderModal.tsx b/src/components/modals/AchievmentsSliderModal.tsx new file mode 100644 index 00000000..9b0f1687 --- /dev/null +++ b/src/components/modals/AchievmentsSliderModal.tsx @@ -0,0 +1,283 @@ +/* eslint-disable @next/next/no-img-element */ +import React, { useState, useEffect, useRef } from "react"; +import { useModalStore } from "@/stores/useModalStore"; +import ArrowLeftIcon from "../icons/ArrowLeftIcon"; +import ArrowRightIcon from "../icons/ArrowRightIcon"; +import CloseIcon from "../icons/CloseIcon"; + +interface AchievmentsSliderModalProps { + title: string; + subtitle: string; + images: string[]; + year: number; +} + +export default function AchievmentsSliderModal({ + title, + subtitle, + images, + year, +}: AchievmentsSliderModalProps) { + const { setModal } = useModalStore(); + + function handleClose() { + setModal(null); + } + + return ( +
+
e.stopPropagation()} + > +
+

Награды

+ +
+ + + +

+ {title} +

+

{subtitle}

+ +
+
+ ); +} + +function ImageSlider({ + images, + className, + year, +}: { + images: string[]; + className?: string; + year: number; +}) { + const multipleImages = images.length > 1; + // начинаем с индекса 1 (реальное первое изображение) + const [slideIndex, setSlideIndex] = useState(multipleImages ? 1 : 0); + const [isTransitioning, setIsTransitioning] = useState(true); + const [isDragging, setIsDragging] = useState(false); + const [startX, setStartX] = useState(0); + const [dragOffset, setDragOffset] = useState(0); + const extendedImages = multipleImages + ? [images[images.length - 1], ...images, images[0]] + : images; + const containerRef = useRef(null); + const handlePrevSlide = () => { + if (!isTransitioning || isDragging) return; + setSlideIndex((prev) => prev - 1); + }; + + const handleNextSlide = () => { + if (!isTransitioning || isDragging) return; + setSlideIndex((prev) => prev + 1); + }; + + const handleIndicatorClick = (index: number) => { + if (!isTransitioning || isDragging) return; + // +1 потому что реальные изображения начинаются с индекса 1 + setSlideIndex(index + 1); + }; + + const handleDragStart = (clientX: number) => { + if (!multipleImages) return; + setIsDragging(true); + setStartX(clientX); + }; + + const handleDragMove = (clientX: number) => { + if (!isDragging || !multipleImages) return; + + const currentPosition = clientX; + const diff = currentPosition - startX; + const containerWidth = containerRef.current?.clientWidth || 0; + const movePercentage = (diff / containerWidth) * 100; + + setDragOffset(movePercentage); + }; + + const handleDragEnd = () => { + if (!isDragging || !multipleImages) return; + + setIsDragging(false); + + const threshold = 30; + + if (dragOffset > threshold) { + setSlideIndex((prev) => prev - 1); + } else if (dragOffset < -threshold) { + setSlideIndex((prev) => prev + 1); + } + + setDragOffset(0); + }; + + const handleMouseDown = (e: React.MouseEvent) => { + e.preventDefault(); + handleDragStart(e.clientX); + }; + + const handleMouseMove = (e: React.MouseEvent) => { + handleDragMove(e.clientX); + }; + + const handleMouseUp = () => { + handleDragEnd(); + }; + + const handleMouseLeave = () => { + if (isDragging) { + handleDragEnd(); + } + }; + + const handleTouchStart = (e: React.TouchEvent) => { + handleDragStart(e.touches[0].clientX); + }; + + const handleTouchMove = (e: React.TouchEvent) => { + handleDragMove(e.touches[0].clientX); + }; + + const handleTouchEnd = () => { + handleDragEnd(); + }; + + // Эффект для мгновенного переноса на противоположный конец после анимации + useEffect(() => { + if (!multipleImages) return; + + // Если мы на клоне последнего изображения (индекс 0) + if (slideIndex === 0) { + const timeout = setTimeout(() => { + setIsTransitioning(false); + setSlideIndex(images.length); + }, 500); + return () => clearTimeout(timeout); + } + + // Если мы на клоне первого изображения (индекс length + 1) + if (slideIndex === images.length + 1) { + const timeout = setTimeout(() => { + setIsTransitioning(false); + setSlideIndex(1); + }, 500); + return () => clearTimeout(timeout); + } + }, [slideIndex, images.length, multipleImages]); + + useEffect(() => { + if (!isTransitioning) { + const timeout = setTimeout(() => { + setIsTransitioning(true); + }, 50); + return () => clearTimeout(timeout); + } + }, [isTransitioning]); + + const getRealIndex = () => { + if (!multipleImages) return 0; + if (slideIndex === 0) return images.length - 1; + if (slideIndex === images.length + 1) return 0; + return slideIndex - 1; + }; + + return ( + <> +
+
+ {extendedImages.map((image, index) => ( + + ))} +
+ + {multipleImages && ( + + )} + + {multipleImages && ( + + )} +
+ {multipleImages && ( +
+ {images.map((_, index) => ( +
+ )} + + ); +} + +function YearTag({ year, className }: { year: number; className?: string }) { + return ( +
+

{year}

+
+ ); +} diff --git a/src/components/pages/AboutPage/AboutExperience.tsx b/src/components/pages/AboutPage/AboutExperience.tsx index 97c011a1..6f79be70 100644 --- a/src/components/pages/AboutPage/AboutExperience.tsx +++ b/src/components/pages/AboutPage/AboutExperience.tsx @@ -11,6 +11,8 @@ import DisplacementCard from "../../displacement/DisplacementCard"; import DisplacementCardsWrapper from "../../displacement/DisplacementCardsWrapper"; import { ArticleCard } from "../BlogPage/ArticleCard"; import AboutEvents from "./AboutEvents"; +import AchievmentsSliderModal from "@/components/modals/AchievmentsSliderModal"; +import { useModalStore } from "@/stores/useModalStore"; function YearsLabel({ years, @@ -39,6 +41,7 @@ export default function AboutExperience() { const searchParams = useSearchParams(); const { data: articles } = useGetArticlesQuery(searchParams.getAll("tags")); const { data: projectsCount } = useGetProjectsCountQuery(); + const { setModal } = useModalStore(); return ( <> @@ -126,7 +129,7 @@ export default function AboutExperience() { + setModal( + + ) + } > + setModal( + + ) + } > GRAFF.estate View -
+

Показываем виды из любой квартиры
еще не построенного жилого комплекса.

-
+
Динамическое изменение перспективы @@ -155,8 +157,10 @@ export default function ResultsAchievements() {
-
Цифровой город
-
+
+ Цифровой город +
+
Маршруты до значимых точек города
в режиме онлайн
-
-
Сценарии жизни
-
+
+
+ Сценарии жизни +
+
Визуализация подстраивается под конкретного
пользователя. Приложение показывает интересные ему
локации в ЖК, подходящие типы планировок @@ -185,8 +191,10 @@ export default function ResultsAchievements() {
-
Инженерные системы
-
+
+ Инженерные системы +
+
Покажем клиенту все внутренние составляющие
и технологичность проекта
@@ -197,13 +205,13 @@ export default function ResultsAchievements() { />
-
+
Смена сезонов
-
+
Покупка парковочных мест и
локеров
-
Облака по расписанию
-
+
+ Облака по расписанию +
+
Интегрировали API, передающее направление и скорость
ветра в реальном времени именно в той локации, где
находится проект @@ -253,10 +263,10 @@ export default function ResultsAchievements() { />
-
+
Добавили в приложения птиц
-
+
Они не только летают в небе, но и садятся на землю или
деревья вокруг ЖК
@@ -270,10 +280,12 @@ export default function ResultsAchievements() { />
-
- Новая модель интерактивного стола +
+ Новая модель
интерактивного стола +
+
+ Более тонкая и изящная
-
Более тонкая и изящная

Наш штат расширился на 30%
Теперь в команде более 70 человек @@ -304,6 +316,9 @@ export default function ResultsAchievements() { src="/img/pages/results/reels/team.png" alt="" /> + ); diff --git a/src/components/pages/Results2025/ResultsEventsReels.tsx b/src/components/pages/Results2025/Desktop/ResultsEventsReels.tsx similarity index 96% rename from src/components/pages/Results2025/ResultsEventsReels.tsx rename to src/components/pages/Results2025/Desktop/ResultsEventsReels.tsx index 13fb1ebe..cd5df45b 100644 --- a/src/components/pages/Results2025/ResultsEventsReels.tsx +++ b/src/components/pages/Results2025/Desktop/ResultsEventsReels.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @next/next/no-img-element */ import { motion, MotionValue, useScroll, useTransform } from "framer-motion"; import React, { useRef } from "react"; @@ -77,11 +78,15 @@ function Events({ scrollYProgress }: { scrollYProgress: MotionValue }) { Взяли первую строчку рейтинга RACA за экспертизу в категории Production

- raca + raca

-
+
diff --git a/src/components/pages/Results2025/ResultsGarland.tsx b/src/components/pages/Results2025/Desktop/ResultsGarland.tsx similarity index 100% rename from src/components/pages/Results2025/ResultsGarland.tsx rename to src/components/pages/Results2025/Desktop/ResultsGarland.tsx diff --git a/src/components/pages/Results2025/ResultsGeneral.tsx b/src/components/pages/Results2025/Desktop/ResultsGeneral.tsx similarity index 83% rename from src/components/pages/Results2025/ResultsGeneral.tsx rename to src/components/pages/Results2025/Desktop/ResultsGeneral.tsx index 16c91d1f..ae87e53b 100644 --- a/src/components/pages/Results2025/ResultsGeneral.tsx +++ b/src/components/pages/Results2025/Desktop/ResultsGeneral.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @next/next/no-img-element */ import HearthIcon from "@/components/icons/HearthIcon"; import PlayIcon from "@/components/icons/PlayIcon"; import { motion, MotionValue, useScroll, useTransform } from "framer-motion"; @@ -18,7 +19,11 @@ export default function ResultsGeneral() { transform: useTransform( scrollYProgress, [0.1, 0.6, 0.65], - ["translate(0%, 0%)", "translate(0%, 0%)", "translate(0%, -400%)"] + [ + "translate(0%, 0%)", + "translate(0%, 0%)", + "translate(0%, -6.944vw)", + ] ), }} className="text-center space-y-[1.528vw] absolute top-[18.056vw] z-10" @@ -72,7 +77,7 @@ export default function ResultsGeneral() { ), x: "-50%", }} - className="line2 font-medium lg:w-[65.222vw] absolute left-1/2" + className="line2 font-medium lg:w-[75.5vw] absolute left-1/2" > 4500 часов люди провели выбирая
одну из них для себя в качестве
@@ -113,7 +118,7 @@ export default function ResultsGeneral() { ), x: "-50%", }} - className="line2 font-medium lg:w-[65.222vw] absolute left-1/2" + className="line2 font-medium lg:w-[72.5vw] absolute left-1/2" > Общая сумма броней превысила
30 миллиардов рублей @@ -126,7 +131,7 @@ export default function ResultsGeneral() { - {/* Градиентный фон */} + {/* Основной фон */} -
+ {/* Затемнение сверху */} + + + {/* Круг */}
@@ -282,11 +324,11 @@ function Appartaments({
-
-
+
+
{floor}
- + {appartamentsCount} квартир
@@ -336,12 +378,12 @@ function Appartaments({ ] ), }} - className="absolute bottom-[6.597vw] right-[20.917vw] flex flex-col gap-[0.556vw]" + className="absolute bottom-[70px] left-[67.917vw] flex flex-col gap-[0.556vw]" > @@ -449,7 +491,7 @@ function Reservations({ }} src="/img/pages/results/general/booking.png" alt="" - className="absolute bottom-0 left-1/2 w-[21.528vw] h-[18.056vw] object-cover z-[10]" + className="absolute bottom-0 left-1/2 w-[27.778vw] h-[23.1vw] object-cover z-[10]" /> ); @@ -532,7 +574,7 @@ function ReservationsTotal({ ] ), }} - className={`absolute size-[24.306vw] rounded-[1.111vw] bg-[#FFFFFF1A] backdrop-blur-[24px] border-[2px] border-[#FFFFFF1A] flex items-center justify-center ${className}`} + className={`absolute size-[24.306vw] p-[1.667vw] rounded-[1.111vw] bg-[#FFFFFF1A] backdrop-blur-[24px] border-[2px] border-[#FFFFFF1A] flex items-center justify-center ${className}`} > @@ -543,7 +585,6 @@ function ReservationsTotal({ diff --git a/src/components/pages/Results2025/ResultsProjects.tsx b/src/components/pages/Results2025/Desktop/ResultsProjects.tsx similarity index 96% rename from src/components/pages/Results2025/ResultsProjects.tsx rename to src/components/pages/Results2025/Desktop/ResultsProjects.tsx index be2b1c0c..567c10b8 100644 --- a/src/components/pages/Results2025/ResultsProjects.tsx +++ b/src/components/pages/Results2025/Desktop/ResultsProjects.tsx @@ -87,7 +87,7 @@ export default function ResultsProjects() { ); } -function ResultsProjectsItem({ +export function ResultsProjectsItem({ src, className, }: { @@ -99,7 +99,7 @@ function ResultsProjectsItem({ src={src} alt="" className={ - "lg:rounded-[1.111vw] lg:h-full lg:aspect-[465/263] object-cover" + "lg:rounded-[1.111vw] md:w-[60.547vw] md:rounded-[2.083vw] lg:h-full lg:aspect-[465/263] object-cover" } /> ); diff --git a/src/components/pages/Results2025/Results2025.tsx b/src/components/pages/Results2025/Results2025.tsx index fa1d8cad..24794664 100644 --- a/src/components/pages/Results2025/Results2025.tsx +++ b/src/components/pages/Results2025/Results2025.tsx @@ -1,12 +1,16 @@ "use client"; import { motion, useScroll, useTransform } from "framer-motion"; -import ResultsGarland from "./ResultsGarland"; +import ResultsGarland from "./Desktop/ResultsGarland"; import { useRef } from "react"; -import ResultsProjects from "./ResultsProjects"; -import ResultsGeneral from "./ResultsGeneral"; -import ResultsEventsReelsWithAchievements from "./ResultsEventsReelsWithAchievements"; +import ResultsProjects from "./Desktop/ResultsProjects"; +import ResultsGeneral from "./Desktop/ResultsGeneral"; +import ResultsEventsReelsWithAchievements from "./Desktop/ResultsEventsReelsWithAchievements"; import ResultsMap from "./ResultsMap"; +import { useMediaQuery } from "usehooks-ts"; +import ResultsGarlandMobile from "./Tablet&Mobile/ResultsGarlandMobile"; +import ResultsGeneralMobile from "./Tablet&Mobile/ResultsGeneralMobile"; +import ResultsProjectsMobile from "./Tablet&Mobile/ResultsProjectsMobile"; export function Results2025() { const containerRef = useRef(null); @@ -15,28 +19,26 @@ export function Results2025() { offset: ["start start", "end start"], }); + const isLg = useMediaQuery("(min-width: 1440px)"); + return (
- - - - - + {isLg ? : } + {isLg ? : } + {isLg ? : null} + {isLg ? : null} + {isLg ? : null} Стать первыми в 2026 diff --git a/src/components/pages/Results2025/Tablet&Mobile/ResultsGarlandMobile.tsx b/src/components/pages/Results2025/Tablet&Mobile/ResultsGarlandMobile.tsx new file mode 100644 index 00000000..8f6dd576 --- /dev/null +++ b/src/components/pages/Results2025/Tablet&Mobile/ResultsGarlandMobile.tsx @@ -0,0 +1,55 @@ +/* eslint-disable @next/next/no-img-element */ +"use client"; +import React, { useRef } from "react"; +import { motion, useScroll, useTransform } from "framer-motion"; + +export default function ResultsGarlandMobile() { + return ( +
+ garland + +

+ Подводим итоги 2025 года
в GRAFF.estate +

+ + + +
+ ); +} + +function BgGradient() { + return ( +
+
+ {/*
+
+
*/} +
+ ); +} diff --git a/src/components/pages/Results2025/Tablet&Mobile/ResultsGeneralMobile.tsx b/src/components/pages/Results2025/Tablet&Mobile/ResultsGeneralMobile.tsx new file mode 100644 index 00000000..9e24715c --- /dev/null +++ b/src/components/pages/Results2025/Tablet&Mobile/ResultsGeneralMobile.tsx @@ -0,0 +1,14 @@ +/* eslint-disable @next/next/no-img-element */ +"use client"; +import React, { useRef } from "react"; +import { motion, useScroll, useTransform } from "framer-motion"; + +export default function ResultsGeneralMobile() { + return ( +
+

+ Закончили разработку
и сдали 24 проекта +

+
+ ); +} diff --git a/src/components/pages/Results2025/Tablet&Mobile/ResultsProjectsMobile.tsx b/src/components/pages/Results2025/Tablet&Mobile/ResultsProjectsMobile.tsx new file mode 100644 index 00000000..5b1bb603 --- /dev/null +++ b/src/components/pages/Results2025/Tablet&Mobile/ResultsProjectsMobile.tsx @@ -0,0 +1,40 @@ +/* eslint-disable @next/next/no-img-element */ +"use client"; +import React, { useRef } from "react"; +import { motion, useScroll, useTransform } from "framer-motion"; +import { ResultsProjectsItem } from "../Desktop/ResultsProjects"; + +export default function ResultsGeneralMobile() { + return ( +
+

+ Закончили разработку
и сдали 24 проекта +

+ +
+
+ + +
+
+ + + +
+
+ + +
+
+ +

+ Последними
из которых стали +

+ +
+ + +
+
+ ); +}